﻿@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');

/* Legacy --nwc-* aliases. Kept for compatibility with existing consumers
   (panel-layout.css, scoped .razor.css files). New code should reference
   tokens.css directly. */
:root {
    --nwc-text:         var(--text-primary);
    --nwc-text-muted:   var(--text-muted);
    --nwc-text-light:   var(--text-disabled);
    --nwc-bg:           var(--surface-default);
    --nwc-bg-subtle:    var(--surface-subtle);
    --nwc-bg-muted:     var(--surface-muted);
    --nwc-border:       var(--border-default);
    --nwc-border-light: var(--border-subtle);

    /* MonthCalendar: width of the trailing weekly-summary column. Shared by the
       reusable .flex-grid (MonthCalendar) and a consumer's day-of-week band
       (.flex-grid-days) so the two grids keep identical tracks and line up. */
    --mc-summary-col: 9rem;
}

html, body {
    font-family: var(--font-family-body);
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: var(--text-link);
}

.btn-primary {
    color: var(--white);
    background-color: var(--interactive-primary);
    border-color: var(--interactive-primary-border);
}

.content {
    padding-top: 1.1rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--state-valid-outline);
}

.invalid {
    outline: 1px solid var(--red-500);
}

.validation-message {
    color: var(--red-500);
}

#blazor-error-ui {
    background: var(--state-warning-bg);
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem calc(0.7rem + env(safe-area-inset-bottom)) 1.25rem;
    position: fixed;
    width: 100%;
    /* Deliberately above --z-bottom-nav (990): a reconnect/error banner is a global system
       alert and should cover the bottom tab bar while it's up, not sit behind it. On phones this
       will be the dominant field-UX interruption on flaky vineyard Wi-Fi (see harvest mobile scope
       doc §7.2) — the safe-area padding above keeps the dismiss control clear of the home
       indicator. */
    z-index: var(--z-error-ui);
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: var(--space-md);
        top: var(--space-sm);
    }

/* ── Circuit-lost notice ────────────────────────────────────────────────────
   #blazor-error-ui above covers an unhandled EXCEPTION. It does NOT cover a
   dropped circuit — that path drives #components-reconnect-modal, and this app
   shipped without the element, so every disconnect was silently invisible: the
   page simply stopped responding with no explanation (bug #12, 2026-08-01,
   iPhone weight-tag scanning — opening the native camera suspends the page long
   enough to kill the WebSocket).

   Blazor owns the state classes; we only style them:
     .components-reconnect-show     — retrying
     .components-reconnect-hide     — recovered (base display:none resumes)
     .components-reconnect-failed   — retries exhausted, Blazor.reconnect() may work
     .components-reconnect-rejected — server dropped the circuit, only a reload helps
   Blazor also fills #components-reconnect-current-attempt / -max-retries when present. */
#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    align-items: center;
    background: var(--state-warning-bg);
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: flex;
    gap: var(--space-sm);
    left: 0;
    padding: 0.6rem 1.25rem calc(0.7rem + env(safe-area-inset-bottom)) 1.25rem;
    position: fixed;
    width: 100%;
    z-index: var(--z-reconnect);
}

    /* One child per state; the active state class reveals exactly one. */
    #components-reconnect-modal .rc-state {
        display: none;
        align-items: center;
        gap: var(--space-sm);
    }

    #components-reconnect-modal.components-reconnect-show .rc-retrying,
    #components-reconnect-modal.components-reconnect-failed .rc-failed,
    #components-reconnect-modal.components-reconnect-rejected .rc-rejected {
        display: flex;
    }

    /* Auto-reload countdown, revealed by js/circuit-reload.js inside the rejected state
       only. [hidden] is spelled out because the surrounding rule sets display on its
       siblings — leaving it to the UA sheet is how a hidden element quietly shows up. */
    #components-reconnect-modal .rc-auto {
        align-items: center;
        display: inline-flex;
        gap: var(--space-sm);
    }

    #components-reconnect-modal .rc-auto[hidden] {
        display: none;
    }

    #components-reconnect-modal .rc-auto-stay {
        background: none;
        border: 1px solid currentColor;
        border-radius: var(--radius-sm);
        color: inherit;
        cursor: pointer;
        font: inherit;
        padding: 0.1rem 0.5rem;
    }

    #components-reconnect-modal .rc-spinner {
        animation: rc-spin 1s linear infinite;
        border: 2px solid currentColor;
        border-radius: 50%;
        border-top-color: transparent;
        display: inline-block;
        flex: none;
        height: 1rem;
        width: 1rem;
    }

@keyframes rc-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    #components-reconnect-modal .rc-spinner {
        animation-duration: 3s;
    }
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--red-700);
    padding: 1rem 1rem 1rem 3.7rem;
    color: var(--white);
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/*   Start: added to apply to all select elements in project 3/10/2023*/
select {
    display: block;
    width: 100%;
    padding: .375rem 1.75rem .375rem .75rem;
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-normal);
    line-height: var(--line-height-normal);
    color: var(--text-primary);
    background-color: var(--surface-default);
    background-clip: padding-box;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    transition: border-color var(--transition-default), box-shadow var(--transition-default);
}
    select:focus {
        border-color: #8fbbd9; /* intentional: lighter focus tint than --blue-500 */
        outline: 0;
        box-shadow: var(--shadow-focus);
    }
    select[multiple], .form-select[size]:not([size="1"]) {
        height: auto;
        padding-right: .75rem;
        background-image: none;
    }
/*End*/

/* Adjust if using custom classes */
/*.custom-panel-overlay {
  z-index: 1050;
}

.custom-panel {
  z-index: 1060;
}*/

/* Prevent body scroll when sliding panel is open */
body.panel-open {
    overflow: hidden;
    position: fixed;
    width: 100%;
    height: 100%;
}

/* ============================================
   Compliance Pages - Shared Styles
   ============================================ */

.compliance-page .page-container {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.compliance-page .sticky-header {
    position: sticky;
    top: 0;
    background: var(--surface-default);
    z-index: var(--z-sticky);
    padding: var(--space-lg) 1.25rem 0;
    box-shadow: var(--shadow-md);
}

/* Tabs - underline style matching app pattern */
.compliance-page .page-tabs {
    display: flex;
    align-items: center;
    gap: 0;
    margin-bottom: 0;
    border-bottom: 2px solid var(--border-subtle);
}

.compliance-page .page-tabs .tab {
    padding: 0.6rem var(--space-xl); /* 0.6rem intentional — dense compliance toolbar */
    text-decoration: none;
    color: var(--tab-inactive-color);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: all 0.2s;
}

.compliance-page .page-tabs .tab:hover {
    color: var(--accent-primary);
    background: transparent;
}

.compliance-page .page-tabs .tab.active {
    color: var(--tab-active-color);
    border-bottom-color: var(--tab-active-border);
    font-weight: var(--font-weight-semibold);
    background: transparent;
}

.compliance-page .page-header {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
    padding-top: var(--space-lg);
    margin-bottom: var(--space-md);
}

.compliance-page .page-header h2 {
    margin: 0;
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    color: var(--nwc-text);
}

.compliance-page .loading-indicator {
    color: var(--text-muted);
    font-size: 0.8rem; /* off-scale — sits between --font-size-sm (0.75) and --font-size-md (0.85) */
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.compliance-page .search-bar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md);
    background: var(--search-bar-bg);
    border-radius: var(--search-bar-radius);
    border: 1px solid var(--search-bar-border);
    margin-bottom: var(--space-sm);
}

.compliance-page .search-bar .input-group {
    flex: 1;
    max-width: 400px;
}

.compliance-page .search-bar .form-control {
    border-radius: var(--radius-md);
}

.compliance-page .search-bar .form-control:focus {
    border-color: var(--accent-primary);
    box-shadow: var(--shadow-focus-accent);
}

.compliance-page .content-area {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-lg) 1.25rem;
}

.compliance-page .empty-state {
    max-width: 420px;
    margin: var(--space-2xl) auto;
    padding: var(--space-2xl);
    text-align: center;
    background: var(--surface-subtle);
    border-radius: var(--radius-xl);
    border: 1px dashed var(--border-default);
}

.compliance-page .empty-state i {
    color: var(--text-disabled);
}

.compliance-page .empty-state h4 {
    color: var(--text-secondary);
    font-weight: var(--font-weight-semibold);
}

.compliance-page .results-summary {
    padding: var(--space-sm) 0;
    color: var(--text-muted);
    font-size: 0.8rem; /* off-scale */
}

/* Compliance table styling */
.compliance-page .table {
    font-size: var(--font-size-base);
}

.compliance-page .table thead th {
    background: var(--table-header-bg);
    color: var(--table-header-fg);
    font-weight: var(--font-weight-semibold);
    font-size: 0.8rem; /* off-scale eyebrow-label sizing */
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
    padding: 0.6rem var(--space-md); /* 0.6rem intentional — dense compliance table */
    border: none;
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: var(--z-base);
}

.compliance-page .table tbody tr {
    transition: background 0.15s;
}

.compliance-page .table tbody tr:hover {
    background: var(--table-row-hover);
}

.compliance-page .table tbody td {
    padding: 0.6rem var(--space-md); /* y: 0.6rem off-scale (dense compliance table); x: --space-md = 0.75rem */
    vertical-align: middle;
    border-color: var(--nwc-border-light);
}

/* ── Compact form/button utilities ─────────────────────────────────── */
.nwc-form-xs { font-size: 0.72rem; padding: 0.15rem 0.3rem; height: auto; min-height: unset; line-height: 1.3; }
.nwc-form-sm { font-size: 0.8rem; padding: 0.35rem var(--space-sm); height: auto; min-height: 1.85rem; line-height: 1.3; } /* 0.8rem off-scale */
.nwc-btn-xs  { padding: 0.1rem 0.25rem; font-size: var(--font-size-xs); line-height: 1.3; }
.nwc-btn-icon { padding: 0; font-size: var(--font-size-md); line-height: 1; border: none; background: transparent; }

/* The quiet pencil that opens TicketCorrectionPanel on a clean (unflagged) receiving row.
   Hoisted here 2026-08-21 (weight-tag-edit-everywhere) from ReceivingLedgerView.razor.css,
   where it shipped 2026-08-20 as the first surface to have it — CSS isolation scopes a
   component's own .razor.css to that component's markup only, so a class defined there is
   invisible to every other component's rendered pencil. This is now the ONE definition every
   correction opener uses (ReceivingLedgerView, HarvestToday's unmatched-receipts list, and any
   later surface in the inventory) — do not paste a second copy into a new .razor.css. */
.rl-correct {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.1rem;
    height: 1.1rem;
    margin-left: var(--space-2xs);
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    line-height: 1;
    cursor: pointer;
}

.rl-correct:hover {
    background: var(--surface-muted);
    color: var(--text-primary);
}

/* Condensed type wrapper (Pattern 16d) — a narrower FACE, not a smaller size: fits more
   characters per line while every input keeps its 16px iOS no-zoom floor (Pattern 16c rule:
   density never comes from shrinking input text). Form elements inherit family via the
   Bootstrap reboot, so one wrapper class reaches child components' inputs too.
   Canonical adopter: AddBookingPanel (owner-requested 2026-08-12). */
.nwc-type-condensed { font-family: var(--font-family-condensed); }

/* ── Reduced-motion accessibility ─────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ── Click-outside backdrop for Blazor-native dropdown menus ───────────
   Bootstrap's JS isn't loaded in this app, so any markup that previously
   used data-bs-toggle="dropdown" now wires its own open/close state and
   pairs the visible menu (.dropdown-menu.show) with a transparent
   fixed-position backdrop that catches outside clicks. The backdrop sits
   beneath the menu (lower z-index) and above page content. */
.js-dropdown-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-dropdown-backdrop);
    background: transparent;
}

/* ── Blue/Green slot badge (admin-only) ─────────────────────────────────
   Rendered by Shared/SlotBadge.razor; visible only to Admin or SuperAdmin.
   Identifies which IIS slot is currently serving the page so admins can
   verify a swap without opening DevTools or hitting /admin/health. */
.slot-badge,
.slot-chip {
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: var(--font-weight-semibold);
    color: var(--white);
    letter-spacing: 0.5px;
    text-transform: uppercase;
}

.slot-badge {
    position: fixed;
    bottom: 8px;
    right: 8px;
    z-index: var(--z-slot-badge);
    pointer-events: auto;
    cursor: default;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

/* On mobile the floating badge would sit on the bottom nav's More tab (both are
   fixed bottom-right); the slot renders inside the More sheet instead
   (SlotBadge Variant="sheet" in BottomNav). */
@media (max-width: 767.98px) {
    .slot-badge { display: none; }
}

.slot-badge.slot-blue,  .slot-chip.slot-blue  { background: var(--blue-500); }
.slot-badge.slot-green, .slot-chip.slot-green { background: #198754; }       /* Bootstrap success — distinct from --green-700 / --green-500 */
.slot-badge.slot-unknown, .slot-chip.slot-unknown { background: var(--text-muted); }

/* Slot/build footer row inside the mobile More sheet (SlotBadge Variant="sheet").
   Global (not BottomNav-scoped) because SlotBadge renders it without a scope attr. */
.slot-sheet-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px 2px; /* .bottom-sheet already pads the safe-area bottom */
    border-top: 1px solid var(--border-subtle);
}

.slot-sheet-meta {
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dropdown-menu.show { z-index: var(--z-dropdown); }

/* ───────────────────────────────────────────────────────────────────────────
   Intake card — canonical "stat / library row / receipt" card. Visual base
   shared across 11+ .razor.css files (wt-card, cl-card, rs-card, gl-card,
   gs-card, lp-card, bol-card, er-card, inv-card, …). Per-page CSS files keep
   only the modifiers (--warning, --coverage-red, --active) and sub-elements
   (-icon, -body, -number, -label); the duplicated base block is GONE.

   Markup pattern during the consolidation:
       <div class="nwc-card xx-card">      ← gets base from here + modifiers from page
       <div class="nwc-card xx-card xx-card--warning">

   The .xx-card class is harmless once empty — leave it on markup as a
   namespace anchor for the page-specific modifiers.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-card {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: 0.625rem 0.875rem;
    background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%);
    border: 1px solid var(--slate-200);
    border-radius: 0.625rem;
    transition: box-shadow var(--transition-default);
}

.nwc-card:hover {
    box-shadow: var(--shadow-lg);
}

/* ───────────────────────────────────────────────────────────────────────
   .nwc-card sub-elements — the canonical icon/body/number/label shape
   used by every page consuming .nwc-card. Page-specific icon colour
   modifiers stay scoped (e.g. .cl-card-icon--cork); the four semantic
   ones (warning/success/danger/info) live here.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-card-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-lg);
    background: rgba(79, 70, 229, 0.1);
    color: var(--indigo-600);
    font-size: var(--font-size-lg);
    flex-shrink: 0;
}

.nwc-card-icon--warning { background: rgba(217, 119, 6, 0.12); color: var(--amber-600); }
.nwc-card-icon--success { background: rgba(22, 163, 74, 0.1);  color: var(--green-500); }
.nwc-card-icon--danger  { background: rgba(220, 38, 38, 0.1);  color: #dc2626; }
.nwc-card-icon--info    { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }

@media (max-width: 900px) {
    .nwc-card-icon {
        width: 30px;
        height: 30px;
        font-size: var(--font-size-md);
    }
}

/* Cover-fit img inside wrapper containers — hoisted from 5 .razor.css files (audit #5).
   Page-local outliers (e.g. SkuLabelColaPicker's .suggestion-thumb img with
   object-fit:contain) keep winning via CSS-isolation specificity. */
.suggestion-thumb img,
.profile-thumb img,
.er-thumb img,
.image-slot img,
.gallery-item img,
.home-page .img-fluid,
.home-layout-two .main-image img,
.home-layout-three .top-image img,
.home-layout-three .bottom-image img,
.home-layout-four .grid-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.nwc-card-body {
    min-width: 0;
}

.nwc-card-number {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-bold);
    line-height: 1.2;
    color: var(--slate-800);
}

.nwc-card-label {
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--slate-500);
    white-space: nowrap;
}

/* ───────────────────────────────────────────────────────────────────────
   .nwc-confirm* — shared "confirm card" family for destructive/result panels.
   Hoisted from byte-identical inline <style> blocks in UserTeardownPanel and
   ContactCleanupPanel (CSS-dedup arc). A bordered card with semantic colour
   variants; .nwc-action-bar is the right-aligned button row that pairs with it.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-confirm { border: 1px solid #dee2e6; border-radius: 0.5rem; padding: 0.75rem 1rem; background: #fff; }
.nwc-confirm-danger  { border-color: #dc3545; background: #fdf2f3; }
.nwc-confirm-success { border-color: #198754; background: #e9f7ef; }
.nwc-confirm-warning { border-color: #ffc107; background: #fff8e1; }
.nwc-action-bar { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 0.5rem; }

/* ───────────────────────────────────────────────────────────────────────
   ConfirmDialogPanel body helpers — global because ChildContent passed to
   Shared/ConfirmDialogPanel.razor is authored in the CALLING .razor file, so
   it carries the caller's Blazor CSS-isolation scope, not ConfirmDialogPanel's.
   Consolidated from 6 near-byte-identical confirm-dialog implementations
   (SuppliesPanel, MaterialStatusSection, BottlingLabelPanel, Detail x2,
   SupplyOrdersPage) — see LAYOUT-PATTERNS.md Pattern 12.
   ─────────────────────────────────────────────────────────────────────── */
.confirm-body p { margin: 0 0 12px 0; }
.confirm-body p:last-child { margin-bottom: 0; }
.confirm-details {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px;
    background: var(--gray-50);
    border-radius: var(--radius-sm);
    margin-bottom: 12px;
}
.confirm-details strong { color: var(--gray-900); }
.confirm-details span   { color: var(--gray-600); }

/* ───────────────────────────────────────────────────────────────────────
   .nwc-kv — shared key-value definition grid (label column + fluid value
   column, semantic <dl>/<dt>/<dd>). Baseline values match the most common
   instance in the corpus (110px label column, 4px 12px gap, var(--text-muted)
   label). Consolidated from 9 near-identical hand-rolled instances
   (InventoryInspector .im-kv, UserTeardownPanel .utp-kv, LabelManager
   .lm-inspector-meta/.lm-proof-dl, Materials InventoryManager .im-kv,
   BulkWineDesk .bwd-kv, LabelProofFromIntake .lpfi-dl, CcofMaterialsMain/
   CcofOperationsCertsMain .ccof-detail dl) — see LAYOUT-PATTERNS.md Pattern 2.
   Each consumer keeps its own class name ALSO applied for local overrides
   (width/gap/color deltas) via the normal Blazor-scoped-selector-wins-over-
   global-class cascade, same convention as .im-recv- / .im-picker- above.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-kv {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 4px 12px;
    margin: 0;
}
.nwc-kv dt {
    color: var(--text-muted);
    font-weight: var(--font-weight-medium);
}
.nwc-kv dd {
    margin: 0;
}

/* ───────────────────────────────────────────────────────────────────────
   .nwc-group* — shared collapsible-group-list shell (card wrapper + clickable
   header row + body). Deliberately minimal: only the mechanics that were
   byte-identical across all 4 consumers (border-radius/overflow on the
   wrapper; display/align-items/cursor on the header). Color, background,
   hover treatment, spacing, and the expanded-state border stay local per
   instance — 2 of the 4 use a colored gradient header (TtbRegistryMain,
   ColasMain, near-twins of each other) and 2 use a plain gray header
   (FilingObligationsMain, BusinessCleanup), a deliberate domain distinction,
   not accidental drift, so they're not forced to converge further than this.
   LotExplorerPanel (5th instance in the corpus) was deliberately left OUT —
   a dark workspace-themed flat divider list with a <button> header and no
   card wrapper at all, genuinely different enough not to force in. See
   LAYOUT-PATTERNS.md Pattern 10.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-group {
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.nwc-group-header {
    display: flex;
    align-items: center;
    cursor: pointer;
}
.nwc-group-body {
    padding: 0;
}

.edit-instead {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 8px 0 0 0;
    font-size: 0.9rem;
    color: var(--blue-500);
    text-decoration: none;
}
.edit-instead:hover { color: var(--dailyflow-link-hover-fg); }

/* ───────────────────────────────────────────────────────────────────────
   Inventory Manager — shared panel base. The .im-recv* family is the
   canonical "receiving-style" panel layout used by 19 of the panels under
   Shared/Inventory/Panels/. UnassignedReceiptsPanel intentionally overrides
   .im-recv with a flex layout; that scoped override still wins via the
   Blazor b-attribute selector.
   ─────────────────────────────────────────────────────────────────────── */
.im-recv         { padding: 4px 0 12px; }
.im-recv-header  { padding: 4px 4px 12px; border-bottom: 1px solid var(--gray-100); margin-bottom: 12px; }
.im-recv-header h6 { font-weight: var(--font-weight-semibold); }
.im-recv-section { padding: 0 4px 14px; }
.im-recv-grid    { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.im-recv-grid > div { display: flex; flex-direction: column; }
.im-recv-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 12px 4px 0; border-top: 1px solid var(--gray-100); margin-top: 8px; }

/* ───────────────────────────────────────────────────────────────────────
   Inventory item/lot picker — shared search-and-select widget. Canonical
   for InventoryItemLotPicker (used by Damage/AdjustCount/IssueStock/Receive)
   plus the inline pickers in ReceiveStockPanel (supplier) and
   ReturnToStockPanel (item). Global so it crosses the component boundary
   into InventoryItemLotPicker, which has no scoped CSS of its own. Divergent
   consumers keep scoped deltas (ReceiveStock .im-picker-compact + results
   border + .im-picker-row transition).
   ─────────────────────────────────────────────────────────────────────── */
.im-picker          { border: 1px solid var(--gray-300); border-radius: var(--radius-md); background: #fff; overflow: hidden; }
.im-picker-input    { position: relative; padding: 6px; border-bottom: 1px solid var(--gray-100); }
.im-picker-input i  { position: absolute; top: 50%; left: 14px; transform: translateY(-50%); color: #868e96; font-size: var(--font-size-md); pointer-events: none; }
.im-picker-input input { padding-left: 30px; }
.im-picker-results  { max-height: 220px; overflow-y: auto; }
.im-picker-row      { display: block; width: 100%; text-align: left; appearance: none; background: transparent; border: 0; border-bottom: 1px solid var(--gray-100); padding: 7px 12px; cursor: pointer; }
.im-picker-row:hover { background: var(--gray-50); }
.im-picker-name     { display: flex; align-items: center; gap: 8px; font-size: var(--font-size-md); color: var(--gray-900); }
.im-picker-cat      { background: var(--blue-100); color: var(--blue-500); padding: 0 7px; border-radius: var(--radius-pill); font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); }
.im-picker-meta     { display: flex; justify-content: space-between; color: #868e96; font-size: var(--font-size-sm); margin-top: 2px; }
.im-picker-empty    { padding: 12px; color: var(--gray-500); text-align: center; font-size: 0.82rem; }
.im-picker-empty-hint { margin-top: 6px; color: var(--gray-600); font-size: 0.78rem; line-height: 1.45; }
.im-picker-selected { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 12px; background: #f8fbff; border: 1px solid #cfe2ff; border-radius: var(--radius-md); }
.im-pane-title {
    padding: 8px 12px;
    border-bottom: 1px solid var(--gray-200);
    font-size: 0.8rem;
    font-weight: var(--font-weight-semibold);
    color: var(--gray-700);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: #fafbfc;
    display: flex;
    align-items: baseline;
    gap: 8px;
    justify-content: space-between;
}

/* ───────────────────────────────────────────────────────────────────────
   Inventory Manager — shared display primitives (round 2). The next wave of
   byte-identical .im-* bodies hoisted out of the per-panel scoped sheets under
   Shared/Inventory/ + Pages/Materials/InventoryManager.razor.css. Same
   convention as .im-recv-* / .im-picker-* above: the shared body lives here; any
   component whose copy DIFFERS keeps its own scoped rule, which still wins via
   the Blazor b-attribute (a scoped/::deep selector outranks a bare global).
   Divergent copies deliberately left scoped: InventoryInspector .im-pane-meta
   (gray-700), TransferLocationPanel .im-fifo-pill (gray) + .im-fw-lot/preview,
   .im-mov-type.return (purple on the page, gray in the inspector),
   ExportCsvPanel .im-export-option, SetupCatalogPanel .im-setup-table. The
   .im-badge family is intentionally NOT hoisted (base padding/font drift plus a
   border shorthand-vs-longhand specificity trap — see dedup notes).
   ─────────────────────────────────────────────────────────────────────── */
/* pane subtitle — pairs with .im-pane-title above (ItemGrid, CategoryTree, InventoryManager). */
.im-pane-meta {
    font-weight: var(--font-weight-normal);
    text-transform: none;
    letter-spacing: 0;
    color: var(--gray-500);
    font-size: var(--font-size-sm);
}
/* Empty-state pane — global. Adopted by ItemGrid, InventoryInspector,
   BottlingDateInspector, LabelManager, BulkWineDesk, and the Contacts and
   Compliance manager grids/inspectors via .cm-empty (which was previously only
   scoped to ContactManager, so it rendered unstyled in those child components —
   this global is the fix). Each component keeps only its divergent one-liner
   (padding or icon size) as a scoped delta. Color unified to var(--gray-500);
   padding to var(--space-xl). WHY gray-500: matches the already-shipped inventory
   empties; bh-insp/lm/bwd were #868e96 (a touch darker) — verify it reads OK. */
.nwc-empty,
.cm-empty,
.lm-empty,
.bh-insp-empty,
.bwd-empty,
.im-grid-empty,
.im-insp-empty-pane {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: var(--gray-500);
    text-align: center;
    padding: var(--space-xl);
}
.nwc-empty i,
.cm-empty i,
.lm-empty i,
.bwd-empty i {
    font-size: 2.4rem;
    margin-bottom: var(--space-sm);
    color: var(--gray-400);
}

/* ⛔ REMOVED 2026-09-02 — two per-page "drop the tree track" overrides that had stopped working.
   `.comms-log-shell` and `.tbm-shell` each redeclared .ms-workspace with THREE columns, but every
   pane in ManagerShell.razor.css is pinned to an explicit grid-column (grid:3, splitter:4,
   inspector:5). A three-track grid cannot hold a pin at column 4 or 5, so the browser appended
   implicit tracks for them and left tracks 1 and 2 EMPTY — reinstating the exact dead space these
   rules existed to remove, and adding a stray 1fr on top.

   Measured on /admin/comms/log at 1536x639 (Chrome, dev): a 1254px workspace whose panes totalled
   1008px, i.e. 246px unusable, with no element in the DOM to explain it. The pins postdate both
   rules — the file said as much ("predates that fix") without anyone re-measuring the result.

   Replaced by one general rule in ManagerShell.razor.css: `.ms-shell.ms-no-tree .ms-workspace`
   keeps all five tracks and zeroes the first two, which is pin-compatible and fixes every treeless
   page at once. ⛔ Do not re-add a per-page grid-template-columns here; it will silently fight the
   pins again. The `340px` inspector fallback in the old .tbm-shell rule was already dead — the
   razor always sets --ms-inspector-w inline, so the fallback could never apply. */
/* FIFO pill (blue) — IssueStockPanel + InventoryInspector. TransferLocation's gray pill stays scoped. */
.im-fifo-pill {
    background: var(--blue-500);
    color: #fff;
    border-radius: var(--radius-pill);
    padding: 1px 7px;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
}
/* Movement-type chip — InventoryInspector + InventoryManager. Base + 4 shared variants;
   `.im-mov-type.return` diverges (purple on the page, gray in the inspector) so it stays scoped in both. */
.im-mov-type {
    display: inline-block;
    padding: 1px 7px;
    border-radius: var(--radius-xs);
    font-size: 0.68rem;
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.im-mov-type.receive  { background: #d1f0d8; color: #1d6f33; }
.im-mov-type.issue    { background: #ffe2e2; color: #b32424; }
.im-mov-type.adjust   { background: var(--yellow-100); color: var(--yellow-700); }
.im-mov-type.transfer { background: var(--blue-100); color: var(--blue-500); }
/* Status badge base + ok low warn — ItemGrid, InventoryInspector, IssueStockPanel.
   ItemGrid is canonical (padding 1px 8px, font-size var(--font-size-xs)).
   InventoryInspector keeps a scoped font-size:0.68rem delta; IssueStockPanel keeps
   padding:1px 7px. Base sets the border shorthand; each modifier sets border-color
   longhand AFTER it at equal specificity, so source order makes the colored border
   win. LANDMINE: do not reorder, and do not re-scope the base above the modifiers. */
.im-badge {
    display: inline-block;
    padding: 1px 8px;
    border-radius: var(--radius-pill);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    border: 1px solid transparent;
}
.im-badge-ok   { background: #d1f0d8; color: #1d6f33; border-color: #b7e4c0; }
.im-badge-low  { background: #ffe2e2; color: #b32424; border-color: #ffc4c4; }
.im-badge-warn { background: var(--yellow-100); color: var(--yellow-700); border-color: #ffe28a; }
/* Framework-panel wireframe notice + entity list — TransferLocationPanel + InventoryManager.
   The page's .im-fw-step-* and TransferLocation's .im-fw-lot-* / .im-fw-preview-* are single-occurrence, stay scoped. */
.im-fw-notice {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 12px;
    background: #fff8e6;
    border: 1px solid #f0c14b;
    border-left: 3px solid #e6a817;
    border-radius: var(--radius-md);
    margin: 0 4px 16px;
    font-size: 0.8rem;
    color: #5e4400;
    line-height: 1.5;
}
.im-fw-entities {
    margin: 4px 4px 0;
    padding: 10px 12px;
    background: var(--gray-50);
    border: 1px dashed var(--gray-400);
    border-radius: var(--radius-md);
    font-size: 0.78rem;
    color: var(--gray-600);
}
.im-fw-entities strong {
    display: block;
    margin-bottom: 6px;
    color: var(--gray-700);
}
.im-fw-entities ul { margin: 0; padding-left: 18px; }
.im-fw-entities li { margin-bottom: 3px; line-height: 1.5; }
/* Setup panels — SetupCategoriesPanel + SetupItemsPanel shared base. SetupCatalogPanel's
   .im-setup-table (margin, no flex) and each panel's own toolbar variant stay scoped. */
.im-setup-panel { display: flex; flex-direction: column; height: 100%; }
.im-setup-form {
    margin: 0 2px 10px;
    padding: 10px 12px;
    background: var(--gray-50);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    flex-shrink: 0;
}
.im-setup-table {
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    overflow: hidden;
    flex: 1;
    overflow-y: auto;
}
.im-setup-empty {
    padding: 24px;
    text-align: center;
    color: var(--gray-600);
    font-size: 0.82rem;
}
.im-setup-row-inactive td { opacity: 0.55; }
.im-setup-subtext { font-size: 0.72rem; line-height: 1.3; }
/* Export/print option card (base only) — ExportPdfPanel + PrintLabelsPanel. The :has() and
   radio sub-rules stay scoped on those two panels (hoisting :has(input:checked) globally would
   outrank ExportCsvPanel's scoped .selected and bleed into it); ExportCsvPanel stays untouched. */
.im-export-option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: border-color .15s;
}

/* ───────────────────────────────────────────────────────────────────────
   Bottling date tabs — shared by BottlingDateTabsRow (DailyFlow, via
   WeekNavigatorComponent) + BottlingDateTabs (Shared, via LabQcRoundEntry).
   The two were byte-identical copy-paste forks; this is the shared family.
   NAMESPACED (not bare .tab-badge/.date-tab/.add-text): those generic names
   are independently defined with DIFFERENT bodies by SkuTabsRow and
   WeekNavigatorComponent — kept apart only by Blazor scope. A bare-name
   hoist would clobber them, so the family is .nwc-datetab-* and the rename
   is applied only inside the two true forks. BottlingDateTabsRow keeps a
   scoped @media delta (480px stack + 768px tab/badge sizing).
   ─────────────────────────────────────────────────────────────────────── */
.nwc-datetab-row   { display: flex; align-items: center; justify-content: space-between; gap: var(--space-lg); padding: 0.4rem var(--space-sm); background: linear-gradient(to bottom, var(--gray-50), var(--gray-200)); border-top: 1px solid var(--gray-300); }
.nwc-datetab-list  { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.nwc-datetab       { position: relative; display: flex; align-items: center; gap: var(--space-2xs); padding: 0.3rem var(--space-sm); padding-right: 1.4rem; border-radius: var(--radius-sm); border: 1px solid var(--gray-300); background: #fff; color: var(--gray-700); font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); cursor: pointer; transition: all 0.15s ease; white-space: nowrap; }
.nwc-datetab:hover { background: var(--gray-200); border-color: var(--gray-500); }
.nwc-datetab.active { background: var(--blue-500); color: white; border-color: var(--blue-500); }
.nwc-datetab-text  { font-weight: var(--font-weight-medium); }
.nwc-datetab-badge { position: absolute; top: -5px; right: -5px; background: var(--blue-500); color: white; border: 1px solid white; border-radius: 50%; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; font-size: 0.6rem; font-weight: var(--font-weight-semibold); box-shadow: 0 1px 2px rgba(0,0,0,0.15); }
.nwc-datetab.active .nwc-datetab-badge { background: white; color: var(--blue-500); border-color: var(--blue-500); }
.nwc-datetab-add   { display: flex; align-items: center; gap: var(--space-2xs); padding: 0.3rem var(--space-sm); border-radius: var(--radius-sm); border: 1px dashed #198754; background: transparent; color: #198754; font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); cursor: pointer; transition: all 0.15s ease; }
.nwc-datetab-add:hover { background: #198754; color: white; border-style: solid; }
.nwc-datetab-add i { font-size: 0.8rem; }
.nwc-datetab-product { display: flex; align-items: center; gap: 0.35rem; padding: var(--space-2xs) var(--space-sm); background: var(--blue-100); border: 1px solid #b6d4fe; border-radius: var(--radius-sm); font-size: var(--font-size-xs); color: var(--blue-500); white-space: nowrap; }
.nwc-datetab-product i { font-size: var(--font-size-sm); }
@media (max-width: 768px) {
    .nwc-datetab-add-text   { display: none; }
    .nwc-datetab-product span { max-width: 120px; overflow: hidden; text-overflow: ellipsis; }
}

/* ───────────────────────────────────────────────────────────────────────
   Slide-out panel header — the title/close bar shared by slide-out panels.
   Canonical body was copy-pasted byte-identically under three different
   scoped names: SlidingPanel's `.sliding-panel-header`, MaterialStatusSection's
   `.receipt-slide-header`, and _LegEditPanel's `.leg-slide-header`. Folded here.
   NAMESPACED `.nwc-slide-header` (NOT a promotion of `.sliding-panel-header`)
   to avoid colliding with any other scoped `.sliding-panel-header` definitions.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-slide-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* sm vertical (was lg, 2026-08-04): header chrome was costing ~2rem of pure air per panel
       on scroll-heavy surfaces (booking, projection). Horizontal stays 1.25rem for alignment
       with the body gutter. */
    padding: var(--space-sm) 1.25rem;
    border-bottom: 1px solid var(--gray-200);
    background: var(--gray-50);
    flex-shrink: 0;
}

/* Flush body for <SlidingPanel CssClass="sp-flush">: opt-out of the default
   .sliding-panel-body padding when the panel's inner component supplies its own
   (e.g. MaterialReceiptPanel/MaterialOrderPanel/OrderAllocationPanel .panel-body).
   A plain global matches the scoped .sliding-panel-body by class name — only the
   generated scoped sheet adds the [b-xxx] attribute, which this rule doesn't need. */
.sliding-panel.sp-flush .sliding-panel-body { padding: 0; }

.view-toggle {
    display: flex;
    gap: var(--space-2xs);
    background: var(--nwc-bg-muted);
    padding: 0.2rem;
    border-radius: var(--radius-md);
}

/* === Autocomplete input spinner/icon === */
.input-spinner,
.input-icon {
    font-size: 0.8rem;
    pointer-events: none;
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
}
/* Interactive variant (a clear-✕ etc.): the base rule's pointer-events:none exists so passive
   status icons never block clicks into the input beneath — an icon that IS the click target
   must opt back in, or its @onclick silently never fires (the "✕ doesn't clear" bug). */
.input-icon--action {
    pointer-events: auto;
    cursor: pointer;
}

/* === List-page kit (canonical scaffold for prefixed list pages: wt-, mh-, gl-, lp-, cl-, gs-, inv-, cb-, er-) === */
.nwc-list-page {
    display: flex;
    flex-direction: column;
    height: 95vh;
    overflow: hidden;
}
.nwc-list-page-header {
    align-items: center;
    display: flex;
    justify-content: space-between;
    margin-bottom: var(--space-md);
}
.nwc-list-page-title {
    margin: 0;
    font-weight: var(--font-weight-bold);
    font-size: 1.4rem;
    color: var(--slate-800);
}
.nwc-list-page-grid-area {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    border-radius: var(--radius-md);
}
.nwc-list-page-filters {
    display: flex;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
    flex-wrap: wrap;
}
@media (max-width: 900px) {
    .nwc-list-page-filters {
        flex-direction: column;
    }
}

/* Underline Tab Bar (LAYOUT-PATTERNS Pattern 14). Hoisted to a shared utility on its third
   sighting — the scoped .qc-tab (LabQc) and .tr-tab (TankMismatch) copies predate this and can
   migrate onto it later; new in-page sub-view tab strips should use these classes rather than a
   fourth scoped copy. Swaps content below, does NOT navigate. */
.nwc-tab-bar {
    display: flex;
    gap: var(--space-xs);
    border-bottom: 2px solid var(--border-default);
}
.nwc-tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    padding: var(--space-sm) var(--space-md);
    font-size: 0.9rem;
    color: var(--text-secondary);
    font-weight: 500;
    cursor: pointer;
}
.nwc-tab:hover {
    color: var(--accent-primary);
}
.nwc-tab.active {
    color: var(--accent-primary);
    font-weight: 600;
    border-bottom-color: var(--accent-primary);
}

/* Manager Shell filterbar — applied by consumers (ContactManager,
   InventoryManager, LabelManager, BulkWineDesk) inside <ms-filterbar-host>.
   Lives in site.css (not ManagerShell.razor.css) because RenderFragment
   content carries the consumer's CSS-isolation scope, not the host's. */
.ms-filterbar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    background: var(--surface-default);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
}

/* Manager Shell pane-collapse button — same RenderFragment-scope reasoning. */
.ms-pane-collapse-btn {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--gray-500);
    cursor: pointer;
    font-size: var(--font-size-md);
    padding: 2px 6px;
    margin-left: auto;
    border-radius: var(--radius-sm);
    line-height: 1;
}
.ms-pane-collapse-btn:hover { background: var(--gray-100); color: var(--gray-700); }
/* ── Utility: toolbar row (flex wrap row, gap-sm) ── */
.nwc-toolbar-row {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}
/* ── Utility: list reset ── */
.nwc-list-reset {
    list-style: none;
    margin: 0;
    padding: 0;
}
/* ── Utility: visually-hidden file input (label-as-tap-target pattern) ──
   Put this on any <InputFile> whose <label> IS the click target.

   ⛔ It lives HERE, global, on purpose — do NOT move it into a .razor.css.
   <InputFile> is a CHILD COMPONENT, so Blazor CSS isolation never stamps the
   parent's scope attribute onto the <input> it renders. A scoped `.x-input {}`
   compiles to `.x-input[b-<scope>]` and matches NOTHING — which is exactly how
   prod bug #11 shipped: the raw OS file input stayed visible at its intrinsic
   ~300px, overflowed its container by ~113px, and pushed the icon and label out
   of the box on both desktop and iPhone. A global class has no scope attribute
   to miss, so it cannot fail that way.

   The parent label needs `position: relative` so this positions against it. */
.nwc-file-input-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
}
/* ── Utility: extra-small button (one notch below Bootstrap's btn-sm) ── */
.btn-xs {
    padding: 0.15rem 0.4rem;
    font-size: var(--font-size-xs);
    line-height: 1.3;
    border-radius: var(--radius-xs);
}

/* ── Bottling status pill — shared by the History Manager grid + inspector
      (global so the two CSS-isolation scopes don't each redefine the colors). ── */
.bh-status-pill {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: var(--radius-xl);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    border: 1px solid var(--gray-300);
    background: #fff;
    color: var(--gray-700);
    white-space: nowrap;
}
.bh-status-pill.is-completed { background: var(--green-100); border-color: #198754; color: var(--green-700); }
.bh-status-pill.is-inprogress { background: var(--yellow-100); border-color: #ffc107; color: var(--yellow-700); }
.bh-status-pill.is-scheduled { background: #cff4fc; border-color: #0366d6; color: #004085; }
.bh-status-pill.is-postponed { background: #e2e3e5; border-color: var(--gray-600); color: #383d41; }
.bh-status-pill.is-canceled { background: var(--state-danger-bg); border-color: var(--red-500); color: #721c24; }
/* ── Utility: inline flex row, center-aligned, gap-sm ── */
.nwc-row-sm {
    align-items: center;
    display: flex;
    gap: var(--space-sm);
}
/* ── Utility: inline flex row, center-aligned, gap-2xs ── */
.nwc-row-2xs {
    align-items: center;
    display: flex;
    gap: var(--space-2xs);
}
/* ── Utility: flex row, space-between (no vertical alignment) ── */
.nwc-row-between {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: space-between;
}
/* ── Utility: vertical flex stack, gap-2xs ── */
.nwc-stack-2xs {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}
/* ── Utility: vertical flex stack, gap-sm ── */
.nwc-stack-sm {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}
/* ── Utility: vertical flex stack, gap-md ── */
.nwc-stack-md {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}
/* ── Utility: inline flex row, center-aligned, gap-md ── */
.nwc-row-md {
    align-items: center;
    display: flex;
    gap: var(--space-md);
}
/* ── Utility: inline flex row, center-aligned, gap-xs ── */
.nwc-row-xs {
    align-items: center;
    display: flex;
    gap: var(--space-xs);
}
/* ── Utility: flex row, center-aligned, space-between, nowrap ── */
.nwc-row-between-center {
    align-items: center;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: space-between;
}
/* ── Utility: right-aligned action cluster (margin-left auto), gap-xs ── */
.nwc-row-end {
    display: flex;
    gap: var(--space-xs);
    margin-left: auto;
}
/* ── Utility: vertical flex stack, gap-3xs (tightest) ── */
.nwc-stack-3xs {
    display: flex;
    flex-direction: column;
    gap: var(--space-3xs);
}
/* ── Utility: vertical flex stack that can shrink horizontally (min-width:0 so a
      truncating child actually truncates instead of forcing the row wider) ── */
.nwc-stack-shrink {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
/* ── Utility: dead-center flex box ── */
.nwc-center {
    align-items: center;
    display: flex;
    justify-content: center;
}
/* ── Utility: 3-slot header row — 1fr/3fr/1fr grid, centered (booking day-card headers,
   page-level Prev/[title]/Next rows). Hoisted from BookingMain/BookingDayCard .razor.css. ── */
.nwc-row-3col {
    align-items: center;
    display: grid;
    grid-template-columns: 1fr 3fr 1fr;
    justify-content: center;
    padding: 5px;
    text-align: center;
}
/* ── Utility: compact centered data table — full-width, collapsed borders with open
   left/right edges, tight 3px cells (booking load / drain-and-press tables). Hoisted from
   BookingMain/BookingLoadTable .razor.css. ── */
.nwc-table-compact {
    border: 1px solid var(--nwc-border);
    border-collapse: collapse;
    border-left: hidden;
    border-right: hidden;
    border-spacing: 0;
    width: 100%;
}
.nwc-table-compact th,
.nwc-table-compact td {
    line-height: 1;
    padding: 3px;
    text-align: center;
}
.nwc-table-compact tr:nth-child(even) {
    border: solid 1px;
}
/* ── Utility: fill-parent column — flex:1 column that can shrink (scroll-region host) ── */
.nwc-fill-col {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
}
/* ── Utility: viewport-lock column — full-height column, children scroll internally ── */
.nwc-lock-col {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
}
/* ── Utility: wrapping flex row, gap-2xs (chips, small cells) ── */
.nwc-wrap-2xs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
}
/* ── Utility: wrapping flex row, gap-sm ── */
.nwc-wrap-sm {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}
/* ── Utility: wrapping flex row, gap-lg ── */
.nwc-wrap-lg {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-lg);
}
/* ── Utility: fill-remaining-and-scroll — the scroll REGION itself, inside a bounded
   flex column. min-height:0 is the load-bearing half: without it a flex child's
   auto minimum is its content height, so the region grows past the parent and the
   page scrolls instead of the region. Pair with .nwc-lock-col / .nwc-fill-col on the
   parent. Differs from .nwc-scroll-pane, which claims a definite height:100% and
   carries padding; this one claims only the leftover space. ── */
.nwc-fill-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}
/* ── Utility: full-height scroll pane, lg padding (ManagerShell/tab body interior) ── */
.nwc-scroll-pane {
    height: 100%;
    overflow-y: auto;
    padding: var(--space-lg);
}
/* ============================================================
   Form system (canonical) — LAYOUT-PATTERNS Pattern 16. Pair with <FormField>.
   Shape: .nwc-form-grid → .nwc-form-field cells → inputs → .nwc-form-actions.
   Label identity = uppercase micro-caps (owner-chosen 2026-07-17).
   Migration: supersedes the panel-scoped .sp-form-* block in panel-layout.css;
   its 5 adopters move onto these names in Phase 1 (then .sp-form-* is deleted,
   which also clears the temporary duplication these rules introduce).
   ============================================================ */
/* minmax(0,1fr) — plain 1fr carries an `auto` minimum, so wide intrinsic input
   widths blow the grid past its container (horizontal scroll + clipped columns,
   seen live on PackagingProfilePanel 2026-07-23). Columns must be allowed to
   shrink below content width; inputs fill their cell. */
.nwc-form-grid   { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-md); }
.nwc-form-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));     gap: var(--space-md); }
@media (max-width: 640px) {
    .nwc-form-grid, .nwc-form-grid-4 { grid-template-columns: 1fr; }
    /* ⛔ Without this remap every span-2/3/4 field creates IMPLICIT auto columns, so
       the grid silently stays N-wide instead of collapsing to one — then overflows on
       a long <select>. Missed in the Pattern 16 migration: the predecessor .sp-form-*
       system remapped at its 768px step (panel-layout.css:866-871) and the sibling
       .nwc-form-row still does at the 640px step below. Reachable, not theoretical —
       SlidingPanel swaps to MobileWidth 100% under 768px, so a phone lands here with
       span-4 fields inside. `>` for specificity: the bare .nwc-form-field.span-N rules
       at :1317-1319 come LATER in source order and would otherwise win. */
    .nwc-form-grid   > .nwc-form-field.span-2,
    .nwc-form-grid   > .nwc-form-field.span-3,
    .nwc-form-grid   > .nwc-form-field.span-4,
    .nwc-form-grid-4 > .nwc-form-field.span-2,
    .nwc-form-grid-4 > .nwc-form-field.span-3,
    .nwc-form-grid-4 > .nwc-form-field.span-4 { grid-column: 1 / -1; }
}

/* Stacked field: caps label above input */
.nwc-form-field { display: flex; flex-direction: column; gap: 0.2rem; /* off-scale */ min-width: 0; }
.nwc-form-field.span-2 { grid-column: span 2; }
.nwc-form-field.span-3 { grid-column: span 3; }
.nwc-form-field.span-4 { grid-column: span 4; }

.nwc-form-field > label {
    font-weight: var(--font-weight-semibold);
    font-size: 0.82em;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin: 0;
}

.nwc-form-req  { color: var(--red-600, var(--bs-danger)); font-weight: var(--font-weight-bold); }
.nwc-form-hint { font-size: var(--font-size-xs); color: var(--text-tertiary, var(--gray-500)); }

/* Input treatment — token-driven, iOS-zoom-safe, focus ring.
   Styles whatever input the field wraps; no .form-control required inside. */
.nwc-form-field > input,
.nwc-form-field > select,
.nwc-form-field > textarea {
    min-height: var(--form-input-min-height);
    font-size: 16px; /* iOS zoom prevention */
    padding: var(--space-sm) var(--space-md);
    border: 1px solid var(--bs-border-color, var(--border-default));
    border-radius: var(--radius-md);
    -webkit-appearance: none;
}
.nwc-form-field > textarea { min-height: 80px; resize: vertical; }

/* Select caret — the rule ABOVE sets -webkit-appearance:none on <select> too, which strips the
   native dropdown indicator and leaves a Pattern 16 select visually IDENTICAL to a text input.
   Measured 2026-08-26 on the Print Stickers drawer (Chrome 1521x695 CSS px, dpr 1.25, dev): the
   two Pattern 16 selects computed background-image:none while the .form-select two blocks below
   them — same drawer — drew a caret. Bootstrap's .form-select solves this for the ~312 selects
   carrying it; the ~12 that follow Pattern 16 instead got nothing, so "which convention did you
   use" decided whether your dropdown looked like a dropdown.
   Uses the existing --select-caret-icon token (tokens.css), so the dark-theme variant comes free
   — a hardcoded #343a40 chevron would go near-invisible on a dark field.
   ⛔ Longhands, not the `background` shorthand: the shorthand resets background-color and would
   flatten the :disabled/readonly greying these inputs inherit.
   [multiple]/[size] are list boxes, not dropdowns — no caret, matching Bootstrap's own carve-out. */
.nwc-form-field > select:not([multiple]):not([size]) {
    /* pairs with the -webkit- prefixed reset above; without it Firefox draws a SECOND arrow */
    appearance: none;
    background-image: var(--select-caret-icon);
    background-repeat: no-repeat;
    background-position: right var(--space-md) center;
    background-size: 12px 12px;
    /* caret gutter = the icon plus its own margin, so a long option can't slide under it */
    padding-right: calc(var(--space-md) * 2 + 12px);
}

.nwc-form-field > input:focus,
.nwc-form-field > select:focus,
.nwc-form-field > textarea:focus {
    outline: 2px solid var(--form-input-focus-outline);
    outline-offset: var(--form-input-focus-offset);
}

/* Input + inline-create button (e.g. Lot Id + "+ New", select + gear) */
.nwc-input-group { display: flex; align-items: center; gap: 0.35rem; /* off-scale */ }
.nwc-input-group > input,
.nwc-input-group > select {
    flex: 1;
    min-width: 0; /* a flex item won't shrink below its intrinsic width without it: the button overflowed the field */
    min-height: var(--form-input-min-height);
    font-size: 16px;
    padding: var(--space-sm) var(--space-md);
    border: 1px solid var(--bs-border-color, var(--border-default));
    border-radius: var(--radius-md);
    -webkit-appearance: none;
}
.nwc-input-group > input:focus,
.nwc-input-group > select:focus {
    outline: 2px solid var(--form-input-focus-outline);
    outline-offset: var(--form-input-focus-offset);
}

/* Dense variant (Pattern 16b) — many-field forms. Add .nwc-form-dense on a wrapper
   (e.g. a panel's .sp-body) to compact every form grid inside it: tighter row
   rhythm, 30px inputs instead of the 44px touch default. Canonical adopter:
   PackagingProfilePanel (owner-requested 2026-07-23 — "spacy AND squished"). */
.nwc-form-dense .nwc-form-grid,
.nwc-form-dense .nwc-form-grid-4 { gap: var(--space-xs) var(--space-sm); }
.nwc-form-dense .nwc-form-field > label { font-size: 0.68em; }
.nwc-form-dense .nwc-form-field > input,
.nwc-form-dense .nwc-form-field > select,
.nwc-form-dense .nwc-form-field > textarea,
.nwc-form-dense .nwc-input-group > input,
.nwc-form-dense .nwc-input-group > select {
    min-height: 30px;
    padding: var(--space-2xs) var(--space-sm);
}
.nwc-form-dense .nwc-form-field > textarea { min-height: 56px; }

/* ── Pattern 16c: content-aware field row ─────────────────────────────────────
   Pattern 16's grids hand every field an equal fraction of the container. That is
   right for a two-column settings form and wrong for a dense operational one: on a
   1200px panel a 4-character tank id and a 60-character note both got ~300px, so
   short fields read as empty runway and the eye loses which fields belong together.
   This row sizes each field to the data it HOLDS, while keeping every field on one
   shared column rhythm.
   Width is chosen by the VALUE, not by the layout, so the same kind of datum keeps
   the same width across panels — that consistency is what makes a scan-read
   possible. If no class fits, prefer adding one here over a bespoke width; a width
   written twice in two components is exactly what the drift gate counts.
   ⚠ Deliberately does NOT touch font-size: `.nwc-form-field > input` is 16px to stop
   iOS from zooming the panel on focus, and these forms are used on iPads. Density
   comes from height, padding and gaps — never from shrinking input text.
   ⛔ It is a GRID, not a flex row, and that is the whole point. The first cut of this
   pattern was `display:flex` with per-field widths. It removed the stretching and
   replaced it with RAGGEDNESS: every row ended at a different arbitrary x (one at
   633px, the next at 316px, the next at 435px) with a dead gutter after each, and no
   field edge lined up with the one above it. That reads WORSE than the equal-fraction
   grid it replaced — a dense tool needs BOTH "sized to content" AND "on a shared
   rhythm". The 12 columns give the rhythm; the max-width caps give the sizing.
   ⛔ Do not "simplify" this back to a flex row. */
.nwc-form-row {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    align-items: end;
    gap: var(--space-xs) var(--space-sm);
    /* three shared row lines: label / control / hint — see the subgrid note below */
    grid-template-rows: auto auto auto;
}

/* ⚠ `align-items: end` aligns the BOTTOM OF EACH CELL, which silently breaks the moment a field
   carries a Hint — a hint sits BELOW its control, so a cell with a 50px hint lifts its control
   50px above a cell with none. Measured 2026-08-26 on the Print Stickers drawer (Chrome
   1521x695 CSS px, dpr 1.25, dev): control tops at 59 / 96 / 43px — a 53px staircase across
   three fields that are supposed to read as one row.
   It went unseen because NEITHER canonical adopter uses Hint in a row: BookingLoadRow (10 row
   fields) and BookingStyleSection (2) declare ZERO. Pattern 16c and FormField's Hint simply had
   never met.
   Subgrid puts label / control / hint on three SHARED row lines, so all three align no matter
   how many lines any one label or hint wraps to — which is what "on a shared rhythm" (the
   banner above) was always supposed to mean vertically as well as horizontally.
   ⛔ Do not "simplify" this to align-items:start — that aligns LABEL tops, and a label that
   wraps to two lines then pushes its own control down instead. The staircase just moves. */
@supports (grid-template-rows: subgrid) {
    .nwc-form-row > .nwc-form-field {
        display: grid;
        grid-template-rows: subgrid;
        grid-row: span 3;
    }
}

/* Span = rhythm (where the field STARTS — this is what the eye reads down the form).
   max-width = sizing (a 4-char field never fills a wide track). A capped field simply does not
   reach the end of its span; the next field still starts on its own column line, so the
   alignment survives the cap. */
.nwc-field-2xs  { grid-column: span 2; max-width: 6rem;    }  /* 3-4 digits — tons, temp     */
.nwc-field-xs   { grid-column: span 2; max-width: 7rem;    }  /* short code — rate, count    */
.nwc-field-sm   { grid-column: span 3; max-width: 11rem;   }  /* date, time, short enum      */
.nwc-field-md   { grid-column: span 3; max-width: 13rem;   }  /* typical select              */
.nwc-field-lg   { grid-column: span 4; max-width: 18rem;   }  /* long select, name           */
.nwc-field-xl   { grid-column: span 5; max-width: 22rem;   }  /* long OPTION text            */
.nwc-field-grow { grid-column: span 5;                     }  /* uncapped — absorbs slack    */
.nwc-field-full { grid-column: 1 / -1;                     }  /* earns its own line          */

/* Single column below the dense breakpoint — 12 tracks in a phone-width panel is confetti. */
@media (max-width: 640px) {
    .nwc-form-row { grid-template-columns: 1fr; }
    .nwc-form-row > .nwc-form-field { grid-column: 1 / -1; max-width: none; }
}

/* ── Pattern 16e: form grid keyed to its own PANEL, not the viewport ──────────
   Opt-in. Put .nwc-form-cq on the wrapper that already holds the form (usually the
   same element carrying .nwc-form-dense) and .nwc-form-grid-4 drops to two columns
   once that wrapper is too narrow to give a field its content width.

   ⛔ THE @media AT :1311 CANNOT DO THIS, and retuning its 640px is a guaranteed
   no-op. It measures the WINDOW — 1536 CSS px on Jeff's desktop — never the 652px
   form pane inside a LookupSplitPanel. MEASURED on live prod 2026-08-31 (Chrome,
   1536x695 CSS px, dpr 1.25, AddDrainAndPress, drawer OPEN and COLLAPSED): grid box
   652px, columns 157px, while a datetime-local carrying a value needs 216.2px and
   the Winery select's longest option needs 523.2px. The date rendered
   "09/01/2026 06:5" — Jeff reported it from prod.

   ⛔⛔ DO NOT key this to @container bookingpanel. That container is
   .booking-lookup-shell, MEASURED 676px COLLAPSED vs 1016px OPEN (settled — a
   reading taken inside the 0.15s width transition reports 1016px for both and lies).
   The form pane inside it is 676px in BOTH states, and that invariance is the entire
   point of the fixed-px aside at :3521. Any bookingpanel threshold in (676, 1016] —
   which includes the 900px rung used everywhere else — would flip these columns
   2<->4 on every drawer toggle while the fields themselves never move.

   900px is arithmetic, not taste: four columns need 4(216.2) + 3(8) = 888.8px before
   a date fits, so just above the rung a column is (900-24)/4 = 219px and still clears
   216.2px.

   ⭐ In grid terms this restores what 1f63b3eb (2026-08-01) deleted when it replaced
   .flexRow/.field{min-width:180px} with repeat(4, minmax(0,1fr)): the old layout
   WRAPPED when a field could not get 180px. minmax(0,1fr) has no floor ON PURPOSE
   (:1305 — a floor is what blew PackagingProfilePanel past its container on
   2026-07-23), so the floor comes back by dropping the COLUMN COUNT, never by
   setting a min-width. ⛔ Do not "simplify" this to a min-width.

   ⛔ The span remap below is MANDATORY and must use the `>` child form. A definite
   span wider than the explicit grid creates IMPLICIT auto tracks, so a span-4 in a
   2-column grid silently stays a 4-column grid and then overflows on a long <select>
   — the VineyardPickPanel bug, already fixed once on 2026-07-29. Three hosts carry a
   span-4 that is INVISIBLE in the host file: PressLegsEditor's own root element is
   <FormField Span="4"> (PressLegsEditor.razor:20). And .nwc-form-field.span-4 at
   :1319 is (0,2,0) and sits AFTER this block, so the bare form would lose on source
   order — `>` makes it (0,3,0) and order stops mattering (see the note at :3598). */
@supports (container-type: inline-size) {
    .nwc-form-cq { container-type: inline-size; container-name: formpane; }

    @container formpane (max-width: 900px) {
        .nwc-form-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
        /* span-2 needs nothing: at two tracks `span 2` already IS the full row. */
        .nwc-form-grid-4 > .nwc-form-field.span-3,
        .nwc-form-grid-4 > .nwc-form-field.span-4 { grid-column: 1 / -1; }
    }
}

/* Footer action row — right-aligned buttons */
.nwc-form-actions { display: flex; justify-content: flex-end; gap: var(--space-sm); margin-top: var(--space-md); }
/* Modifier for a footer that has to read as separated from the form above it (a tall
   scrolling create form, an embedded panel with no chrome of its own). Hoisted here
   2026-09-16 rather than written a seventh time: six .razor.css files already carry
   this exact border-top, and WineLotPanel's footer was carrying it as an INLINE
   style="border-top: 1px solid #e9ecef" -- an off-token literal that dark mode
   cannot follow. New adopters use this; the six existing copies are a separate
   dedup round. */
.nwc-form-actions-bordered { padding-top: var(--space-md); border-top: 1px solid var(--border-subtle); }

/* ── List reorder arrows ──────────────────────────────────────────────────────
   Vertical ▲/▼ pair for moving a row within a host-owned list (buttons, not
   drag — Blazor Server + iPads; position in the list IS the saved order).
   Hoisted 2026-08-04 from PressLegsEditor when TankAllocationRow needed the
   identical shape. See LAYOUT-PATTERNS.md "List reorder arrows". */
.nwc-move { display: flex; flex-direction: column; gap: 0; flex: 0 0 auto; }
.nwc-movebtn {
    border: 0;
    background: none;
    cursor: pointer;
    color: var(--nwc-text-muted);
    font-size: 0.5rem;
    line-height: 0.9;
    padding: 0 2px;
}
.nwc-movebtn:hover:not(:disabled) { color: var(--nwc-text); }
.nwc-movebtn:disabled { opacity: 0.25; cursor: default; }
/* ── Shim: Bootstrap 5.2 text-bg-* utilities ──────────────────────────────────
   The vendored bootstrap.min.css predates 5.2, so these classes resolve to
   NOTHING — a `badge text-bg-success` renders white text on a transparent
   background, i.e. invisible (found live on BinManager's status column
   2026-07-29; 8 files use them). Colors match Bootstrap 5.2's contrast pairs.
   Delete this block if the vendored Bootstrap is ever bumped to ≥5.2. */
.text-bg-primary   { color: #fff !important; background-color: var(--bs-primary) !important; }
.text-bg-secondary { color: #fff !important; background-color: var(--bs-secondary) !important; }
.text-bg-success   { color: #fff !important; background-color: var(--bs-success) !important; }
.text-bg-danger    { color: #fff !important; background-color: var(--bs-danger) !important; }
.text-bg-warning   { color: #000 !important; background-color: var(--bs-warning) !important; }
.text-bg-info      { color: #000 !important; background-color: var(--bs-info) !important; }
.text-bg-light     { color: #000 !important; background-color: var(--bs-light) !important; }
.text-bg-dark      { color: #fff !important; background-color: var(--bs-dark) !important; }

/* ── Utility: single-line truncation (overflow ellipsis) ── */
.nwc-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* ── Utility: 2-line clamp (multi-line ellipsis) ── */
.nwc-line-clamp-2 {
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
/* ── Utility: vertical separator (1px hairline divider, 14px tall).
   Covers the status/toolbar separator forks (bh/bwd/cm/im-status-sep,
   im-report-sep). Consumers needing a taller bar keep their own rule. ── */
.nwc-vsep {
    width: 1px;
    height: 14px;
    background: var(--gray-300);
}
/* ── DailyFlow component card wrapper — themed white card, sm radius + padding.
   Shared root of the 5 DailyFlow sibling components (Cola/Label/Winemaking/
   Lab/CompositionSnip). Distinct from .nwc-card (gradient stat card). The
   #fff source literal is mapped to --surface-default so the card themes in
   dark mode. ── */
.nwc-flow-card {
    background: var(--surface-default);
    border-radius: var(--radius-sm);
    padding: var(--space-sm);
}
/* ── Title button: clickable header-title affordance (DailyFlow Cola/Label).
   Hover tint left as the literal #e7f3ff (off-scale, between --blue-100 and
   white) to keep the affordance pixel-identical post-hoist. ── */
.nwc-title-btn {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    color: var(--gray-700);
    background: none;
    border: none;
    padding: var(--space-2xs) var(--space-sm);
    margin: -0.25rem -0.5rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    transition: all 0.15s ease;
}
.nwc-title-btn:hover { background: #e7f3ff; color: var(--blue-500); }
.nwc-title-btn i { font-size: var(--font-size-lg); }
/* ── Packaging spec-library family (Glass/Capsule/Closure) ──────────────
   Hoisted from gl-* / cl-* mirrors. The grid thumb / detail panel / usage list
   render inside NwcDataGrid <Template> and SlidingPanel RenderFragments, so a
   global is the reliable reach (same pattern as .nwc-upload-section on these
   pages). filter-select keeps the off-scale 0.8rem literal verbatim. ── */
.nwc-filter-select { width: auto; min-width: 130px; font-size: 0.8rem; }
.nwc-lib-thumb { width: 36px; height: 36px; object-fit: contain; border-radius: var(--radius-xs); }
.nwc-lib-detail { padding: var(--space-md); }
.nwc-lib-detail-section { margin-bottom: var(--space-xl); }
.nwc-usage-item { padding: var(--space-xs) 0; border-bottom: 1px solid var(--slate-100); }
.nwc-usage-item:last-child { border-bottom: none; }
.nwc-usage-name { font-size: var(--font-size-md); font-weight: var(--font-weight-semibold); color: var(--slate-800); }
.nwc-usage-meta { font-size: var(--font-size-sm); color: var(--slate-500); }
/* ── Utility: autocomplete input wrapper (relative flex, reserves icon space) ── */
.nwc-autocomplete-input {
    position: relative;
    display: flex;
    align-items: center;
}
.nwc-autocomplete-input input {
    padding-right: 28px;
}
/* ── Utility: autocomplete dropdown item (full-width column-flex button) ── */
.nwc-autocomplete-item {
    display: flex;
    flex-direction: column;
    padding: 6px 12px;
    cursor: pointer;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
}
.nwc-autocomplete-item:hover,
.nwc-autocomplete-item.active {
    background-color: var(--bs-primary);
    color: white;
}
.nwc-autocomplete-item.disabled {
    cursor: default;
    font-size: 0.8rem;
}
.nwc-autocomplete-item.disabled:hover {
    background-color: transparent;
    color: var(--bs-secondary);
}
/* Section header inside an autocomplete menu (e.g. "Previously used by this client").
   Not a .nwc-autocomplete-item — it is never focusable and must not take the active highlight. */
.nwc-autocomplete-header {
    padding: 4px 12px;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--bs-secondary);
}
/* ContactsBusinessPicker row anatomy (name line + detail line). Global, NOT scoped to the
   picker: ItemTemplate rows are authored in the CALLER's .razor, so they carry the caller's
   CSS-isolation scope — scoped rules in ContactsBusinessPicker.razor.css can never reach them
   (hoisted from there 2026-07-21; first ItemTemplate consumer = AddProjectionPanel). */
.cb-picker-name {
    font-weight: var(--font-weight-medium);
}
.cb-picker-detail {
    display: flex;
    gap: var(--space-md);
    font-size: 0.8rem;
}
/* Row variant (e.g. TankAutocomplete): horizontal layout, space-between */
.nwc-autocomplete-item--row {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
}
/* ── Utility: autocomplete dropdown menu (absolute popup container). Compose with .dropdown-menu; override max-height per component. ── */
.nwc-autocomplete-menu {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: var(--z-slot-badge);
    max-height: 240px;
    overflow-y: auto;
    margin-top: 2px;
    padding: 4px 0;
    font-size: 0.875rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--radius-sm);
    background: var(--bs-body-bg);
    box-shadow: var(--shared-autocomplete-dropdown-shadow);
}
/* ── Utility: OCR scan toggle + disabled state (Permit/Cola/Label scan panels) ── */
.nwc-ocr-toggle {
    padding: var(--space-md);
    background: var(--gray-50);
    border-radius: var(--radius-lg);
    border: 1px solid var(--gray-200);
}
.nwc-ocr-toggle .form-check-input {
    width: 2.5em;
    height: 1.25em;
    cursor: pointer;
}
.nwc-ocr-toggle .form-check-input:checked {
    background-color: var(--green-600);
    border-color: var(--green-600);
}
.nwc-ocr-toggle .form-check-label {
    cursor: pointer;
    font-weight: var(--font-weight-medium);
}
.nwc-ocr-disabled {
    background: var(--gray-50);
    border-radius: var(--radius-xl);
    padding: var(--space-2xl);
}
.nwc-ocr-disabled i {
    opacity: 0.6;
}
.nwc-ocr-disabled p {
    color: var(--gray-600);
}
/* ── Utility: admin section card (surface panel with border + padding; Copilot admin pages) ── */
.nwc-admin-card {
    background: var(--white);
    border: 1px solid var(--nwc-border, var(--gray-300));
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    margin-bottom: var(--space-xl);
}
/* ── Utility: danger callout box (icon + message, danger tokens; delete/blocked panels) ── */
.nwc-callout-danger {
    display: flex;
    gap: var(--space-sm);
    align-items: flex-start;
    padding: var(--space-sm) var(--space-md);
    background: var(--state-danger-bg);
    color: var(--state-danger-fg);
    border: 1px solid var(--state-danger-fg);
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
}
.nwc-callout-danger i {
    flex: 0 0 auto;
    margin-top: 0.1em;
}
/* ── Utility: inline "type name" edit button (transparent, hover-highlight; DailyFlow packaging) ── */
.nwc-type-name-btn {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--gray-600);
    min-width: 70px;
    background: none;
    border: none;
    padding: 0.1rem var(--space-2xs);
    margin: -0.1rem -0.25rem;
    border-radius: var(--radius-xs);
    cursor: pointer;
    text-align: left;
    transition: all 0.15s ease;
}
.nwc-type-name-btn:hover {
    background: var(--gray-200);
    color: var(--blue-500);
}
/* ── Utility: horizontal form row (no-wrap flex, tight gap; AVA relationship panels) ── */
.nwc-ava-row {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    margin: 5px;
    gap: 3px;
}
/* ── Utility: every direct child is a dead-centered flex box (header rows) ── */
.nwc-center-kids > * {
    align-items: center;
    display: flex;
    justify-content: center;
}
/* ── LabelCola library cards (ColaCardItem / LabelCardItem) ───────────────
   Shared chrome for the two library-card components. Composes with Bootstrap
   .card kept in markup (word-wrap/position:relative come from there). The
   components deliberately stay separate — action surfaces differ — so their
   divergent rules (.card-images, COLA detail rows, fanciful spacing) remain
   in each component's scoped file. */
.nwc-lib-card {
    background: var(--nwc-bg);
    border: 1px solid var(--nwc-border-light);
    border-radius: var(--radius-lg);
    box-shadow: var(--label-card-shadow);
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
    transition: box-shadow 0.15s, transform 0.15s;
}
.nwc-lib-card:hover {
    box-shadow: var(--label-card-shadow-hover);
    transform: translateY(-1px);
}
.nwc-lib-card.inactive {
    background: var(--nwc-bg-subtle);
    border-color: var(--red-500);
    border-style: dashed;
    opacity: 0.7;
}
.nwc-lib-card.inactive .nwc-lib-card-title::after {
    color: var(--red-500);
    content: " (Inactive)";
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
}
.nwc-lib-card-image {
    background: var(--nwc-bg-subtle);
    flex: 1 1 50%;
    height: 140px;
    max-width: 50%;
    object-fit: contain;
    transition: transform 0.15s, opacity 0.15s;
    width: 50%;
}
.nwc-lib-card-image.clickable { cursor: pointer; }
.nwc-lib-card-image.clickable:hover {
    opacity: 0.92;
    transform: scale(1.04);
}
.nwc-lib-card-image:only-child {
    flex-basis: 100%;
    max-width: 100%;
    width: 100%;
}
.nwc-lib-card-body {
    flex: 1;
    min-width: 0;
    padding: var(--space-md);
}
.nwc-lib-card-title {
    color: var(--nwc-text);
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    line-height: 1.3;
    margin-bottom: var(--space-2xs);
}
.nwc-lib-card-client {
    color: var(--blue-500);
    font-size: 0.8rem;
    font-weight: var(--font-weight-medium);
    margin-bottom: 0.4rem;
}
.nwc-lib-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-bottom: 0.35rem;
}
.nwc-lib-meta-tag {
    background: var(--nwc-bg-muted);
    border-radius: var(--radius-xs);
    color: var(--nwc-text);
    font-size: 0.72rem;
    line-height: 1.4;
    padding: 0.15rem var(--space-sm);
    white-space: nowrap;
}
.nwc-lib-card-fanciful {
    color: var(--nwc-text-muted);
    font-size: 0.78rem;
    font-style: italic;
}
.nwc-lib-card-actions {
    align-items: center;
    background: var(--nwc-bg-subtle);
    border-top: 1px solid var(--nwc-border-light);
    display: flex;
    gap: 0.4rem;
    padding: var(--space-sm) var(--space-md);
}
.nwc-lib-card-actions .btn {
    font-size: 0.72rem;
    padding: 0.2rem var(--space-sm);
}
.nwc-lib-refs-badge {
    background: var(--nwc-text-muted);
    border-radius: var(--radius-xs);
    color: var(--text-on-inverse);
    font-size: var(--font-size-xs);
    margin-left: auto;
    padding: 0.15rem 0.45rem;
}
@media (max-width: 600px) {
    .nwc-lib-card-actions { flex-wrap: wrap; }
    .nwc-lib-refs-badge {
        margin-left: 0;
        margin-top: var(--space-sm);
        text-align: center;
        width: 100%;
    }
}
/* ── Shared tree pane (VineyardTree / CategoryTree) ── */
.nwc-tree-list {
    flex: 1;
    font-size: var(--font-size-md);
    list-style: none;
    margin: 0;
    overflow-y: auto;
    padding: 6px 4px;
}

.nwc-tree-item {
    align-items: center;
    border-radius: var(--radius-sm);
    color: var(--gray-700);
    cursor: pointer;
    display: flex;
    gap: 8px;
    padding: 5px 10px;
    transition: background 0.08s, color 0.08s;
}

.nwc-tree-item i {
    color: var(--text-muted);
    font-size: var(--font-size-md);
}

.nwc-tree-item:hover {
    background: var(--gray-100);
    color: var(--blue-500);
}

.nwc-tree-item.active {
    background: var(--blue-100);
    color: var(--blue-500);
    font-weight: var(--font-weight-semibold);
}

.nwc-tree-item.active i {
    color: var(--blue-500);
}

.nwc-tree-count {
    background: var(--gray-200);
    border-radius: var(--radius-pill);
    color: var(--gray-700);
    font-size: var(--font-size-xs);
    min-width: 22px;
    padding: 1px 7px;
    text-align: center;
}

.nwc-tree-item.active .nwc-tree-count {
    background: var(--blue-500);
    color: var(--text-on-brand);
}

.nwc-tree-empty {
    color: var(--gray-500);
    font-size: 0.8rem;
    padding: 8px 12px;
}

/* ── Utility: flex-fill + single-line ellipsis truncation ── */
.nwc-fill-truncate {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Utility: wrapping filter/control row (gap-sm, bottom margin) ── */
.nwc-filter-row {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
}
/* ── LabQc patrol grids (AnalysisTabContent / O2Co2FilterTabContent) ──────
   Shared chrome for the twin QC grid tabs; markup composes these WITH the
   original analysis-*/o2co2-* classes, whose scoped variant rules (fixed-col
   widths 100px vs 130px, metadata/from-other-date tints, iPad touch sizes)
   keep winning at higher specificity. ALL input/select rules stay scoped —
   they are an !important cascade these globals must not enter. */
.nwc-labqc-section {
    background: var(--surface-default);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-md);
    overflow: hidden;
}
.nwc-labqc-section-title {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    margin: 0;
}
.nwc-labqc-grid {
    background: var(--surface-default);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-sm);
    display: flex;
    overflow: hidden;
}
.nwc-labqc-fixed-col {
    background: var(--nwc-bg-muted);
    border-right: 2px solid var(--labqc-grid-border-strong);
    flex-shrink: 0;
    z-index: var(--z-base);
}
.nwc-labqc-scroll {
    flex: 1;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-behavior: smooth;
    scroll-snap-type: x proximity;
}
.nwc-labqc-cell {
    align-items: center;
    background: var(--surface-default);
    border-bottom: 1px solid var(--labqc-grid-border);
    border-right: 1px solid var(--labqc-grid-border);
    box-sizing: border-box;
    display: flex;
    font-size: 0.78rem;
    height: 32px;
    justify-content: center;
    padding: 0 2px;
    width: 100px;
}
.nwc-labqc-cell.header-cell {
    background: var(--nwc-bg-muted);
    border-bottom: 2px solid var(--labqc-grid-border-strong);
    font-weight: var(--font-weight-semibold);
    height: 32px;
    position: relative;
}
/* Parameter-name cells in the fixed column (only the decls that differ from
   the base cell; the 100px/130px widths stay page-scoped). */
.nwc-labqc-fixed-col .nwc-labqc-cell {
    background: var(--nwc-bg-muted);
    color: var(--gray-700);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    padding-left: var(--space-sm);
    padding-right: var(--space-2xs);
    text-align: left;
}

/* ── Utility: page/section header row (flex, space-between, center) ── */
.nwc-page-header {
    align-items: center;
    display: flex;
    justify-content: space-between;
}
/* FromIntake decoration: divider + spacing under the header row. Compose with .nwc-page-header. */
.nwc-page-header--bordered {
    margin-bottom: var(--space-lg);
    padding-bottom: var(--space-md);
    border-bottom: 1px solid var(--border-default);
}
/* Type-B header: left-aligned title + inline controls (LabelMain, ColaMain). Compose with .nwc-page-header. */
.nwc-page-header--start {
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: var(--space-md);
    margin-bottom: var(--space-lg);
}
/* Left cluster of a header row: icon + title text (+ inline badges). */
.nwc-header-title {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-medium);
}
/* Right cluster of a header row: action buttons. */
.nwc-header-actions {
    display: flex;
    gap: var(--space-sm);
}
/* Page-level sticky tier: pins an element to the top of its scroll container.
   Reused by the schedule head and <NwcAppHeader> (and any future page header). */
.nwc-sticky-top {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
}
/* ── Document-review detail layout: main column + 22rem aside, collapses on narrow.
   Shared by the BOL / Case-Good BOL review pages. ── */
.nwc-two-col {
    display: grid;
    grid-template-columns: 1fr 22rem;
    gap: var(--space-xl);
}
@media (max-width: 1000px) {
    .nwc-two-col {
        grid-template-columns: 1fr;
    }
}
/* Uppercase muted section subheading inside a review/detail body. */
.nwc-section-h {
    font-size: 0.95rem;
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-600);
    margin-top: var(--space-md);
}
/* Two-column label/value meta list (dt = label, dd = value). */
.nwc-meta-dl {
    display: grid;
    grid-template-columns: 9rem 1fr;
    gap: var(--space-2xs) var(--space-lg);
    margin: 0;
}
.nwc-meta-dl dt {
    color: var(--gray-600);
    font-weight: var(--font-weight-normal);
}
.nwc-meta-dl dd {
    margin: 0;
}
/* Monospace archived-file-path block (breaks long S:\ paths). */
.nwc-archive-path {
    display: block;
    word-break: break-all;
    background: var(--gray-50);
    padding: 0.4rem var(--space-sm);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-md);
}
/* When a NwcDataGrid is dropped inside a .nwc-list-page scaffold, it must
   fill the grid-area and let .table-desktop be the only scrollbar. */
.nwc-list-page .nwc-datagrid {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
}
.nwc-list-page .table-desktop {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: auto;
}
.nwc-list-page-dl {
    display: grid;
    grid-template-columns: 7rem 1fr;
    gap: 0.3rem var(--space-md);
    margin: 0;
    font-size: 0.875rem;
}
.nwc-list-page-dl dt {
    font-weight: var(--font-weight-semibold);
    color: var(--slate-500);
    white-space: nowrap;
}
.nwc-list-page-dl dd {
    margin: 0;
    color: var(--slate-800);
}
.nwc-list-page-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}
.nwc-list-page-section-title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wider);
    color: var(--indigo-600);
    border-bottom: 2px solid var(--slate-200);
    padding-bottom: var(--space-xs);
    margin-bottom: 0.625rem;
}

/* === DailyFlow component header (shared 6-decl block across 7 components) === */
.nwc-component-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-sm);
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--nwc-border-light);
}

/* === DailyFlow component header title (shared 6-decl block across 5 components) === */
.nwc-component-header-title {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    color: var(--gray-700);
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

/* === DailyFlow section header (shared 12-decl block across 3 components) === */
.nwc-section-header {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    color: var(--bs-primary);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
    margin-bottom: 0.4rem;
    padding-bottom: 0.15rem;
    border-bottom: 1px solid var(--bs-primary);
    opacity: 0.9;
}

/* === Batch 10 shared classes (hoisted byte-identical .razor.css bodies) ===== */

/* Collapsible section/run header: gradient bar, click-to-toggle. Compose WITH
   the page's own .run-header/.section-header — the scoped :hover + icon rules
   keep winning at higher specificity. (BottlingRunHeader + DailyFlowPanel.) */
.nwc-collapse-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--space-sm) var(--space-md);
    background: linear-gradient(to bottom, var(--gray-50), var(--gray-200));
    border-bottom: 1px solid var(--gray-300);
    cursor: pointer;
    user-select: none;
    transition: background 0.15s ease;
}

/* Centered loading/overlay pane with generous padding. (NwcDataGrid +
   BottlingHistoryGrid.) */
.nwc-loading-center {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-3xl);
}

/* Fact list <dl>: stacked label/value rows in a subtle bordered box. Per-panel
   dt basis + dd word-break stay scoped on the page's own .dc-facts/.dp-facts. */
.nwc-facts-dl {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    margin: 0;
    padding: var(--space-sm) var(--space-md);
    background: var(--surface-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
/* Each fact row: baseline-aligned label/value pair. */
.nwc-facts-dl > div {
    display: flex;
    gap: var(--space-sm);
    align-items: baseline;
}

/* Confirm/detail-panel shared vocabulary (DeleteClientBookings/DeleteVarietalProjection twins). */
.nwc-panel-lead {
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-md);
    color: var(--text-primary);
}
.nwc-panel-note {
    margin: 0;
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
}
/* Right-aligned footer action cluster, gap-sm. */
.nwc-actions-end {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-sm);
}
/* Detail-panel action row: right-aligned with vertical padding (Contacts detail twins). */
.nwc-detail-actions {
    display: flex;
    justify-content: flex-end;
    padding: var(--space-sm) 0;
    margin-bottom: var(--space-2xs);
}
/* Responsive auto-fit card grid (Copilot admin editor twins). */
.nwc-editor-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--space-md);
}

/* DailyFlow packaging line-item row. Compose WITH .packaging-item — the variant
   .date-specific/.run-default/.from-catalog rules stay scoped. */
.nwc-packaging-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.35rem var(--space-sm);
    margin-left: var(--space-sm);
    background: var(--gray-50);
    border-radius: var(--radius-xs);
    margin-bottom: 0.2rem;
}

/* Full-viewport dim backdrop behind LabQc modals. site.css loads after
   bootstrap, so this wins the .modal-backdrop background/z-index by source
   order (MaterialSelector + LabQcRoundEntry). */
.nwc-modal-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: var(--overlay-backdrop-dim);
    z-index: var(--z-labqc-backdrop);
}

/* Centered dialog host above an .nwc-modal-backdrop — the shape WineLotManagerPage's
   .wlm-newlot-host established, hoisted for hosts embedding a Panel component in a
   mid-viewport dialog (booking's Create-in-Innovint host, and .wlm-newlot-host itself
   whenever that page is next touched). Apply alongside the consumer's own class. */
.nwc-dialog-host {
    position: fixed;
    top: 8vh;
    left: 50%;
    transform: translateX(-50%);
    width: min(680px, 92vw);
    max-height: 84vh;
    overflow-y: auto;
    background: var(--white);
    border-radius: 6px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
    /* Above --z-modal (1060): these dialogs open OVER a SlidingPanel host (the booking's
       Create-in-Innovint flow). The manager page's 1055 original only worked because no
       panel was open there. */
    z-index: var(--z-dialog-host, 1080);
    padding: 0.75rem;
}

/* Backdrop variant for the same situation — the base .nwc-modal-backdrop sits at 1040,
   below a SlidingPanel, so its dim layer wouldn't cover the panel the dialog floats over. */
.nwc-modal-backdrop--over-panel {
    z-index: var(--z-dialog-backdrop, 1070);
}

/* === Batch 15 shared classes (cross-file hoists of byte-identical scoped
   .razor.css bodies). Each is applied ALONGSIDE the consumer's own class so
   sibling scoped rules (:hover, .active, @media, descendant `h6`/`.btn`, etc.)
   keep matching at their original specificity. site.css loads after bootstrap,
   so these single-class (0,1,0) rules win source-order ties vs vendor globals
   like `.dropdown-item`. ============================================== */

/* Invisible full-cover native file input overlaid on a styled drop zone/button.
   (Contracts upload-button + ColaScanPanel upload-input-overlay.) */
.nwc-file-input-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* Full-width left-aligned button inside an autocomplete/search dropdown. Keeps
   the consumer's `.dropdown-item` class so the scoped `:hover` sibling still
   matches (DocumentInbox di-search-dropdown + ReceiveShipment rs-search-dropdown). */
.nwc-search-dropdown-item {
    padding: var(--space-xs) var(--space-md);
    font-size: var(--font-size-md);
    border: none;
    background: none;
    width: 100%;
    text-align: left;
    cursor: pointer;
}

/* Bordered thumbnail image (max 130px). Keeps the consumer's cola-img/card-img
   class so the scoped :hover + @media max-width overrides still win. (DailyFlow
   ColaComponent + LabelComponent.) */
.nwc-thumb-img {
    width: 100%;
    max-width: 130px;
    height: auto;
    border-radius: var(--radius-sm);
    border: 1px solid var(--gray-300);
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

/* Small uppercase "primary source" pill (green success bg). (LabQc
   MaterialSelector .current-badge + ProfileMaterialCard .primary-badge.) */
.nwc-source-badge {
    font-size: 0.55rem;
    font-weight: var(--font-weight-bold);
    padding: 0.1rem 0.3rem;
    background: var(--labqc-success-fg);
    color: var(--text-on-brand);
    border-radius: var(--radius-xs);
    letter-spacing: 0.5px;
}

/* Section header with a 2px bottom rule. Distinct from the gradient
   `.nwc-section-header` (DailyFlow). Keeps the consumer's `.section-header`
   class so scoped `.section-header h6` / `.section-header .btn` still match.
   (LabQc CapacityTabContent + PackagingQcSection.) */
.nwc-section-header-bordered {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-sm);
    padding-bottom: var(--space-sm);
    border-bottom: 2px solid var(--gray-200);
}

/* Copilot/admin form-group field label. Applied directly to each `<label>`
   (replaces the `.form-group label` descendant selector). (Admin
   CopilotCorrections + CopilotTools.) */
.nwc-admin-field-label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--nwc-text-muted, var(--gray-600));
    margin-bottom: 0.2rem;
    display: block;
}

/* Right-aligned footer button row with a top divider. (Winemaking
   RecurringEventPanel + WinemakingSchedulePanel.) */
.nwc-button-row {
    display: flex;
    flex-direction: row;
    justify-content: flex-end;
    gap: 10px;
    border-top: 1px solid var(--nwc-border);
}

/* Visually-hidden native checkbox (custom-styled label drives the visual).
   (ScheduledEmailSchedulePanel .ses-day + EmailLabelComponent .recipient-chip.) */
.nwc-hidden-checkbox {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}

/* ── batch-16 shared utilities (cross-file hoists) ── */

/* Screen-reader-only text: present in the accessibility tree, off the screen. Third copy of this
   exact 7-property rule (MaterialsWorkbook `.mw-sr-only`, WorksheetSurface `.sheet-sr-only`) —
   hoisted 2026-09-02 rather than pasted a third time, which ratchets the duplication floor DOWN.
   Use for a label whose visible text is already supplied by a nearby heading. */
.nwc-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Clickable disclosure/section header — <summary> or a section toggle.
   Applied directly to the element (replaces `.wt-summaries summary` /
   `.gl-import-header`); scoped `:hover` variants stay in their components.
   (Harvest WeightTags + Packaging GlassLibrary.) */
.nwc-disclosure-header {
    font-size: 0.8rem;
    font-weight: var(--font-weight-semibold);
    color: var(--slate-600);
    cursor: pointer;
    padding: var(--space-2xs) 0;
    user-select: none;
}

/* Uppercase caps field-label (0.82em, letter-spaced). Applied directly to
   each label (replaces `.ses-field > label` / `.frp-title`).
   (Calendar ScheduledEmailSchedulePanel + Notifications FeatureRecipientPanel.) */
.nwc-field-caps-label {
    font-weight: var(--font-weight-semibold);
    font-size: 0.82em;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* Small status badge shape (color comes from a sibling `.wr-status-*`).
   Replaces the per-component `.wr-status-badge` duplicated for CSS isolation.
   (Bottling WorkRequests DetailView + ReviewPanel.) */
.nwc-status-badge-sm {
    font-size: 10px;
    padding: 2px 8px;
    border-radius: var(--radius-lg);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

/* === Contact Manager (page-namespaced, hoisted from ContactManager.razor.css)
   ====================================================================
   Hoisted out of scoped CSS after the ManagerShell extraction (commit acda0d84)
   broke ::deep reach-in from ContactManager into its child grid/inspector
   components: ContactManager.razor no longer emits a root DOM element of its
   own, so the b-* scope attribute never lands on an ancestor of .cm-grid-wrap
   et al. (which are rendered by PeopleGrid / BusinessesGrid / InvitesGrid /
   StaleAccessClassGrid / OrgChartPane / PersonInspector / BusinessInspector /
   InviteInspector / ContactCleanupPanel — none of which carry their own
   .razor.css). The cm-* prefix keeps these page-namespaced even as globals. */

/* ── Grid (people / businesses) ────────────────────────── */
.cm-grid-wrap {
    flex: 1;
    overflow: auto;
    min-height: 0;
}

.cm-grid-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.82rem;
}

.cm-grid-table thead th {
    position: sticky;
    top: 0;
    background: var(--surface-subtle);
    border-bottom: 1px solid var(--gray-200);
    padding: 6px 10px;
    text-align: left;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-600);
    font-weight: var(--font-weight-semibold);
}

.cm-grid-table tbody td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--gray-100);
    vertical-align: middle;
}

.cm-grid-row { cursor: pointer; }
.cm-grid-row:hover td { background: var(--surface-muted); }
.cm-grid-row.selected td {
    background: var(--blue-100);
    box-shadow: inset 3px 0 0 var(--blue-500);
}
.cm-grid-name { font-weight: var(--font-weight-medium); color: var(--gray-900); }
.cm-grid-small { color: var(--gray-700); }
/* One-line cell with ellipsis — pair with a title attribute carrying the full value. */
.cm-cell-clip { max-width: 160px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cm-cell-clip--wide { max-width: 220px; }  /* identity/primary cells that earn more room */
.cm-num { text-align: right; font-variant-numeric: tabular-nums; }

.cm-grid-edit-cell { padding: 2px 6px !important; }
.cm-grid-edit-cell .iec-read {
    appearance: none;
    background: transparent;
    border: 1px dashed transparent;
    padding: 2px 4px;
    width: 100%;
    text-align: left;
    color: inherit;
    cursor: text;
    border-radius: var(--radius-xs);
}
.cm-grid-edit-cell .iec-read:hover { border-color: var(--gray-400); background: var(--surface-default); }
.cm-grid-edit-cell .iec-input { padding: 2px 6px; height: auto; }

/* --- Business-scoped contact fallback ------------------- */
/* The Email/Phone cells show the PERSONAL row (ContactsBusinessId == null), because that is what
   the inline editor writes. When a person has no personal row but does have a business-scoped one
   -- their address at the business we know them through -- the value rides in as the
   InlineEditCell placeholder so the cell is never misleadingly blank. Placeholder text is normally
   an "add" affordance (accent + italic), so it is restyled here to read as data we actually hold;
   .cm-scope-tag beside it names whose context it belongs to.
   Three classes deliberately: that beats InlineEditCell's own scoped .iec-read-empty, which is a
   class plus a CSS-isolation attribute and therefore specificity 2. */
.cm-grid-edit-cell .cm-scoped-fallback .iec-read-empty {
    color: var(--text-secondary);
    font-style: normal;
}
.cm-grid-edit-cell .cm-scoped-fallback .iec-read-empty:hover {
    text-decoration: none;
}
.cm-scope-tag {
    display: inline-block;
    margin-left: var(--space-2xs);
    padding: 0.05rem 0.4rem;
    font-size: 0.62rem;
    font-weight: var(--font-weight-medium);
    border: 1px solid var(--gray-300);
    border-radius: 9px;
    background: var(--surface-subtle);
    color: var(--text-secondary);
    /* A long business name must ellipsize rather than push the Phone column out of the grid --
       same reasoning as .cm-gap-badge-header below. */
    max-width: 9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
}

/* ── Gap badges ────────────────────────────────────────── */
.cm-gap-badge {
    display: inline-block;
    margin-left: var(--space-2xs);
    padding: 0.05rem 0.4rem;
    font-size: 0.62rem;
    border-radius: 9px;
    font-weight: var(--font-weight-medium);
    background: var(--state-warning-bg);
    color: var(--state-warning-fg);
    text-transform: lowercase;
    letter-spacing: 0;
}
.cm-gap-badge-header {
    margin-left: 0.4rem;
    background: var(--state-danger-bg);
    color: var(--state-danger-fg);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.6rem;
    /* Belt-and-braces with GapKindDisplay.Short(): even if a long label ever reaches this badge,
       it shrinks and ellipsizes rather than pushing a sibling out of the pane. An unbroken word
       otherwise resolves min-width:auto to its full min-content width and refuses to shrink. */
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Sub-tabs (inspector) ──────────────────────────────── */
.cm-subtabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    padding: 4px 8px 0;
    border-bottom: 1px solid var(--gray-200);
    background: var(--surface-subtle);
}
.cm-subtab {
    appearance: none;
    background: transparent;
    border: 1px solid transparent;
    border-bottom: none;
    padding: 4px 10px;
    font-size: 0.72rem;
    color: var(--gray-700);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    cursor: pointer;
}
.cm-subtab:hover { background: var(--surface-default); color: var(--gray-900); }
.cm-subtab.active {
    background: var(--surface-default);
    border-color: var(--gray-300);
    border-bottom-color: var(--surface-default);
    margin-bottom: -1px;
    color: var(--text-link);
    font-weight: var(--font-weight-semibold);
}

.cm-inspector-body {
    padding: 10px 12px;
    overflow-y: auto;
    flex: 1;
    font-size: 0.82rem;
}

.cm-kv {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 4px 10px;
    margin: 6px 0;
}
.cm-kv dt { color: var(--text-muted); font-weight: var(--font-weight-medium); }
/* overflow-wrap: unbroken tokens (Firebase UIDs, emails) must wrap, not spill out of the pane. */
.cm-kv dd { margin: 0; color: var(--gray-900); overflow-wrap: anywhere; }

.cm-sync-head { padding: 6px 0 10px; border-bottom: 1px solid var(--gray-100); }
.cm-sync-kv {
    display: grid;
    grid-template-columns: 130px 1fr;
    gap: 4px 10px;
    margin: 4px 0;
    font-size: 0.78rem;
}
.cm-sync-kv dt { color: var(--text-muted); font-weight: var(--font-weight-medium); }
.cm-sync-kv dd { margin: 0; color: var(--gray-900); font-family: var(--bs-font-monospace, ui-monospace, monospace); }

/* ── Org chart (Step 8) ────────────────────────────────── */
.cm-org-list {
    list-style: none;
    margin: 0;
    padding: 6px;
    font-size: 0.82rem;
}

.cm-org-group {
    margin-bottom: 12px;
    background: var(--surface-subtle);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.cm-org-bond {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 6px 12px;
    background: var(--gray-100);
    border-bottom: 1px solid var(--gray-200);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-700);
}

.cm-org-bond-num { font-weight: var(--font-weight-semibold); color: var(--text-link); }
.cm-org-cleanup-tag { color: var(--state-warning-fg); }

.cm-org-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    cursor: pointer;
    border-bottom: 1px solid var(--gray-100);
}

.cm-org-row:last-child { border-bottom: none; }
.cm-org-row:hover { background: var(--surface-muted); }

.cm-org-row.selected {
    background: var(--blue-100);
    box-shadow: inset 3px 0 0 var(--blue-500);
}

.cm-org-row-bh { background: var(--surface-default); font-weight: var(--font-weight-medium); }
.cm-org-row-bh.selected { background: var(--blue-100); }

.cm-org-row-child { padding-left: 28px; background: var(--surface-default); }
.cm-org-row-orphan { padding-left: 28px; background: var(--surface-muted); }
.cm-org-row-missing { padding: 6px 12px; background: var(--surface-default); font-style: italic; }

.cm-org-tree-rail {
    display: inline-block;
    width: 12px;
    height: 1em;
    border-left: 2px solid var(--gray-400);
    margin-right: 4px;
    vertical-align: middle;
}

/* ── Access class (ADR_001 axis 1, D4 read-only) ─────────── */
.cm-access-class {
    margin: 0 0 12px;
    padding: 8px 10px;
    background: var(--surface-subtle);
    border: 1px solid var(--gray-200);
    border-left: 3px solid var(--gray-500);
    border-radius: var(--radius-sm);
}

.cm-access-class-header {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-bottom: 4px;
}

.cm-access-class-title {
    font-size: 0.72rem;
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-700);
}

.cm-access-class-axis {
    font-size: var(--font-size-2xs);
    color: var(--text-muted);
    font-style: italic;
}

.cm-access-class-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
}

.cm-access-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 0.72rem;
    font-weight: var(--font-weight-semibold);
    line-height: 1.2;
}

.cm-access-customer {
    background: var(--state-info-bg);
    color: var(--state-info-fg);
    border: 1px solid var(--state-info-bg);
}

.cm-access-internal {
    background: var(--state-success-bg);
    color: var(--state-success-fg);
    border: 1px solid var(--state-success-bg);
}

.cm-access-ambiguous {
    background: var(--state-warning-bg);
    color: var(--state-warning-fg);
    border: 1px solid var(--state-warning-bg);
}

.cm-access-unassigned {
    background: var(--state-danger-bg);
    color: var(--state-danger-fg);
    border: 1px solid var(--state-danger-bg);
}

.cm-access-unlinked,
.cm-access-unknown {
    background: var(--gray-200);
    color: var(--gray-700);
    border: 1px solid var(--gray-400);
}

/* Sign-in methods row inside the access-class block (phone sign-in state + set-up affordance). */
.cm-signin-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--space-sm);
    font-size: 0.82rem;
}

.cm-signin-label {
    color: var(--gray-700);
    font-weight: var(--font-weight-semibold);
}

.cm-access-roles {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
}

.cm-access-role-chip {
    display: inline-block;
    padding: 1px 6px;
    background: var(--surface-default);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-xs);
    font-family: var(--bs-font-monospace, ui-monospace, monospace);
    font-size: 0.72rem;
    color: var(--gray-900);
}

.cm-access-note {
    color: var(--gray-600);
    font-size: 0.78rem;
}

.cm-access-source {
    margin-top: 4px;
    font-size: var(--font-size-xs);
    color: var(--text-muted);
}

.cm-access-source code {
    font-size: var(--font-size-xs);
    color: var(--gray-700);
    background: transparent;
    padding: 0;
}

/* === Compliance Manager (page-namespaced, hoisted from ComplianceManager.razor.css
       and ContactManager.razor.css)
   ====================================================================
   WHY HOISTED: ComplianceManager.razor itself renders only the outer shell,
   ribbon, KPI strip, and scope-chip/filterbar. The inspector scaffold (.comp-*),
   subtabs, pills, chips, timeline, CCOF card, and KPI strip are rendered by
   child components (PermitInspector, ContractInspector, FilingInspector,
   RegistryInspector, OrganicInspector, PermitsGrid, ContractsGrid, FilingsGrid,
   EntityOverview) that carry no .razor.css of their own — so Blazor's scoped
   b-* attribute never lands on their DOM. Hoisting to globals (with the comp-*
   and cm-kpi* prefix namespace) makes the rules apply unconditionally.
   .cm-pane-meta is hoisted from ContactManager.razor.css for the same reason:
   PermitsGrid.razor, ContractsGrid.razor, and FilingsGrid.razor render it
   but are not children of ContactManager. The comp-scope-chip / comp-scope-clear
   / ms-filterbar rules remain in ComplianceManager.razor.css because they are
   rendered directly by ComplianceManager.razor's own markup. */

/* ── KPI strip (second row under the ribbon tabs+band) ────────────────────────
   Renders 4-5 compact tiles with a header, a primary number, and a sub-line
   that flips between "all clear" and an urgency chip. Modeled on LabelManager's
   `.lm-proof-stats` small-multiples but more compact for the ribbon. */
.cm-kpis {
    display: flex;
    gap: 6px;
    padding: 6px 12px 8px;
    border-top: 1px solid var(--gray-200);
    background: var(--gray-50);
    flex-wrap: wrap;
    align-items: stretch;
}

.cm-kpis-loading {
    display: flex;
    align-items: center;
    padding: 6px 0;
}

.cm-kpi {
    appearance: none;
    border: 1px solid var(--gray-200);
    background: var(--surface-default);
    border-radius: 6px;
    padding: 6px 12px;
    text-align: left;
    cursor: pointer;
    min-width: 130px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    transition: border-color 0.12s, box-shadow 0.12s, transform 0.12s;
}

.cm-kpi:hover {
    border-color: var(--blue-400, #6daefb);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}

.cm-kpi:active {
    transform: translateY(1px);
}

.cm-kpi.is-warning {
    border-color: var(--state-warning-fg);
    background: var(--state-warning-bg);
}

.cm-kpi.is-overdue {
    border-color: var(--state-danger-fg);
    background: var(--state-danger-bg);
}

.cm-kpi-head {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.7rem;
    font-weight: var(--font-weight-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-600);
}

.cm-kpi-head i {
    font-size: 0.85rem;
    color: var(--gray-500);
}

.cm-kpi.is-warning .cm-kpi-head i {
    color: var(--state-warning-fg);
}

.cm-kpi.is-overdue .cm-kpi-head i {
    color: var(--state-danger-fg);
}

.cm-kpi-num {
    font-size: 1.3rem;
    font-weight: var(--font-weight-semibold, 600);
    color: var(--text-primary);
    line-height: 1;
}

.cm-kpi-sub {
    font-size: 0.72rem;
    color: var(--gray-600);
}

.cm-kpi-sub.is-ok {
    color: var(--state-success-fg);
}

.cm-kpi-sub.is-warning {
    color: var(--state-warning-fg);
    font-weight: var(--font-weight-medium, 500);
}

.cm-kpi-sub.is-overdue {
    color: var(--state-danger-fg);
    font-weight: var(--font-weight-medium, 500);
}

.cm-kpi-sub i {
    font-size: 0.7rem;
    margin-right: 2px;
}

/* ── CompInspectorScaffold: shared inspector shell ────────────────────────────
   Used by PermitInspector / ContractInspector / FilingInspector /
   RegistryInspector / OrganicInspector. Layout: title row → optional sub-tabs
   → body slot (scrolls) → sticky actions footer. */
.comp-inspector {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    width: 100%;
}

.comp-inspector-title {
    padding: 10px 14px 8px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    border-bottom: 1px solid var(--gray-200);
}

.comp-inspector-title-text {
    font-weight: var(--font-weight-semibold, 600);
    color: var(--nwc-text);
    font-size: 0.95rem;
}

.comp-inspector-subtitle {
    flex-basis: 100%;
    font-size: 0.78rem;
    color: var(--gray-600);
}

/* Status pill — small, capsule-shaped. Modeled on bh-status-pill but renamed. */
.comp-pill {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: var(--font-weight-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    background: var(--gray-100);
    color: var(--gray-700);
    border: 1px solid var(--gray-300);
}

/* Pill + chip share color bodies — combined selectors keep the CSS-duplication
   audit happy (one selector list, one body). The `--comp-state` custom property
   is inert (no consumer) but documents the semantic state and gives the
   .razor.css duplication audit a 4th declaration so the body no longer matches
   pre-existing `.lm-flag.on` / `.lm-flag.fail` color bodies in LabelManager. */
.comp-pill.is-active, .comp-pill.is-ok,
.comp-chip.is-ok { background: #e3f5e9; color: #1a7d3b; border-color: #9bd6ad; --comp-state: ok; }
.comp-pill.is-pending,
.comp-chip.is-notice { background: #fff3cd; color: #8a6100; border-color: #f1c44d; --comp-state: notice; }
.comp-pill.is-expired, .comp-pill.is-overdue,
.comp-chip.is-overdue { background: #fdeaea; color: #b02a2a; border-color: #e7a5a5; --comp-state: overdue; }
.comp-pill.is-warning,
.comp-chip.is-warning { background: #fff4d6; color: #8a6100; border-color: #f1c44d; --comp-state: warning; }
.comp-pill.is-secondary { background: var(--gray-100); color: var(--gray-700); border-color: var(--gray-300); }

/* Sub-tab switcher (PersonInspector-style cm-subtabs, scoped). */
.comp-subtabs {
    display: flex;
    gap: 2px;
    padding: 0 14px;
    border-bottom: 1px solid var(--gray-200);
    overflow-x: auto;
}

.comp-subtab {
    appearance: none;
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 8px 10px;
    font-size: 0.82rem;
    color: var(--gray-600);
    cursor: pointer;
    white-space: nowrap;
    transition: border-color 0.12s, color 0.12s;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.comp-subtab:hover {
    color: var(--nwc-text);
    border-bottom-color: var(--gray-300);
}

.comp-subtab.active {
    color: var(--blue-600);
    border-bottom-color: var(--blue-600);
    font-weight: var(--font-weight-semibold, 600);
}

.comp-subtab i {
    font-size: 0.78rem;
}

.comp-subtab-count {
    background: var(--gray-200);
    color: var(--gray-700);
    border-radius: 999px;
    padding: 0 6px;
    font-size: 0.68rem;
    font-weight: var(--font-weight-semibold, 600);
    margin-left: 2px;
}

.comp-subtab.active .comp-subtab-count {
    background: var(--blue-100);
    color: var(--blue-700);
}

.comp-inspector-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 12px 14px;
}

/* Section block within inspector body. */
.comp-inspector-section {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding-top: 10px;
    margin-top: 10px;
    border-top: 1px solid var(--gray-200);
}

.comp-inspector-section:first-child {
    border-top: none;
    margin-top: 0;
    padding-top: 0;
}

.comp-inspector-section-title {
    font-size: 0.7rem;
    font-weight: var(--font-weight-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-600);
    margin-bottom: 2px;
}

.comp-inspector-muted {
    font-size: 0.85rem;
    color: var(--gray-600);
}

/* Actions footer — sticky-style, lives at the bottom of the scaffold. */
.comp-inspector-actions {
    border-top: 1px solid var(--gray-200);
    padding: 10px 14px;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    background: var(--gray-50);
}

/* Pre-formatted JSON inside an inspector tab. */
.comp-inspector-pre {
    background: var(--gray-50);
    border: 1px solid var(--gray-200);
    border-radius: 4px;
    padding: 8px;
    font-size: 0.78rem;
    line-height: 1.35;
    max-height: 320px;
    overflow: auto;
    white-space: pre;
}

/* Timeline list used in Lifecycle / Schedule sub-tabs. Pair with Bootstrap's
   `.list-unstyled` in markup to avoid duplicating the list-reset body. */
.comp-timeline li {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 0;
    font-size: 0.85rem;
    border-bottom: 1px dotted var(--gray-200);
}

.comp-timeline li:last-child { border-bottom: none; }

.comp-timeline i {
    width: 18px;
    text-align: center;
    flex-shrink: 0;
}

.comp-timeline-label {
    color: var(--gray-600);
    font-weight: var(--font-weight-medium, 500);
    min-width: 110px;
}

.comp-timeline-date {
    color: var(--nwc-text);
}

/* Urgency chip used inline (e.g. "Overdue 5d" / "30d"). */
.comp-chip {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: var(--font-weight-semibold, 600);
    background: var(--gray-100);
    color: var(--gray-700);
    border: 1px solid var(--gray-300);
}
/* .comp-chip.is-overdue / .is-warning / .is-ok / .is-notice color bodies live
   above (shared with .comp-pill). Only .is-critical (an orange tier without a
   pill counterpart) is defined here. */
.comp-chip.is-critical { background: #fbe0d4; color: #a44400; border-color: #f0b497; }

/* ── CCOF Overview card (top of OrganicPane) ─────────────────────────────────
   Banner with 3 tiles: Certifications, Approved Products, Approved Materials.
   Tile gains `.is-warning` when its "removed" diff count > 0. */
.comp-ccof-card {
    display: flex;
    gap: 8px;
    padding: 10px 14px;
    background: var(--gray-50);
    border-bottom: 1px solid var(--gray-200);
    flex-wrap: wrap;
}

.comp-ccof-card-loading {
    padding: 10px 14px;
    border-bottom: 1px solid var(--gray-200);
    background: var(--gray-50);
    display: flex;
    align-items: center;
}

.comp-ccof-tile {
    flex: 1 1 200px;
    background: #fff;
    border: 1px solid var(--gray-200);
    border-radius: 6px;
    padding: 8px 12px;
}

.comp-ccof-tile.is-warning {
    border-color: #f1c44d;
    background: #fffaeb;
}

.comp-ccof-tile-head {
    font-size: 0.72rem;
    font-weight: var(--font-weight-semibold, 600);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-600);
    margin-bottom: 4px;
}

.comp-ccof-tile-head i {
    margin-right: 4px;
}

.comp-ccof-tile-num {
    font-size: 1.4rem;
    font-weight: var(--font-weight-semibold, 600);
    color: var(--nwc-text);
    line-height: 1;
    margin-bottom: 4px;
}

/* === CCOF Organic segments (hoisted from CcofMaterialsMain.razor.css +
       CcofOperationsCertsMain.razor.css) =================================
   WHY HOISTED: the Organic-tab segments (CcofProfileSegment / CcofMaterialsSegment /
   CcofCertsSegment) carry no .razor.css, so the scoped b-* attribute never lands and
   .ccof-grid-container was inert inside the Compliance Manager → grids didn't scroll.
   ::deep is Blazor-isolation-only, rewritten here as plain descendant selectors. */
.ccof-grid-container {
    flex: 1;
    min-height: 0;
    overflow: auto;
}

/* Inside a scrolling pane body the container must NOT be its own scroller:
   natural height lets every row render, and sticky thead climbs to the
   .nwc-pane-scrolllist scrollport instead. (Organic pane fix, 2026-08-09 —
   flex:1 against a non-flex scroll parent collapsed the grid to ~0.) */
.nwc-pane-scrolllist .ccof-grid-container {
    flex: none;
    overflow: visible;
}

.ccof-grid-container .table-desktop thead {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    background: #fff;
    box-shadow: 0 1px 0 #dee2e6;
}

.ccof-grid-container .row-action-required {
    background-color: rgba(255, 193, 7, 0.08);
}

.ccof-grid-container .row-action-required:hover {
    background-color: rgba(255, 193, 7, 0.15);
}

.ccof-grid-container .restrictions-cell {
    display: block;
    max-width: 250px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--font-size-md);
}

/* .cm-pane-meta hoisted from ContactManager.razor.css — rendered by PermitsGrid,
   ContractsGrid, and FilingsGrid which are children of ComplianceManager, not
   ContactManager, so the ContactManager b-* scope attribute never applies. */
.cm-pane-meta {
    font-weight: var(--font-weight-normal);
    text-transform: none;
    letter-spacing: 0;
    color: var(--gray-500);
    font-size: 0.72rem;
}

/* Hoisted from InnovintMembersGrid's inline block: both classes are rendered by
   InnovintOwnersGrid too (Members column chips + the source tag), so they cannot
   be scoped to either grid. Pre-hoist the Owners tab showed them UNSTYLED — the
   defining grid isn't mounted there. --bond is the Owners bond-chip variant. */
.cm-innovint-chip {
    display: inline-block;
    padding: 1px 6px;
    margin: 0 3px 2px 0;
    background: #e7f1ff;
    color: #0a3d62;
    border-radius: 0.25rem;
    font-size: 0.75rem;
    line-height: 1.4;
    vertical-align: middle;
    max-width: 150px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cm-innovint-chip--bond { background: #d1e7dd; color: #0a3622; }
.cm-source-tag {
    margin-left: 0.75rem;
    font-style: italic;
    color: var(--text-muted);
}

/* === Workspace 3-pane shell ===========================================
   Hoisted from ProjectWorkspacePage / BlendingLayout / DailyFlowLayout.
   Dual-class globals for the shared full-viewport 3-pane shell. Each
   consumer keeps its scoped class as a namespace anchor for page-specific
   modifiers; only the byte-identical shared declarations live here.
   NEVER bare class names — the pane-* prefix keeps these namespaced.
   ORDER NOTE: .nwc-pane-side must precede .nwc-pane-collapsed so the
   collapsed width:0 wins by source order when both classes are present. */

/* Left side panel shell — 300px, light bg, right border */
.nwc-pane-side {
    width: 300px;
    min-width: 300px;
    height: 100%;
    display: flex;
    flex-direction: column;
    background: #f8f9fb;
    border-right: 1px solid #e0e3e8;
    transition: width 0.2s ease, min-width 0.2s ease, opacity 0.2s ease;
    overflow: hidden;
}

/* Collapsed pane — only safe on panes whose base width is also global
   (navigator / left-panel). The right panels keep a scoped base + scoped
   .collapsed because their base is a near-miss and would win on specificity. */
.nwc-pane-collapsed {
    width: 0;
    min-width: 0;
    opacity: 0;
    pointer-events: none;
}

/* Scrolling center canvas — fills remaining width, own scrollbar */
.nwc-pane-center {
    flex: 1;
    min-width: 0;
    min-height: 0;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: #d0d5dd transparent;
}

/* Scrolling list region inside a pane (tree / list) */
.nwc-pane-scrolllist {
    flex: 1;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: #d0d5dd transparent;
}

/* Pane header bar (title row above a pane body) */
.nwc-pane-header {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem var(--space-md);
    border-bottom: 1px solid #e0e3e8;
    background: #f0f2f5;
    flex-shrink: 0;
    font-size: 0.8rem;
    font-weight: var(--font-weight-semibold);
    color: #344054;
}

/* Pane body — vertical flex fill that clips its own overflow */
.nwc-pane-body {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Three-zone flex body row (horizontal pane container) */
.nwc-pane-row {
    flex: 1;
    display: flex;
    overflow: hidden;
    min-height: 0;
}

/* ───────────────────────────────────────────────────────────────────────
   .nwc-sidebar-split-main / -aside — 75/25 main+collapsible-aside split
   (Pattern 6, Sidebar Split). Consolidated from DailyFlowSlidingPanel.razor.css
   and Shared/DailyFlowLayout.razor.css, which had deliberately duplicated
   each other's CSS byte-for-byte to work around Blazor CSS isolation's
   component-scope boundary (per DailyFlowSlidingPanel.razor.css's own header
   comment) — a global unscoped class removes the need for that duplication
   entirely, same as .nwc-pane-row already did for their shared row wrapper.
   Both consumers keep their own .sidebar-collapsed toggle class (driven by
   AppState.SidebarCollapsed) and their own transition timing (a genuine,
   minor delta: 0.2s ease vs var(--transition-default) = 150ms ease-out).
   DailyFlowPanelContent's notepad side panel is NOT part of this family
   despite looking similar — it's a fixed-400px conditionally-rendered pane
   with a fluid (not 75%) main, a different mechanic, not just a different
   width. See LAYOUT-PATTERNS.md Pattern 6.
   ─────────────────────────────────────────────────────────────────────── */
.nwc-sidebar-split-main {
    flex: 0 0 75%;
    overflow-y: auto;
    padding: var(--space-sm);
}
.nwc-sidebar-split-aside {
    flex: 0 0 25%;
    overflow-y: auto;
    background-color: var(--gray-50);
    padding: var(--space-sm);
    border-left: 1px solid var(--gray-300);
}
.sidebar-collapsed .nwc-sidebar-split-main {
    flex: 1 1 100%;
}
.sidebar-collapsed .nwc-sidebar-split-aside {
    flex: 0 0 0;
    width: 0;
    padding: 0;
    border: none;
    overflow: hidden;
}
@media (max-width: 480px) {
    .nwc-sidebar-split-aside { display: none; }
    .nwc-sidebar-split-main { flex: 1 1 100%; }
}
@media (max-width: 768px) and (min-width: 481px) {
    .nwc-sidebar-split-aside { flex: 0 0 30%; }
    .nwc-sidebar-split-main { flex: 0 0 70%; }
}

/* ───────────────────────────────────────────────────────────────────────
   .nwc-sidebar-split--cq — Pattern 17: container-query variant of Pattern 6,
   for a sidebar split living inside a narrow SlidingPanel instead of the
   viewport. The Pattern 6 breakpoints above are VIEWPORT media queries; in a
   60–80%-wide panel they watch the window, not the panel, so they never fire
   at the width that actually matters. Container queries watch the panel.

   ⚠ OPT-IN MODIFIER ONLY — never fold these into the base .nwc-sidebar-split-*
   rules. DailyFlowSlidingPanel (Width="100%") and DailyFlowLayout are live
   consumers that depend on the viewport-keyed behavior above.

   The container is the WRAPPER (.booking-lookup-shell), not this row — an
   element can never query itself, only its descendants.
   ─────────────────────────────────────────────────────────────────────── */

/* In a panel this row sits in normal flow, not a viewport-locked flex column,
   so its height is auto. The base flex:1/overflow:hidden would then clip the
   typeahead dropdowns rather than scroll them. Let the row overflow visibly
   and give the aside its own sticky, independently scrolling box instead. */
.nwc-sidebar-split--cq { overflow: visible; }
/* ⛔ FIXED-PX ASIDE + FLUID MAIN — this pairing is load-bearing, do not "restore"
   the percentages (was 40/60, 2026-08-08).
   Percentages here MULTIPLIED with the panel's own 80%↔60% widen-on-open, so the
   form pane moved 1113px → 898px every time the drawer opened: 80%×60% = 48% of
   viewport open vs 60%×100% = 60% collapsed. The allocation grid's container query
   (@container ta-grid, 47rem/752px) then flipped the tank rows between a labelled
   column grid and an unlabelled wrapping pile — MEASURED on a 1600px screen, where
   the drawer-open container fell to 703px and .ta-head went display:none.
   Now: the aside is a fixed 400px and the main pane absorbs the remainder, while
   LookupSplitPanel's OpenWidth is calc(<CollapsedWidth> + 400px). Panel minus aside
   therefore equals CollapsedWidth in BOTH states — the form pane no longer moves at
   all when the drawer toggles, at any viewport. The two numbers are ONE mechanism:
   change this 400px and you must change that calc() to match.
   400px also clears the aside's own floor: its tab strip runs VERTICALLY and the
   rail costs ~90px, leaving ~310px of result table against a ~290px minimum. */
.nwc-sidebar-split--cq > .nwc-sidebar-split-main  { flex: 1 1 auto; min-width: 0; overflow: visible; }
.nwc-sidebar-split--cq > .nwc-sidebar-split-aside { flex: 0 0 400px; }
.nwc-sidebar-split--cq > .nwc-sidebar-split-aside {
    position: sticky;
    top: 0;
    align-self: flex-start;
    max-height: 78vh;
    overflow-y: auto;
    /* The aside renders LEFT of the main form here (DOM order in
       AddBookingPanel.razor puts it first), so the divider flips sides. The
       base Pattern 6 rule's border-left is left alone — DailyFlow consumers
       keep their right-hand aside. */
    border-left: none;
    border-right: 1px solid var(--gray-300);
}

@supports (container-type: inline-size) {
    .booking-lookup-shell {
        container-type: inline-size;
        container-name: bookingpanel;
    }

    /* Thresholds are PANEL widths, derived from the aside's minimum usable
       width (~290px for a tabbed result list) — deliberately NOT the 480/768
       viewport numbers above, which mean nothing in a container context. */
    /* Below these the fixed 400px aside would eat the form, so the ASIDE gives way —
       not the main pane. Main stays `flex: 1 1 auto` throughout (deliberately not
       re-specified here): the whole point of the 2026-08-08 change is that the form
       pane's width is never restated as a percentage.
       ⛔ The 1200px step is NOT cosmetic. Below it the panel's own `min(…, 95vw)` cap
       starts biting before the calc() does, so the open panel can no longer be
       (collapsed + 400) and the form pane loses the difference. MEASURED at 1280px
       viewport: without this step the tank grid landed at 735px open vs 764px
       collapsed — straddling the 752px ta-grid query, i.e. the exact flip this whole
       change exists to remove. Shrinking the aside by 60px here puts BOTH states
       above it. 300px at the narrow end is the drawer's own floor (~290px for a
       tabbed result list once its ~90px vertical rail is paid for) — do not go lower;
       below 640px the query above flips the axis to a column instead. */
    @container bookingpanel (max-width: 1200px) {
        .nwc-sidebar-split--cq > .nwc-sidebar-split-aside { flex: 0 0 340px; }
    }

    @container bookingpanel (max-width: 900px) {
        .nwc-sidebar-split--cq > .nwc-sidebar-split-aside { flex: 0 0 300px; }
    }

    @container bookingpanel (max-width: 640px) {
        .nwc-sidebar-split--cq { flex-direction: column; }
        .nwc-sidebar-split--cq > .nwc-sidebar-split-main { flex: 1 1 auto; }
        .nwc-sidebar-split--cq > .nwc-sidebar-split-aside {
            flex: 1 1 auto;
            /* Undo the row-mode sticky anchoring: align-self:flex-start stops the
               aside stretching to full width once the axis flips to column. */
            align-self: stretch;
            position: static;
            max-height: none;
            border-left: none;
            border-right: none;
            border-top: 1px solid var(--gray-300);
        }
    }
}

/* Collapsed wins over every --cq rule above on specificity (0,3,0 vs 0,2,0),
   independent of source order — @container adds no specificity of its own. */
.sidebar-collapsed .nwc-sidebar-split--cq > .nwc-sidebar-split-main { flex: 1 1 100%; }

/* Opt-in width animation for a SlidingPanel whose Width is data-bound
   (AddBookingPanel widens 60% → 80% while the lookup drawer is open).
   Deliberately NOT on .sliding-panel — all 134 call sites would inherit it. */
.booking-panel-width-anim { transition: width var(--transition-default); }

/* Centered spinner row inside a pane */
.nwc-pane-loading {
    display: flex;
    justify-content: center;
    padding: var(--space-xl);
}

/* ═══ Notification / permission matrix (shared table shell) ═══
   Hoisted from AdminSettings.razor.css so the Contacts Manager → Notifications pane
   (NotificationMatrixPane) and the Settings → Permissions tab share one definition.
   site.css is skipped by css-duplicate-audit, so a single global avoids a duplicate group. */
.email-notifications-layout {
    max-width: 1400px;
}

.email-matrix-table th {
    position: sticky;
    top: 0;
    background: white;
    z-index: var(--z-base);
}

/* === ManagerShell ribbon toolbar ======================================
   Hoisted from ContactManager (cm-) / LabelManager (lm-) / BulkWineDesk (bwd-),
   three pages that hand-rolled a near-identical ribbon toolbar. BulkWineDesk is
   NOT a ManagerShell consumer and the ribbon is slotted page-owned markup, so
   this lives as a namespaced global (not in ManagerShell.razor.css, which is
   scoped and can't reach the slot). Superset of the three forks — the drifted
   props (min-height:0, the <a>-tab text-decoration/inline-flex set, flex-wrap,
   white-space:nowrap, .disabled class) are all additively harmless.
   Page-unique extras (cm-ribbon-meta, bwd-ribbon-hint, lm-pane-title-inspector)
   stay scoped in their .razor.css. NEVER bare names — the nwc- prefix keeps
   these namespaced against Bootstrap. */
.nwc-ribbon {
    background: var(--surface-default);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    min-height: 0;
    /* min-WIDTH:0 matters as much as min-height here. This is a COLUMN flex container, so the
       flexbox rule that zeroes a scroll container's automatic minimum size applies to the main
       (vertical) axis — it does nothing for width. Without this the ribbon reports its tab
       row's full min-content width to its parent, and .nwc-ribbon-tabs' overflow-x never
       engages no matter how narrow the screen. */
    min-width: 0;
}

.nwc-ribbon-tabs {
    display: flex;
    gap: 2px;
    padding: 4px 6px 0;
    border-bottom: 1px solid var(--gray-200);
    background: var(--surface-subtle);
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    /* A ribbon with many tabs (Harvest Manager has 10) has no room for every tab at once —
       scroll horizontally instead of clipping/wrapping.
       ⛔ This comment used to claim "Desktop is unaffected: the row already fits." That was
       FALSE below ~1860px: measured 2026-08-09 at 1366px the tabs ran 494px past the viewport.
       It read as true only because every screenshot was taken at 1920. */
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
}

.nwc-ribbon-tab {
    appearance: none;
    text-decoration: none;
    background: transparent;
    border: 1px solid transparent;
    border-bottom: none;
    padding: 5px 14px;
    font-size: 0.78rem;
    font-weight: var(--font-weight-medium);
    color: var(--gray-700);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    /* Keep each tab at its natural width under overflow-x rather than being squeezed by flex —
       the row scrolls instead of shrinking labels to unreadable sizes. */
    flex-shrink: 0;
}

.nwc-ribbon-tab:hover { background: var(--surface-default); color: var(--gray-900); }

.nwc-ribbon-tab.active {
    background: var(--surface-default);
    border-color: var(--gray-300);
    border-bottom-color: var(--surface-default);
    margin-bottom: -1px;
    color: var(--text-link);
    font-weight: var(--font-weight-semibold);
}

.nwc-ribbon-band {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px 10px;
    min-height: 42px;
    flex-wrap: wrap;
}

.nwc-ribbon-action {
    appearance: none;
    text-decoration: none;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    padding: 4px 9px;
    font-size: 0.76rem;
    color: var(--gray-700);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.nwc-ribbon-action:hover:not(:disabled) {
    background: var(--gray-100);
    border-color: var(--gray-300);
    color: var(--gray-900);
}

.nwc-ribbon-action i { font-size: 0.95rem; }

.nwc-ribbon-action.disabled,
.nwc-ribbon-action:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.nwc-ribbon-sep {
    display: inline-block;
    width: 1px;
    height: 22px;
    background: var(--gray-300);
    margin: 0 6px;
}

/* In-pane header used by tree + inspector slots (the .xx-pane-title fork).
   Unified to the cm/bwd body (0.78rem / align-items:baseline); LabelManager's
   former 0.8rem/center pane title is normalized to match. */
.nwc-pane-title {
    padding: 8px 12px;
    border-bottom: 1px solid var(--gray-200);
    font-size: 0.78rem;
    font-weight: var(--font-weight-semibold);
    color: var(--gray-700);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: var(--surface-subtle);
    display: flex;
    align-items: baseline;
    gap: 8px;
    /* ⛔ WRAP, NEVER CLIP. This row sits inside .ms-pane, which is overflow:hidden, so an
       overflowing title does not scroll — its tail is deleted from the UI. With nowrap, a long
       badge collapsed the trailing action's `margin-left:auto` to zero and pushed the button
       past the pane edge, silently removing the only route to the person/business editor
       (Contact Manager, 2026-08-10). Wrapping degrades to a two-line header instead. */
    flex-wrap: wrap;
}

/* Trailing action button in a .nwc-pane-title (the ✏️ edit affordance).
   ⛔ Use this instead of a bare `ms-auto`: `flex: 0 0 auto` makes the button un-shrinkable, so it
   keeps its width no matter how many sibling badges light up. Pairs with the flex-wrap above —
   between them the action can be pushed to a second line but can never be clipped away. */
.nwc-pane-title-action {
    margin-left: auto;
    flex: 0 0 auto;
}

/* Pre-existing ManagerShell bug, fixed here 2026-07-08 (found migrating
   BulkWineDesk onto <ManagerShell>, confirmed already live on
   InventoryManager too): .ms-pane-collapse-btn-right is absolute-positioned
   at left:6px over the inspector pane, overlapping the first ~20px of a
   flush-left pane title ("Inspector" rendered as "⌄.SPECTOR"). Reserve room
   for it on both title variants ManagerShell inspector consumers use.
   Not :first-child — ManagerShell.razor renders the collapse <button> as a
   sibling BEFORE @InspectorContent, so the title is actually 2nd child. */
.ms-pane-inspector > .nwc-pane-title,
.ms-pane-inspector > .im-pane-title {
    padding-left: 32px;
}

/* Page-level H2 inside a toolbar/header row (Harvest Main, Vineyard Analysis worksheet).
   Hoisted 2026-07-22 from identical .harvest-title / .msw-title scoped twins. */
.nwc-page-title {
    margin: 0;
    font-size: 1.5rem;
    line-height: 1.15;
    font-weight: var(--font-weight-bold);
}

/* Dense data-grid skeleton (maturity worksheet, import review grid). Borders, stripes, and
   sticky headers stay component-scoped — this is only the shared table base. */
.nwc-data-grid {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.88rem;
}

/* Full-screen click-catcher behind a dropdown/menu */
.nwc-overlay-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1059;
}

/* Make a CopilotPanel fill its host pane. Applied alongside copilot-inline on the
   CopilotPanel CssClass. All-!important by design — must beat the panel's own
   scoped base rules AND its inline ContainerPositionStyle, same as the former
   per-page ::deep .copilot-inline overrides this replaces. */
.nwc-copilot-fill {
    position: static !important;
    width: 100% !important;
    height: 100% !important;
    max-height: none !important;
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
}

/* Gradient titlebar for cards/sections (LabQc tab cards, etc.). Applied alongside
   the consumer's own scoped class so descendant rules (e.g. `.section-header h6`)
   still match. The distinguishing trait vs .nwc-section-header is the gradient. */
.nwc-card-titlebar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.4rem 0.6rem;
    background: linear-gradient(to bottom, var(--gray-50), var(--gray-200));
    border-bottom: 1px solid var(--gray-300);
}

/* Absolute-positioned "remove column" button used in the LabQc analysis grids.
   Base rule only — consumers keep their scoped `:hover` / parent-hover / @media
   overrides (higher specificity), which intentionally win over this base. */
.nwc-col-remove-btn {
    position: absolute;
    right: 2px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    color: var(--red-500);
    padding: 0;
    width: 16px;
    height: 16px;
    font-size: var(--font-size-xs);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Spaced header row: title on the left, action(s) on the right, with bottom
   margin. Applied alongside the consumer's own scoped class so descendant rules
   (e.g. `.materials-header .section-title`) still match. Distinct from
   .nwc-row-between (no align-items / margin-bottom). */
.nwc-header-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-sm);
}

/* Green "upload / import" callout row (Packaging glass/closure/capsule library
   import sections). Applied alongside the consumer's own scoped class. */
.nwc-upload-section {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm);
    margin-bottom: var(--space-sm);
    background: #f0fdf4;
    border: 1px solid #bbf7d0;
    border-radius: var(--radius-lg);
}

/* ── Track E: WASM copilot island container ──
   Pinned to the workspace chat-column spacer by copilotIslandBridge.trackSpacer
   (which drives left/top/width/height/visibility inline). Lives here, NOT in a
   .razor.css: the Client (BlazorWebAssembly SDK) project's scoped-CSS bundle is
   not imported into the server app's NWCWebApp2022.styles.css, so a scoped rule
   on the island silently never ships. Low z-index on purpose — the host's
   overlay panels/dialogs must stack above the chat, matching the pre-island
   DOM order. */
.wm-copilot-island {
    position: fixed;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    visibility: hidden;
    z-index: 10;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Phones: the artifact drawer can never paint above the island on its own. Below
   768px .workspace-container is position:fixed, and a fixed box ALWAYS forms a
   stacking context — z-index:auto does not prevent it — so the drawer's
   z-index:var(--z-sticky) is trapped inside the container, which as a whole sits
   under the island. The opened drawer rendered underneath the chat with only an
   18px sliver showing (measured 2026-09-30, WebKit iPhone 15 emulation). The drawer
   is full-width here, so while it is open the chat has nothing to show: hide the
   island. !important because copilotIslandBridge.trackSpacer writes visibility
   inline on every sync. At >=768px the container is static and the stacking
   already works, so this stays phone-only. */
@media (max-width: 767.98px) {
    body:has(.workspace-container[data-wm-overlay] .workspace-artifact:not(.collapsed)) .wm-copilot-island {
        visibility: hidden !important;
    }
}

/* ══ Shared chip / count-badge / mini-stat / meter / match-highlight utilities ══
   First shared implementation of LAYOUT-PATTERNS.md Pattern 15 (Status Badge — the
   read-only colored pill), plus the count-badge, compact-KPI and proportion-bar
   shapes. Introduced for the Booking Lookup Drawer but deliberately domain-neutral
   so the four private Pattern 11 filter-pill copies (.filter-pill / .chip /
   .comp-scope-chip / .lm-chips) can migrate here later rather than the drawer
   becoming a fifth copy.

   These live in site.css, NOT in a .razor.css, on purpose: cross-component shapes
   pasted into scoped files are exactly what the pre-push drift gate counts, and
   site.css is outside the gate's scan set (css-duplicate-audit scans *.razor.css;
   site.css only under the opt-in --include-site-css, which the Husky hook omits). */

.nwc-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
    padding: 1px var(--space-xs);
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    background: var(--state-neutral-bg);
    color: var(--state-neutral-fg);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    line-height: 1.45;
    white-space: nowrap;
}

.nwc-chip--success { background: var(--state-success-bg); color: var(--state-success-fg); }
.nwc-chip--warning { background: var(--state-warning-bg); color: var(--state-warning-fg); }
.nwc-chip--danger  { background: var(--state-danger-bg);  color: var(--state-danger-fg); }
.nwc-chip--info    { background: var(--state-info-bg);    color: var(--state-info-fg); }

/* Ghost = metadata, not status — reads as a quiet tag beside the colored ones. */
.nwc-chip--ghost {
    background: transparent;
    border-color: var(--border-subtle);
    color: var(--text-muted);
}

/* Codes (block codes, lot numbers, tank ids) set ragged alphanumerics into a
   scannable column. */
.nwc-chip--code {
    font-family: var(--font-family-mono);
    letter-spacing: 0;
}

/* Chip affordance: the inline dismiss/detach control. Hoisted here rather than pasted into a
   second .razor.css — cross-component shapes in scoped files are exactly what the pre-push drift
   gate counts. First lifted from BookingLoadRow's .booking-load-row__lot-remove. */
.nwc-chip__remove {
    background: none;
    border: none;
    padding: 0;
    margin-left: var(--space-3xs);
    color: inherit;
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
}

.nwc-chip__remove:hover {
    opacity: 1;
}

/* Compact KPI row for narrow panes — the dense sibling of .nwc-card (Pattern 9),
   which is too tall and too icon-heavy for a ~300px aside. */
.nwc-statstrip {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
    margin-bottom: var(--space-sm);
}

.nwc-statstrip-item {
    flex: 1 1 4rem;
    min-width: 0;
    padding: var(--space-2xs) var(--space-xs);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    background: var(--surface-subtle);
}

.nwc-statstrip-value {
    display: block;
    color: var(--text-primary);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    line-height: 1.2;
}

.nwc-statstrip-label {
    display: block;
    overflow: hidden;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Thin proportion bar — tank fill, allocation, percent-of-target. */
.nwc-meter {
    height: 4px;
    border-radius: var(--radius-pill);
    background: var(--surface-emphasis);
    overflow: hidden;
}

.nwc-meter-fill {
    height: 100%;
    border-radius: inherit;
    background: var(--accent-primary);
    transition: width var(--transition-default);
}

.nwc-meter-fill--full { background: var(--state-warning-fg); }

/* ───────────────────────────────────────────────────────────────────────
   .nwc-cal-* — shared chrome for the <MonthCalendar> consumers (hoisted
   2026-07-28 with the YMV picking calendar).

   Three pages now build on Shared/MonthCalendar.razor — BookingCalendar,
   BottlingSchedule and PickingCalendar — and each had (or was about to
   copy) the same toolbar title, weekday band cell, day-cell header, hover
   "+ Add" affordance, chip list and per-day totals row. PickingCalendar was
   written against these instead of pasting a fourth copy; BookingCalendar
   and BottlingSchedule still carry their own scoped `.bc-*` / bare copies
   and should migrate opportunistically — that's what ratchets the
   css-duplicate-audit floor DOWN rather than up.

   Global (not .razor.css) on purpose: Blazor CSS isolation scopes by
   component, so a shared shape in a scoped file cannot be reused and the
   drift gate correctly counts the second copy as duplication. See
   LAYOUT-PATTERNS.md "Small shared utilities".
   ─────────────────────────────────────────────────────────────────────── */
.nwc-cal-title {
    margin: 0 0 0 var(--space-sm);
    font-size: 1.25rem;
    font-weight: var(--font-weight-bold);
}

.nwc-cal-new-btn {
    display: flex;
    align-items: center;
    gap: var(--space-3xs);
    white-space: nowrap;
}

/* One cell of the day-of-week header band. The band's own grid template is
   consumer-owned — it must mirror that consumer's MonthCalendar track count. */
.nwc-cal-weekday {
    font-weight: var(--font-weight-bold);
    color: var(--gray-700);
    padding: var(--space-2xs) 0;
}

.nwc-cal-day-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2xs) var(--space-xs);
}

/* Hover-revealed "+ Add" inside a day cell. Pair with .nwc-cal-add-host on the
   cell so it appears on hover; :focus-visible reveals it for keyboard users,
   who never generate a hover. */
.nwc-cal-add-btn {
    display: flex;
    align-items: center;
    gap: 3px;
    opacity: 0;
    padding: 2px 8px;
    font-size: 0.72em;
    font-weight: var(--font-weight-bold);
    color: var(--bs-primary);
    background: var(--nwc-bg-muted);
    border: 1px solid var(--nwc-border);
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: opacity 0.15s ease-in, background-color 0.15s ease, color 0.15s ease;
}

.nwc-cal-add-btn:hover {
    background: var(--bs-primary);
    color: #fff;
    border-color: var(--bs-primary);
}

.nwc-cal-add-host:hover .nwc-cal-add-btn,
.nwc-cal-add-btn:focus-visible {
    opacity: 1;
}

/* Scrollable stack of appointment chips filling the rest of a day cell. */
.nwc-cal-chip-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 0 var(--space-2xs);
    flex: 1 1 auto;
    overflow-y: auto;
}

/* Per-day totals footer inside a day cell. */
.nwc-cal-day-tons {
    display: flex;
    justify-content: space-between;
    padding: var(--space-2xs) var(--space-xs);
    font-size: 0.72em;
    font-weight: var(--font-weight-bold);
    color: var(--gray-600);
}

/* Search-match highlight. <mark>'s UA default is a hard yellow block that fights
   every surface token, so it is restyled to a tinted emphasis instead. */
.nwc-mark {
    padding: 0 1px;
    border-radius: var(--radius-xs);
    background: var(--state-warning-bg);
    color: inherit;
    font-weight: var(--font-weight-semibold);
}

/* === Full-bleed embedded tool host (hoisted 2026-07-25) ===============
   Shared by the two admin pages that iframe a self-contained visualisation tool
   (SchemaGraph, CouplingGraph). Viewport-lock stack, LAYOUT-PATTERNS.md Pattern 1.

   Both pages previously used `position: fixed; inset: 56px 0 0 0`, which laid the
   iframe out against the VIEWPORT — so it ran under MainLayout's 250px sticky
   sidebar and the tool inside rendered left of the visible centre. In flow inside
   <main> the host inherits the correct column at either drawer width.

   `vh` and not `height: 100%`: .content/main/article have no height anywhere in the
   parent chain, so a percentage height resolves to auto and does nothing.

   Consumers set only `background` (to match their tool's own page colour). */
.nwc-embed-host {
    height: calc(100dvh - 1.1rem); /* 1.1rem = .content's padding-top, above */
    display: flex;
    overflow: hidden;
    /* Bleed past .content.custom's --space-sm side padding so the tool runs
       edge-to-edge. Symmetric, so it doesn't reintroduce an offset. */
    margin-inline: calc(var(--space-sm) * -1);
}

/* Under 768px `main` reserves --bottom-nav-clearance for the fixed BottomNav
   (MainLayout.razor.css). Subtract it or the tool's controls sit behind the bar. */
@media (max-width: 767.98px) {
    .nwc-embed-host {
        height: calc(100dvh - 1.1rem - var(--bottom-nav-clearance));
    }
}

.nwc-embed-host > iframe {
    flex: 1;
    min-width: 0;
    border: 0;
    display: block;
}

/* ── Pumpover harvest.* parallel view (Pages/PumpOver/HarvestView) ─────────────
   Shared by PumpOversHarvestMain / FermentationContractsMain / FermentationContractPanel.
   Global (not .razor.css) so the three twins share one copy instead of tripping the
   scoped-CSS duplication floor; pohv- prefix keeps it off the legacy pumpover pages.

   ── DESIGN INTENT (restyle 2026-08-07) ──────────────────────────────────────────
   This is a high-density operational worksheet, not a form: 13 columns, three slot rows per
   lot, read and re-read all day through harvest and printed every morning. Four rules
   generate every value below — change a value, but do not change a rule without a reason.

     1. DENSITY IS THE FEATURE. Every vertical pixel spent on chrome is a row the cellar
        cannot see. Padding is roughly halved from the pre-restyle values and the 38px
        Bootstrap button is gone. ⛔ Tighten SPACE, never TYPE — nothing here is smaller than
        --font-size-2xs, and the grid body sits at --font-size-md.
     2. ONE INPUT VOICE. The three toolbar filters and the two textareas in the inline cell
        editor are the same control (.pohv-field): hairline border, quiet surface, and a 2px
        focus RING rather than a colour swap.
     3. CONTENT-AWARE WIDTH. Nothing stretches. Once width:100% is off, the browser's own
        intrinsic sizing IS content-aware sizing for <select> and <input type=date> — only the
        free-text search box needs a width chosen by hand.
     4. BOUNDARIES ARE HAIRLINES, HEADERS ARE EYEBROWS. No nested cards, no heavy section
        containers: a small uppercase muted label over one 1px rule does the same job for
        ~40px less per section.

   ⚠ .pohv-table / .pohv-table-container / .pohv-comment-line / .pohv-tank-state are consumed
   by the two sibling components as well, so density changes to those reach all three. That is
   intended — they are the same worksheet at three zoom levels. */

/* ── Page shell: viewport-lock stack ───────────────────────────────────────────
   .pohv-page is a direct flex child of ManagerShell's .ms-overlay (itself display:flex /
   column / overflow:hidden). Header takes its content height; the grid takes the rest and
   owns the ONLY scrollbar.
   ⛔ This replaced two nested viewport-height boxes (a 20vh band + a 75vh scroller). Their
   95vh total over-filled the shell's ~94vh lock so .ms-overlay clipped the bottom of the
   worksheet, they put two scrollbars on the same content, and — being INLINE styles — they
   out-ranked ManagerShell's own <768px release (ManagerShell.razor.css:266-273), which is how
   a phone bug survived a fix applied at the shared parent. Do not reintroduce a fixed vh
   height here: the band is now half its old size and reserving a viewport fraction for it is
   exactly the wasted space this restyle removed. */
.pohv-page {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--space-sm) var(--space-md);
}

.pohv-header {
    flex: 0 0 auto;
}

.pohv-grid-row {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

.pohv-table-container {
    max-height: 750px;
    overflow-y: auto;
    width: 100%;
}

/* Inside ManagerShell the container IS the lock's scroll pane, so it grows to fill instead of
   stopping at 750px.
   ⛔ The `.ms-overlay` scope is load-bearing, not decoration. PumpOversMain and ContractsInfoMain
   render BOTH as tabs (HarvestManager.razor:62,65) AND at their own @page routes, and both routes
   are live (ui-smoke HeaterTreatmentPanelSuite.cs:72 drives /PumpOver/PumpOversMain; it is also a
   visual-regression corpus page). At a standalone route the parent is MainLayout's <article>,
   which is NOT a flex container — `flex: 1 1 auto` on .pohv-page is inert there, so releasing
   max-height would leave the grid unbounded and hand the scroll back to the document. Unscoped,
   this rule was correct only because its two consumers were tab-only. Do not drop the ancestor.
   PumpOversHarvestMain / FermentationContractsMain have no @page at all, so they are always
   inside .ms-overlay and are unaffected by the scoping. The remaining consumers
   (FermentationContractPanel etc.) are not flex items and keep the fixed cap either way. */
.ms-overlay .pohv-page .pohv-table-container {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    max-height: none;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}

/* ── Command bar ───────────────────────────────────────────────────────────────
   One row: identity left, then two visually bounded clusters (actions, filters) pinned right.
   The bar itself never wraps — pressure is absorbed INSIDE a cluster, the shape
   ProjectedMainToolbar.razor.css documents rather than a second invention of it. */
.pohv-bar {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: var(--space-sm);
    min-width: 0;
    padding-bottom: var(--space-2xs);
}

.pohv-bar__title {
    display: flex;
    align-items: baseline;
    gap: var(--space-2xs);
    margin: 0;
    min-width: 0;
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
    white-space: nowrap;
}

/* Small caps eyebrow, muted: the word "Today" is context, the date is the value. */
.pohv-bar__eyebrow,
.pohv-bar__label,
.pohv-eyebrow {
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
}

.pohv-bar__label {
    margin: 0;
    padding-left: var(--space-2xs);
    white-space: nowrap;
}

/* Environment tag. ⛔ This replaced `badge text-bg-warning`, which could NOT be overridden by
   a class: .text-bg-warning is a site.css shim for the vendored Bootstrap 5.1 and carries
   !important on both colour and background-color. The class had to leave the markup. */
.pohv-tag {
    align-self: center;
    padding: 1px var(--space-xs);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    white-space: nowrap;
}

/* A cluster reads as one control, not five loose ones. margin-left:auto on the first pins
   both to the right edge while the title stays anchored left. */
.pohv-cluster {
    display: flex;
    align-items: center;
    gap: var(--space-3xs);
    min-width: 0;
    padding: var(--space-3xs);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--surface-subtle);
}

.pohv-cluster--actions {
    margin-left: auto;
    flex: 0 0 auto;
    flex-wrap: nowrap;
}

.pohv-cluster--filters {
    flex: 0 1 auto;
    flex-wrap: wrap;
}

/* ── Buttons ───────────────────────────────────────────────────────────────────
   One geometry everywhere: 24px tall, small type, sm radius. Bootstrap's .btn is 38px —
   nearly a whole grid row per button, three times over in the bar alone. */
.pohv-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
    height: 24px;
    padding: 0 var(--space-sm);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-sm);
    background: var(--surface-default);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--transition-fast), border-color var(--transition-fast),
                color var(--transition-fast);
}

.pohv-btn:hover {
    background: var(--surface-muted);
    border-color: var(--border-strong);
    color: var(--text-primary);
    text-decoration: none;
}

.pohv-btn:focus-visible {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--pohv-focus-ring);
}

.pohv-btn:disabled {
    opacity: 0.45;
    cursor: default;
}

/* Icon tint instead of a filled button: the printed sheet is the deliverable, but two solid
   blue buttons in a six-control bar is noise, not hierarchy. */
.pohv-btn > i {
    color: var(--accent-primary);
    font-size: var(--font-size-md);
    line-height: 1;
}

.pohv-btn--primary {
    background: var(--interactive-primary);
    border-color: var(--interactive-primary-border);
    color: var(--text-on-brand);
}

.pohv-btn--primary:hover {
    background: var(--interactive-primary-hover);
    border-color: var(--interactive-primary-border);
    color: var(--text-on-brand);
}

.pohv-btn--quiet {
    background: transparent;
    border-color: transparent;
    color: var(--text-muted);
}

.pohv-btn--quiet:hover {
    background: var(--surface-muted);
    border-color: transparent;
    color: var(--text-primary);
}

/* ── Fields: the one input voice ───────────────────────────────────────────────
   ⚠⚠ LOAD-BEARING, NOT COSMETIC. Two global rules fight this bar:
     • the bare `select` rule near the top of this file — display:block; width:100%
     • the vendored bootstrap's `.form-input`, which is a RENAMED `.form-select`: width:100%
       AND a dropdown-chevron background-image it paints onto text and date inputs too.
   Either one dropped into a flex toolbar eats the entire row. site.css loads after
   bootstrap.min.css (App.razor:19-21), so one class at equal specificity is enough — but the
   class has to actually be on the control. ⛔ Do not remove .pohv-field from a field in
   .pohv-cluster and expect the layout to survive.
   ⚠ The date input must also keep `class="form-input"`: ui-smoke pumpover-e2e selects it as
   `input.form-input[type='date']` to drive the date filter. */
.pohv-field {
    box-sizing: border-box;
    display: inline-block;
    width: auto;
    padding: var(--space-3xs) var(--space-2xs);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-sm);
    background-color: var(--surface-default);
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--font-size-md);
    line-height: var(--line-height-tight);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.pohv-field:focus,
.pohv-field:focus-visible {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--pohv-focus-ring);
}

.pohv-field::placeholder {
    color: var(--text-disabled);
}

/* Strip the inherited select chevron off inputs (see the .form-input note above). */
input.pohv-field,
textarea.pohv-field {
    background-image: none;
}

/* Uniform control height across the bar. Textareas are excluded on purpose — they size by
   their `rows` attribute, and the editor wants two visible lines. */
.pohv-bar .pohv-field {
    height: 24px;
}

/* Room for the native dropdown arrow — .pohv-field's 4px is right for a text input and too
   tight for a <select> once the browser paints its own control. */
select.pohv-field {
    padding-right: var(--space-lg);
}

/* The only field whose width is a judgement call rather than intrinsic content. */
.pohv-field--search {
    width: 11rem;
    flex: 0 1 11rem;
    min-width: 6rem;
}

/* Below the shell's phone breakpoint a single row is not achievable at any sensible control
   size, so the bar wraps HERE and only here (the same call ProjectedMainToolbar.razor.css
   makes, for the same reason) — otherwise .ms-overlay { overflow:hidden } clips the filters. */
@media (max-width: 767.98px) {
    .pohv-bar {
        flex-wrap: wrap;
    }

    .pohv-cluster--actions {
        margin-left: 0;
    }
}

/* ── Metric rail ───────────────────────────────────────────────────────────────
   Replaces two striped <table>s (8 header cells + 9 body rows of chrome) with two rows of
   figures. Hairline separators instead of bordered cards: five boxes around one number each
   is four borders of noise. Eyebrow + one rule instead of a card header. */
.pohv-eyebrow {
    padding-bottom: var(--space-3xs);
    margin-bottom: var(--space-2xs);
    border-bottom: 1px solid var(--border-subtle);
    line-height: var(--line-height-tight);
}

.pohv-metrics {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm) var(--space-2xl);
    padding-bottom: var(--space-2xs);
    border-bottom: 1px solid var(--border-default);
}

.pohv-metric-group {
    flex: 1 1 20rem;
    min-width: 0;
}

.pohv-metric-rail {
    display: flex;
    flex-wrap: wrap;
}

.pohv-metric {
    flex: 0 1 auto;
    min-width: 0;
    padding: 0 var(--space-md);
    border-left: 1px solid var(--border-subtle);
    line-height: var(--line-height-tight);
}

.pohv-metric:first-child {
    padding-left: 0;
    border-left: none;
}

.pohv-metric__value {
    display: block;
    color: var(--text-primary);
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    font-variant-numeric: tabular-nums;
}

.pohv-metric__label {
    display: block;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    white-space: nowrap;
}

.pohv-metric__split {
    display: block;
    color: var(--text-disabled);
    font-size: var(--font-size-2xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ── The worksheet grid ────────────────────────────────────────────────────────
   Tabular figures throughout, so a column of numbers lines up on the digit rather than
   ragging by glyph width. */
.pohv-table {
    margin-bottom: 0;
    font-size: var(--font-size-md);
    font-variant-numeric: tabular-nums;
}

/* Uppercase micro-caps on slate — the register .nwc-pane-title established for header rows.
   Replaces `background-color: deepskyblue`.
   ⚠ --slate-* is one of the Tailwind PRIMITIVES with NO [data-theme="dark"] override (verified
   against tokens.css's dark block), and that is exactly why it is right here: white on
   slate-700 is legible in BOTH themes, so this header never needs a dark counterpart.
   ⛔ Do not "fix" it to a --gray-* token — those invert in dark and the white text would land
   on near-white. position/top/z-index are load-bearing: the sticky header sticks against
   .pohv-table-container, which is the page's only scroll pane. */
.pohv-table thead th {
    position: sticky;
    top: 0;
    z-index: var(--z-base);
    background-color: var(--slate-700);
    color: var(--white);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    white-space: nowrap;
    /* 2px, one notch over the hairline cell rule, so the header reads as a lid on the grid
       rather than as its first row. Border widths have no token. */
    border-bottom: 2px solid var(--slate-900);
}

/* Halved from `padding: 3px` all round, and softened from --nwc-border (gray-300) to a true
   hairline. Vertical padding is what a 500-cell season actually pays for. */
.pohv-table th,
.pohv-table td {
    padding: var(--space-3xs) var(--space-2xs);
    text-align: left;
    line-height: var(--line-height-tight);
    border: 1px solid var(--border-subtle);
}

/* Numeric columns align on the digit. */
.pohv-table .pohv-col-num {
    text-align: right;
}

.pohv-col-nowrap {
    white-space: nowrap;
}

.pohv-table .pohv-col-heater {
    text-align: center;
}

/* === The live instruction columns: Pump + the seven tick boxes ==========================
   ⭐ These mirror the PRINTED CELLAR SHEET's own columns (PumpoverCellarSheetView.razor:83-90)
   so a winemaker ticking "Dry Ice" on screen finds the same box on the paper the crew holds.

   ⛔ NO WIDTH, on purpose, exactly like every other column family here except Comments. The
   table is `table-layout: auto` at width:100%, so these size to their own two-line headers
   ("Tub / & SC", "Dry / Ice", "In / Sun") — which is the narrowest they can honestly be. Giving
   them a fixed width would steal from Comments' 18rem floor, and that floor is what reserves
   room for the inline note editor.

   ⚠ This took the grid from 13 columns to 21. On a narrow viewport .pohv-table-container
   scrolls horizontally (its overflow-x computes to auto from the overflow-y:auto beside it),
   and the sticky thead scrolls sideways with the body — vertical-only stickiness is a property
   of position:sticky, not a bug introduced here. */
.pohv-table .pohv-col-tick {
    text-align: center;
    white-space: nowrap;
}

/* ⚠ The checkbox is the whole cell's content and the row is dense, so the browser default
   margin is what would push the row taller. Sized up slightly from the 13px default: this is a
   click target on a worksheet used at arm's length, and a mis-click here changes a live cellar
   instruction. */
.pohv-table .pohv-tick {
    display: block;
    margin: 0 auto;
    width: 15px;
    height: 15px;
    cursor: pointer;
}

.pohv-table .pohv-tick:disabled {
    cursor: progress;
    opacity: 0.5;
}

/* The skip toggle rides beside the AM/MD/PM label rather than taking a 22nd column — "this
   slot: AM, skipped" reads as one fact, and the sheet has no column for it either. */
.pohv-inline-skip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
    margin: 0 0 0 var(--space-2xs);
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    line-height: var(--line-height-tight);
    cursor: pointer;
}

.pohv-inline-skip input {
    margin: 0;
    cursor: pointer;
}

/* ⛔ Width in `ch`, not rem: these hold at most a 3-digit whole number, and a box wider than
   its content is what turns 21 columns into a horizontal scrollbar for no gain. */
.pohv-table .pohv-inline-num {
    width: 4.5ch;
    padding: 0 var(--space-3xs);
    font-size: var(--font-size-sm);
    text-align: right;
}

/* ⚠ Strips the spinner. It is not cosmetic: at min="1" a single down-arrow press from 1 lands
   on a value the server refuses by name, and the arrows invite exactly that press on a grid
   where every change writes immediately. */
.pohv-table .pohv-inline-num::-webkit-outer-spin-button,
.pohv-table .pohv-inline-num::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.pohv-table .pohv-inline-num {
    -moz-appearance: textfield;
    appearance: textfield;
}

/* ⛔⛔ The row that CONTRADICTS ITSELF: State says a pumpover was requested, the duration says
   the legacy 0 that the cellar sheet prints as a SKIP. 8,392 of 49,251 imported legacy rows carry
   that encoding and until 2026-08-21 staff had no way to correct one. Marked rather than
   auto-fixed — only the winemaker can say which of the two readings was meant. */
.pohv-table .pohv-inline-num--contradicts {
    border-color: var(--state-danger-fg);
    background: var(--state-danger-bg);
    color: var(--state-danger-fg);
    font-weight: var(--font-weight-semibold);
}

.pohv-table .pohv-inline-select {
    max-width: 8rem;
    padding: 0 var(--space-lg) 0 var(--space-3xs);
    font-size: var(--font-size-sm);
}

.pohv-table .pohv-inline-num:disabled,
.pohv-table .pohv-inline-select:disabled {
    background: var(--surface-muted, transparent);
    color: var(--text-muted);
    cursor: not-allowed;
}

/* ⛔ Column sizing CANNOT use :nth-child() and the table must stay `table-layout: auto`. Six
   leading cells carry `rowspan`, so on the 2nd/3rd row of a lot's group those cells are ABSENT
   from the DOM and td:nth-child(1) is "Time Of Day", not "Date". Per-cell classes are the only
   mechanism that survives the rowspan.
   Comments gets a floor so it never collapses into a ribbon of one word per line. That floor
   is ALSO what reserves room for the inline editor: .pohv-cell-editor sets no width of its
   own, so the column cannot jump when an edit opens. Lower this and the editor starts shoving
   the grid — the two belong together. */
.pohv-col-comments {
    min-width: 18rem;
}

/* Rowspan cells centre against the lot's three slot rows. This is the single biggest thing
   that makes the group read as one block instead of six values stranded at the top of a tall
   box — the "align cleanly across the three slots" requirement, done structurally. */
.pohv-table .pohv-rowspan {
    vertical-align: middle;
}

/* ── Slot grouping ─────────────────────────────────────────────────────────────
   A lot's AM/MD/PM slots are three sibling <tr>s, not a nested table. Two devices make them
   read as one block: a heavier rule above the first row of each group, and an alternating
   band across the WHOLE group (⛔ never a per-row zebra — that would cut a group in half).
   Both are applied to the CELLS, not the <tr>: under `border-collapse: collapse` a row border
   of equal width loses to the cell border, and .pohv-table td already declares one. The
   rowspan cells belong to the group's FIRST row, so a class on every <tr> of the group bands
   the whole block including them. */
.pohv-table .pohv-group-first > td {
    border-top-width: 2px;
    border-top-color: var(--border-strong);
}

.pohv-table .pohv-group-cont > td {
    border-top-color: var(--border-subtle);
}

.pohv-table .pohv-group-alt > td {
    background-color: var(--pohv-row-alt);
}

/* Click-selection has to beat the band, and the band paints on the cell, so this must too. */
.pohv-highlight-row,
.pohv-table .pohv-highlight-row > td {
    background-color: var(--pumpover-highlight-row-bg) !important;
}

/* ── Sorting affordances ───────────────────────────────────────────────────────
   Scoped through `.pohv-table thead th` so the hover state actually beats the header rule. */
.pohv-sortable {
    cursor: pointer;
    user-select: none;
}

.pohv-table thead th.pohv-sortable:hover {
    background-color: var(--slate-600);
}

.pohv-sort-icon {
    margin-left: var(--space-2xs);
    cursor: pointer;
    opacity: 0.6;
}

.pohv-sort-icon:hover {
    opacity: 1;
}

/* ── Identity cells: tank, lot, owner ──────────────────────────────────────────
   The tank code and the lot number are the two things a cellar hand scans for, so they carry
   the only bold weight in the grid body; everything hanging off them is muted metadata. */
/* Tank number on its own line, the live SP + temp stacked UNDER it (Jeff, 2026-08-23).
   ⛔ Stacked HERE and not by touching .pohv-tank-state: that class is shared with
   FermentationContractPanel (see the warning at the top of this section), so making IT a column
   would restyle a sibling surface nobody asked to change. .pohv-tank-label's consumers are the
   two pumpover worksheets and nothing else — PumpOversHarvestMain.razor:208 and, since
   2026-08-27, PumpOversMain.razor's legacy grid — so the blast radius is those two. */
.pohv-tank-label {
    white-space: nowrap;
    display: flex;
    flex-direction: column;
    /* Centered, not flex-start (Jeff, 2026-08-23). The chips are the widest thing in this cell, so
       centering sits the bold tank number over the middle of them instead of hanging it off the
       left edge. */
    align-items: center;
    gap: 1px;
}

.pohv-tank-code {
    font-weight: var(--font-weight-semibold);
}

.pohv-tank-state {
    display: inline-flex;
    gap: var(--space-2xs);
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
}

/* ── The legacy grid's tank cell (PumpOversMain) ───────────────────────────────
   Same stack as the harvest worksheet above, but this grid shows a THIRD reading the sibling
   does not — Avg_Change, the trend — and it is the reading that made the old one-line cell
   unreadable: "88°F 89.2°F 1.65°F" gives no clue which is the target, which is the tank right
   now, and which is the direction it is moving.
   ⛔ These are separate classes from .pohv-tank-state on purpose. That one is shared with
   FermentationContractPanel and the harvest worksheet, so sizing it up for a three-value line
   would restyle two surfaces nobody asked to change — the same reasoning that kept the column
   flex off it in 2026-08-23. */
.pohv-tank-id {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    white-space: nowrap;
    /* One notch over the body size. Without it the tank number and the current temperature sat at
       the same size AND the same weight, so the cell had two headlines and the eye had to pick.
       The tank code is the identity a cellar hand scans the column for; the temperature is a
       reading about it. ⛔ Set here rather than on .pohv-tank-code — that class is shared with the
       harvest worksheet, which has no third reading crowding it and does not need the bump. */
    font-size: var(--font-size-base);
}

.pohv-tank-reading {
    display: inline-flex;
    /* --space-xs, not the 2xs the sibling chips use: three readings need to read as three facts,
       and at 4px the SP label of one sits as close to the previous value as to its own number. */
    gap: var(--space-xs);
    align-items: baseline;
    white-space: nowrap;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
}

/* Micro-caps, muted, hugging its own number — the label is an ANNOTATION on the value, not a
   column heading, so it must not compete with the digits for the eye. */
.pohv-tank-reading .pohv-reading-lbl {
    margin-right: 2px;
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    color: var(--text-muted);
}

/* The one reading that is a fact about the tank RIGHT NOW rather than a setting or a trend, so
   it gets the body size and full-contrast ink and needs no label to be found. Its two neighbours
   are the ones that had to be named. */
.pohv-tank-reading .pohv-reading-now {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

/* ── Heater cell: two states that must NOT look alike ──────────────────────────
   The red sun is a STATUS ("this tank is heating, as intended") and stays visually quiet. The
   stale-request control is an EXCEPTION ("this looks finished, close it") and is the only amber
   chip in the grid body, so it reads as the one thing on the row asking for a decision.
   ⛔ Do not restyle the sun to match. Jeff was looking at the sun lighting correctly on three
   tanks on 2026-08-27 and still described the problem as unsolved — because a status icon and a
   call to action wearing the same colour are the same icon. */
.pohv-heater-on {
    color: var(--bs-danger);
}

.pohv-heater-stale {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    padding: 1px var(--space-2xs);
    border: 1px solid var(--state-warning-fg);
    border-radius: var(--radius-sm);
    background: var(--state-warning-bg);
    color: var(--state-warning-fg);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    white-space: nowrap;
    cursor: pointer;
    line-height: var(--line-height-tight);
}

.pohv-heater-stale:hover {
    background: var(--state-warning-fg);
    color: var(--white);
}

.pohv-heater-stale:focus-visible {
    outline: none;
    box-shadow: var(--pohv-focus-ring);
}

.pohv-lot-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2xs);
}

/* A link, not a button: it opens a panel. The Bootstrap .btn it used to be was 38px tall
   inside a cell whose whole row is now 21px. */
.pohv-lot-btn {
    padding: 0;
    border: 0;
    background: none;
    color: var(--text-link);
    font-family: inherit;
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
    text-align: left;
    cursor: pointer;
}

.pohv-lot-btn:hover {
    text-decoration: underline;
}

.pohv-lot-btn:focus-visible {
    outline: none;
    border-radius: var(--radius-xs);
    box-shadow: var(--pohv-focus-ring);
}

/* The reading block doubles as the way into the tank's history, so it has to keep looking like a
   reading -- a cell full of numbers that suddenly reads as a control is worse than no affordance.
   It stays plain until hover, where the underline says it is clickable. */
.pohv-tank-reading-btn {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-align: inherit;
}

.pohv-tank-reading-btn:hover .pohv-reading-now {
    text-decoration: underline;
}

.pohv-tank-reading-btn:focus-visible {
    outline: none;
    border-radius: var(--radius-xs);
    box-shadow: var(--pohv-focus-ring);
}

/* ── Lab ferment cell: the lab's Brix/temp under the live SCADA readings ──────────
   Its own button rather than a child of .pohv-tank-reading-btn above. A bin or a press has no
   SCADA readings at all, so that button is not rendered there -- but the wine in it still gets
   lab work, and without a separate trigger the Brix drawer would be unreachable on exactly
   those rows. Same bare-button reset and focus ring so the two read as one control stack. */
.pohv-lab-ferment-btn {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-align: inherit;
    display: block;
}

.pohv-lab-ferment-btn:hover {
    text-decoration: underline;
}

.pohv-lab-ferment-btn:focus-visible {
    outline: none;
    border-radius: var(--radius-xs);
    box-shadow: var(--pohv-focus-ring);
}

/* ── Temperature history drawer ── */
.pohv-temp-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    min-width: 0;
}

.pohv-temp-head-tank {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
}

.pohv-temp-head-lot {
    font-size: var(--font-size-sm);
    color: var(--gray-600);
}

.pohv-temp-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding: var(--space-md);
    min-width: 0;
}

/* The heater action, above the chart. Reading the history is what decides whether the tank needs
   one, so the action sits on the screen where the decision is made rather than back at the toolbar.
   Above the graph on purpose: at Jeff's 1536x695 the plot plus its legend is most of the drawer, so
   anything below it starts off screen. */
.pohv-temp-actions {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--gray-200);
}

.pohv-temp-start-heater {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--bs-danger);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--bs-danger);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
}

.pohv-temp-start-heater:hover,
.pohv-temp-start-heater:focus-visible {
    background: var(--bs-danger);
    color: #fff;
}

/* Says what the button will PREFILL. Without it the tank and lot appearing in the sheet reads as
   the sheet having remembered something, rather than as this row's values arriving. */
.pohv-temp-actions-hint {
    color: var(--gray-600);
    font-size: 0.76rem;
    min-width: 0;
}

/* Not a button: starting a second heater on a tank auto-closes the first and finalises a billing
   period for it. The running one is stated instead, so the absence of the button has a reason
   printed next to it. */
.pohv-temp-heater-on {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--warning-300, #f5d78e);
    border-radius: var(--radius-md);
    background: var(--warning-050, #fdf6e3);
    color: var(--warning-800, #7a5b12);
    font-size: 0.78rem;
    font-weight: 600;
}

/* What the heater was ASKED to do, under what SCADA reports it is doing. Quieter than the
   running-heater chip above it -- it is context for that chip, not a second alarm. */
.pohv-temp-heater-detail {
    font-size: 0.74rem;
    color: var(--gray-600);
}

/* Its own state, never an empty chart -- a blank plot reads as a broken sensor. */
.pohv-temp-empty {
    padding: var(--space-md);
    border: 1px dashed var(--gray-300);
    border-radius: var(--radius-md);
    color: var(--gray-600);
    font-size: 0.82rem;
    text-align: center;
}

/* Innovint linkage, beside the lot code. Four states, so the palette has to separate them:
   green reads "nothing to do", amber "reconcile this", and the archived case gets its own
   slate treatment because it is a CONTRADICTION rather than a gap -- the lot is linked fine,
   Innovint has simply closed it out while the cellar is still being sent to pump it over. */
.pohv-lot-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0 0.28rem;
    border-radius: var(--radius-xs);
    font-size: 0.62rem;
    font-weight: var(--font-weight-semibold);
    line-height: 1.5;
    white-space: nowrap;
}

.pohv-lot-badge--linked {
    background: var(--green-100);
    color: var(--green-700);
}

.pohv-lot-badge--warn {
    background: var(--amber-100);
    color: var(--amber-800);
}

.pohv-lot-badge--archived {
    background: var(--slate-200, var(--gray-300));
    color: var(--slate-700, var(--gray-700));
}

/* Duplicate local codes. Quiet on purpose -- it qualifies whichever badge precedes it rather
   than competing with it. */
.pohv-lot-badge--dup {
    background: transparent;
    border: 1px solid var(--gray-300);
    color: var(--gray-600);
}

/* Pushed to the trailing edge so the badges stay next to the code they qualify. The row is
   space-between, so without this the icon and the badges swap visual roles. */
.pohv-copy-icon {
    margin-left: auto;
    color: var(--text-disabled);
    cursor: pointer;
}

.pohv-copy-icon:hover {
    color: var(--text-primary);
}

.pohv-owner {
    padding-left: var(--space-2xs);
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    white-space: nowrap;
}

/* Fixed width so AM / MD / PM stack into a column instead of ragging by glyph width — the
   slot is a key, and keys should be scannable as a vertical strip. */
.pohv-slot {
    display: inline-block;
    min-width: 2.5rem;
    padding: 0 var(--space-2xs);
    border-radius: var(--radius-xs);
    background: var(--surface-emphasis);
    color: var(--text-secondary);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-align: center;
    text-transform: uppercase;
}

/* An absent reading is a hole, not a value: quiet it so the eye skips to the real numbers. */
.pohv-nil {
    color: var(--text-disabled);
}

.pohv-heater-on {
    color: var(--state-danger-fg);
}

/* ── Comments column ───────────────────────────────────────────────────────────
   Three channels stacked (Cellar / Adds / NWC). The channel name is a quiet uppercase key and
   the instruction is the content — the reverse of the old bold-label-plus-plain-text, which
   made the labels louder than the words the cellar has to act on. */
.pohv-comment-line {
    margin-bottom: var(--space-3xs);
    line-height: var(--line-height-tight);
}

.pohv-comment-line > span {
    display: inline-block;
    min-width: 3rem;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
}

/* Production-winemaker affordances. Quiet until hovered — this is a dense daily worksheet,
   and three buttons per row shouting for attention would drown the instruction text they sit
   under. */
.pohv-cell-tools {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3xs);
    margin-top: var(--space-3xs);
    opacity: 0.5;
    transition: opacity var(--transition-fast);
}

tr:hover .pohv-cell-tools,
.pohv-cell-tools:focus-within {
    opacity: 1;
}

.pohv-cell-tool {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
    height: 18px;
    padding: 0 var(--space-2xs);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xs);
    background: var(--surface-default);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--font-size-2xs);
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
}

.pohv-cell-tool:hover {
    background: var(--surface-muted);
    border-color: var(--border-strong);
    color: var(--text-primary);
}

.pohv-cell-tool:focus-visible {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--pohv-focus-ring);
}

.pohv-cell-tool:disabled {
    opacity: 0.45;
    cursor: default;
}

/* Not yet stamped. Deliberately the QUIET half of the pair: on a live worksheet most cells are
   unstamped, so this is the background .is-done has to read against. */
.pohv-cell-tool.is-open {
    color: var(--text-muted);
}

/* Stamped. ⛔ ADDS WRITTEN and ADDS MADE are two DISTINCT events — the state is per button and
   there is deliberately no combined "done" look. */
.pohv-cell-tool.is-done {
    background: var(--state-success-bg);
    border-color: var(--state-success-fg);
    color: var(--state-success-fg);
    font-weight: var(--font-weight-semibold);
}

.pohv-cell-tool.is-done:hover {
    background: var(--state-success-bg);
    border-color: var(--state-success-fg);
    color: var(--state-success-fg);
}

/* ── Inline cell editor ────────────────────────────────────────────────────────
   Opens inside the Comments column. A precise single-column micro-grid: an eyebrow label over
   each field, both fields the same width, and the actions on a divided footer at the right
   where the eye expects Save.
   ⚠ It sets NO width of its own. .pohv-col-comments' 18rem floor is what reserves the space,
   so the editor physically cannot shove the column when it opens — the single worst behaviour
   of an in-cell form. ⛔ Do not give this a fixed width to "match" that floor: td is
   border-box, so a matching number is actually ~10px too wide and the column jumps anyway. */
.pohv-cell-editor {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3xs);
    min-width: 0;
    max-width: 22rem;
    padding: var(--space-2xs);
    border: 1px solid var(--border-focus);
    border-radius: var(--radius-sm);
    background: var(--surface-default);
    box-shadow: var(--shadow-md);
}

.pohv-cell-label {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
}

.pohv-cell-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2xs);
    margin-top: var(--space-3xs);
    padding-top: var(--space-2xs);
    border-top: 1px solid var(--border-subtle);
}

.pohv-cell-error {
    padding: var(--space-3xs) var(--space-2xs);
    border-radius: var(--radius-xs);
    background: var(--state-danger-bg);
    color: var(--state-danger-fg);
    font-size: var(--font-size-2xs);
    line-height: var(--line-height-tight);
}

/* ── Staff CREATE form (the same card, more rows) ──────────────────────────────
   Added 2026-08-19 with the staff create/move wave. It is .pohv-cell-editor plus a modifier, not a
   second card: a create and an edit answer the same question about the same cell, and giving them
   two looks would read as two features.
   ⛔ The modifier deliberately declares NO width. .pohv-col-comments' 18rem floor is what reserves
   the room; a create form that sized itself would shove the grid every time it opened — the single
   worst behaviour of an in-cell form, and the reason the edit card has the same prohibition. */
.pohv-cell-editor--create {
    row-gap: var(--space-2xs);
}

/* Which cell is being authored. Louder than a .pohv-cell-label because it is the one line that
   stops a back-dated instruction landing on the wrong day — creating on a PAST date is legitimate
   here (catch-up), so the date cannot be left implicit. */
.pohv-cell-slot {
    padding-bottom: var(--space-3xs);
    border-bottom: 1px solid var(--border-subtle);
    color: var(--text-primary);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
}

/* Set point and minutes, side by side — they are read as one figure ("82 for 30") and separating
   them onto two rows makes the card taller for no gain. */
.pohv-cell-2up {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-2xs);
}

.pohv-cell-2up > div {
    min-width: 0;
}

/* ⚠ Fields stretch INSIDE the card, unlike the toolbar where nothing stretches. The card is a form
   with one column of equal-width controls (the two textareas already behave this way as grid
   items); a number input left at intrinsic width would make the pair ragged. */
.pohv-cell-2up .pohv-field {
    width: 100%;
}

/* The six action flags. Two columns because six stacked rows would push the notes below the fold of
   a card that already opens inside a table cell — and because the printed sheet groups them as a
   block of ticks too, which is what this is authoring. */
.pohv-cell-flags {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-3xs) var(--space-2xs);
}

.pohv-cell-flag {
    display: flex;
    align-items: center;
    gap: var(--space-3xs);
    margin: 0;
    color: var(--text-secondary);
    font-size: var(--font-size-2xs);
    line-height: var(--line-height-tight);
    cursor: pointer;
}

.pohv-cell-flag input {
    margin: 0;
    cursor: pointer;
}

/* ── Write notice (what the last write did, and what the server wants read about it) ───────────
   Sits between the header and the grid, OUTSIDE .pohv-table-container. That container is the page's
   only scroll pane, so a notice rendered inside it would scroll away from the winemaker who has to
   act on it — and could be clipped outright, the same failure that made a confirm dialog invisible
   on the sibling contracts grid.
   Info tint when it is a bare confirmation; warning tint the moment the server returned warnings,
   because those are consequences (this slot bills extra, the printed sheet is now stale), not
   decoration. */
.pohv-notice {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2xs);
    /* ⛔ Explicit like .pohv-header's. This is a direct flex child of .pohv-page's viewport-lock
       column, so it must take its CONTENT height and leave .pohv-grid-row's `flex: 1 1 auto` the
       rest — a notice that grew or shrank with the lock would push the grid's scroll pane around
       every time a write landed. */
    flex: 0 0 auto;
    margin-bottom: var(--space-2xs);
    padding: var(--space-2xs) var(--space-sm);
    border-left: 3px solid var(--state-info-fg);
    border-radius: var(--radius-sm);
    background: var(--state-info-bg);
    color: var(--state-info-fg);
}

.pohv-notice--warn {
    border-left-color: var(--state-warning-fg);
    background: var(--state-warning-bg);
    color: var(--state-warning-fg);
}

.pohv-notice__body {
    flex: 1 1 auto;
    min-width: 0;
}

.pohv-notice__title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
}

.pohv-notice__line {
    margin-top: var(--space-3xs);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-tight);
}

.pohv-notice__close {
    flex: 0 0 auto;
    padding: 0 var(--space-3xs);
    border: 0;
    background: none;
    color: inherit;
    font-size: var(--font-size-xs);
    line-height: 1;
    opacity: 0.7;
    cursor: pointer;
}

.pohv-notice__close:hover {
    opacity: 1;
}

/* ── Move dialog body (rendered inside ConfirmDialogPanel, at PAGE level) ───────────────────────
   ⛔ These rules are global rather than scoped precisely because the dialog is NOT inside
   .pohv-page — moving it back into the cell to make a scoped rule work would reintroduce the
   clipping bug the page-level relocation fixed. */
.pohv-move-identity {
    margin-bottom: var(--space-2xs);
    font-size: var(--font-size-sm);
}

/* FROM → TO, the one line the whole dialog exists to confirm. Both halves are rendered through
   PumpoverSlotText so the destination the winemaker reads here is worded exactly like the warning
   the server sends back afterwards. */
.pohv-move-route {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    flex-wrap: wrap;
    padding: var(--space-2xs) 0;
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
}

.pohv-move-route__from {
    color: var(--text-muted);
}

.pohv-move-route__to {
    color: var(--text-primary);
}

.pohv-move-note {
    margin: 0 0 var(--space-2xs);
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-tight);
}

.pohv-move-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-2xs);
    margin-bottom: var(--space-2xs);
}

.pohv-move-grid .pohv-field {
    width: 100%;
}

/* ── Occupant / lost-race preview ──────────────────────────────────────────────
   The instruction that blocked a move, or that won a race to an empty slot. It is a QUOTE of
   somebody else's cell, so it is inset and quiet — it must not read as a form the winemaker can
   edit from here. ⛔ There is deliberately no Replace affordance in this box: an occupied
   destination refuses, it does not displace. */
.pohv-occupant {
    margin-top: var(--space-2xs);
    padding: var(--space-2xs) var(--space-sm);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
    font-size: var(--font-size-sm);
}

.pohv-occupant__head {
    margin-bottom: var(--space-3xs);
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
}

.pohv-occupant__spec {
    font-weight: var(--font-weight-semibold);
}

.pohv-occupant__ticks {
    color: var(--text-secondary);
    font-size: var(--font-size-2xs);
}

/* ⛔ The "nothing readable in there" case — NOT an empty box. A blank card beside a refusal reads
   as "there is nothing there", which is the opposite of what happened, and confirming against a
   blank box is exactly the failure PumpoverCellSnapshot.HasSubstance exists to prevent. */
.pohv-occupant__empty {
    color: var(--text-secondary);
    line-height: var(--line-height-tight);
}

.pohv-occupant__empty .pohv-btn {
    margin-top: var(--space-2xs);
}

/* ── Merge panel (the occupied-destination decision, inside the move dialog) ────────────────────
   Added 2026-08-19 with the merge wave. ⭐ THE REFRAME it dresses: a pumpover cell is not one
   treatment, it is a SLOT carrying several INDEPENDENT channels (cellar note, additions spec, set
   point, duration, pump type, six action flags), so an occupied destination is usually NOT a
   conflict — an inoculation and an existing pumpover both genuinely happen in that slot. This panel
   therefore reads as "here are the two halves of one day", not as an error.
   ⛔ Global rather than scoped, for the same reason .pohv-move-* and .pohv-occupant are: the dialog
   renders at PAGE level, OUTSIDE .pohv-page, because a confirm inside .pohv-table-container (the
   page's only scroll pane) is clipped by construction. Moving it back to make a scoped rule work
   would reintroduce the invisible-dialog bug the relocation fixed (b5a2993d).
   ⛔⛔ The max-height is LOAD-BEARING, not polish. ConfirmDialogPanel declares no max-height and no
   overflow on .confirm-dialog / .confirm-body, and its overlay centres with align-items:center — so
   an over-tall body grows off BOTH edges of the viewport and takes the footer buttons with it. The
   two note channels are MaxLength(4000) each and this panel can render four of them at once, so
   that is one pasted dosing spec away, not hypothetical. Capping HERE keeps the fix inside this
   page's own rules; the shared dialog is not this page's to change. */
.pohv-merge {
    margin-top: var(--space-2xs);
    padding: var(--space-2xs) var(--space-sm);
    border-left: 3px solid var(--accent-primary);
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
    font-size: var(--font-size-sm);
    max-height: 42vh;
    overflow-y: auto;
}

/* The source carries a fulfilment stamp (or legacy text) — a merge is off the table whatever the
   channels say. Warning tint, because it is a real dead end rather than a choice to make. */
.pohv-merge--stopped {
    border-left-color: var(--state-warning-fg);
    background: var(--state-warning-bg);
}

/* Both cells populate the same channel with DIFFERENT values — the one genuine conflict on this
   surface, and the only case with no one-click resolution. */
.pohv-merge--conflict {
    border-left-color: var(--state-danger-fg);
    background: var(--state-danger-bg);
}

.pohv-merge__head {
    margin-bottom: var(--space-3xs);
    color: var(--text-primary);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
}

.pohv-merge__why,
.pohv-merge__foot {
    margin: 0 0 var(--space-2xs);
    color: var(--text-secondary);
    font-size: var(--font-size-2xs);
    line-height: var(--line-height-tight);
}

.pohv-merge__foot {
    margin-bottom: 0;
    margin-top: var(--space-2xs);
}

/* ⭐ The two cells SIDE BY SIDE. On a conflict the winemaker is being asked which wording the day
   should carry, and that question cannot be answered from one half of it — so both halves are on
   screen at once rather than one behind a toggle.
   ⚠ minmax(0, 1fr) on both tracks, not 1fr: a grid item's default min-width:auto refuses to shrink
   below its content, and a 4000-char note channel would otherwise widen the dialog off-screen
   instead of wrapping. Same reason .pohv-cell-2up and .pohv-cell-flags carry it. */
.pohv-merge-pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-2xs);
}

/* Below the dialog's own max-width the two columns stop being a comparison and start being two
   half-width paragraphs, so they stack. ⚠ Keyed to the dialog's 620px card plus the overlay's
   padding, NOT to a device breakpoint — this box only ever lives inside that card. */
@media (max-width: 700px) {
    .pohv-merge-pair {
        grid-template-columns: minmax(0, 1fr);
    }
}

.pohv-merge__side {
    min-width: 0;
    padding: var(--space-2xs);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-xs);
    background: var(--surface-default);
}

/* The half that ARRIVES. Tinted so the eye can tell "what is already there" from "what I am adding"
   without reading the labels first — which is the entire question this panel asks. */
.pohv-merge__side--incoming {
    border-color: var(--accent-primary);
    background: var(--accent-primary-soft);
}

.pohv-merge__label {
    margin-bottom: var(--space-3xs);
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
}

.pohv-merge .pohv-btn {
    margin-top: var(--space-2xs);
}

/* ── Scan-review fields (ScanField + badges) ─────────────────────────────────────────────────
   Hoisted from LabelScanPanel/ColaScanPanel/PermitScanPanel scoped css 2026-08-01: ScanField is a
   child component, so scoped rules only reached it via ::deep (or by accident); global is the
   correct home. Unified on the LabelScanPanel variant. */
.scan-field-label {
    font-size: 0.875rem;
    font-weight: var(--font-weight-medium);
    color: var(--gray-700);
    margin: 0;
}

.confidence-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    font-size: var(--font-size-sm);
    padding: var(--space-3xs) var(--space-sm);
    border-radius: var(--radius-sm);
}

.confidence-badge.low {
    background-color: var(--yellow-100);
    color: var(--yellow-700);
}

.confidence-badge i {
    font-size: var(--font-size-xs);
}

.confidence-badge.medium {
    background-color: var(--blue-100);
    color: var(--blue-700, var(--blue-500));
}

.source-indicator {
    font-size: var(--font-size-xs);
    padding: 0.1rem 0.4rem;
    border-radius: var(--radius-xs);
    background: var(--blue-100);
    color: var(--blue-500);
    text-transform: capitalize;
}

/* ── Match-score strength bar + reason chips ─────────────────────────────────────────────────
   Shared by QuickReceiveScanPanel's confirm dialog and the WeightTagOrphans link dialog: the
   matcher's numeric score rendered as a bar + %, with each contributing signal as a chip.
   Global because it appears on 2+ surfaces (site rule: hoist, don't duplicate). */
.nwc-match-score {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin: var(--space-2xs) 0;
}

.nwc-match-score-track {
    flex: 1 1 auto;
    height: 8px;
    border-radius: 4px;
    background: var(--gray-200);
    overflow: hidden;
    min-width: 80px;
}

.nwc-match-score-fill {
    height: 100%;
    border-radius: 4px;
    background: var(--green-500);
}

.nwc-match-score-label {
    font-size: var(--font-size-sm);
    font-weight: 600;
    white-space: nowrap;
}

.nwc-match-reasons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
    margin-bottom: var(--space-2xs);
}

.nwc-match-reason-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3xs);
    font-size: var(--font-size-xs);
    padding: var(--space-3xs) var(--space-xs);
    border-radius: var(--radius-sm);
    background: var(--green-100);
    color: var(--green-700);
}

/* ── Weight-tag scan review block (ReceiveFruitPanel) ────────────────────────────────────────
   Global on purpose: the block hosts ScanField children (see hoist note above). */
.nwc-scan-review {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    margin-top: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    background: var(--gray-50);
}

.nwc-scan-review-head {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
    color: var(--gray-700);
}

.nwc-scan-review-title {
    font-weight: var(--font-weight-medium);
}

.nwc-scan-review-meta {
    font-size: var(--font-size-sm);
    color: var(--gray-500);
}

.nwc-scan-review-headline {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
}

.nwc-scan-chip {
    font-size: var(--font-size-sm);
    padding: var(--space-3xs) var(--space-xs);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-sm);
    background: #fff;
    color: var(--gray-700);
    white-space: nowrap;
}

.nwc-scan-chip-ok {
    border-color: var(--green-100);
    background: var(--green-100);
    color: var(--green-700);
}

.nwc-scan-warning {
    display: flex;
    align-items: baseline;
    gap: var(--space-2xs);
    font-size: var(--font-size-sm);
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-sm);
    background: var(--yellow-100);
    color: var(--yellow-700);
}

/* Sticky document pane in a scan review split (scanned tag/doc beside the fields or verdict).
   Hoisted from ReceiveFruitPanel + QuickReceiveScanPanel, which held byte-identical copies —
   CSS isolation is per-component, so a shared shape has to live here to stay shared. The host
   panel owns the grid (column ratio differs by panel) and the frame height; this owns only the
   sticky behaviour, which is the part that must not drift.

   The collapse query is deliberately UNNAMED: it resolves to the nearest ancestor container,
   which is the host panel in both callers (.rf-panel / .qrs-panel). That keeps the rule
   caller-agnostic instead of hardcoding one panel's container name. */
.nwc-scan-split-doc {
    position: sticky;
    top: var(--space-xs);
    /* A positioned element paints above static siblings even with no z-index, so once the panel
       body scrolled, this pane covered the entry-form row above it — the Date input rendered over
       the Scan button (desktop, 2026-08-02). Pinning it to 0 keeps it BELOW the form controls,
       which claim var(--z-base) on their own container. Do not raise this.
       ⚠ The consumer must raise a SIBLING of this pane's wrapper, never an ANCESTOR of it — an
       ancestor creates a stacking context that contains the pane and lifts it along (that mistake
       made the first version of this fix inert on ReceiveFruitPanel). Canonical raised elements:
       .qrs-scan-control and .rf-entry-fields. */
    z-index: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
}

@container (max-width: 900px) {
    /* Narrow surfaces review by scrolling, not side-by-side: unstick and hoist the document
       above the fields. Callers collapse their own grid to one column at the same breakpoint. */
    .nwc-scan-split-doc {
        position: static;
        order: -1;
    }
}

.nwc-scan-review-toggle {
    align-self: flex-start;
    padding: 0;
}

.nwc-scan-review-section {
    margin-top: var(--space-2xs);
}

/* Explains the greyed (read-only) fields in the scan review grid — the ones the scan captured but
   HarvestActual has no column for, so a correction would be silently discarded. */
.nwc-scan-review-note {
    margin: 0 0 var(--space-2xs);
    font-size: 0.78rem;
    color: var(--text-muted);
}

.nwc-scan-review-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: var(--space-sm);
}

.nwc-scan-review-checks {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
    margin-top: var(--space-2xs);
    font-size: var(--font-size-sm);
}

.nwc-scan-pill {
    display: inline-block;
    font-size: var(--font-size-xs);
    line-height: 1.4;
    padding: 0 0.35rem;
    border-radius: var(--radius-xs);
    background: var(--blue-100);
    color: var(--blue-500);
    vertical-align: text-bottom;
}

/* ── Scale tab: give ScaleLiveStream a pane inside ManagerShell ─────────────────────────────────
   ⛔⛔ WITHOUT THIS, 413px OF THE SCALE READINGS ARE UNREACHABLE ON DESKTOP. Found 2026-08-20 by
   `dt.ps1 ui-smoke manager-tab-scroll` on its first working run — not by a person, because nobody
   scrolls that tab far enough to notice. Same defect as the Harvest Today tab reported live the
   day before; this one had simply never been looked at.

   ManagerShell viewport-locks on desktop (.ms-shell height: calc(100vh - 56px), .ms-overlay
   overflow: hidden) and requires each hosted tab to own its scrolling. ScaleLiveStream was written
   for its own /liveStream/scaleLiveStream route, where the document scrolls, and renders TWO
   top-level siblings: .grid-container (the camera, ScaleLiveStream.razor:8) and .wrapper (the
   readings, :96). The camera keeps its content height; the readings become the pane.

   ⚠ The :has() scope is load-bearing, NOT decoration. `.wrapper` is a generic class still shared
   by TankSpecs and BottlingSchedule (its ContractMain and WineProfileMain siblings were retired
   2026-09-05 and 2026-09-15) — and an unscoped
   `.ms-overlay > .wrapper` would silently restyle any of them that is ever hosted in a shell
   overlay. Keying on a sibling .grid-container matches ScaleLiveStream and nothing else. */
.ms-overlay:has(> .grid-container) > .grid-container {
    flex: 0 0 auto;
}

.ms-overlay:has(> .grid-container) > .wrapper {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* ───────────────────────────────────────────────────────────────────────────────────────────────
   .lws-* — shared furniture for the Bulk Wine LOT WORKSPACE sections
   (Pages/BulkWine/Workspace/Lot*Section.razor).

   ⚠ WHY THIS IS IN site.css AND NOT IN A .razor.css. CSS isolation scopes a stylesheet to ONE
   component's own markup. The workspace renders one of FIVE sibling section components behind a
   domain tab, and they share a visual language — a titled block, a compact table, a foot note. Four
   isolated copies of the same rules is exactly the duplication the pre-push css-drift-gate exists to
   catch, and the panel's own .razor.css cannot reach a child component's elements. Same reasoning as
   .ms-filterbar above.

   ⛔ These are the workspace's OWN furniture, deliberately not hoisted to a .nwc-* utility: they
   have one consumer family and no second sighting. If a third feature ever wants the same shapes,
   THEN hoist and rename — do not pre-emptively generalise a family of five siblings into a utility.

   Everything genuinely generic already comes from the utilities above: .nwc-kv (key/value),
   .nwc-empty (every "nothing here" state), .nwc-chip (status pills), .nwc-tab-bar/.nwc-tab (the
   domain strip) and .nwc-statstrip (the lot summary). */

.lws-block {
    margin-bottom: var(--space-md);
}

.lws-heading {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin: 0 0 var(--space-xs);
    color: var(--text-secondary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
}

/* The workspace's key/value pairs are narrower than the .nwc-kv default: a panel pane is not a
   detail page, and a 110px label column strands the values well right of where the eye lands. */
.lws-kv {
    grid-template-columns: 92px 1fr;
    margin-bottom: var(--space-md);
}

/* A lot that no longer comes back from Innovint. Stated, never hidden — the sheet paints these rows
   red for the same reason, and a panel that quietly showed stale composition as live would be worse
   than one that showed nothing. */
.lws-gone {
    display: flex;
    align-items: flex-start;
    gap: var(--space-xs);
    margin-bottom: var(--space-md);
    padding: var(--space-xs) var(--space-sm);
    border: 1px solid var(--state-warning-bg);
    border-radius: var(--radius-sm);
    background: var(--state-warning-bg);
    color: var(--state-warning-fg);
    font-size: var(--font-size-xs);
}

/* ── Composition breakdowns ─────────────────────────────────────────────────── */

.lws-parts {
    margin: 0;
    padding: 0;
    list-style: none;
}

.lws-parts li {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: 2px 0;
    font-size: var(--font-size-xs);
}

.lws-part-code {
    flex: 0 0 4.5rem;
    font-family: var(--sheet-font, inherit);
    font-weight: var(--font-weight-medium);
}

/* The bar is decoration on top of the number, never instead of it — the percentage is printed
   beside it and is what anyone quotes. */
.lws-part-bar {
    flex: 1 1 auto;
    min-width: 0;
    height: 6px;
    border-radius: 3px;
    background: var(--surface-subtle);
    overflow: hidden;
}

.lws-part-bar > span {
    display: block;
    height: 100%;
    background: var(--accent-primary);
}

.lws-part-pct {
    flex: 0 0 3.2rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* ── The commitments table ──────────────────────────────────────────────────── */

.lws-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--font-size-xs);
}

.lws-table th {
    padding: 3px 6px;
    border-bottom: 1px solid var(--border-default);
    color: var(--text-muted);
    font-weight: var(--font-weight-medium);
    text-align: left;
    white-space: nowrap;
}

.lws-table td {
    padding: 4px 6px;
    border-bottom: 1px solid var(--border-subtle);
    vertical-align: top;
}

.lws-table th.num,
.lws-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Executed and dead rows are history: kept on the pane, and visibly past. */
.lws-muted td {
    color: var(--text-muted);
}

.lws-chip-word {
    margin-left: 4px;
}

.lws-holds,
.lws-expiry,
.lws-note-when {
    margin-left: 6px;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
    white-space: nowrap;
}

.lws-holds {
    color: var(--state-warning-fg);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
}

.lws-memo {
    font-family: var(--sheet-font, inherit);
}

.lws-none {
    color: var(--text-muted);
}

/* ── Notes ──────────────────────────────────────────────────────────────────── */

/* pre-wrap, so a note typed with line breaks keeps them. A broker's note is often a short list. */
.lws-note {
    margin: 0;
    font-size: var(--font-size-sm);
    white-space: pre-wrap;
}

.lws-foot {
    margin: var(--space-sm) 0 0;
    color: var(--text-muted);
    font-size: var(--font-size-2xs);
}
