/* =====================================================================
   Shared UI components — Parts Inventory
   ===================================================================== */

/* ── Buttons ─────────────────────────────────────────────────────────── */
.btn {
    display:         inline-flex;
    align-items:     center;
    gap:             var(--space-2);
    padding:         var(--space-2) var(--space-4);
    border:          2px solid transparent;
    border-radius:   var(--radius-md);
    font-size:       var(--font-size-sm);
    font-weight:     600;
    line-height:     1.4;
    white-space:     nowrap;
    cursor:          pointer;
    transition:      background var(--transition-fast),
                     color var(--transition-fast),
                     border-color var(--transition-fast),
                     box-shadow var(--transition-fast);
    user-select:     none;
}
.btn:disabled { opacity: .5; pointer-events: none; }

.btn-primary {
    background: var(--color-primary);
    color:      var(--color-text-inverse);
    border-color: var(--color-primary);
}
.btn-primary:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }

.btn-accent {
    background: var(--color-accent);
    color:      var(--color-text-inverse);
    border-color: var(--color-accent);
}
.btn-accent:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }

.btn-outline {
    background:   transparent;
    /* `--color-link` not `--color-primary` — this IS ink, and `--color-primary` never changes for
       dark mode (it's also the background of primary buttons). See variables.css's own comment on
       `--color-link` for the measured contrast failure this avoids. Border stays `--color-primary`
       deliberately — a border in the same family reads as "outline of the primary action," and
       borders don't carry the same WCAG text-contrast requirement text does. */
    color:        var(--color-link);
    border-color: var(--color-primary);
}
.btn-outline:hover { background: var(--color-primary); color: var(--color-text-inverse); }

.btn-ghost {
    background:   transparent;
    color:        var(--color-text-secondary);
    border-color: transparent;
}
.btn-ghost:hover { background: var(--color-bg-subtle); }

.btn-danger {
    background:   var(--color-danger);
    color:        #fff;
    border-color: var(--color-danger);
}
.btn-danger:hover { background: #a01020; }

.btn-sm { padding: var(--space-1) var(--space-3); font-size: var(--font-size-xs); }
.btn-lg { padding: var(--space-3) var(--space-6); font-size: var(--font-size-md); }
.btn-icon { padding: var(--space-2); }

/* ── Badges ─────────────────────────────────────────────────────────── */
.badge {
    display:       inline-flex;
    align-items:   center;
    gap:           var(--space-1);
    padding:       2px var(--space-2);
    border-radius: var(--radius-pill);
    font-size:     var(--font-size-xs);
    font-weight:   600;
    line-height:   1.5;
    white-space:   nowrap;
}
.badge-primary  { background: var(--color-primary-light); color: #fff; }
.badge-accent   { background: var(--color-accent);        color: #fff; }
.badge-success  { background: var(--color-success-bg);    color: var(--color-success);  border: 1px solid var(--color-success-border); }
.badge-warning  { background: var(--color-warning-bg);    color: var(--color-warning);  border: 1px solid var(--color-warning-border); }
.badge-danger   { background: var(--color-danger-bg);     color: var(--color-danger);   border: 1px solid var(--color-danger-border); }
.badge-info     { background: var(--color-info-bg);       color: var(--color-info);     border: 1px solid var(--color-info-border); }
.badge-muted    { background: var(--color-bg-subtle);     color: var(--color-text-muted); }

/* ── Cards ───────────────────────────────────────────────────────────── */
.card {
    background:    var(--color-bg-surface);
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow:    var(--shadow-sm);
}
.card-body   { padding: var(--space-6); }
.card-header {
    padding:       var(--space-4) var(--space-6);
    border-bottom: 1px solid var(--color-border-subtle);
    font-weight:   700;
    font-size:     var(--font-size-lg);
}
.card-footer {
    padding:       var(--space-4) var(--space-6);
    border-top:    1px solid var(--color-border-subtle);
    background:    var(--color-bg-subtle);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* ── Stat cards (dashboard) ─────────────────────────────────────────── */
.stat-card {
    background:    var(--color-bg-surface);
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    padding:       var(--space-5) var(--space-6);
    box-shadow:    var(--shadow-sm);
    display:       flex;
    align-items:   center;
    gap:           var(--space-4);
    transition:    box-shadow var(--transition-base);
}
.stat-card:hover { box-shadow: var(--shadow-md); }
/* Only cards wired to a click-through earn the pointer + hover cue. A purely informational card
   (Inventory Value) resets the base rule's hover shadow so it stops implying it's clickable when
   it isn't — 2026-09-01, added alongside the dashboard's first real click-throughs. */
.stat-card--static:hover { box-shadow: var(--shadow-sm); }
.stat-card[role="button"] { cursor: pointer; }
.stat-card[role="button"]:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* A grid item won't shrink below its content's min-width by default, which lets a long
   unwrappable value push the card past its track. */
.stat-card { min-width: 0; }

.stat-card__icon {
    width:          48px;
    height:         48px;
    border-radius:  var(--radius-md);
    display:        flex;
    align-items:    center;
    justify-content:center;
    font-size:      22px;
    flex-shrink:    0;
}
.stat-card__icon--primary { background: rgba(30,58,82,.12); color: var(--color-link); }
.stat-card__icon--accent  { background: rgba(224,112,16,.12); color: var(--color-accent); }
.stat-card__icon--success { background: var(--color-success-bg); color: var(--color-success); }
.stat-card__icon--warning { background: var(--color-warning-bg); color: var(--color-warning); }
.stat-card__icon--danger  { background: var(--color-danger-bg);  color: var(--color-danger); }

/* The text column must be allowed to shrink. A flex child defaults to min-width:auto, so a long
   value (an 8-figure inventory total) refuses to narrow and simply runs out past the card edge. */
.stat-card > div { min-width: 0; }

.stat-card__value {
    font-size:   var(--font-size-2xl);
    font-weight: 800;
    line-height: 1;
    color:       var(--color-text);
}
/* Money can be an order of magnitude longer than a count — "$12,345,678" vs "43". Give it its own
   size and keep the digits on one line; a wrapped or clipped dollar figure is worse than a smaller
   one. Cents are dropped at this scale (see formatMoneyWhole) — the title carries the exact value. */
.stat-card__value--money {
    font-size:            var(--font-size-xl);
    white-space:          nowrap;
    font-variant-numeric: tabular-nums;
    letter-spacing:       -.01em;
}
.stat-card__label {
    font-size: var(--font-size-sm);
    color:     var(--color-text-muted);
    margin-top: var(--space-1);
}
.stat-card__note {
    font-size:  var(--font-size-xs);
    color:      var(--color-warning);
    margin-top: 2px;
}

/* ── Toast notifications ─────────────────────────────────────────────── */
#toast-container {
    position:   fixed;
    bottom:     calc(var(--bottom-nav-h) + var(--space-4));
    right:      var(--space-4);
    z-index:    var(--z-toast);
    display:    flex;
    flex-direction: column;
    gap:        var(--space-2);
    pointer-events: none;
}
@media (min-width: 769px) {
    #toast-container { bottom: var(--space-4); }
}

.toast {
    display:       flex;
    align-items:   flex-start;
    gap:           var(--space-3);
    padding:       var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    background:    var(--color-text);
    color:         var(--color-text-inverse);
    box-shadow:    var(--shadow-lg);
    font-size:     var(--font-size-sm);
    font-weight:   500;
    max-width:     340px;
    /* 🔴 NOT `auto`. The container is pointer-events:none so clicks fall through, and this line
       put them back — on an element parked in the bottom-right corner, which is exactly where
       every modal's primary button sits. A "Found via Digi-Key" toast therefore covered CREATE on
       the New Part form for its full 3.5 s: you looked a part up, clicked Create, and nothing
       happened. Found 2026-08-20 by elementFromPoint(935,683) returning the toast, after it had
       silently eaten several saves during the seed drive.
       Nothing in the app listens for a click on a toast — they dismiss on a timer — so `auto`
       bought nothing and cost the one button the user reaches for next. */
    pointer-events: none;
    animation:     slideInToast .22s ease both;
}
@keyframes slideInToast {
    from { opacity: 0; transform: translateX(20px); }
    to   { opacity: 1; transform: translateX(0); }
}
.toast--success { background: var(--color-success); }
.toast--warning { background: var(--color-warning); }
.toast--error   { background: var(--color-danger); }
.toast--info    { background: var(--color-info); }

/* ── Confirm dialog ─────────────────────────────────────────────────── */
.confirm-overlay {
    position:  fixed; inset: 0;
    background: rgba(0,0,0,.5);
    display:   flex; align-items: center; justify-content: center;
    z-index:   var(--z-modal);
    padding:   var(--space-4);
}
/* A dialog with another dialog ON TOP of it.  (sweep item E34)
   "Add a location" opens the picker; "＋ New Location" inside it opens a second dialog, and the
   first stayed fully lit behind — its picker list still reading as live when it no longer was.
   The layer beneath is dimmed and pushed back so only one dialog ever looks operable; `inert` (set
   in App._pushOverlay) is what actually takes it out of reach of the mouse, the keyboard and the
   accessibility tree, so this rule is purely the part you can see. */
.confirm-overlay.is-under {
    background: rgba(0,0,0,.2);      /* the layer above supplies the rest of the darkening */
}
.confirm-overlay.is-under .confirm-dialog {
    opacity:    .45;
    filter:     saturate(.6);
    transform:  scale(.97);
    transition: opacity .15s ease, transform .15s ease;
}
.confirm-dialog {
    background:    var(--color-bg-surface);
    border-radius: var(--radius-xl);
    padding:       var(--space-8);
    max-width:     400px; width: 100%;
    box-shadow:    var(--shadow-xl);
}
.confirm-dialog h3 { font-size: var(--font-size-lg); margin-bottom: var(--space-3); }
.confirm-dialog p  { color: var(--color-text-secondary); margin-bottom: var(--space-6); }
.confirm-actions   { display: flex; gap: var(--space-3); justify-content: flex-end; }

/* ─── THE ✕, EVERYWHERE ────────────────────────────────────────────────────────────────────────
   🔴 the owner's ruling, 2026-08-27, after driving 10.5 and finding "Cancel" beside "Cancel order":
   *"i still think confusion is removed with the x for close … change ALL 'X' css to be more
   standard red background, white text … increase the font size a bit so not 'searching' and
   consistent with what a user expects with modern UI"* — then, on whether it was only confirms:
   *"same thing in my eyes, also all forms!"*

   So the footer decline is gone from confirms and from forms, and the ✕ carries the whole job of
   getting out. That makes its VISIBILITY a requirement, not decoration — an escape you have to
   search for is not an escape.

   ⚠️ SPECIFICITY, deliberately. `.modal-close` carries `btn btn-ghost btn-icon`, and `.btn-ghost`
   is (0,1,0). A bare `.modal-close` rule is also (0,1,0) and would win or lose on source order
   alone — which is exactly how `.text-right` ended up applied and inert against `.data-table td`,
   the bug behind *"cost prices do not line up"*. `.modal-close.btn` is (0,2,0) and wins outright.
   Do not "simplify" this selector.

   One rule, ten files: FormModal plus every module that hand-rolled a modal header. */
.modal-close.btn,
.confirm-close,
.scanner-modal__close {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    padding: 0;
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    background: var(--color-danger);
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: filter .12s ease;
}
.modal-close.btn:hover,
.confirm-close:hover,
.scanner-modal__close:hover { background: var(--color-danger); filter: brightness(1.12); }

/* ✅ RULED 2026-08-27 — the owner, after looking at it on the iPad: *"looks good to me."* Trialled in
   CSS and KEPT; do not re-litigate. Earlier in the same exchange: *"boy id like to use emoji
   whereever possible simple, recognizeable,
   and prob very similar os to os?"* He is right about the class of glyph he means: ❌ ✅ ⚠️ ➕ are
   drawn as the same SHAPE by Apple, Microsoft and Google. Line weight and shade differ; nobody
   misreads a red X. (Pictorial emoji — tools, documents — are the ones that genuinely diverge.)

   Swapped in CSS rather than markup on purpose: the ✕ appears 51 times across 14 files, and NOT
   all of them are closes — several are inline REMOVE actions (the ✕ beside each vendor on an RFQ
   row). Turning those into a big red ❌ would be wrong, and is exactly what E51 exists to prevent:
   one glyph per ACTION, not one glyph everywhere. Targeting the two close CLASSES hits only closes.

   ⚠️ Trade knowingly accepted: an emoji cannot be recoloured, so dark-mode contrast and the
   hover tint no longer apply to the mark itself — only to the box behind it, which is why the box
   goes transparent here. Deleting this block returns to the styled white-on-red ✕ of f2fde50 —
   kept as the stated FALLBACK if ❌ ever renders badly on the plant's Surface Pros.

   ⬅️ STILL OPEN, the owner's own words: *"we may tweak some inline ones maybe not."* The INLINE ✕
   — remove-a-vendor-from-an-RFQ and its siblings — is a DIFFERENT ACTION from closing a dialog and
   is deliberately untouched. That call belongs to E51.  */
.modal-close.btn,
.confirm-close,
.scanner-modal__close {
    background: transparent;
    color: transparent;
    font-size: 0;
}
.modal-close.btn:hover,
.confirm-close:hover { background: transparent; filter: none; transform: scale(1.08); }
.modal-close.btn::before,
.confirm-close::before,
.scanner-modal__close::before {
    content: "❌";
    font-size: 21px;
    line-height: 1;
    color: initial;
}
.modal-close.btn:focus-visible,
.confirm-close:focus-visible { outline: 2px solid var(--color-danger); outline-offset: 2px; }

/* Confirms had no header row at all — the ✕ needs somewhere to sit beside the title. */
.confirm-header      { display: flex; align-items: flex-start; justify-content: space-between;
                       gap: var(--space-3); margin-bottom: var(--space-3); }
.confirm-header h3   { margin-bottom: 0; }

/* ── Loading overlay ─────────────────────────────────────────────────── */
#loading-overlay {
    position:       fixed; inset: 0;
    background:     var(--color-bg-surface);
    display:        flex; align-items: center; justify-content: center;
    z-index:        var(--z-overlay);
    backdrop-filter:blur(2px);
}
#loading-overlay.hidden { display: none; }

/* ── Empty state ─────────────────────────────────────────────────────── */
.empty-state {
    text-align: center;
    padding:    var(--space-12) var(--space-6);
    color:      var(--color-text-muted);
}
.empty-state__icon  { font-size: 48px; margin-bottom: var(--space-4); }
.empty-state__title { font-size: var(--font-size-lg); font-weight: 600; color: var(--color-text-secondary); margin-bottom: var(--space-2); }

/* ── Inventory stock badges ─────────────────────────────────────────── */
.stock-badge { font-size: var(--font-size-xs); font-weight: 700; padding: 2px 8px; border-radius: var(--radius-pill); }
.stock-badge--ok       { background: var(--color-in-stock-bg);    color: var(--color-in-stock); }
.stock-badge--low      { background: var(--color-low-stock-bg);   color: var(--color-low-stock); }
.stock-badge--out      { background: var(--color-out-of-stock-bg); color: var(--color-out-of-stock); }

/* ── Role badge ─────────────────────────────────────────────────────── */
.role-badge            { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: var(--radius-pill); text-transform: uppercase; letter-spacing: .04em; }
.role-badge--admin     { background: #2d1080; color: #fff; }
.role-badge--manager   { background: var(--color-primary); color: #fff; }
.role-badge--user      { background: var(--color-bg-subtle); color: var(--color-text-secondary); }
.role-badge--readonly  { background: var(--color-bg-muted); color: var(--color-text-muted); }

/* ── Refresh Modal (brand logo click) ──────────────────────────────── */
.refresh-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.55);
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.refresh-modal {
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: 14px;
    padding: 32px 28px 24px;
    width: 300px;
    text-align: center;
    box-shadow: 0 12px 40px rgba(0,0,0,0.45);
}
.refresh-modal-icon    { font-size: 44px; margin-bottom: 12px; }
.refresh-modal-title   { font-size: 1.1rem; font-weight: 700; color: var(--color-text-secondary); margin-bottom: 6px; }
.refresh-modal-desc    { font-size: .825rem; color: var(--color-text-muted); margin-bottom: 14px; line-height: 1.5; }

/* ── E39: build status — "am I running today's build?" ────────────────────
   Lives in the Refresh dialog because that is where you already go to fix a
   stale one: the diagnosis and the remedy in the same place. */
.build-status { text-align: left; margin: 0 0 18px; padding: 10px 12px;
    background: var(--color-bg-subtle); border-radius: var(--radius-md);
    font-size: var(--font-size-xs); }
.build-row { display: flex; gap: 8px; align-items: baseline; padding: 3px 0;
    color: var(--color-text-secondary); }
.build-row__mark   { flex: 0 0 auto; }
.build-row__label  { flex: 0 0 92px; color: var(--color-text); }
.build-row__detail { flex: 1; min-width: 0; word-break: break-word; }
.build-row--warn   { color: var(--color-warning); }
.build-hint { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--color-border-subtle);
    color: var(--color-text-muted); }
.refresh-modal-offline { padding: 10px 14px; border-radius: 8px; background: var(--color-bg-subtle); color: var(--color-text-muted); font-size: .8rem; margin-bottom: 14px; }
.refresh-modal .btn-refresh-now {
    display: block; width: 100%;
    padding: 13px; border-radius: 8px; border: none;
    background: var(--color-accent); color: #fff;
    font-size: .95rem; font-weight: 600; cursor: pointer;
    margin-bottom: 10px;
}
.refresh-modal .btn-refresh-now:hover { opacity: .9; }
.refresh-modal .btn-refresh-cancel {
    display: block; width: 100%;
    padding: 11px; border-radius: 8px; border: none;
    background: var(--color-bg-subtle); color: var(--color-text-muted);
    font-size: .9rem; cursor: pointer;
}
.refresh-modal .btn-refresh-cancel:hover { color: var(--color-text-secondary); }

/* ── Camera Scanner ──────────────────────────────────────────────────── */
/* ── The scanner panel follows the THEME, like everything else ──────────────
   Owner, 2026-08-06: *"why does scan screen not follow the lightmode/dark mode like all other
   screens? its always dark"* — and he was right that it was the only thing in the app that didn't.
   This block was written as a camera viewfinder, where a black chrome around the video is the right
   call, and it hardcoded `#111` with `#fff` text and a stack of `rgba(255,255,255,…)`. But the panel
   long ago stopped being just a viewfinder: it carries a Take Photo button, a manual entry field, a
   Submit and a live result list. That is a FORM, and a form has no business ignoring the theme.
   Everything here now reads from the variables, so light follows the OS exactly like every other
   surface. **The one thing deliberately left black is the video area itself** (`#tt-scanner-reader`)
   — that is the camera feed, and black is correct behind it in either theme. */
.scanner-overlay {
    position:   fixed;
    inset:      0;
    /* Matches the app's own modal scrim (app.html `.modal-overlay.open`) instead of a near-opaque
       black that only made sense when the panel behind it was a viewfinder. */
    background: rgba(0,0,0,.5);
    display:    flex;
    align-items: center;
    justify-content: center;
    z-index:    1200;
    padding:    var(--space-4);
    opacity:    0;
    transition: opacity .2s ease;
    pointer-events: none;
}
.scanner-overlay.open {
    opacity:        1;
    pointer-events: all;
}

.scanner-modal {
    background:    var(--color-bg-surface);
    color:         var(--color-text);
    border:        1px solid var(--color-border);
    border-radius: var(--radius-xl);
    width:         100%;
    max-width:     440px;
    max-height:    92vh;
    max-height:    92dvh;           /* never taller than the screen — dvh, or iOS puts the
                                       scanner's controls under the browser chrome */
    display:       flex;
    flex-direction: column;
    overflow:      hidden;
    box-shadow:    0 24px 64px rgba(0,0,0,.7);
}

.scanner-modal__header {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    padding:         var(--space-4) var(--space-5);
    border-bottom:   1px solid var(--color-border-subtle);
    flex-shrink:     0;             /* header (with ✕) always visible */
}
.scanner-modal__title {
    color:       var(--color-text);
    font-weight: 600;
    font-size:   var(--font-size-sm);
}
/* Styled by the ONE shared ✕ rule above — see "THE ✕, EVERYWHERE". This bespoke block used to
   give the scanner its own grey glyph, which is why it kept the old look after the app-wide change:
   a third close implementation nobody had counted. Left as a marker so it is not re-added. */

.scanner-modal__body {
    padding:    var(--space-4);
    flex:       1 1 auto;
    min-height: 0;
    overflow-y: auto;   /* video can scroll; header + footer stay pinned */
}

/* Container Quagga2 / html5-qrcode renders into.
   position:relative is critical — Quagga2 injects position:absolute canvas
   elements; without this they escape the container and blank the screen. */
#tt-scanner-reader {
    width:         100%;
    min-height:    220px;
    border-radius: var(--radius-lg);
    overflow:      hidden;
    /* Deliberately black in BOTH themes — this is the camera feed itself, not chrome. */
    background:    #000;
    position:      relative;
}
#tt-scanner-reader video {
    width:      100% !important;
    max-height: 55vh;
    display:    block;
    object-fit: cover;
}
/* Keep Quagga2's overlay canvas inside the reader box */
#tt-scanner-reader canvas {
    position:   absolute !important;
    top:        0 !important;
    left:       0 !important;
    max-width:  100% !important;
    max-height: 55vh !important;
}

.scanner-hint {
    color:      var(--color-text-muted);
    text-align: center;
    font-size:  var(--font-size-xs);
    margin-top: var(--space-3);
}
.scanner-error {
    color:       var(--color-danger);
    text-align:  center;
    font-size:   var(--font-size-sm);
    margin-top:  var(--space-3);
    white-space: pre-wrap;
    line-height: 1.5;
}

.scanner-modal__footer {
    padding:         var(--space-3) var(--space-5) var(--space-5);
    display:         flex;
    justify-content: center;
    flex-shrink:     0;   /* Cancel button always reachable */
}
.scanner-cancel-btn {
    color:        var(--color-text-secondary) !important;
    border-color: var(--color-border) !important;
    font-size:    var(--font-size-sm);
}
.scanner-cancel-btn:hover {
    color:        var(--color-text) !important;
    border-color: var(--color-text-muted) !important;
}

/* "Starting camera…" status line above the video */
.scanner-status {
    color:       var(--color-text-muted);
    font-size:   var(--font-size-xs);
    text-align:  center;
    padding:     var(--space-2) 0 var(--space-3);
    font-style:  italic;
}

/* "or enter manually" divider */
.scanner-divider {
    display:         flex;
    align-items:     center;
    gap:             var(--space-3);
    padding:         var(--space-2) var(--space-5);
    color:           var(--color-text-muted);
    font-size:       var(--font-size-xs);
    text-transform:  uppercase;
    letter-spacing:  .06em;
}
.scanner-divider::before,
.scanner-divider::after {
    content:    '';
    flex:       1;
    height:     1px;
    background: var(--color-border-subtle);
}

/* Manual entry row */
.scanner-manual {
    display:    flex;
    gap:        var(--space-2);
    padding:    0 var(--space-4) var(--space-3);
}
.scanner-manual-input {
    flex:            1;
    background:      var(--color-bg-subtle) !important;
    border-color:    var(--color-border)    !important;
    color:           var(--color-text)      !important;
    font-family:     monospace;
    font-size:       var(--font-size-sm);
    text-transform:  uppercase;
    letter-spacing:  .04em;
}
/* The input uppercases + letter-spaces what you TYPE (part numbers read better that way),
   but applying that to the placeholder made the hint too wide and it got clipped. Keep the
   placeholder in normal case/spacing so the whole hint fits. */
.scanner-manual-input::placeholder {
    color:          var(--color-text-muted) !important;
    text-transform: none;
    letter-spacing: normal;
    font-family:    var(--font-family-base, system-ui, sans-serif);
}
.scanner-manual-input:focus {
    border-color: var(--color-accent) !important;
    outline:      none;
    box-shadow:   0 0 0 2px rgba(224,112,16,.35);
}

/* Live part matches under the manual/handheld box — same type-to-search behavior as the
   pickers elsewhere. A handheld still just types the code + Enter and never sees this. */
.scanner-manual-results {
    margin:        0 var(--space-4) var(--space-3);
    max-height:    190px;
    overflow-y:    auto;
    border-radius: var(--radius-md, 8px);
}
.scanner-manual-results:empty { display: none; }
.scanner-manual-results:not(:empty) {
    border:     1px solid var(--color-border-subtle);
    background: var(--color-bg-subtle);
}
.scanner-manual-result {
    display:       block;
    width:         100%;
    text-align:    left;
    padding:       var(--space-2) var(--space-3);
    background:    none;
    border:        0;
    border-bottom: 1px solid var(--color-border-subtle);
    color:         var(--color-text);
    cursor:        pointer;
    font-size:     var(--font-size-sm);
}
.scanner-manual-result:last-child { border-bottom: 0; }
.scanner-manual-result:hover,
.scanner-manual-result:focus-visible { background: var(--color-bg-muted); outline: none; }
.scanner-manual-result .pn   { font-family: monospace; font-weight: 600; }
.scanner-manual-result .meta { display: block; color: var(--color-text-muted); font-size: var(--font-size-xs); margin-top: 2px; }

/* ── Photo-capture (primary scan method) ─────────────────────────────── */
.scanner-photo-cta {
    padding:    var(--space-2) 0 var(--space-1);
    text-align: center;
}
.scanner-photo-btn {
    width:         100%;
    padding:       var(--space-4);
    border:        none;
    border-radius: var(--radius-lg);
    background:    var(--color-accent);
    color:         #fff;
    font-size:     var(--font-size-md);
    font-weight:   700;
    cursor:        pointer;
    transition:    filter .15s;
}
.scanner-photo-btn:hover   { filter: brightness(1.08); }
.scanner-photo-btn:active  { filter: brightness(.95); }
.scanner-photo-help {
    color:      var(--color-text-muted);
    font-size:  var(--font-size-xs);
    margin-top: var(--space-2);
    line-height: 1.5;
}
.scanner-preview {
    width:         100%;
    max-height:    40vh;
    object-fit:    contain;
    border-radius: var(--radius-lg);
    margin-top:    var(--space-3);
    background:    #000;
}

/* Defined scan window overlay for live mode.
   Stays white-on-dark in BOTH themes on purpose — it is drawn ON TOP of the camera picture, not on
   the panel, so it has to read against whatever the lens is looking at, not against the surface. */
.scanner-window {
    position:       absolute;
    top:            42%;   /* thin slot — only ONE stacked barcode fits (must match _coverCropRect) */
    bottom:         42%;
    left:           8%;
    right:          8%;
    border:         2px solid rgba(255,255,255,.35);
    border-radius:  var(--radius-md);
    box-shadow:     0 0 0 100vmax rgba(0,0,0,.35);
    pointer-events: none;
    z-index:        2;
}
.scanner-window__corner {
    position: absolute;
    width:    22px;
    height:   22px;
    border:   3px solid var(--color-accent);
}
.scanner-window__corner.tl { top: -2px; left: -2px;  border-right: none;  border-bottom: none; border-radius: var(--radius-md) 0 0 0; }
.scanner-window__corner.tr { top: -2px; right: -2px; border-left: none;   border-bottom: none; border-radius: 0 var(--radius-md) 0 0; }
.scanner-window__corner.bl { bottom: -2px; left: -2px;  border-right: none; border-top: none;  border-radius: 0 0 0 var(--radius-md); }
.scanner-window__corner.br { bottom: -2px; right: -2px; border-left: none;  border-top: none;  border-radius: 0 0 var(--radius-md) 0; }

/* "Use live scanner instead" link */
.scanner-live-toggle {
    display:        block;
    margin:         var(--space-3) auto 0;
    background:     none;
    border:         none;
    color:          var(--color-text-secondary);
    font-size:      var(--font-size-xs);
    text-decoration:underline;
    cursor:         pointer;
    padding:        var(--space-2);
}
.scanner-live-toggle:hover { color: var(--color-text); }

/* Multi-barcode picker — choose which decoded code to use */
.scanner-picker { padding: var(--space-2) 0; }
.scanner-picker__title {
    color:        var(--color-text-secondary);
    font-size:    var(--font-size-sm);
    text-align:   center;
    margin-bottom: var(--space-3);
}
.scanner-picker__btn {
    display:        flex;
    flex-direction: column;
    gap:            2px;
    width:          100%;
    text-align:     left;
    padding:        var(--space-3) var(--space-4);
    margin-bottom:  var(--space-2);
    background:     var(--color-bg-subtle);
    border:         1px solid var(--color-border);
    border-radius:  var(--radius-md);
    cursor:         pointer;
    color:          var(--color-text);
}
.scanner-picker__btn:hover {
    border-color: var(--color-accent);
    background:   rgba(224,112,16,.15);
}
.scanner-picker__type {
    font-size:      var(--font-size-xs);
    color:          rgba(255,255,255,.5);
    text-transform: uppercase;
    letter-spacing: .04em;
}
.scanner-picker__val {
    font-family:    monospace;
    font-size:      var(--font-size-md);
    letter-spacing: .03em;
    word-break:     break-all;
}

/* ── Scan workflow (Add / Use) ───────────────────────────────────────── */
.scan-home { max-width: 560px; }
.scan-mode-grid {
    display:               grid;
    grid-template-columns: 1fr 1fr;
    gap:                   var(--space-4);
    margin-bottom:         var(--space-5);
}
.scan-mode-btn {
    display:        flex;
    flex-direction: column;
    align-items:    center;
    gap:            4px;
    padding:        var(--space-6) var(--space-4);
    border:         2px solid var(--color-border);
    border-radius:  var(--radius-lg);
    background:     var(--color-bg-surface);
    /* A <button> does NOT inherit `color` — it defaults to the UA's `buttontext`. The card set a
       background but never a color, so in light mode the bold "Use Stock" / "Transfer" labels came
       out near-white on white and simply WERE NOT THERE, while the sub-line survived because it
       sets its own var. Found by rendering the light palette during the scanner theme fix. */
    color:          var(--color-text);
    cursor:         pointer;
    transition:     transform .12s, border-color .12s, background .12s;
}
.scan-mode-btn:active { transform: scale(.97); }
.scan-mode-btn__icon  { font-size: 2rem; line-height: 1; }
.scan-mode-btn__label { font-size: var(--font-size-md); font-weight: 800; }
.scan-mode-btn__sub   { font-size: var(--font-size-xs); color: var(--color-text-muted); }
.scan-mode-btn--add:hover { border-color: var(--color-success); background: rgba(34,160,80,.06); }
.scan-mode-btn--use:hover { border-color: var(--color-accent);  background: rgba(224,112,16,.06); }
/* Transfer now sits in the grid beside Use — same tile shape/size (base .scan-mode-btn). */
.scan-mode-btn--xfer:hover { border-color: var(--color-primary); background: var(--color-bg-subtle); }

.scan-options {
    display:        flex;
    flex-direction: column;
    gap:            var(--space-4);
    padding:        var(--space-4);
    border:         1px solid var(--color-border-subtle);
    border-radius:  var(--radius-lg);
    background:     var(--color-bg-subtle);
    margin-bottom:  var(--space-5);
}
.scan-toggle { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-sm); cursor: pointer; }
.scan-loc .form-label { display: block; margin-bottom: var(--space-1); }

.scan-tally {
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    overflow:      hidden;
}
.scan-tally__head {
    padding:        var(--space-2) var(--space-4);
    background:     var(--color-bg-subtle);
    font-size:      var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: .05em;
    color:          var(--color-text-secondary);
    font-weight:    700;
}
.scan-tally__row {
    display:         flex;
    justify-content: space-between;
    align-items:     center;
    padding:         var(--space-2) var(--space-4);
    border-top:      1px solid var(--color-border-subtle);
}
.scan-tally__pn  { font-family: monospace; font-size: var(--font-size-sm); }
.scan-tally__qty { font-weight: 800; }
.scan-tally__qty.is-add { color: var(--color-success); }
.scan-tally__qty.is-use { color: var(--color-accent); }

/* Confirm / not-found cards (reuse .modal-overlay) */
.scan-confirm__head {
    padding:       var(--space-4) var(--space-5);
    font-weight:   800;
    font-size:     var(--font-size-md);
    border-bottom: 1px solid var(--color-border-subtle);
}
.scan-confirm__head.is-add { color: var(--color-success); }
.scan-confirm__head.is-use { color: var(--color-accent); }
.scan-confirm__body   { padding: var(--space-5); }
.scan-confirm__pn     { font-size: var(--font-size-lg); font-weight: 800; }
.scan-confirm__desc   { color: var(--color-text-secondary); font-size: var(--font-size-sm); margin: var(--space-1) 0 var(--space-2); }
.scan-confirm__desc code { background: var(--color-bg-subtle); padding: 1px 5px; border-radius: 4px; font-size: .85em; }
.scan-confirm__meta   { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.scan-confirm__actions {
    display:         flex;
    gap:             var(--space-3);
    justify-content: flex-end;
    padding:         var(--space-4) var(--space-5);
    border-top:      1px solid var(--color-border-subtle);
}

.scan-stepper {
    display:         flex;
    align-items:     center;
    justify-content: center;
    gap:             var(--space-3);
    margin-top:      var(--space-4);
}
.scan-stepper__btn {
    width:         48px;
    height:        48px;
    border:        1px solid var(--color-border);
    border-radius: var(--radius-md);
    background:    var(--color-bg-surface);
    color:         var(--color-text);
    font-size:     1.5rem;
    cursor:        pointer;
}
.scan-stepper__btn:active { background: var(--color-bg-subtle); }
.scan-stepper__val {
    width:        80px;
    height:       48px;
    text-align:   center;
    font-size:    var(--font-size-lg);
    font-weight:  800;
    border:       1px solid var(--color-border);
    border-radius:var(--radius-md);
    background:   var(--color-bg-surface);
    color:        var(--color-text);
}
.scan-choices { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-3); }
.scan-choice  { justify-content: flex-start; }

/* Placement picker (which location to add to / use from) */
.scan-places { display: flex; flex-direction: column; gap: var(--space-2); }
.scan-place {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    gap:             var(--space-3);
    width:           100%;
    padding:         var(--space-3) var(--space-4);
    border:          1px solid var(--color-border);
    border-radius:   var(--radius-md);
    background:      var(--color-bg-surface);
    color:           var(--color-text);   /* buttons don't inherit color — see .scan-mode-btn */
    cursor:          pointer;
    font-size:       var(--font-size-sm);
    text-align:      left;
}
.scan-place:hover  { border-color: var(--color-primary); background: var(--color-bg-subtle); }
.scan-place strong { font-size: var(--font-size-md); }
.scan-place--new   { justify-content: center; color: var(--color-accent); font-weight: 700; }

/* "It's not here" on the Use confirm card (E26). Understated on purpose — it is the exception, not
   the action, and it must not compete with the Use button. */
/* Two siblings, one row: "It's not here" (the count is zero) and "Fix count" (the count is wrong
   by some other amount). They read as a pair because they ARE a pair — grouping them is the point
   of moving Fix count out of the footer (Owner, 2026-08-11). */
.scan-nothere-row { text-align: center; margin-top: var(--space-2);
    display: flex; justify-content: center; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.scan-nothere-row .scan-nothere-confirm {
    background: none; border: none; cursor: pointer;
    color: var(--color-text-secondary);
    font-size: var(--font-size-sm);
    text-decoration: underline;
    padding: var(--space-1) var(--space-2);
}
.scan-nothere-row .scan-nothere-confirm:hover { color: var(--color-accent); }

/* Transfer destination: "…or any location in the plant" — the storage map, in line under the
   part's own placements. Separated by a rule so the shortcuts above still read as the quick path. */
.scan-dest-any {
    margin-top:  var(--space-3);
    padding-top: var(--space-3);
    border-top:  1px solid var(--color-border-subtle);
}
.scan-dest-any .scan-confirm__meta { margin-bottom: var(--space-2); }

/* Negative-stock warning in the Use confirm card */
.scan-warn {
    margin-top:     var(--space-3);
    padding:        var(--space-2) var(--space-3);
    background:     rgba(224,112,16,.12);
    border:         1px solid rgba(224,112,16,.45);
    border-radius:  var(--radius-md);
    color:          var(--color-accent);
    font-size:      var(--font-size-sm);
    font-weight:    700;
    text-align:     center;
}
.scan-fixcount {
    background:      none;
    border:          none;
    cursor:          pointer;
    color:           var(--color-accent);
    text-decoration: underline;
    font-size:       var(--font-size-sm);
    padding:         var(--space-2);
}
.btn-warn-go {
    background:   var(--color-accent) !important;
    border-color: var(--color-accent) !important;
    color:        #fff !important;
}

/* Add / Use Stock chooser overlay (launched from Parts) */
.scan-actions__head {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    padding:         var(--space-4) var(--space-5);
    border-bottom:   1px solid var(--color-border-subtle);
}
.scan-actions__title { font-size: var(--font-size-lg); font-weight: 800; }
.scan-actions__close {
    background:  none;
    border:      none;
    cursor:      pointer;
    font-size:   1.2rem;
    line-height: 1;
    color:       var(--color-text-muted);
    padding:     var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
}
.scan-actions__close:hover { color: var(--color-text-secondary); }
.scan-actions__body { padding: var(--space-5); overflow-y: auto; }
.btn-stock-actions { font-weight: 700; }
.parts-show-inactive {
    display:     flex;
    align-items: center;
    gap:         6px;
    font-size:   var(--font-size-sm);
    color:       var(--color-text-secondary);
    cursor:      pointer;
    white-space: nowrap;
}

/* ── A list inside a form scrolls ITSELF ──────────────────────────────────
   🔴 Owner, 2026-08-21, after six Grainger cross-references landed on one part:
   *"this will make the entire form continue to grow, i think the form should be static, and the
   part list from the vendor be a scrollable container showing 3 or 4 parts in the window"*.

   The dropdowns already worked this way (SearchPicker, the builder's find results, the line-card
   search) — but every STANDING list in a form did not, so each one grew without limit and pushed
   the fields below it off the screen. One class, every such list, so the next one inherits it.
   ~4 rows visible; `overscroll-behavior` keeps the wheel from scrolling the modal behind it. */
.form-scroll-list { max-height: 132px; overflow-y: auto; overscroll-behavior: contain; }

/* Read-only "stock by location" breakdown on the Parts edit form */
.loc-breakdown {
    margin-top:    var(--space-4);
    padding:       var(--space-3) var(--space-4);
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    background:    var(--color-bg-subtle);
}
.loc-breakdown__head {
    font-size:      var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: .05em;
    font-weight:    700;
    color:          var(--color-text-secondary);
    margin-bottom:  var(--space-2);
}
.loc-breakdown__row {
    display:         flex;
    justify-content: space-between;
    align-items:     center;
    padding:         var(--space-1) 0;
    border-top:      1px solid var(--color-border-subtle);
    font-size:       var(--font-size-sm);
}
.loc-breakdown__row:first-of-type { border-top: none; }

/* E25/E12 — releasing an empty spot. The control only renders at qty 0 and only for someone who
   may place stock, so it is deliberately quiet: this is tidying, not a destructive action. */
.loc-breakdown__right { display: inline-flex; align-items: center; gap: var(--space-3); }
.btn-link-danger {
    background:   none;
    border:       none;
    padding:      0;
    cursor:       pointer;
    font-size:    var(--font-size-xs);
    color:        var(--color-text-muted);
    text-decoration: underline;
}
.btn-link-danger:hover { color: var(--color-danger); }

/* "Link a barcode" control on the part form */
.link-bc { margin-top: var(--space-4); }
.link-bc-list { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.link-bc-chip {
    display:        inline-flex;
    align-items:    center;
    gap:            6px;
    padding:        2px 8px;
    border-radius:  999px;
    background:     var(--color-bg-subtle);
    border:         1px solid var(--color-border-subtle);
    font-family:    monospace;
    font-size:      var(--font-size-xs);
}
.link-bc-chip button {
    border: none; background: none; cursor: pointer;
    color: var(--color-text-muted); font-size: 12px; line-height: 1; padding: 0;
}
.link-bc-chip button:hover { color: var(--color-danger); }

/* Clear (✕) inside the Parts search box */
.search-bar .search-clear {
    position:        absolute;
    right:           6px;
    top:             50%;
    transform:       translateY(-50%);
    width:           22px;
    height:          22px;
    border:          none;
    border-radius:   50%;
    background:      var(--color-bg-muted, rgba(0,0,0,.07));
    color:           var(--color-text-muted);
    font-size:       12px;
    line-height:     1;
    cursor:          pointer;
    display:         flex;
    align-items:     center;
    justify-content: center;
}
.search-bar .search-clear:hover { color: var(--color-text-secondary); }
.search-bar .form-control { padding-right: 1.9rem; }

/* Only ONE clear button. `input[type=search]` makes WebKit render its own ✕, which sat right next to
   the styled .search-clear above — two X's in the same box. Suppressed only on a bar that carries our
   replacement, so every other module keeps the native one as its only way to clear. */
.search-bar.has-custom-clear input[type="search"]::-webkit-search-cancel-button,
.search-bar.has-custom-clear input[type="search"]::-webkit-search-decoration {
    -webkit-appearance: none;
    appearance:         none;
    display:            none;
}

/* 🔴 THE SEARCH BOX MUST NEVER BE SQUEEZED TO NOTHING.  (Owner, 2026-08-13, on an iPad in landscape:
   *"header with search bar definately got squeezed out of useable size"*.)

   `.search-bar` carries `flex: 1; min-width: 0` (forms.css). `min-width: 0` is right for a flex item
   that may shrink below its content — and wrong for the one control people TYPE INTO, because it
   makes the search box the only thing that gives when the row runs out of room. Parts now carries
   four buttons beside it (How it works · Find by shape · Build a part · New Part) where every other
   module has two, so Parts is where the row ran out first.

   A real basis plus a real minimum makes the TOOLBAR wrap instead of the input collapsing. */
.panel-toolbar .search-bar { flex: 1 1 260px; min-width: 240px; }

/* 🔴 KEEP THE SEARCH BAR, SHRINK THE BUTTONS.  (Owner, 2026-08-13: *"i think u should maintain the
   search bar width, and change the how it works, maybe removing as its in the navigation, if more
   room needed then find by shape, build a part drop text to icon only"*.)

   Two changes make the room, and neither of them costs the search box a pixel:
     1. The duplicate "🗺️ How it works" button is GONE from the toolbar (CrudModule) — it is already
        a nav item under both Inventory and Purchasing.
     2. Below, the remaining action labels collapse to their icon.

   Chosen over stacking the buttons onto a second row, which was the first attempt: stacking keeps
   every label but pushes the table down on every tablet, permanently, to solve a problem that only
   exists in the toolbar. Collapsing a label is reversible the instant the window widens.

   ⚠️ Only where the width is actually constrained — Owner: *"only when on a mobile device?"* A
   desktop has the room and keeps its words. 1024px is the iPad-landscape line the earlier 768px
   rule stopped short of, which is why the squeeze was never caught. */
@media (max-width: 1024px) {
    /* The words are still on the button via title/aria-label — hidden, not deleted, so hover and
       assistive tech both still read "Find by shape" rather than an unlabelled glyph. */
    .panel-actions .btn-txt { display: none; }
    .panel-actions .btn:has(.btn-ico) { padding-left: var(--space-3); padding-right: var(--space-3); }
    .panel-actions .btn-ico { font-size: 1.05rem; line-height: 1; }
}

@media (max-width: 768px) {
    .scan-mode-btn { padding: var(--space-6) var(--space-2); }
}

/* 📷 icon button in the search bar toolbar */
.btn-scan-icon {
    background:    none;
    border:        1px solid var(--color-border);
    border-radius: var(--radius-md);
    cursor:        pointer;
    font-size:     1.25rem;
    line-height:   1;
    padding:       5px 9px;
    color:         var(--color-text-secondary);
    transition:    border-color .15s, background .15s;
    flex-shrink:   0;
}
.btn-scan-icon:hover {
    border-color: var(--color-primary);
    background:   var(--color-bg-subtle);
}

/* Small "📷 Scan" link below a form input */
.btn-scan-field {
    display:       inline-flex;
    align-items:   center;
    gap:           4px;
    background:    none;
    border:        1px solid var(--color-border);
    border-radius: var(--radius-md);
    cursor:        pointer;
    font-size:     var(--font-size-xs);
    color:         var(--color-text-muted);
    padding:       4px 10px;
    margin-top:    var(--space-2);
    transition:    border-color .15s, color .15s;
}
.btn-scan-field:hover {
    border-color: var(--color-primary);
    color:        var(--color-link);
}

/* ── Data-change refresh banner ──────────────────────────────────────── */
.dv-banner {
    display: none;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    background: var(--color-primary);
    color: var(--color-text-inverse);
    font-size: var(--font-size-sm);
    position: sticky;
    top: 0;
    z-index: 100;
}
.dv-banner--visible { display: flex; }
.dv-banner__msg  { flex: 1; }
.dv-banner__refresh { flex-shrink: 0; background: rgba(255,255,255,.2); border-color: rgba(255,255,255,.4); color: #fff; }
.dv-banner__refresh:hover { background: rgba(255,255,255,.3); }
.dv-banner__dismiss {
    background: none;
    border: none;
    color: rgba(255,255,255,.7);
    font-size: var(--font-size-base);
    cursor: pointer;
    padding: 0 var(--space-1);
    line-height: 1;
}
.dv-banner__dismiss:hover { color: #fff; }

/* ── Parts: Location expand/collapse ─────────────────────────────────── */
.btn-loc-expand {
    background: none;
    border: none;
    color: var(--color-text-secondary);
    font-size: inherit;
    cursor: pointer;
    padding: 0;
    display: inline-block;
    opacity: 0.8;
    line-height: inherit;
}
.btn-loc-expand:hover { opacity: 1; color: var(--color-text); text-decoration: underline; }

/* Tighter horizontal padding on ✏️ 🗑️ action buttons so the Actions column stays compact */
.actions-cell .btn { padding-left: 6px; padding-right: 6px; }

.part-loc-row td.part-loc-cell {
    background: var(--color-bg-subtle);
    padding: var(--space-2) var(--space-4) var(--space-3) var(--space-8);
    border-bottom: 2px solid var(--color-border);
}

.part-loc-table {
    border-collapse: collapse;
    font-size: var(--font-size-xs);
    width: auto;
    min-width: 260px;
}
.part-loc-table th {
    text-align: left;
    padding: 3px 20px 3px 0;
    color: var(--color-text-secondary);
    font-weight: 600;
    border-bottom: 1px solid var(--color-border);
    white-space: nowrap;
}
.part-loc-table td {
    padding: 3px 20px 3px 0;
    vertical-align: middle;
    border-bottom: 1px solid var(--color-border-subtle);
    white-space: nowrap;
}
.part-loc-table tr:last-child td { border-bottom: none; }

/* ── Part search widget (Adjustments create form) ─────────────────── */
.part-search-wrap { position: relative; }
.part-search-row { display: flex; gap: var(--space-2); align-items: center; }
.part-search-row .form-control { flex: 1; min-width: 0; }
.part-search-scan-btn { flex-shrink: 0; }

/* ── Coming Soon placeholders (admin dev preview) ─────────────────────── */
.nav-soon {
    margin-left:    auto;
    font-size:      9px;
    font-weight:    700;
    text-transform: uppercase;
    letter-spacing: .05em;
    background:     rgba(224,112,16,.25);
    color:         var(--color-accent);
    padding:       2px 6px;
    border-radius: var(--radius-pill);
}
.coming-soon { max-width: 720px; margin: 0 auto; }
.coming-soon__hero { text-align: center; padding: var(--space-8) var(--space-4) var(--space-6); }
.coming-soon__icon { font-size: 56px; margin-bottom: var(--space-3); }
.coming-soon__title {
    display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
    gap: var(--space-3); font-size: var(--font-size-2xl); font-weight: 700;
    color: var(--color-text); margin-bottom: var(--space-2);
}
.coming-soon__tagline {
    color: var(--color-text-muted); font-size: var(--font-size-md);
    max-width: 520px; margin: 0 auto; line-height: 1.5;
}
.coming-soon__card {
    background: var(--color-bg-surface); border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); padding: var(--space-6);
}
.coming-soon__card h3 {
    font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: .05em;
    color: var(--color-text-secondary); margin: 0 0 var(--space-4);
}
.coming-soon__features { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-3); }
.coming-soon__features li {
    display: flex; gap: var(--space-3); align-items: flex-start;
    font-size: var(--font-size-sm); color: var(--color-text); line-height: 1.5;
}
.coming-soon__features li::before { content: "✓"; color: var(--color-success); font-weight: 700; flex-shrink: 0; }
.coming-soon__phase { text-align: center; margin-top: var(--space-5); color: var(--color-text-muted); font-size: var(--font-size-xs); }

/* ── Vendor detail modal (Phase 2) ────────────────────────────────────── */
.vendor-section-title { font-size: var(--font-size-sm); font-weight: 700; color: var(--color-text); margin: 0 0 var(--space-3); }
.vendor-linecard-add, .vendor-contact-add { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-3); }
.vendor-linecard-add .form-control { flex: 1; min-width: 0; }
.vendor-contact-add .form-control { flex: 1; min-width: 120px; }
.vendor-chip-list { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-2); }
.vendor-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--color-bg-subtle); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-pill); padding: 4px 6px 4px 12px; font-size: var(--font-size-sm); }
.vendor-chip button { border: none; background: none; cursor: pointer; color: var(--color-text-muted); font-size: 13px; line-height: 1; padding: 0 2px; }
.vendor-chip button:hover { color: var(--color-danger); }
.vendor-contact-list { display: grid; gap: var(--space-2); margin-bottom: var(--space-3); }
.vendor-contact-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); background: var(--color-bg-subtle); border-radius: var(--radius-md); }
.vendor-contact-row .vc-main { flex: 1; min-width: 0; }
.vendor-contacts-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.vc-rc { display: inline-flex; align-items: center; gap: 4px; font-size: var(--font-size-xs); color: var(--color-text-secondary); cursor: pointer; flex-shrink: 0; }
.lc-results { max-height: 280px; overflow-y: auto; margin-top: var(--space-2); }
.lc-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--color-border-subtle); }
.recip-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); margin-bottom: var(--space-2); }
.recip-table th, .recip-table td { border-bottom: 1px solid var(--color-border-subtle); padding: var(--space-2); text-align: left; }
.recip-table th:nth-child(2), .recip-table th:nth-child(3),
.recip-table td:nth-child(2), .recip-table td:nth-child(3) { width: 48px; text-align: center; }

/* ── Project detail modal (Phase 3) ───────────────────────────────────── */
.proj-part-add { display: flex; gap: var(--space-2); align-items: flex-start; margin-bottom: var(--space-3); }
.proj-part-list { margin-top: var(--space-2); }
.proj-part-list .data-table { font-size: var(--font-size-sm); }

/* ── Data tables: never had their own surface styling ────────────────────
   Found 2026-09-14, the owner testing live on an iPad (Chrome for iOS -- WebKit underneath,
   same engine as Safari): "in users module edit and delete not respecting dark mode."
   His screenshot showed the header row correctly dark but every body row (and the
   edit/delete icons sitting on them) rendering on a plain white surface. Root cause:
   `.data-table` / `.table-wrapper` (js/components/DataTable.js, used by EVERY list in
   the app) never had an explicit background, text color, or cell padding rule anywhere
   in this file -- not here, not in base.css or app.css. Every list has always relied on
   the raw <table> rendering transparently over the page's own dark background, which
   happened to read correctly wherever it was tested before now (this session's own
   desktop testing included) but evidently not on every engine/render path. Rather than
   chase exactly which implicit behavior was the gap, this gives every part of the table
   an explicit color, removing the guesswork -- the same `.card` surface treatment
   already used everywhere else in the app, instead of an unstyled element depending on
   inheriting the right thing. */
.table-wrapper {
    background:    var(--color-bg-surface);
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow:    var(--shadow-sm);
    overflow:      hidden;
}
/* `.table-container` is what Reports wraps its tables in (js/modules/reports.js) and it had NO rule
   at all — an 8-column report ran off the right edge of an iPad and the last columns were simply
   gone (Owner, 2026-10-06). Same surface as .table-wrapper; a table wider than the panel scrolls. */
.table-container {
    background:    var(--color-bg-surface);
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow:    var(--shadow-sm);
    overflow-x:    auto;
    -webkit-overflow-scrolling: touch;
}
.table-container > .data-table { min-width: 100%; }
.table-container .data-table th { white-space: nowrap; }
.data-table { width: 100%; border-collapse: collapse; background: var(--color-bg-surface); color: var(--color-text); }
.data-table thead th {
    background:     var(--color-bg-subtle);
    color:          var(--color-text-secondary);
    text-align:     left;
    font-size:      var(--font-size-xs);
    font-weight:    700;
    text-transform: uppercase;
    letter-spacing: .04em;
    padding:        var(--space-3) var(--space-4);
    border-bottom:  1px solid var(--color-border-subtle);
    white-space:    nowrap;
}
.data-table th.sortable { cursor: pointer; user-select: none; }
.data-table th.sortable:hover { color: var(--color-text); }
.data-table th .sort-icon { color: var(--color-link); }
.data-table tbody td {
    padding:       var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border-subtle);
    color:         var(--color-text);
    vertical-align: middle;
}
.data-table tbody tr:last-child > td { border-bottom: none; }
.data-table tbody tr.clickable-row { cursor: pointer; }
.data-table tbody tr.clickable-row:hover > td { background: var(--color-bg-subtle); }
.data-table .actions-cell { white-space: nowrap; }

/* ── DataTable expandable rows (chevron reveals a child row) ───────────── */
.data-table .expand-cell { width: 44px; text-align: center; padding: 0; }
/* Owner 2026-10-12 13:47: "the chevron size for the project expand should be larger, if u dont know u miss it" */
.expand-toggle { background: none; border: 1px solid var(--color-border); cursor: pointer; color: var(--color-text-secondary);
    font-size: 16px; line-height: 1; padding: 5px 9px; border-radius: var(--radius-sm);
    transition: color .15s ease, background .15s ease; }
.expand-toggle:hover { color: var(--color-text); background: var(--color-bg-subtle); }
.data-table tr.expand-row > td { background: var(--color-bg-subtle);
    border-bottom: 1px solid var(--color-border-subtle); padding: 0; }

/* ── E37: the row a nav badge is ABOUT ────────────────────────────────────
   A count you cannot locate in the list is a riddle, not a signal — that is
   the defect this exists to fix. Built from the EXISTING warning/accent
   theme contract (both are redefined for dark mode in variables.css) rather
   than a new color, so it reads correctly in light and dark for free.
   The left bar carries the meaning; the tint alone is too quiet on a long
   list, and color alone is not a signal anyone should have to rely on. */
.data-table tr.row-attention > td { background: var(--color-warning-bg); }
.data-table tr.row-attention > td:first-child {
    box-shadow: inset 3px 0 0 0 var(--color-accent); }
.data-table tr.row-attention:hover > td { background: var(--color-warning-bg); filter: brightness(1.03); }
/* (The approvals-card equivalent lives further down, AFTER .appr-card itself — see below.) */

/* ── Grouped lists (Owner 2026-10-14) — one table, a <tbody> per status group ──────────────
   Requisitions · Quotes · Purchase Orders · Returns · Jobs · Projects: grouped by status in the
   chain's order, each heading a chevron (collapsed by default), "📌 On your desk" fixed open on top
   carrying the same amber as the rows a badge is about. Theme tokens only, so dark mode follows. */
.data-table tr.grp-head > td { background: var(--color-bg-subtle); cursor: pointer; user-select: none;
    padding: 7px 12px; border-top: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border-subtle); }
.data-table tr.grp-head:hover > td { background: var(--color-bg-muted); }
.data-table tbody.grp:first-of-type tr.grp-head > td { border-top: none; }
.data-table tr.grp-head--fixed > td { cursor: default; background: var(--color-warning-bg);
    box-shadow: inset 3px 0 0 0 var(--color-accent); }
.data-table tr.grp-head--fixed:hover > td { background: var(--color-warning-bg); }
/* No box, unlike a row's own expand chevron (.expand-toggle): the whole bar is the control here. */
.grp-toggle { background: none; border: none; border-radius: var(--radius-sm); cursor: pointer;
    color: var(--color-text-secondary); font-size: 14px; line-height: 1; width: 30px; height: 28px; margin-right: 8px;
    vertical-align: middle; }
.grp-head--fixed .grp-toggle { display: none; }
.grp-title { vertical-align: middle; }
.grp-count { vertical-align: middle; margin-left: 6px; font-weight: 700; color: var(--color-text-secondary);
    font-variant-numeric: tabular-nums; }
.grp-desk { font-weight: 700; color: var(--color-text); }
/* A group nested inside a group steps in and reads lighter than a top-level one. */
.data-table tr.grp-head--nested > td { background: var(--color-bg-surface); border-top-color: var(--color-border-subtle); }
.data-table tr.grp-head--nested:hover > td { background: var(--color-bg-subtle); }
.grp-why { font-weight: 400; color: var(--color-text-secondary); font-size: var(--font-size-sm); }
.data-table tr.grp-more > td, .data-table tr.grp-note > td { text-align: center; padding: 6px 12px; }
/* The record you just came back from, lit for a moment (its group opened to show it). */
.data-table tr.row-reveal > td { animation: tt-row-reveal 2.4s ease-out; }
@keyframes tt-row-reveal { 0%, 35% { background: var(--color-info-bg); } 100% { background: transparent; } }
@media (max-width: 820px) {
    .data-table tr.grp-head, .data-table tr.grp-more, .data-table tr.grp-note {
        border: none; background: transparent; padding: 0; margin: 12px 0 6px; }
    .data-table tr.grp-head > td, .data-table tr.grp-more > td, .data-table tr.grp-note > td {
        display: block; text-align: left; border-radius: var(--radius-md); padding: 8px 10px; }
    .data-table tr.grp-head > td::before, .data-table tr.grp-more > td::before, .data-table tr.grp-note > td::before { content: none; }
}

/* ── E38: the chain's history on a requisition ────────────────────────────
   Collapsed by default. "Where is it?" is answered above by the chain block;
   this answers "how did it get there", which is the second question, not the
   first — and an always-open list of steps would push the parts off screen. */
.req-tl { margin: var(--space-3) 0; }
.req-tl__summary { cursor: pointer; font-size: var(--font-size-sm);
    color: var(--color-text-secondary); padding: 4px 0; }
.req-tl__summary:hover { color: var(--color-text); }
.req-tl__list { list-style: none; margin: var(--space-2) 0 0; padding: 0 0 0 var(--space-2);
    border-left: 2px solid var(--color-border-subtle); }
.req-tl__row { display: flex; gap: var(--space-2); align-items: baseline; flex-wrap: wrap;
    padding: 5px 0 5px var(--space-3); font-size: var(--font-size-sm); }
.req-tl__what { color: var(--color-text); }
.req-tl__who  { color: var(--color-text-secondary); }
.req-tl__when { color: var(--color-text-muted); font-size: var(--font-size-xs); margin-left: auto; }
.req-tl__note { flex-basis: 100%; color: var(--color-text-muted); font-size: var(--font-size-xs); }
/* The requisition's own move, folded into the event that caused it (E38). Its own line so it reads
   as a consequence rather than a second event, and indented so the eye skips it when scanning. */
.req-tl__spine { flex-basis: 100%; color: var(--color-text-secondary); font-size: var(--font-size-xs);
    padding-left: var(--space-3); }
/* In the modal HEADER (moved there 2026-09-01, third relocation) the header doesn't scroll like the
   body did — opening History on a requisition with many steps (REQ-0066 had 15) would otherwise push
   the header itself to whatever height the list needs. Capped and made independently scrollable so it
   can never grow past a fixed budget, no matter how long the chain's history gets. */
.modal-header .req-tl { margin: 0; }
.modal-header .req-tl__list { max-height: 220px; overflow-y: auto; }

/* ── RFQ detail + pricing matrix (Phase 4) ────────────────────────────── */
.rfq-head { display: flex; gap: var(--space-3); align-items: flex-end; flex-wrap: wrap; margin-bottom: var(--space-4); }
.rfq-head label { display: flex; flex-direction: column; gap: 4px; font-size: var(--font-size-xs); color: var(--color-text-secondary); }
.rfq-vendor-add { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-3); }
.rfq-vendor-add .form-control { flex: 1; min-width: 0; }
/* Same E50 fault as the lines/vendors lists (below) — a many-line RFQ grew this table without a
   ceiling, same as everything else in the modal. Vertical cap added alongside the existing
   horizontal one; thead pinned so the part/vendor labels stay visible while scrolling rows. */
.rfq-matrix-wrap { overflow-x: auto; max-height: 16rem; overflow-y: auto; }
.rfq-matrix { border-collapse: collapse; width: 100%; font-size: var(--font-size-sm); }
.rfq-matrix th, .rfq-matrix td { border: 1px solid var(--color-border-subtle); padding: var(--space-2); text-align: left; vertical-align: top; }
.rfq-matrix th { background: var(--color-bg-subtle); white-space: nowrap; }
.rfq-matrix thead th { position: sticky; top: 0; z-index: 1; }
.rfq-matrix-part { min-width: 160px; }
.rfq-cell { white-space: nowrap; }
.rfq-cell .rfq-price { width: 90px; display: inline-block; }
.rfq-cell--min { background: var(--color-success-bg); }
.rfq-cell--awarded { outline: 2px solid var(--color-success); outline-offset: -2px; }
.rfq-award { border: none; background: none; cursor: pointer; font-size: 18px; color: var(--color-accent); padding: 0 4px; }
.rfq-award:disabled { opacity: .3; cursor: default; }
.rfq-price-val { font-weight: 600; }
.rfq-v-pick { display: inline-flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
.rfq-vendor-row { display: flex; align-items: center; gap: var(--space-2); padding: 5px 0; flex-wrap: wrap; }
/* Quote modal */
.rfq-quote-head { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-3); }
.rfq-quote-head label { display: flex; flex-direction: column; gap: 4px; font-size: var(--font-size-xs); color: var(--color-text-secondary); }
.rfq-quote-head .form-control { min-width: 120px; }
.q-att-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-3); background: var(--color-bg-subtle); border-radius: var(--radius-md); margin-bottom: 4px; }

/* ── Manufacturer picker (parts form, Phase 1 fix) ────────────────────── */
.mfr-status { min-height: 15px; margin-top: 4px; }
.mfr-ok  { color: var(--color-success); font-size: var(--font-size-xs); font-weight: 600; }
.mfr-new { color: var(--color-text-secondary); font-size: var(--font-size-xs); }

/* ── Category combobox (parts form) ───────────────────────────────────── */
.cat-input-row { display: flex; gap: 6px; align-items: center; }
.cat-input-row .form-control { flex: 1; min-width: 0; }
.cat-arrow { flex-shrink: 0; border: 1px solid var(--color-border); background: var(--color-bg-surface); border-radius: var(--radius-md); cursor: pointer; padding: 0 12px; height: 38px; color: var(--color-text-secondary); font-size: 13px; }
.cat-arrow:hover { border-color: var(--color-primary); background: var(--color-bg-subtle); }
.ext-link { font-size: 11px; color: var(--color-text-muted); }
.vendor-search-hint { font-size: var(--font-size-xs); color: var(--color-text-muted); margin: -2px 0 var(--space-3); }

/* ── RFQ send-email vendor checkboxes (Phase 4 fix) ───────────────────── */
.rfq-vendor-row { display: flex; align-items: center; gap: var(--space-2); padding: 4px 0; }
.rfq-vendor-row label { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; font-size: var(--font-size-sm); cursor: pointer; }
/* the row's buttons live apart from the label, so a tap near one never toggles the checkbox (2026-10-13) */
.rfq-vendor-row .rfq-v-acts { display: inline-flex; align-items: center; gap: var(--space-2); flex: none; flex-wrap: wrap; margin-left: auto; position: relative; z-index: 1; }
/* Their number beside ours (Owner 2026-10-13 10:28) — on the compare grid under each price, and in
   the quote modal's "<vendor>'s #" column. Monospace so a catalog number reads as one. */
.rfq-cell-vpn { display: block; white-space: normal; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: var(--font-size-xs); color: var(--color-text-muted); margin-top: 2px; word-break: break-all; }
.rfq-quote-lines { table-layout: fixed; width: 100%; }   /* six columns in a modal: auto layout + the shared 12/16 cell padding pushed Ext $ off-screen */
.rfq-quote-lines thead th, .rfq-quote-lines tbody td, .rfq-quote-lines tfoot td { padding: var(--space-2) var(--space-2); }
.rfq-quote-lines tbody td:first-child { overflow: hidden; }
.rfq-quote-lines .q-vpn { width: 100%; min-width: 100px; padding: 4px 8px; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); }
.rfq-quote-desc { max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rfq-vendor-row .rfq-link-numbers { white-space: nowrap; }
.rfq-vendor-row .vendor-email { color: var(--color-text-muted); font-size: var(--font-size-xs); }

/* ── Approvals (Phase 5) ──────────────────────────────────────────────── */
.appr-card { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); margin-bottom: var(--space-2); background: var(--color-bg-surface); }
.appr-main { min-width: 0; }
.appr-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; flex-shrink: 0; }
/* A card that opens its order reads as clickable: pointer, left accent, hover lift, focus ring. */
.appr-card--open { cursor: pointer; border-left: 3px solid var(--color-primary); transition: background .12s ease, border-color .12s ease, box-shadow .12s ease; }
.appr-card--open:hover { background: var(--color-bg-elevated, rgba(127,127,127,.08)); border-color: var(--color-primary); box-shadow: 0 2px 10px rgba(0,0,0,.14); }
.appr-card--open:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.appr-open-hint { flex-shrink: 0; white-space: nowrap; font-size: var(--font-size-xs); font-weight: 600; color: var(--color-link); margin-left: auto; }
.appr-card--open .appr-actions { margin-left: 0; }

/* ── E37: the approvals card a nav badge is ABOUT ─────────────────────────
   🔴 MUST stay BELOW `.appr-card` and `.appr-card--open`. It was first written
   up beside the table's `.row-attention`, ~60 lines earlier — same specificity,
   so `.appr-card`'s own `background` simply won on source order and the marked
   card rendered identically to every other one. The owner saw it immediately:
   *"approval shows in pending list, no highlight."* Equal specificity means
   ORDER decides, and grouping a rule by topic instead of by cascade is how you
   ship a style that silently does nothing.
   The left bar is a `border-left` here, not the table's inset shadow, because
   `.appr-card--open` already owns that border and a shadow would sit inside it. */
.appr-card--attention { background: var(--color-warning-bg);
    border-left: 3px solid var(--color-accent); }
.appr-card--attention:hover { background: var(--color-warning-bg); border-color: var(--color-border-subtle);
    border-left-color: var(--color-accent); }

.part-search-dropdown {
    position: absolute;
    top: 100%;
    left: 0; right: 0;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    z-index: 200;
    max-height: 260px;
    overflow-y: auto;
    margin-top: 2px;
}
.part-search-option {
    padding: var(--space-3) var(--space-4);
    cursor: pointer;
    border-bottom: 1px solid var(--color-border-subtle);
    font-size: var(--font-size-sm);
}
.part-search-option:last-child { border-bottom: none; }
.part-search-option:hover { background: var(--color-bg-subtle); }
.part-search-empty { color: var(--color-text-muted); font-style: italic; cursor: default; }
.part-selected-chip {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
}
.btn-chip-clear {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--color-text-secondary);
    font-size: var(--font-size-sm);
    padding: 0;
    line-height: 1;
    flex-shrink: 0;
}
.btn-chip-clear:hover { color: var(--color-danger); }

/* Datasheet / link affordances (parts list + any url form field) */
.ds-link { text-decoration: none; opacity: .75; margin-left: 4px; }
.ds-link:hover { opacity: 1; }
.url-field { display: flex; gap: var(--space-2); align-items: stretch; }
.url-field input { flex: 1 1 auto; min-width: 0; }
.url-field .url-open { flex: 0 0 auto; white-space: nowrap; }
.url-field .url-open:disabled { opacity: .45; cursor: default; }

/* ── Settings → Storage Map (E25) ─────────────────────────────────────────────
   The plant's spots, grouped by area. Reads as a walk through the building, which is also
   how the count sheet (E27) will print — hence no sort_order column: the coordinates
   already sort into walking order. */
.spot-area { margin-bottom: var(--space-5); }
.spot-area__head {
    display:       flex;
    align-items:   baseline;
    gap:           var(--space-3);
    font-weight:   600;
    padding:       var(--space-2) 0;
    border-bottom: 1px solid var(--color-border);
    margin-bottom: var(--space-1);
    /* Now a <button> (chevron expand/collapse) — reset it back to looking like the plain
       row heading it always was. */
    width:      100%;
    background: none;
    border-left: none; border-right: none; border-top: none;
    text-align: left;
    cursor:     pointer;
    color:      inherit;
    font-size:  inherit;
    font-family: inherit;
}
.spot-area__chevron {
    display:         inline-block;
    width:           1em;
    color:           var(--color-text-muted);
    transition:      transform 0.15s ease;
}
.spot-area--collapsed .spot-area__head { margin-bottom: 0; border-bottom-color: var(--color-border-subtle); }
.spot-row {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    gap:             var(--space-3);
    padding:         var(--space-2) 0 var(--space-2) var(--space-4);
    border-bottom:   1px solid var(--color-border-subtle);
    font-size:       var(--font-size-sm);
}
.spot-row--retired { opacity: 0.55; }
.spot-row__where { flex: 1 1 auto; display: flex; align-items: center; gap: var(--space-2); }
.spot-row__what  { flex: 0 0 auto; }
.spot-actions    { display: inline-flex; gap: var(--space-3); flex: 0 0 auto; }
.btn-link {
    background: none; border: none; padding: 0; cursor: pointer;
    font-size: var(--font-size-xs); color: var(--color-accent); text-decoration: underline;
}
.settings-section-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-4); margin-bottom: var(--space-4);
}
.settings-section-head h3 { margin: 0 0 2px; }

/* A locked settings panel states the rule in words. A greyed-out control still looks operable —
   The owner's standing rule that a locked record shows TEXT, not disabled inputs. */
.settings-locked {
    padding:       var(--space-5);
    border:        1px dashed var(--color-border);
    border-radius: var(--radius-md);
    color:         var(--color-text-muted);
    font-size:     var(--font-size-sm);
    line-height:   1.6;
}

/* ── RBAC Permissions — role/person graphical view (RBAC_PLAN.md §8n) ──────────────
   Same chevron language as guide.css's <details> sections (› rotating 90° open on click) --
   one visual pattern for "this is a disclosure" across the app, not a second one invented here.
   Every color/spacing/radius value below is a real token from variables.css -- the PREVIOUS
   version of this screen reached for `--color-border-tertiary` / `--color-background-secondary`
   / `--border-radius-md`, none of which variables.css defines, so those declarations were
   silently inert. Replaced wholesale with this block; nothing here invents a new token. */
.rbac-module-group {
    background:      var(--color-bg-surface);
    border:          1px solid var(--color-border-subtle);
    border-radius:   var(--radius-lg);
    box-shadow:      var(--shadow-sm);
    margin-bottom:   var(--space-3);
    overflow:        hidden;
}
.rbac-module-group > summary {
    list-style:      none;
    cursor:          pointer;
    user-select:     none;
    display:         flex;
    align-items:     center;
    gap:             var(--space-2);
    padding:         var(--space-3) var(--space-4);
    border-bottom:   1px solid var(--color-border-subtle);
    font-weight:     600;
}
.rbac-module-group > summary::-webkit-details-marker { display: none; }
.rbac-module-group > summary::before {
    content:         '›';
    font-size:       1rem;
    color:           var(--color-text-secondary);
    transition:      transform .15s;
    flex-shrink:     0;
}
.rbac-module-group[open] > summary::before { transform: rotate(90deg); }
.rbac-module-group > summary:hover { color: var(--color-link); }
.rbac-module-name {
    font-size:       var(--font-size-xs);
    font-weight:     700;
    letter-spacing:  .06em;
    text-transform:  uppercase;
    color:           var(--color-primary-light);
}
.rbac-module-count {
    font-size:       var(--font-size-xs);
    color:           var(--color-text-muted);
    margin-left:     auto;
    /* Guaranteed minimum gap from .rbac-module-name, independent of the flex `gap` above
       actually taking effect -- found 2026-09-14 next to the dark-mode table fix, from the
       same iPad screenshot ("Inventory2 resources", no space at all, next to "Reference
       Data6 resources" and "Work3 resources"). `gap` on the flex `summary` should already
       separate these two spans, and `margin-left: auto` should push this one flush right --
       but padding costs nothing here and can't be defeated by a `display` that doesn't land
       the way `gap` can, so this is the one property in this rule that is never silently a
       no-op. */
    padding-left:    var(--space-2);
    font-weight:     400;
    text-transform:  none;
    letter-spacing:  normal;
}

.rbac-resource-row      { padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--color-border-subtle); }
.rbac-resource-row:last-child { border-bottom: none; }
.rbac-resource-row-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.rbac-resource-name     { font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text); min-width: 150px; flex-shrink: 0; }

.rbac-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.rbac-chip {
    display:         inline-flex;
    align-items:     center;
    gap:             4px;
    font:            inherit;
    border:          none;
    background:      none;
    padding:         0;
    cursor:          default;
}
.rbac-chip--live { cursor: pointer; }
.rbac-chip--live:hover .badge { filter: brightness(1.08); box-shadow: 0 0 0 2px var(--color-border-subtle) inset; }
.rbac-chip small {
    font-size:       var(--font-size-xs);
    font-weight:     700;
    color:           var(--color-text-muted);
    text-transform:  uppercase;
    letter-spacing:  .02em;
}

.rbac-field-toggle {
    font:            inherit;
    font-size:       var(--font-size-xs);
    font-weight:     700;
    color:           var(--color-accent-dark);
    background:      var(--color-bg-subtle);
    border:          1px solid var(--color-border-subtle);
    border-radius:   var(--radius-pill);
    padding:         2px 10px;
    margin-left:     auto;
    cursor:          pointer;
}
.rbac-field-toggle:hover { background: var(--color-bg-muted); }

.rbac-field-panel {
    margin-top:      var(--space-2);
    padding:         var(--space-3);
    background:      var(--color-bg-subtle);
    border:          1px solid var(--color-border-subtle);
    border-radius:   var(--radius-md);
    display:         flex;
    flex-direction:  column;
    gap:             var(--space-2);
}
.rbac-fr-row {
    display:         flex;
    align-items:     center;
    gap:             var(--space-4);
    background:      var(--color-bg-surface);
    border:          1px solid var(--color-border-subtle);
    border-radius:   var(--radius-sm);
    padding:         var(--space-2) var(--space-3);
    flex-wrap:       wrap;
}
.rbac-fr-name { flex: 1; font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text); min-width: 120px; }
.rbac-fr-col {
    font-size:       var(--font-size-xs);
    font-weight:     700;
    text-transform:  uppercase;
    color:           var(--color-text-muted);
    text-align:      center;
    width:           56px;
    display:         flex;
    flex-direction:  column;
    align-items:     center;
    gap:             2px;
}
.rbac-fr-none { color: var(--color-text-muted); }
.rbac-fr-note { font-size: var(--font-size-xs); color: var(--color-text-muted); margin: 0; }

.rbac-legend {
    display:         flex;
    flex-wrap:       wrap;
    gap:             var(--space-4);
    font-size:       var(--font-size-xs);
    color:           var(--color-text-secondary);
    background:      var(--color-bg-subtle);
    border:          1px solid var(--color-border-subtle);
    border-radius:   var(--radius-md);
    padding:         var(--space-2) var(--space-3);
    margin-bottom:   var(--space-3);
}
.rbac-legend span { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }

/* ── Rollup (Users Edit form) ────────────────────────────────────────────────────────────
   The condensed, read-only view of what a person's role(s) grant -- one line per module,
   counts only. The full graphical detail (same chips/chevrons as above) is one click away via
   the "Customize" link, not duplicated inline a third time. */
.rbac-rollup-box    { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow: hidden; background: var(--color-bg-subtle); }
.rbac-rollup-row    { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--color-border-subtle); font-size: var(--font-size-sm); }
.rbac-rollup-row:last-child { border-bottom: none; }
.rbac-rollup-row .rbac-module-name  { min-width: 110px; }
.rbac-rollup-detail { flex: 1; min-width: 0; font-size: var(--font-size-xs); line-height: 1.7; color: var(--color-text); }
.rbac-rollup-k      { display: inline-block; min-width: 58px; color: var(--color-text-muted); }
.rbac-rollup-pp     { color: var(--color-info); font-style: italic; }
.rbac-rollup-none   { color: var(--color-text-muted); }
.rbac-rollup-addon  { color: var(--color-text-muted); font-style: italic; margin-top: 2px; }
.job-credit         { color: var(--color-success); }
.rbac-rollup-cta {
    display:         inline-flex;
    align-items:     center;
    gap:             6px;
    font-size:       var(--font-size-sm);
    font-weight:     700;
    color:           #fff;
    background:      var(--color-primary);
    border:          none;
    border-radius:   var(--radius-md);
    padding:         var(--space-2) var(--space-4);
    margin-top:      var(--space-3);
    cursor:          pointer;
}
.rbac-rollup-cta:hover { background: var(--color-primary-hover); }


/* E26 — a flagged placement. Quiet enough to live in a list, loud enough that the next person
   looking at this part cannot miss that the shelf and the system disagree. */
.loc-breakdown__row--flagged { background: var(--color-danger-bg); }
.disc-flag { font-size: var(--font-size-xs); }

/* ── E27 count sheet ──────────────────────────────────────────────────────────
   Built to be PRINTED and carried round the plant on a clipboard, so the screen layout and the
   paper layout are the same thing. PDF is the browser's own "Save as PDF" — the server has no PDF
   writer, and adding one would be a dependency that dies at the VM move. */
.cs-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-5); }
.cs-head h3 { margin: 0 0 4px; }
.cs-actions { display: flex; align-items: center; gap: var(--space-3); flex: 0 0 auto; }
.cs-area { margin-bottom: var(--space-6); break-inside: avoid; }
.cs-area__head { margin: 0 0 var(--space-2); }
.cs-qty { font-variant-numeric: tabular-nums; font-weight: 600; }
/* The write-in box — the whole reason this is printed rather than just read. */
.cs-count { border-bottom: 1px solid var(--color-border); min-width: 90px; white-space: nowrap; }
.cs-row--empty td { color: var(--color-text-muted); }
/* on screen the box is a real box. 16px on purpose — iOS Safari zooms the page on focus
   for anything smaller, and a count sheet is used on a tablet. The delta beside it is the whole
   feedback: ✓ matches, −n short, +n over. */
.cs-input { width: 84px; max-width: 100%; display: inline-block; text-align: right; padding: 6px 8px;
            font-size: 16px; font-variant-numeric: tabular-nums; }
.cs-delta { display: inline-block; min-width: 2.4em; margin-left: 6px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cs-delta--ok    { color: var(--color-success); }
.cs-delta--short { color: var(--color-danger); }
.cs-delta--over  { color: var(--color-warning); }
.cs-delta--bad   { color: var(--color-danger); }
.cs-row--found td { padding-top: 2px; }
/* Sticky, so "Submit" is reachable from rack 9 without scrolling back to the top; sits above the
   mobile bottom nav. Hidden until something has been typed — an empty sheet has nothing to submit. */
.cs-submitbar { position: sticky; bottom: 0; z-index: 5; display: flex; align-items: center; justify-content: flex-end;
                gap: var(--space-3); margin-top: var(--space-4); padding: var(--space-3) var(--space-4);
                background: var(--color-bg-surface); border-top: 1px solid var(--color-border); box-shadow: 0 -4px 12px rgba(0,0,0,.06); }
.cs-submitbar[hidden] { display: none; }
.count-view__bar { margin-bottom: var(--space-4); }
/* Reports' read-only sheet: the Counted column exists for PAPER only (it never fills in on screen). */
.cs-table--noinput .cs-count-h, .cs-table--noinput .cs-count { display: none; }
@media (max-width: 768px) { .cs-submitbar { bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px)); } }

@media print {
    /* Everything that is chrome rather than sheet. */
    .sidebar, .topbar, .bottom-nav, .report-nav, .cs-actions, #toast-container,
    .panel-toolbar, .panel-actions { display: none !important; }
    body, #main-content, .tab-panel { background: #fff !important; color: #000 !important; }
    .cs-table, .cs-table th, .cs-table td { border-color: #999 !important; color: #000 !important; }
    .cs-area { page-break-inside: avoid; }
    .cs-count { border-bottom: 1px solid #000 !important; }
    /* a half-filled sheet prints with what was typed; the chrome around it does not. */
    .cs-input { border: none !important; box-shadow: none !important; background: none !important; padding: 0 !important; }
    .cs-delta, .cs-submitbar, .cs-found { display: none !important; }
    .cs-table--noinput .cs-count-h, .cs-table--noinput .cs-count { display: table-cell !important; }
    .badge { border: 1px solid #666 !important; color: #000 !important; background: none !important; }
    a[href]:after { content: ""; }   /* no URL noise on paper */
}

/* E26 in the scan flow — the flag sits beside the location button, not inside it, so tapping the
   location still just picks it. Tap-first (iPad): both targets stay full height. */
.scan-place-row { display: flex; align-items: stretch; gap: var(--space-2); }
.scan-place-row .scan-place { flex: 1 1 auto; }
.scan-place-row .scan-nothere { flex: 0 0 auto; padding: 0 var(--space-2); white-space: nowrap; }

/* The shared chain block (E2/E6, E30) — same treatment wherever the chain is shown. */
.chain-flag {
    margin: var(--space-3) 0;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: var(--color-bg-subtle);
    border-left: 3px solid var(--color-border);
}
.chain-flag--danger { border-left-color: var(--color-danger); }
.chain-flag--ok     { border-left-color: var(--color-success); }

/* E9 — nav badges. Small, quiet, and only ever on a nav item the role can actually use.
   Counts are decided server-side; this is only the paint. */
.nav-badge {
    margin-left:   auto;
    min-width:     20px;
    padding:       1px 6px;
    border-radius: var(--radius-pill);
    background:    var(--color-accent);
    color:         #fff;
    font-size:     11px;
    font-weight:   700;
    line-height:   1.5;
    text-align:    center;
    flex:          0 0 auto;
}
.bottom-nav-item .nav-badge { margin-left: 2px; }

/* E19 — what a job has cost. Three figures, because "committed" and "received" answer different
   questions and a single total would hide the one E17's close gate depends on. */
.job-cost-summary {
    display: flex;
    gap: var(--space-6);
    padding: var(--space-3) 0 var(--space-2);
}
.job-cost-summary strong { font-size: var(--font-size-lg); font-variant-numeric: tabular-nums; }

/* Session tally: why a row is flagged. A bare ⚠ beside a number reads as if the NUMBER is wrong,
   when the number is the one part that is right — the note names the shortfall instead. */
.scan-tally__note { display: block; font-size: var(--font-size-xs); color: var(--color-text-muted);
    margin-top: 2px; }

/* ─────────────────────────────────────────────────────────────────────────────
   E45 · Part Builder — the graphical spec picker (js/modules/partbuilder.js)

   Owner, 2026-08-13: build the number by picking a picture, the way McMaster does,
   and use the same pictures to FIND an existing part. The tiles below serve both
   modes; there is no second stylesheet for the finder.
   ───────────────────────────────────────────────────────────────────────────── */

/* The knockout color the tile SVGs paint their internal gaps with (thread ridges, bolt slots).
   It must be the TILE's own background or the drawing shows light gouges on a dark card. */
.pb-modal { --tile-bg: var(--color-bg-surface); }

.pb-lede   { margin: 0 0 18px; color: var(--color-text-muted); max-width: 62ch; }
.pb-h3     { margin: 0 0 14px; font-size: 1.05rem; font-weight: 700; }
.pb-fields { display: flex; flex-direction: column; gap: 20px; }
.pb-field  { display: flex; flex-direction: column; gap: 7px; }
.pb-label  { font-weight: 600; font-size: .88rem; }
.pb-req    { color: var(--color-danger); }
.pb-hint   { font-size: .8rem; color: var(--color-text-muted); }

/* The tiles. auto-fill rather than a fixed count so a 13-type fastener list and a
   6-shape steel list both stay readable, and it collapses cleanly on a tablet. */
.pb-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    gap: 10px;
}
.pb-tiles--family { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }

/* 🔴 THEME-AWARE, AND IT WAS NOT.  (Owner, 2026-08-13: *"appearance, readability … NOT good"*.)
   `--color-surface` and `--color-primary-soft` DO NOT EXIST in variables.css — the names are
   `--color-bg-surface` and there is no soft-primary token at all. An undefined custom property
   silently takes its fallback, so every tile painted `#fff` and the selected tile `#eef3f8`: white
   cards in a dark theme. Worse, `color: inherit` then pulled the modal's LIGHT text (#e2eaf0) onto
   that white card, which is why the family labels were effectively invisible while the smaller gray
   notes stayed readable — the notes set their own color and the labels did not.
   A wrong variable name fails silently and looks like a design mistake. Painting an explicit
   foreground next to every explicit background is what stops it happening again. */
.pb-tile {
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    padding: 12px 10px;
    background: var(--color-bg-surface);
    border: 2px solid var(--color-border);
    border-radius: 8px;
    cursor: pointer;
    text-align: center;
    color: var(--color-text);
    transition: border-color .12s, background .12s, transform .12s;
}
.pb-tile:hover  { border-color: var(--color-primary-light); transform: translateY(-1px); }
.pb-tile:focus-visible { outline: 2px solid var(--color-primary-light); outline-offset: 2px; }
/* Selected: tint the SURFACE rather than swapping to a fixed color, so it reads as chosen in
   both themes instead of turning into a light card on a dark page. */
.pb-tile.is-on  { border-color: var(--color-primary-light);
                  background: var(--color-bg-muted); color: var(--color-text); }
.pb-tile--wide  { align-items: flex-start; text-align: left; }

.pb-tile__label { font-weight: 600; font-size: .84rem; line-height: 1.25; color: var(--color-text); }
.pb-tile__note  { font-size: .72rem; color: var(--color-text-muted); line-height: 1.3; }

/* A ROW OF EXAMPLE DRAWINGS ON THE FAMILY TILE.  (Owner, 2026-08-17: *"many techs wont want to read,
   can u add small icon examples in each family choice?"*)
   Smaller than a choosing icon on purpose — these illustrate what the family COVERS, they are not
   themselves the thing being picked, so they must not compete with the label. `flex-wrap` because
   Sheet declares one and the others declare three. */
.pb-tile__icons { display: flex; flex-wrap: wrap; gap: 2px; align-items: center; margin-bottom: 2px; }

/* A nested question panel — head and drive under the type that owns them. It reads as a REFINEMENT
   of the tile above it rather than a peer question, which is what it actually is: two of seventeen
   fastener types have a head style at all. The left rule and the inset are doing the work; there is
   no overlay, deliberately, because a dialog inside the builder is the pattern that produced the
   stacked-overlay bug (fixed 2026-08-17) and E34, still open. */
.pb-nest {
    margin: -8px 0 0 14px;
    border-left: 2px solid var(--color-primary-light);
    border-radius: 0 8px 8px 0;
    background: var(--color-bg-surface);
    padding: 12px 14px 14px;
}
.pb-nest__head {
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--color-text-muted);
    margin-bottom: 10px;
}
.pb-nest__body { display: flex; flex-direction: column; gap: 16px; }

/* The trail of answered picture questions. Each is a button back to that step — Owner, 2026-08-18:
   "allowing the user to back up per step or cancel completely if a selection is made incorrectly".
   Steps REPLACE each other rather than stacking, so the pane never scrolls to reach the live
   question; the history lives here instead, one line high. */
.pb-crumbs {
    display: flex; flex-wrap: wrap; gap: 6px;
    padding-bottom: 4px; margin-bottom: 4px;
    border-bottom: 1px solid var(--color-border);
}
.pb-crumb {
    display: inline-flex; align-items: baseline; gap: 6px;
    padding: 5px 10px; border-radius: 999px;
    border: 1px solid var(--color-border);
    background: var(--color-bg-surface);
    color: var(--color-text); font: inherit; font-size: .78rem;
    cursor: pointer; transition: border-color .12s, background .12s;
}
.pb-crumb:hover { border-color: var(--color-link); background: var(--color-bg-hover, var(--color-bg-surface)); }
.pb-crumb:focus-visible { outline: 2px solid var(--color-link); outline-offset: 2px; }
.pb-crumb__k { color: var(--color-text-muted); font-size: .72rem; }
.pb-crumb__v { font-weight: 600; }
/* 🔴 NO "✕". The owner, driving, 2026-08-18: *"those 'x' after each choice probab;y does not belong? i
   mean if user wants to go back, they click Back"*. He is right — it read as *delete this answer*
   when it means *return to this question*, and Back already says that unambiguously. The chip stays
   clickable, because jumping back three steps at once is genuinely useful, but it no longer
   advertises itself as a destructive control. */
/* Smaller tiles inside the panel: these are a refinement, and giving them the same visual weight as
   the type grid is what made the original layout read as two equal questions. */
.pb-nest .pb-tiles { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 8px; }
.pb-nest .pb-tile  { padding: 10px 8px; }
.pb-nest .pb-icon  { width: 52px; height: 33px; }
.pb-tile__icons .pb-icon { width: 40px; height: 25px; opacity: .85; }

/* The wait state while the family list is in flight — see partbuilder.js open(). */
.pb-lede--wait  { color: var(--color-text-muted); font-style: italic; }
/* Bigger than the old 48×32 because the drawings now carry shading and a drive recess —
   detail that is simply invisible at the previous size. */
.pb-icon        { width: 62px; height: 39px; flex: none; }

/* Footer: the generated number, or the matches. */
/* 🔴 THE BUTTONS MUST NOT GROW WITH THE RESULTS.  (Owner, 2026-08-13: *"see the buttons on the
   bottom? they resize depending on how many are in the match list"*.)

   The footer is a flex row and its default `align-items: stretch` made every child as tall as the
   tallest — so the more matches were found, the taller Back and Close became. Pinned to the top
   instead, which keeps them a fixed size no matter what the list does. */
.modal .pb-preview { flex: 1; min-width: 0; text-align: left; }
.pb-modal .modal-footer { align-items: flex-start; }
.pb-actions { display: flex; align-items: flex-start; align-self: flex-start;
              gap: 8px; flex: none; }
.pb-actions .btn { align-self: flex-start; }

.pb-num  { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
           font-size: 1.05rem; font-weight: 700; letter-spacing: .02em; word-break: break-all; }
.pb-num--wait { font-family: inherit; font-size: .86rem; font-weight: 500; color: var(--color-text-muted); }
.pb-desc { font-size: .8rem; color: var(--color-text-muted); }
.pb-warn { margin-top: 6px; font-size: .8rem; color: var(--color-warning, #b7791f); }

.pb-locked-note { color: var(--color-text-muted); }
input.is-locked { background: var(--color-bg-muted); color: var(--color-text); font-weight: 600; }

/* Find mode results. Scrolls inside itself so the modal footer never grows off screen. */
.pb-found-head { font-size: .8rem; font-weight: 700; color: var(--color-text-muted); margin-bottom: 4px; }
.pb-found { list-style: none; margin: 0; padding: 0; max-height: 210px; overflow-y: auto; }
.pb-found__row {
    display: grid; grid-template-columns: minmax(0,1.1fr) minmax(0,1.4fr) minmax(0,1fr) auto;
    gap: 10px; align-items: baseline; width: 100%;
    padding: 7px 8px; background: none; border: 0; border-bottom: 1px solid var(--color-border, #e2e8f0);
    cursor: pointer; text-align: left; color: inherit; font-size: .82rem;
}
.pb-found__row:hover { background: var(--color-bg-muted); }
.pb-found__pn    { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; }
.pb-found__desc  { color: var(--color-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pb-found__where { color: var(--color-text-muted); }
.pb-found__nowhere { font-style: italic; opacity: .8; }
.pb-found__qty   { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pb-found__qty.is-low { color: var(--color-danger, #c0392b); }

@media (max-width: 640px) {
    .pb-tiles { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); }
    .pb-found__row { grid-template-columns: 1fr auto; row-gap: 2px; }
    .pb-found__desc, .pb-found__where { grid-column: 1 / -1; }
}

/* ── Vendor cross-reference on the part detail (E45) ─────────────────────────
   The vendor's number is a machine string people read character by character off a
   packing slip, so it gets a monospace face and a little breathing room — the same
   treatment the generated part number gets in the builder preview. */
.vp-xref .vp-head { margin-bottom: 4px; font-weight: 600; }
.vp-xref .vp-row  { padding: 2px 0; font-size: var(--font-size-sm); }
.vp-xref .vp-num  {
    font-family:     ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size:       .92em;
    letter-spacing:  .01em;
    background:      var(--color-bg-muted);
    color:           var(--color-text);
    padding:         1px 5px;
    border-radius:   var(--radius-sm);
}

/* ── Cost history on the part detail (BACKLOG §1) ────────────────────────────
   Collapsed by default — a long buying history would otherwise push the stock and
   placements off screen to answer a question nobody has asked yet. */
.ch-wrap    { margin: -4px 0 var(--space-3); }
.ch-body    { margin-top: 6px; padding: 8px 10px; border-left: 3px solid var(--color-primary);
              background: var(--color-bg-subtle); border-radius: var(--radius-sm); }
.ch-summary { margin-bottom: 6px; }
.ch-scroll  { overflow-x: auto; }                 /* the table scrolls, never the dialog */
.ch-table   { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); }
.ch-table th, .ch-table td { padding: 3px 8px 3px 0; text-align: left; white-space: nowrap; }
.ch-table th { font-size: var(--font-size-xs); color: var(--color-text-muted); font-weight: 600; }
.ch-table tbody tr + tr td { border-top: 1px solid var(--color-border); }
/* Prices line up column-wise only with tabular figures — otherwise 1s and 7s drift. */
.ch-table .ch-num { text-align: right; font-variant-numeric: tabular-nums; }
.ch-delta   { font-size: var(--font-size-xs); font-weight: 700; margin-left: 4px; }
.ch-delta.is-up   { color: var(--color-danger); }   /* paying MORE is the bad direction */
.ch-delta.is-down { color: var(--color-success); }

/* ── Batch mode's declared destination (scan.js) ─────────────────────────────
   Shown, never hidden: it acts on every scan in the run, and a setting that changes
   what a button does without being visible is the trap this replaced. */
.scan-intent {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
    margin-top: var(--space-2); padding: 6px 10px;
    background: var(--color-bg-muted); color: var(--color-text);
    border-left: 3px solid var(--color-primary); border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
}
.scan-intent-inline {
    margin-top: 10px; padding: 6px 10px; text-align: left;
    background: var(--color-bg-muted); color: var(--color-text);
    border-left: 3px solid var(--color-primary); border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
}

/* ── Picker "➕ New …" row ────────────────────────────────────────────────────
   🔴 ONE CLASS, NOT INLINE STYLES REPEATED PER PICKER.  (Owner, 2026-08-17: *"u need to check all areas
   where +New.... exists it should be a css change so its universal"*.)

   `SearchPicker` hand-rolled this row twice in JS — two style strings and a pair of mouseenter /
   mouseleave listeners to fake `:hover` — while `GroupedPicker` used a real button class. Same
   question, two implementations, and only one of them was themeable, which is why the color could go
   to 1.36:1 in dark mode without anyone noticing ([[shared-component-rule]]).

   Color comes from `--color-link`, which flips with the theme; see variables.css for the measurement.
   Any picker that grows an add row should use these classes rather than inventing a third copy. */
.picker-add {
    width: 100%;
    text-align: left;
    padding: 8px 10px;
    margin-top: 2px;
    background: transparent;
    border: none;
    border-top: 0.5px solid var(--color-border);
    border-radius: 0 0 var(--radius-md) var(--radius-md);
    cursor: pointer;
    color: var(--color-link);
    font-size: 13px;
    font-weight: 600;
}
.picker-add:hover { background: var(--color-bg-subtle); }

/* Pinned variant — first row of a long list, so it must stay put AND stay opaque or the rows scroll
   through it. */
.picker-add--top {
    margin-top: 0;
    margin-bottom: 2px;
    background: var(--color-bg-surface);
    border-top: none;
    border-bottom: 0.5px solid var(--color-border);
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    position: sticky;
    top: 0;
    z-index: 2;
}
.picker-add--top:hover { background: var(--color-bg-subtle); }

/* ── Vendor cross-reference rows (part detail) ────────────────────────────
   Collapsed a row answers who sells it and what they call it; the chevron opens
   the commercial terms. Keeps five suppliers readable instead of a wall of small
   print — the same shape the vendor screen uses for parts. */
.vp-row        { padding: 3px 0; }
.vp-row-head   { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.vp-chev       { border: none; background: none; cursor: pointer; padding: 0 2px;
                 color: var(--color-text-secondary); font-size: 12px; line-height: 1; }
.vp-chev:hover { color: var(--color-link); }
.vp-row-body   { padding: 2px 0 4px 20px; }

/* One line per cross-reference on the vendor screen — their number, ours, two actions.
   A vendor with fifty parts is a wall at three lines each; this list is scanned, not read. */
.vp-line       { display: flex; align-items: center; justify-content: space-between;
                 gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--color-border-subtle); }
.vp-line:last-child { border-bottom: none; }
.vp-line-nums  { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vp-line-acts  { flex: 0 0 auto; display: flex; gap: 2px; }
.vp-line .icon-btn { border: none; background: none; cursor: pointer; padding: 2px 4px;
                     font-size: 13px; line-height: 1; opacity: .75; }
.vp-line .icon-btn:hover { opacity: 1; }

/* Part detail: collapsed answers "who sells this" and nothing else. */
/* ⚠️ `display:flex` BEATS the `hidden` attribute — [hidden] only sets display:none in the UA sheet,
   so any later display rule silently un-hides every row. Every one was expanded until this was
   added. Keep the [hidden] rule adjacent to whatever sets display, or it will happen again. */
.vp-row-body[hidden] { display: none !important; }
/* Same trap in the Use form's charge block (UseCharge): its Use / Return boxes are `.form-group`
   (display:flex), so `hidden` never hid the Return box — "Returned from where?" showed under a Use.
   Found 2026-10-10 driving the project Use (Projects phase 2B). */
.use-charge[hidden], .use-charge [hidden] { display: none !important; }
.vp-row-body   { display: flex; align-items: center; gap: 4px; }
/* The two actions sit against the right edge, in the same column as the vendor screen's —
   a ragged action column makes a list of ten look like ten different rows.
   ⚠️ NOT `.icon-btn:first-of-type` — the number is itself a <button>, so `of-type` counts it and
   the margin lands on the LINK, pushing nothing. Anchor on the first child instead. */
.vp-row-body > :first-child { margin-right: auto; }
.vp-row-body .icon-btn { border: none; background: none; cursor: pointer; padding: 2px 4px;
                         font-size: 13px; line-height: 1; opacity: .75; }
.vp-row-body .icon-btn:hover { opacity: 1; }

/* ── Job status strip (2026-10-06) ──────────────────────────────── */
.job-status-strip {
    display:       flex;
    flex-wrap:     wrap;
    align-items:   center;
    gap:           var(--space-3) var(--space-4);
    padding:       var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
    background:    var(--color-bg-subtle, var(--color-bg));
    border:        1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
}
.job-status-strip__state { display: inline-flex; align-items: center; gap: var(--space-2); }
.job-status-strip__live  { font-size: var(--font-size-xs); color: var(--color-warning); }
.job-status-strip__moves { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-left: auto; }
.job-status-strip__why   { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-right: var(--space-2); }
.job-status-strip button[disabled] { opacity: .5; cursor: not-allowed; }
/* ── ⏱ Project Time Sheet — the month calendar (js/modules/timesheet.js) ──────── */
.ts-bar     { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-5); margin-bottom: var(--space-3); }
.ts-week    { display: flex; align-items: center; gap: var(--space-2); }
.ts-week .ts-date { width: auto; }
.ts-person  { display: flex; align-items: center; gap: var(--space-2); flex: 1; min-width: 260px; }
.ts-person .ts-picker { flex: 1; max-width: 320px; }
.ts-title   { margin-bottom: var(--space-2); font-size: var(--font-size-sm); }
.ts-entering { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.ts-entering .ts-project { width: auto; max-width: 420px; }
.ts-wrap    { overflow-x: auto; }
.ts-cal     { table-layout: fixed; min-width: 760px; }
.ts-cal th  { text-align: center; }
.ts-cal th.ts-wcell { width: 150px; }
.ts-cal td  { vertical-align: top; padding: 4px 5px 6px; height: 74px; border-right: 1px solid var(--color-border-subtle); }
.ts-cal td.ts-out   { background: var(--color-bg-subtle); }
.ts-cal td.ts-out .ts-dnum { color: var(--color-text-muted); }
.ts-cal td.ts-today { background: var(--color-info-bg); }
.ts-cal td.ts-future { opacity: .55; }
.ts-dnum    { font-size: var(--font-size-xs); font-weight: 700; color: var(--color-text-secondary); margin-bottom: 3px; }
.ts-wlabel  { white-space: nowrap; margin-bottom: 3px; }
.ts-cell    { width: 100%; text-align: center; padding: 3px 4px; font-size: var(--font-size-sm); height: 30px; }
.ts-cell-week { font-weight: 700; }
.ts-cell:disabled { background: var(--color-bg-muted); color: var(--color-text-muted); cursor: not-allowed; }
.ts-cell.ts-changed { border-color: var(--color-warning); background: var(--color-warning-bg); }
.ts-cell.ts-bad { border-color: var(--color-danger); background: var(--color-danger-bg); }
.ts-lock    { display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .04em; color: var(--color-text-muted); background: var(--color-bg-muted);
              border: 1px dashed var(--color-border); border-radius: var(--radius-sm); padding: 6px 0; width: 100%; text-align: center; cursor: help; }
.ts-lock-inline { width: auto; padding: 0 5px; cursor: default; }
.ts-both    { font-size: 10px; color: var(--color-danger); font-weight: 700; margin-bottom: 3px; }
.ts-wsum    { font-size: var(--font-size-sm); font-weight: 700; padding: 5px 6px; border-radius: var(--radius-sm); border-left: 3px solid; background: var(--color-bg-subtle); }
.ts-chips   { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 3px; }
.ts-chip    { font-size: 11px; line-height: 1.2; padding: 2px 5px; border-radius: var(--radius-sm); border: 1px solid transparent; border-left-width: 3px;
              background: var(--color-bg-subtle); color: var(--color-text); cursor: pointer; white-space: nowrap; font-family: inherit; }
.ts-chip:hover { filter: brightness(.95); }
.ts-chip.ts-chip-on { font-weight: 700; }
/* six project colours, by position in the list — a border on chips and boxes, so a glance tells projects apart */
.ts-c0 { border-left-color: #2b6cb0 !important; } .ts-c1 { border-left-color: #2f855a !important; } .ts-c2 { border-left-color: #c05621 !important; }
.ts-c3 { border-left-color: #6b46c1 !important; } .ts-c4 { border-left-color: #b7791f !important; } .ts-c5 { border-left-color: #c53030 !important; }
.ts-cell.ts-c0, .ts-cell.ts-c1, .ts-cell.ts-c2, .ts-cell.ts-c3, .ts-cell.ts-c4, .ts-cell.ts-c5 { border-left-width: 3px; }
.ts-foot    { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); margin-top: var(--space-3); }
.ts-totals  { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ts-tot-chip { font-size: var(--font-size-xs); padding: 2px 7px; border-radius: var(--radius-sm); background: var(--color-bg-subtle); border: 1px solid var(--color-border-subtle); }
.ts-tot-all { background: var(--color-info-bg); border-color: var(--color-info-border); }
.ts-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.ts-msg-bad { color: var(--color-danger); font-weight: 600; }
.ts-actions button[disabled] { opacity: .5; cursor: not-allowed; }
/* 💲 Labor Rates (Settings) */
.lr-grid td, .lr-grid th { padding: 6px 8px; vertical-align: middle; }
.lr-grid .lr-rate { width: 110px; text-align: right; }
.lr-grid .lr-eff  { text-align: right; white-space: nowrap; }
.lr-grid tr.lr-other td { color: var(--color-text-muted); }
.job-status-strip__dates { display: inline-flex; gap: var(--space-3); white-space: nowrap; }
.prj-res-list { display: flex; flex-wrap: wrap; gap: 6px; }
.prj-res-chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; border: 1px solid var(--color-border-subtle);
                border-radius: var(--radius-md); background: var(--color-bg-subtle); font-size: var(--font-size-sm); }
.prj-res-chip.prj-res-off { opacity: .55; }
.prj-res-chip .btn-icon { border: none; background: none; cursor: pointer; padding: 0 2px; font-size: 12px; opacity: .7; }
.prj-res-chip .btn-icon:hover { opacity: 1; color: var(--color-danger); }
/* ── 🗂 Project breakdown tree (0038) ───────────────────────────────────── */
.prj-bd-tree { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); overflow: hidden; }
.prj-bd-row  { display: flex; align-items: center; gap: 6px; padding: 4px 8px 4px calc(8px + var(--depth, 0) * 18px); border-top: 1px solid var(--color-border-subtle); font-size: var(--font-size-sm); }
.prj-bd-row:first-child { border-top: none; background: var(--color-bg-subtle); }
.prj-bd-row.prj-bd-hidden { display: none; }
.prj-bd-chev { width: 22px; height: 22px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-bg-surface); cursor: pointer; font-size: 14px; line-height: 20px; text-align: center; color: var(--color-text-secondary); padding: 0; flex: none; }
.prj-bd-chev:hover:not(:disabled) { color: var(--color-text); background: var(--color-bg-subtle); }
.prj-bd-chev.prj-bd-leaf { border-color: transparent; background: none; }
.prj-bd-chev.prj-bd-leaf { cursor: default; color: var(--color-text-muted); }
.prj-bd-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prj-bd-stats { display: inline-flex; gap: 10px; font-size: var(--font-size-xs); color: var(--color-text-secondary); white-space: nowrap; }
.prj-bd-acts { display: inline-flex; gap: 2px; flex: none; }
.prj-bd-acts .btn { padding: 0 5px; line-height: 20px; }
.prj-bd-inline { padding: 6px 10px 6px 34px; }
.prj-bd-inline .prj-bd-tree { max-width: 760px; }
/* PO lines inside the detail (Owner 2026-10-12 13:28: the Receive button scrolled off to the right):
   the table fits the modal — long part numbers and manufacturers wrap, never the row. */
.po-lines { table-layout: fixed; width: 100%; }   /* 2026-10-13: fixed — the columns can never exceed the box, so a long part number wraps (Owner: "wrap the part number") instead of pushing Receive off the edge */
.po-lines td, .po-lines th { white-space: normal; overflow-wrap: anywhere; }
.po-lines td:first-child { word-break: break-word; overflow-wrap: anywhere; }
.po-lines .po-nowrap { white-space: nowrap; overflow-wrap: normal; }   /* EA and $1.25 never break — only the part number wraps */
/* The qty box over the button, both the full width of the Receive column — one width, one edge
   (Owner 2026-10-13 14:09: "receive button should = the qty, doesnt look right"). */
.po-rcv-wrap { display: flex; flex-direction: column; gap: 4px; align-items: stretch; }
.po-rcv-wrap .po-rcv, .po-rcv-wrap .po-rcv-btn { width: 100%; min-width: 0; box-sizing: border-box; text-align: center; }
.prj-bd-name.prj-bd-clickable { cursor: pointer; }
.prj-bd-name.prj-bd-clickable:hover { text-decoration: underline; }
.prj-bd-row.prj-bd-on { background: var(--color-info-bg); }
.prj-bd-parts { padding: 4px 8px 8px calc(8px + var(--depth, 0) * 18px); background: var(--color-bg-subtle); border-top: 1px solid var(--color-border-subtle); }
.prj-bd-parts-head { padding: 4px 0 6px; }
.prj-bd-bom { font-size: var(--font-size-sm); background: var(--color-bg-surface); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); }
.prj-bd-bom td, .prj-bd-bom th { padding: 5px 8px; white-space: normal; }
.prj-bd-bom { table-layout: fixed; }
.prj-bd-bom .prj-bd-part { overflow: hidden; }
.prj-bd-bom .prj-bd-desc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prj-bd-bom th:nth-child(2) { width: 110px; } .prj-bd-bom th:nth-child(3) { width: 160px; } .prj-bd-bom th:nth-child(4) { width: 150px; }
/* Ref: who used it, the date beneath — wraps inside its column (it ran past the panel's edge, Owner 2026-10-16). */
.prj-bd-bom .prj-bd-ref { overflow-wrap: anywhere; }
.prj-bd-po { font-weight: 700; }
.btn-warning { background: var(--color-warning-bg); color: var(--color-warning); border: 1px solid var(--color-warning-border); }
.btn-warning:hover { filter: brightness(.96); }
