/*
 * BoxFlow — components.css
 * LAYER 2 — SEMANTIC TOKENS + SHARED COMPONENT CLASSES
 *
 * ┌─────────────────────────────────────────────────────────────┐
 * │  DEVELOPER RULE:                                            │
 * │  Write ONLY semantic tokens in page styles and components.  │
 * │  var(--surface-card), var(--brand-primary), var(--text-muted)│
 * │  Never write var(--color-ink-700) in a page style.         │
 * │  Never write #112240 anywhere.                              │
 * └─────────────────────────────────────────────────────────────┘
 *
 * ┌─────────────────────────────────────────────────────────────┐
 * │  DESIGN FIRM:                                               │
 * │  To remap the brand (e.g. accent changes from gold to blue):│
 * │  1. Add the new primitive to tokens.css if it doesn't exist │
 * │  2. Update the semantic mapping below — one line per role   │
 * │  Do not touch any class rules below the semantic block.     │
 * └─────────────────────────────────────────────────────────────┘
 *
 * FILE STRUCTURE:
 *   Part A — Semantic token definitions (role → primitive)
 *   Part B — Reset
 *   Part C — Shared component classes (use semantic tokens only)
 *
 * Loaded after tokens.css, before any page inline <style>.
 * Must be linked in every page <head> in this order:
 *   <link rel="stylesheet" href="/css/tokens.css">
 *   <link rel="stylesheet" href="/css/components.css">
 */

/* ============================================================
   PATTERN INDEX — read this before writing any UI element.

   Before adding any new class to a page style block:
   1. Find your pattern here — use the existing class.
   2. If close but not identical — is the difference meaningful
      (different function/meaning) or cosmetic (2px, one colour)?
      If cosmetic → use the existing class.
      If meaningfully different → evaluate updating the existing
      class vs adding a new one. Document the decision.
   3. If genuinely new → add it here AND in the correct section
      below. Never add a class to a page without adding it here.

   ── LAYOUT ───────────────────────────────────────────────────
   Page background + flex column     → .main
   Content padding area              → .content        (24px padding; 14px up to 860px)
   Sticky topbar                     → .topbar
   Topbar left (hamburger + title)   → .topbar-left
   Topbar action buttons             → .topbar-actions
   Notification bell slot            → .topbar-bell
   Page title text                   → .page-title
   Hamburger button                  → .hamburger
   Back link                         → .back-link

   ── SIDEBAR ──────────────────────────────────────────────────
   Sidebar shell                     → .sidebar        (injected by nav.js)
   Brand area (same height as topbar) → .sidebar-brand  (height: var(--topbar-height))
   Nav list                          → .sidebar-nav
   Nav section heading               → .nav-section
   Nav item link                     → .nav-item
   Nav icon wrapper                  → .nav-icon
   Sidebar footer                    → .sidebar-footer
   Mobile backdrop                   → .sidebar-overlay
   User info row                     → .user-info
   User avatar circle                → .user-avatar
   User name                         → .user-name
   User role                         → .user-role
   Sign out button                   → .btn-logout
   Collapsed icon rail (narrow desktop) → .sidebar-collapsed  (added by nav.js ResizeObserver)
   Hover overlay expand              → .sidebar-hover         (added on mouseenter)

   ── BUTTONS ──────────────────────────────────────────────────
   Base button                       → .btn             (always required)
   ⛔ DEPRECATED — primary           → .btn.btn-primary  DO NOT USE — use .action-btn.action-btn--primary
   Ghost (subtle border)             → .btn.btn-ghost
   ⛔ DEPRECATED — danger            → .btn.btn-danger   DO NOT USE — use .action-btn.action-btn--warning
   ⛔ DEPRECATED — success           → .btn.btn-success  DO NOT USE — use .action-btn.action-btn--primary
   Small size modifier               → .btn.btn-sm      (pair with .btn-ghost only)
   Small oval pill, 2-state          → .pill-primary    (.pill-primary--on for active — same
                                        border/hover language as .action-btn, smaller padding)

   ── BADGES ───────────────────────────────────────────────────
   Base badge                        → .badge           (always required)
   Active (green)                    → .badge.badge-active
   Inactive (red)                    → .badge.badge-inactive
   Prospect (grey)                   → .badge.badge-prospect
   Custom badge colours              → define in page style, class name: .badge-{meaning}
   Scheduled (hatched amber)         → .badge.badge-scheduled
   Loaded (info blue)                → .badge.badge-loaded
   Pending, submitted, partial (amber) → .badge plus the badge-pending class (also the "Erasure pending" tag on Users; add .no-dot to hide the dot)
   Role (Admin, Owner, Supervisor, Sales, Purchase, Store) → .badge plus badge-admin, badge-owner, badge-supervisor, badge-sales, badge-purchase or
                                        badge-store (colours are the --role-* tokens)
   Received, approved, dispatched, ready (green) and overdue, rejected (red) → .badge plus the badge-* class of that name
   The pill is one size (0.75rem, 700) for every badge class; a colour class never sets size, weight or padding

   ── DISPATCH ────────────────────────────────────────────────
   Urgency group wrapper             → .dispatch-group
   Group header (uses shared)        → .grp-header (same as customerpo)
   Group body (grid or list)         → .dispatch-group-body
   Packing slip card (Load Today)    → .slip-card, .slip-card-header, .slip-card-items
   Ref pills (DIS + PO)             → .slip-refs > .slip-ref-pill
   Transit card (In Transit)         → .transit-card
   Compact list row (Scheduled)      → .dispatch-compact-row
   Transit days counter              → .transit-days, .transit-days--stale
   Dispatcher mobile card            → .dispatcher-load-card
   Dispatcher greeting               → .dispatcher-greeting, .dispatcher-headline
   Warning action button             → .action-btn--warning  (amber delete)

   ── ROW ACTIONS AND PEOPLE LIST (Users) ──────────────────────
   44px high on a phone / touch    → automatic on every .action-btn and .segment-btn (no modifier)
   Icon-only button                → .action-btn.action-btn--icon  (borderless mode: + .action-btn--bare; a red icon is BX.icons.trashDanger, tinted in the icon itself)
   Row of icon buttons             → .user-actions
   Name cell: name + tag           → .user-name-line, .user-link, .u-username
   Tag whose text is in ink colour → .badge-ink  (added to a status badge: tint and border keep the status, the text is --text-primary; the Users "Locked" and "Erasure pending" tags)
   Dialog icon close button        → .modal-close.modal-close--icon;   dialog that scrolls its body → .modal--scroll;   dialog text → .modal-text(--muted)
   Group header parts (Users)      → .grp-header, .grp-dot, .grp-label, .grp-count, .grp-line, .grp-toggle (+ --warning on dot and label)
   Muted (inactive/deleted) row    → .u-off on the tr / .list-item
   Fixed-column list table         → .table.users-table
   Keep a phrase on one line       → .nowrap  (one rule shared with .col-num, .fl-count)

   ── A PERSON'S PAGE (userview.html): no class of its own ───────
   Header                          → .detail-hdr > .detail-hdr-top > .detail-name-row (.user-avatar, .detail-name, .badge) + .detail-meta (.detail-meta-id, .detail-meta-sep)
   Buttons under the header        → .action-bar > .action-btn
   Set-up progress / "what changes" → .brief > .brief-line  (.brief-dim, .brief-warn, .brief-sep)
   Sections                        → .detail-cols.detail-cols--flat > .detail-main > .card (.card-head, .card-title, .card-body);  side list → .detail-aside > .detail-sh
   Rows (factories, channels, sessions) → .list-item.list-item--flat.list-item--static (.list-item-head, -title, -meta, -body, -text); tick box rows → .checkbox-label
   Segment / tick box at 44px on a phone → automatic on .segment-btn;  .list-item--flat .checkbox-label has its own 44px rule
   State word beside a label       → .opt .text-sm .col-warning
   Invalid field                   → input / select / textarea [aria-invalid="true"] inside .modal-body  (red border; the rule is in the Forms section)

   ── PAGE FEEDBACK ────────────────────────────────────────────
   Page-level alert bar              → .page-alert      (collapsed when empty, never display:none: it is a live region)
   Success alert                     → .page-alert.page-alert-success
   Error alert                       → .page-alert.page-alert-error
   Warning alert                     → .page-alert.page-alert-warning
   Info alert                        → .page-alert.page-alert-info
   Alert text / dismiss control      → .page-alert-msg / .page-alert-close   (written by toast.js, not by a page)
   Alert copy fixed to the viewport  → .page-alert.page-alert-fixed          (toast.js, when #pageAlert is scrolled out of view)
   Toast (corner notice)             → .bx-toast  (+ --success / --error / --warning / --info), parts .bx-toast-icon / -msg / -close / -actions / -btn.
                                       Built by BX.showToast in toast.js; its rules are INJECTED by toast.js (self-contained component, semantic tokens only), not in this file.
                                       Never write a .bx-toast element in a page.
   Payment cue (one line per party)  → .pay-cue  (+ .col-danger for Due)
   Text for assistive tech only      → .sr-only
   Dialog error message              → .m-alert.m-alert-error  (.show to display)
   Empty state centre block          → .empty
   Empty state title                 → .empty-title
   Empty state subtitle              → .empty-sub

   ── FILTERS ──────────────────────────────────────────────────
   Filter bar wrapper                → .filters
   Search input                      → .search-input
   Filter dropdown                   → .filter-select
   Compact inline label+select       → .groupby-row, .groupby-label
                                        (small "Group by" toggle inside a
                                        section body, not a list-page filter
                                        bar — .filter-select is sized for
                                        that, this is a single 2-4-option
                                        toggle. Promoted 2026-07-29 from
                                        rmview.html's page-local .rv-inv-mode,
                                        which predates this and is unchanged.)

   ── TEXT COLOR UTILITIES ──────────────────────────────────────
   Danger text (error/overdue)       → .col-danger
   Warning text (due today/at-risk)  → .col-warning
   Info text (brand accent)          → .col-info
   Success text (approved/on-target) → .col-success

   ── TEXT SIZE UTILITIES ──────────────────────────────────────
   The entire type scale is exactly 3 sizes, each with exactly ONE
   allowed weight — this is a pairing rule, not a floor. Never set
   font-size/font-weight ad hoc in a page style; use these.
   12px, always weight 700           → .text-xs   (0.75rem)
   14px, always weight 500           → .text-sm   (0.875rem)
   16px, base body weight            → .text-base (1rem)
   font-weight:600 does not exist in this font stack (silently
   renders 700) — never use it, anywhere, at any size.

   ── STATS ROW ────────────────────────────────────────────────
   Stats grid container              → .stats-row       (internals owned by statcards.js)

   ── OTIF HEATMAP ─────────────────────────────────────────────
   Heatmap panel (hidden by default) → .hm-panel        (toggle .open to show)
   Cell row                          → .hm-row          (inline-flex, gap: 3px)
   Base cell                         → .hm-cell         (40px × 28px, mono 12px)
   Cell — good (≥80%)                → .hm-cell.hm-cell--good
   Cell — mid (50–79%)               → .hm-cell.hm-cell--mid
   Cell — bad (<50%)                 → .hm-cell.hm-cell--bad
   Cell — empty (no data)            → .hm-cell.hm-cell--empty
   Month label row                   → .hm-months       (inline-flex, gap: 3px)
   Month label                       → .hm-mo           (40px wide, centred)
   Average text                      → .hm-avgs
   Empty note                        → .hm-note
   Mobile half-year nav              → .hm-nav          (segmented toggle)
   Nav button                        → .hm-nav-btn
   Active nav button                 → .hm-nav-btn.hm-nav-btn--active

   ── MODALS ───────────────────────────────────────────────────
   Modal backdrop                    → .modal-overlay
   Modal card                        → .modal
   Modal header                      → .modal-header
   Modal title                       → .modal-title
   Modal close button                → .modal-close
   Modal body                        → .modal-body
   Modal footer                      → .modal-footer
   Modal close drawn as an icon      → .modal-close.modal-close--icon  (44px target; xmark icon inside)
   Dialog body text                  → .modal-text, .modal-text--muted

   ── FORMS — FIELDS ───────────────────────────────────────────
   Field label (auto-applied)        → label            (uppercase, 11px, muted)
   Input / select / textarea         → input, select, textarea (auto-applied)
   Checkbox / radio                  → input[type=checkbox], input[type=radio]
   Checkbox + label as one unit      → .checkbox-label  (flex row, body text style)
   Multi-select chip group wrapper   → .checkbox-group
   Chip/pill checkbox item           → .type-chip
   Field wrapper with bottom margin  → .fg              (margin-bottom: 13px)
   2-column field grid               → .fr
   3-column field grid               → .fr-3
   Optional label modifier           → .opt             (inline in <label>: <span class="opt">optional</span>)
   Hint text below a field           → .form-hint       (block below <input>, 11px muted)
   Section divider heading           → .form-section    (uppercase brand-primary, with border)
   Form-level alert                  → .form-alert      (hidden by default, Pattern B)
   Form alert error state            → .form-alert.form-alert-error
   Form alert success state          → .form-alert.form-alert-success

   ── FORMS — FP-OVERLAY ───────────────────────────────────────
   Form overlay shell                → .fp-overlay      (fixed fullscreen mobile, centred modal desktop)

   Option A — standalone form pages (public/forms/*.html):
     Single header + footer, visible both mobile + desktop.
     Used by: customer-form, sampleform, customerpo-form,
              supplierpo-form, supplier-form, rm-receipt-form, rm-form
     Header                          → .fp-head
     Module label (above title)      → .fp-label
     Form title                      → .fp-title        (also used in Option B mobile header)
     Form subtitle / ID display      → .fp-sub
     Body (scrollable)               → .fp-body
     Footer (Cancel + Save)          → .fp-foot

   Option B — inline overlays embedded in list/view pages:
     Desktop: title-bar card. Mobile: header strip. Two elements, one visible per breakpoint.
     Used by: inline overlays in customerview, supplierview, dispatchview, etc.
     Desktop title bar               → .fp-title-bar
     Desktop title text              → .fp-title-bar-text
     Mobile header strip             → .fp-header
     Mobile back button              → .fp-back
     Body                            → .fp-body         (same class both options)
     Footer                          → .fp-footer
   Close button (both options)       → .fp-close

   ── FORMS — LINE CARDS ───────────────────────────────────────
   Removable line item card          → .line-card       (supplierpo, customerpo, rm-receipt)
   Line card header row              → .line-card-header
   Line number label                 → .line-num

   ── INFO CARDS ───────────────────────────────────────────────
   Overview info card                → .info-card       (customerview, customerpoview, sampleview)
   Card section heading              → .info-card-title
   Label + value row                 → .info-row
   Row label                         → .info-label
   Row value                         → .info-value

   ── TABS ─────────────────────────────────────────────────────
   Tab bar wrapper                   → .tabs
   Individual tab                    → .tab
   Active tab                        → .tab.active
   Tab content panel                 → .tab-panel
   Active panel                      → .tab-panel.active

   ── TIMELINE ─────────────────────────────────────────────────
   Timeline wrapper                  → .timeline
   Timeline item (date + card)       → .tl-item
   Date column                       → .tl-date-col
   Date text                         → .tl-date
   Created-by text                   → .tl-by
   Activity card                     → .tl-card
   Auto-logged card variant          → .tl-card.tl-card-auto
   Dot connector                     → .tl-dot
   Activity type label               → .tl-type
   Activity notes                    → .tl-notes
   Metadata row                      → .tl-meta
   Edited notice                     → .tl-edited

   ── MOVEMENT FEED ────────────────────────────────────────────
   Movement box container            → .mv-box           (card with heading + chips + entries)
   Heading                           → .mv-heading       (1rem/700)
   Filter chip row                   → .mv-chips         (flex wrap)
   Square filter chip                → .mv-chip          (6px radius, .mv-chip--on for active)
   Day group header                  → .mv-grp           (dot + label + count + line + chevron)
   Month/year group header           → .mv-mo            (muted, no dot)
   Movement entry                    → .mv-entry         (.mv-entry-top + .mv-entry-bot)
   Entry dot                         → .mv-dot           (8px, --in/--out/--gate/--adj)
   Verb text                         → .mv-verb          (sans, --in/--out/--gate/--adj)
   Item code                         → .mv-code          (mono, brand, clickable)
   Quantity                          → .mv-qty           (mono, right-aligned)
   Time/date                         → .mv-time          (mono, placeholder)
   Context text                      → .mv-ctx           (sans, muted)
   Empty state                       → .mv-empty
   Verb-paired context (inline)      → .mv-paired        (added 2026-07-19, sans, muted, sits right after .mv-verb)
   Reversal marker                   → .mv-reversed      (added 2026-07-19, sans, muted, was inline style)
   Cluster expand toggle             → .mv-cluster-toggle (added 2026-07-19, no-chrome button, matches .mv-qty's mono/right)
   Cluster children wrapper          → .mv-cluster-children (added 2026-07-19, indented list of the cluster's individual rows)
   Cluster child row                 → .mv-cluster-child (added 2026-07-19, .mv-code + .mv-qty only, no verb/time/ctx — those are already on the parent summary row)

   ── FILTER DROPDOWN ──────────────────────────────────────────
   Filter wrapper (position:relative) → .filter-wrap
   Dropdown panel                    → .filter-panel      (.open to show)
   Section label                     → .filter-lbl        (0.75rem uppercase)
   Chip container                    → .filter-opts
   Footer (Apply + Clear)            → .filter-foot
   Active tag row                    → .filter-tags
   Single tag pill                   → .filter-tag        (brand subtle, dismissable)
   Tag dismiss ×                     → .filter-tag-x

   ── PROMPT OVERLAYS ──────────────────────────────────────────
   Post-action prompt backdrop       → .prompt-overlay
   Prompt card                       → .prompt-box
   Icon                              → .prompt-icon
   Title                             → .prompt-title
   Subtitle                          → .prompt-sub
   Option button                     → .prompt-option
   Dismiss button                    → .prompt-dismiss

   ── ACTION ROW (mobile only) ─────────────────────────────────
   Mobile action buttons below topbar → .action-row     (hidden desktop, shown mobile on detail pages)

   ── DO NOT USE ───────────────────────────────────────────────
   .optional        → use .opt instead
   .form-row        → use .fr instead
   .form-group      → use .fg instead
   .form-section-title → use .form-section instead
   .field-note      → use .form-hint instead
   Old tokens (--navy, --gold, --white, --grey, --error, --success, --warning)
                    → see backwards-compat aliases below — do not use in new code


   ── FINISHED GOODS MODULE ─────────────────────────────────────
   FG list table                     → .table.fg-table  (shared .table base + fg-specific overrides)
   FG ID column cell (th + td)       → .fg-col-id       (hidden on mobile)
   Specification column cell (th+td) → .fg-col-specs    (hidden on mobile)
   Status row variants               → .fg-row--draft, .fg-row--inactive, .fg-row--discontinued
   Product cell                      → .fg-product-cell, .fg-product-name, .fg-product-customer
   Mobile-collapsed sub-row          → .fg-product-mobile-sub, .fg-mobile-id, .fg-mobile-sep
   BOM version label                 → .fg-bom-version (.--approved, .--pending, .--draft)
   Status pills                      → .fg-pills-cell (container) + shared .badge.badge-nobom / .badge-draft
   Customer filter bar               → .fg-customer-bar (.fg-customer-bar.visible)

   FG detail 60/40 split wrapper     → .fgv-wrap
   FG detail left column             → .fgv-left
   FG detail right column (sticky)   → .fgv-right
   FG identity display ID            → .fgv-display-id
   FG identity name                  → .fgv-name
   FG stock bar track                → .fgv-stock-bar-track, .fgv-stock-seg
   FG stock tiles                    → .fgv-stock-tiles, .fgv-stock-tile
   Collapsible section               → .fgv-collapsible (.is-collapsed), .fgv-coll-header, .fgv-coll-body
   BOM layer stack                   → .fgv-layer-stack, .fgv-layer-row (.fgv-layer-row--liner, --flute)
   BOM layer grid (new)              → .bom-grid, .bom-row (.bom-row--liner, --flute), .bom-badge, .bom-paper, .bom-gsm, .bom-rm
   Route flow (serpentine cards)     → .route-flow, .route-card, .route-arrow
   DSP grid (demand/supply/prod)     → .fgv-dsp
   Manufacturing params              → .fgv-mfg
   FG sticky aside                   → .fgv-aside
   Movements label                   → .fgv-mvt-label
   Movements action buttons          → .fgv-mvt-actions, .fgv-mvt-action-btn (.is-active)
   Movements filter pills            → .fgv-mvt-filters, .fgv-mvt-filter-pill (.is-active)
   Movements table                   → .fgv-mvt-table-wrap, .fgv-mvt-row, .fgv-mvt-col-headers
   Movements pagination              → .fgv-mvt-pagination, .fgv-pg-btn, .fgv-pg-count

   FG form toggle group              → .fgf-toggle-group, .fgf-toggle-btn (.active)
   FG form layer builder             → .fgf-layer-builder, .fgf-layer-row (.fgf-layer-liner, .fgf-layer-flute)
   FG form layer badge               → .fgf-layer-badge
   FG form calc estimates block      → .fgf-calc-block, .fgf-calc-grid
   FG form production preview        → .fgf-preview-block
   FG form locked spec summary       → .fgf-locked-summary
   FG form unit pair                 → .fgf-unit-pair
   FG form route table               → .fgf-route-table
   FG form tooling table             → .fgf-tooling-table
   ============================================================ */

/* ============================================================
   PART A — SEMANTIC TOKENS
   Maps role names to primitive values from tokens.css.
   This is the only place primitives are referenced.
   ============================================================ */

:root {

    /* ── Surfaces ─────────────────────────────────────────────
       What it does: defines the background hierarchy of the app */
    --surface-page:      var(--color-ink-900);   /* full page bg */
    --surface-card:      var(--color-ink-700);   /* cards, sidebar, topbar, modals */
    --surface-raised:    var(--color-ink-500);   /* avatar bg, active hover, input focus tint */
    --surface-border:    var(--color-ink-border);/* all dividers, card borders, input borders */
    --surface-overlay:   rgba(0,0,0,0.6);        /* modal/drawer backdrop — not a brand colour */

    /* ── Brand ────────────────────────────────────────────────
       What it does: the visual identity accent used for CTAs,
       active states, and key highlights throughout the UI.
       To rebrand: point these to new primitives only.          */
    --brand-primary:     var(--color-blue-dark);        /* #46A6EF */
    --brand-secondary:   var(--color-blue-dark-hover);  /* #5BB9FF */
    --brand-subtle:      var(--color-blue-dark-a08);
    --brand-subtle-hover:var(--color-blue-dark-a14);
    --brand-border:      var(--color-blue-dark-a20);

    /* ── Text ─────────────────────────────────────────────────
       Hierarchy: primary → secondary → muted                  */
    --text-primary:      var(--color-text-dk-1);  /* #F3F2EE */
    --text-secondary:    var(--color-text-dk-2);  /* #C6C4C0 */
    --text-muted:        var(--color-text-dk-3);  /* #918F8A */

    /* ── Status ───────────────────────────────────────────────
       Tied to meaning — these rarely change on brand rebrand   */
    --status-error:          var(--color-red-dk);        /* #F66D67 */
    --status-error-bg:       var(--color-red-dk-a14);
    --status-error-border:   var(--color-red-dk-a28);
    --status-success:        var(--color-green-dk);      /* #4EBE7D */
    --status-success-bg:     var(--color-green-dk-a14);
    --status-success-border: var(--color-green-dk-a28);
    --status-warning:        var(--color-amber-dk);      /* #F4A437 */
    --status-warning-bg:     var(--color-amber-dk-a14);
    --status-warning-border: var(--color-amber-dk-a30);
    --status-info:           var(--color-info-dk);       /* #43B2E1 */
    --status-info-bg:        var(--color-info-dk-a14);
    /* Status TEXT (Ra 2026-10-21): the colour for message text drawn on a status tint. Dark theme: the status colour itself already passes 4.5:1. */
    --status-success-text:   var(--status-success);
    --status-error-text:     var(--status-error);
    --status-warning-text:   var(--status-warning);
    --status-info-text:      var(--status-info);
    --status-sample-text:    var(--color-purple-500);
    --status-info-border:    var(--color-info-dk-a28);  /* same strength as the success and error borders (a28); the a14 tint is the background */
    --role-owner:            var(--color-role-owner-dk);
    --role-owner-bg:         var(--color-role-owner-dk-a14);
    --role-sales:            var(--color-role-sales-dk);
    --role-sales-bg:         var(--color-role-sales-dk-a14);
    --role-purchase:         var(--color-role-purchase-dk);
    --role-purchase-bg:      var(--color-role-purchase-dk-a14);
    --role-store:            var(--color-role-store-dk);
    --role-store-bg:         var(--color-role-store-dk-a14);
    --role-supervisor:       var(--color-role-supervisor-dk);
    --role-supervisor-bg:    var(--color-role-supervisor-dk-a14);

    /* ── Entity-type chips (shared by renderTimeline .tl2-ent + activity-form
       .act-ent/.act-picker-badge). Dedicated per-type categorical palette —
       NOT the semantic status tokens. Dark values are dark-mode-tuned: subtle
       tinted bg + light legible text (all text >=7:1 on the dark surface).
       Light values live in the light block below (activity form's hues). */
    --ent-sample-bg:   rgba(139,124,246,0.15); --ent-sample-fg: #b3a8f5;
    --ent-po-bg:       rgba(110,140,185,0.16);  --ent-po-fg:     #93a7c9;  /* muted slate-blue (desaturated, cyan lean removed) — a considered blue, not the AI azure */
    --ent-receipt-bg:  rgba(60,160,110,0.15);  --ent-receipt-fg:#7fc4a0;
    --ent-dispatch-bg: rgba(210,160,60,0.15);  --ent-dispatch-fg:#e0b56b;
    --ent-fg-bg:       rgba(120,170,70,0.15);  --ent-fg-fg:     #a9cf7f;
    --ent-rm-bg:       rgba(170,130,90,0.15);  --ent-rm-fg:     #c1a884;

    /* ── Typography ───────────────────────────────────────────*/
    --font-ui:           var(--font-sans);    /* all body text, labels, buttons */
    --font-data:         var(--font-mono);    /* IDs, codes, numbers, display values */
    --font-heading:      var(--font-display); /* page titles, brand display moments */

    /* ── Surfaces — extended ──────────────────────────────────── */
    --surface-sunk:          var(--color-ink-900);    /* table headers, input bg (same as page in dark) */
    --surface-sidebar:       var(--color-ink-700);    /* sidebar — stays dark in both themes */
    --surface-border-strong: var(--surface-border);   /* hover/focus border (same as border in dark) */

    /* ── Text — extended ─────────────────────────────────────── */
    /* Placeholder text must reach 4.5:1 (Ra 2026-10-21): it is the muted colour. The old value (--color-text-dk-4, #65635E) measured 2.9:1. */
    --text-placeholder:      var(--text-muted);

    /* ── Tint overlays — theme-aware ─────────────────────────── */
    /* Dark: white rgba overlays lighten the navy surface.        */
    /* Light: resolved to SOLID colours (not rgba) — see below.   */
    --tint-hover:        rgba(255,255,255,0.04);  /* input bg, nav hover, ghost btn */
    --tint-hover-strong: rgba(255,255,255,0.09);  /* ghost btn hover */
    --tint-subtle:       rgba(255,255,255,0.03);  /* line-card bg, faint tint */
    --tint-border:       rgba(255,255,255,0.10);  /* inner form borders */
    --tint-border-soft:  rgba(255,255,255,0.07);  /* softer inner borders */

    /* ── Layout ───────────────────────────────────────────────*/
    --sidebar-width:     240px;
    --topbar-height:     56px;

    /* ── Ghost button ─────────────────────────────────────────*/
    --ghost-bg:          var(--tint-hover);
    --ghost-bg-hover:    var(--tint-hover-strong);
    --nav-hover-bg:      rgba(255,255,255,0.09);  /* visible on #181B1F sidebar */

    /* ── Density ─────────────────────────────────────────────
       Controls table/row/spacing rhythm globally.             */
    --row-h:     36px;   /* table row height */
    --cell-py:    8px;   /* table cell vertical padding */
    --cell-px:   12px;   /* table cell horizontal padding */
    --list-px:   16px;   /* list-card horizontal padding — the mobile
                            counterpart of --cell-px */
    --stack:     14px;   /* vertical gap between sibling components */
    --section:   24px;   /* gap between major page sections */

    /* ── Radius ──────────────────────────────────────────────
       Never use raw border-radius numbers in new code.        */
    --radius-sm:  4px;   /* inputs, chips, small inline elements */
    --radius:     6px;   /* buttons, badges, filter fields */
    --radius-lg: 10px;   /* cards, modals, overlays */

    /* ── Z-index stack ───────────────────────────────────────
       Existing hardcoded values match exactly.                */
    --z-sticky:   50;   /* topbar, sticky fp-head */
    --z-nav:     100;   /* sidebar */
    --z-overlay: 200;   /* .modal-overlay */
    --z-search:  250;   /* search palette — above modals, below fp-overlay */
    --z-fp:      300;   /* .fp-overlay — fullscreen forms */
    --z-toast:   400;   /* toast — always above everything */

}


/*
 * ── Backwards-compatibility aliases ───────────────────────────
 * Self-injecting components (samplecard.js, dispatchcard.js,
 * rmreceiptcard.js, statcards.js) use old token names
 * in their injected <style> blocks and inline style strings.
 * (toast.js is NOT in this list: its injected sheet uses semantic tokens only.)
 * These aliases map old names → semantic tokens so they continue
 * to work on converted pages until Phase 3 updates each component.
 *
 * PHASE 3: once all components are updated, delete this block.
 * DO NOT use these names in any page style or new component.
 * ─────────────────────────────────────────────────────────────
 */
:root {
    --navy:        var(--surface-page);
    --navy-mid:    var(--surface-card);
    --navy-light:  var(--surface-raised);
    --navy-border: var(--surface-border);
    --gold:        var(--brand-primary);
    --gold-light:  var(--brand-secondary);
    --white:       var(--text-primary);
    --grey:        var(--text-muted);
    --text:        var(--text-secondary);
    --error:       var(--status-error);
    --success:     var(--status-success);
    --warning:     var(--status-warning);
    --font-body:   var(--font-ui);
    --sidebar-w:   var(--sidebar-width);
    --topbar-h:    var(--topbar-height);
}




/* ── LIGHT THEME ─────────────────────────────────────────────
   Semantic tokens remapped to light palette primitives.
   Applied when <html data-theme="light">.

   Switch (nav.js):
     const pref = localStorage.getItem('bf-theme') || 'dark';
     document.documentElement.setAttribute('data-theme', pref);
   Toggle:
     const next = current === 'dark' ? 'light' : 'dark';
     document.documentElement.setAttribute('data-theme', next);
     localStorage.setItem('bf-theme', next);

   KEY DESIGN CHOICE: Tint tokens resolve to SOLID colours in
   light mode, not rgba overlays. Overlays are invisible on white.
   ─────────────────────────────────────────────────────────── */

[data-theme="light"] {

    /* ── Surfaces ────────────────────────────────────────────── */
    --surface-page:          var(--color-paper-page);
    --surface-card:          var(--color-paper-card);
    --surface-sunk:          var(--color-paper-sunk);
    --surface-raised:        var(--color-paper-tint);
    --surface-border:        var(--color-paper-border);
    --surface-border-strong: var(--color-paper-border-strong);
    --surface-sidebar:       var(--color-paper-sunk); /* warm grey, not dark */

    /* ── Ghost button — white bg lifts off page ──────────────── */
    --ghost-bg:          var(--color-paper-card);
    --ghost-bg-hover:    var(--color-paper-sunk);
    --nav-hover-bg:      var(--color-paper-card);  /* #FFFFFF on #F6F4F1 sidebar */

    /* ── Brand — exact handoff #006EB8 ─────────────────────────── */
    --brand-primary:      var(--color-blue-light);        /* #006EB8 */
    --brand-secondary:    var(--color-blue-light-hover);  /* #005BAA */
    --brand-subtle:       var(--color-blue-light-a06);
    --brand-subtle-hover: var(--color-blue-light-a10);
    --brand-border:       var(--color-blue-light-a22);

    /* ── Text ────────────────────────────────────────────────── */
    --text-primary:     var(--color-carbon-900);
    --text-secondary:   var(--color-carbon-700);
    --text-muted:       var(--color-carbon-500);
    /* Placeholder text must reach 4.5:1 (Ra 2026-10-21): it is the muted colour. The old value (--color-carbon-400, #96989C) measured 2.9:1. */
    --text-placeholder: var(--text-muted);

    /* ── Status — exact handoff oklch conversions ──────────────── */
    --status-error:          var(--color-red-lt);        /* #C53637 */
    --status-error-bg:       var(--color-red-lt-a10);
    --status-error-border:   var(--color-red-lt-a25);
    --status-success:        var(--color-green-lt);      /* #0D844C */
    --status-success-bg:     var(--color-green-lt-a10);
    --status-success-border: var(--color-green-lt-a25);
    --status-warning:        var(--color-amber-lt);      /* #D28500 */
    --status-warning-bg:     var(--color-amber-lt-a12);
    --status-warning-border: var(--color-amber-lt-a30);
    --status-info:           var(--color-info-lt);       /* #007CA5 */
    --status-info-bg:        var(--color-info-lt-a10);
    /* Status TEXT (Ra 2026-10-21): darker shades of the four status colours, for message text on its own tint. Each is at least 4.7:1 on the tint over the page, card and
       sunk surfaces (tests/test-status-text-contrast.js measures them); the plain status colours stay for icons, borders and large marks. */
    --status-success-text:   var(--color-green-lt-text);   /* #0B7241 */
    --status-error-text:     var(--color-red-lt-text);     /* #B53233 */
    --status-warning-text:   var(--color-amber-lt-text);   /* #935700 */
    --status-info-text:      var(--color-info-lt-text);    /* #006B8E */
    --status-sample-text:    var(--color-purple-lt-text);  /* #7A3FB5 */
    --status-info-border:    var(--color-info-lt-a25);
    --role-owner:            var(--color-role-owner-lt);
    --role-owner-bg:         var(--color-role-owner-lt-a10);
    --role-sales:            var(--color-role-sales-lt);
    --role-sales-bg:         var(--color-role-sales-lt-a10);
    --role-purchase:         var(--color-role-purchase-lt);
    --role-purchase-bg:      var(--color-role-purchase-lt-a10);
    --role-store:            var(--color-role-store-lt);
    --role-store-bg:         var(--color-role-store-lt-a10);
    --role-supervisor:       var(--color-role-supervisor-lt);
    --role-supervisor-bg:    var(--color-role-supervisor-lt-a10);

    /* ── Entity-type chips (light) — matches the activity form's established
       per-type hues; shared by renderTimeline + activity form (see dark block). */
    --ent-sample-bg:   #eeedfe; --ent-sample-fg: #5b4fc7;
    --ent-po-bg:       #e6f0fa; --ent-po-fg:     #1a6fb5;
    --ent-receipt-bg:  #e7f6ef; --ent-receipt-fg:#126846;
    --ent-dispatch-bg: #fdf4e0; --ent-dispatch-fg:#8a5e10;
    --ent-fg-bg:       #ecf4e0; --ent-fg-fg:     #3b7a12;
    --ent-rm-bg:       #f3ece2; --ent-rm-fg:     #6f4a17;

    /* ── Tints — SOLID colours, not rgba ─────────────────────── */
    --tint-hover:        var(--color-paper-sunk);
    --tint-hover-strong: var(--color-paper-tint);
    --tint-subtle:       var(--color-paper-sunk);
    --tint-border:       var(--color-paper-border);
    --tint-border-soft:  var(--color-paper-border);
}


/* ============================================================
   PART B — RESET
   ============================================================ */

*,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0; }


/* ============================================================
   PART C — SHARED COMPONENT CLASSES
   Rules:
   - Every value uses var(--semantic-token) — never a primitive,
     never a raw hex value.
   - Classes listed here appear on 2+ pages. Page-specific
     classes belong in that page's inline <style> block only.
   - To change how a component looks across all pages: edit here.
   - To override for one page: use a modifier class in that
     page's <style> block. Never redefine the base class.
   ============================================================ */


/* ── Body ─────────────────────────────────────────────────── */

body {
    font-family: var(--font-ui);
    background:  var(--surface-page);
    color:       var(--text-secondary);
    min-height:  100vh;
}
body.sidebar-open  { overflow: hidden; }
body.no-scroll     { overflow: hidden; }

/* ── Icons (global) ───────────────────────────────────────── */
/* Root-cause fix (2026-07-26): every BX.icons.* glyph is a raw inline
   <svg> with no wrapper (icons.js's add() registers plain markup, no
   shared class). An inline SVG's browser default is
   vertical-align:baseline, which reserves invisible descender space
   below the glyph — so any icon concatenated inline with text (a
   factory tag, a chevron, a badge) renders visibly low against that
   text everywhere, unless the one spot happens to have its own fix.
   Confirmed this was already independently rediscovered and patched
   ad hoc at least 7 times across this file and nav.js before this rule
   existed. vertical-align has no effect on flex items, so this is a
   no-op (not a behavior change) for every icon that already lives
   inside a `display:flex; align-items:center` container — .nav-item,
   .action-btn, etc. — those keep working exactly as before. It only
   changes anything for icons sitting as genuine inline content next to
   text, which is exactly the class of bug this fixes. */
svg { vertical-align: middle; }


/* ── Sidebar ──────────────────────────────────────────────── */

.sidebar {
    /* bg uses --surface-sidebar (dark in dark mode, sunk grey in light mode) */
    width:        var(--sidebar-width);
    background:   var(--surface-sidebar);
    border-right: 1px solid var(--surface-border);
    display:      flex;
    flex-direction: column;
    position:     fixed;
    top: 0; left: 0;
    height:       100dvh;
    z-index:      var(--z-nav);
    transition:   transform 0.3s, width 0.2s ease;
    overflow:     hidden; /* clip text during collapse */
}
.sidebar-brand {
    height:          var(--topbar-height); /* matches topbar exactly — same visual band */
    padding:         0 20px;
    border-bottom:   1px solid var(--surface-border);
    display:         flex;
    align-items:     center;
    justify-content: space-between;  /* brand-left ← → avatar */
    gap:             10px;
    flex-shrink:     0;
}
.sidebar-brand-left {
    display:     flex;
    align-items: center;
    gap:         10px;
    min-width:   0;
}
/* Right side of the sidebar brand row — holds the theme toggle only.
   User identity + factory switcher live in the main topbar instead
   (see .bf-factory-wrap / .user-avatar below), not here. */
.sidebar-brand-right {
    display:     flex;
    align-items: center;
    gap:         10px;
    flex-shrink: 0;
}
.brand-icon {
    width:       36px;
    height:      36px;
    display:     flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* No background — SVG mark is self-contained. Emoji placeholder removed. */
}
/* BOX-PENDING-NAV-BRAND: font-family/-size/-weight/letter-spacing brought in line with
   login.html's certified .gw-brand-name rule (var(--font-ui), 22px, 700, -0.02em) —
   read directly off the current login.html, not retyped from memory. Verified visually
   in the sidebar's own 240px-wide, 20px-padded row before shipping (see EVIDENCE.md) —
   this is a tighter context than the login page and was checked, not assumed to fit. */
.brand-text {
    font-family:    var(--font-ui);
    font-size:      22px;
    font-weight:    700;
    color:          var(--text-primary);
    letter-spacing: -0.02em;
}
.brand-text span { color: var(--brand-primary); }

.sidebar-nav {
    flex:       1;
    overflow-y: auto;
    padding:    10px 8px;   /* handoff: 10px 8px */
}
.nav-section {
    padding:        12px 10px 6px;
    display:        flex;
    align-items:    flex-end;
    font-size:      10px;
    font-weight:    700;
    letter-spacing: 1.2px;   /* handoff: 1.2px */
    text-transform: uppercase;
    color:          var(--text-muted);  /* label/meta tier — bumped from --text-placeholder (ink-4) for legible contrast on section headers */
    white-space:    nowrap;
    overflow:       hidden;
}
/* Role header (first section, replaces the old static 'Work' label) —
   brand-accented so it reads as identity, not a domain category, same
   token already used for .nav-item.active's icon. text-overflow added
   here (not on the base rule) since this is the one section header
   whose text is data-driven (the role name) rather than a fixed,
   known-safe label — a future long role name should truncate visibly
   with an ellipsis, not clip silently mid-word. */
.nav-section-role {
    color:          var(--brand-secondary);
    text-overflow:  ellipsis;
}
.nav-item {
    display:         flex;
    align-items:     center;
    gap:             10px;
    padding:         0 10px;  /* handoff: 0 10px */
    height:          40px;    /* keep 40px — handoff 30px, but touch target floor */
    color:           var(--text-muted);
    text-decoration: none;
    font-size:       14px;    /* handoff: 13px */
    transition:      background 0.1s, color 0.1s;
    border-radius:   var(--radius);
    position:        relative;
    white-space:     nowrap;
    overflow:        hidden;
}
.nav-item:hover {
    color:      var(--text-primary);
}
.nav-item.active {
    color:      var(--brand-secondary);   /* the darker blue: --brand-primary on the sidebar's own tint was 4.49:1 in the light theme */
    background: var(--brand-subtle);
    font-weight:500;
}
/* ── Nav badge — count pill on sidebar nav items (Approvals, Notifications) ── */
/* Handoff spec: soft-red bg, deep-red text. Exact match to .nav-badge in handoff. */
.nav-badge {
    margin-left:  auto;
    font-size:    10px;
    font-weight:  700;
    font-family:  var(--font-data);
    padding:      1px 5px;
    border-radius:9px;
    background:   var(--status-error-bg);    /* --err-soft equivalent: 14% alpha red */
    color:        var(--status-error-text);  /* solid red number; the -text shade reaches 4.5:1 on the tint in the light theme */
    min-width:    18px;
    text-align:   center;
}
/* ── Topbar bell badge — absolute dot with count ─────────── */
#notifBadge {
    position:    absolute;
    top:         -3px;
    right:       -3px;
    min-width:   16px;
    height:      16px;
    border-radius:10px;
    background:  var(--status-error-bg);
    color:       var(--status-error-text);
    font-family: var(--font-data);
    font-size:   9px;
    font-weight: 700;
    padding:     0 4px;
    border:      2px solid var(--surface-card);
    display:     none;          /* shown via JS when count > 0 */
    align-items: center;
    justify-content: center;
    line-height: 1;
    box-sizing:  border-box;
}
/* Active left bar — inset like handoff (not full-height border-left) */
.nav-item.active::before {
    content:       '';
    position:      absolute;
    left:          0;
    top:           6px;
    bottom:        6px;
    width:         2px;
    background:    var(--brand-primary);
    border-radius: 0 2px 2px 0;   /* handoff exact: flat at sidebar edge, rounded inward */
}
.nav-icon {
    width:       16px;   /* handoff: 16px */
    height:      16px;
    flex-shrink: 0;
    display:     flex;
    align-items: center;
    justify-content: center;
}
/* SVG icons inherit currentColor from .nav-item — colour is automatic.
   .nav-item        → var(--text-muted)
   .nav-item:hover  → var(--text-primary)
   .nav-item.active → var(--brand-primary)
   Design firm: swap path data in NAV_CONFIG in nav.js. */
.nav-icon svg {
    width:   16px;   /* handoff: 16px */
    height:  16px;
    display: block;
}
.sidebar-footer {
    padding:        8px;
    border-top:     1px solid var(--surface-border);
    flex-shrink:    0;
}
.sidebar-overlay {
    display:    none;
    position:   fixed;
    inset:      0;
    background: var(--surface-overlay);
    z-index:    99;
}
.sidebar-overlay.show { display: block; }

/* ── Sidebar collapsed — narrow desktop ──────────────────────
   Triggered by nav.js ResizeObserver when .main width < 700px.
   Sidebar shrinks to 48px icon rail. On hover expands as overlay.
   Works on every page — nav.js injects sidebar on all pages.    */

.sidebar-collapsed {
    width: 48px;
}

/* Hide text in collapsed state. User avatar lives in the main topbar now,
   not the sidebar, so it's no longer part of this list. */
.sidebar-collapsed .brand-text,
.sidebar-collapsed #bf-theme-btn,
.sidebar-collapsed .signout-label { display: none; }
.sidebar-collapsed .sidebar-brand { justify-content: center; }
.sidebar-collapsed .sidebar-brand-left { gap: 0; }

/* Section labels in collapsed state — keep same vertical space as expanded
   so icons don't shift when sidebar hover-expands on touch devices.
   font-size:0 hides text. min-height locks total height to match expanded
   (12px pad-top + ~13px content + 6px pad-bottom = 31px). Without this,
   collapsed sections are 18px vs expanded 31px → 13px shift per section
   → cumulative misalignment causes wrong item under cursor on hover-expand. */
.sidebar-collapsed .nav-section {
    font-size:        0;
    align-items:      center;
    padding:          12px 0 6px;
    min-height:       31px;     /* matches expanded rendered height exactly */
    box-sizing:       border-box;
    position:         relative;
    overflow:         visible;
}
.sidebar-collapsed .nav-section::after {
    content:    '';
    position:   absolute;
    left:       8px;
    right:      8px;
    top:        50%;
    height:     1px;
    background: var(--surface-border);
    transform:  translateY(-50%);
}

/* Centre brand icon in collapsed rail */
.sidebar-collapsed .sidebar-brand { padding: 0; gap: 0; }
.sidebar-collapsed .brand-icon { width: 28px; height: 28px; }
.sidebar-collapsed .brand-icon svg { width: 22px; height: 22px; }

/* Centre nav items — icon only. font-size:0 collapses bare text nodes.
   .nav-icon has explicit width/height so it stays visible.
   No height override — inherits 40px from base .nav-item rule. */
.sidebar-collapsed .nav-item {
    padding:          0;
    justify-content:  center;
    gap:              0;
    font-size:        0;
    border-radius:    0;
}
/* Hide active ::before bar in icon-rail mode */
.sidebar-collapsed .nav-item.active::before { display: none; }
/* Collapsed icon size — 22px gives SVGs more room and reduces
   visual inconsistency between paths with different optical weights */
.sidebar-collapsed .nav-icon {
    width:  22px;
    height: 22px;
}
.sidebar-collapsed .nav-icon svg {
    width:  22px;
    height: 22px;
}

/* Centre signout icon in collapsed footer */
.sidebar-collapsed .sidebar-footer {
    padding:     8px 0;
    align-items: center;
}
.sidebar-collapsed .btn-signout {
    padding:         0;
    justify-content: center;
    gap:             0;
    font-size:       0;
    border-radius:   0;
}

/* NOTE: .main margin-left on collapse is managed by nav.js ResizeObserver
   via body.sidebar-is-collapsed class — not CSS sibling selector — because
   .sidebar is inside #bf-sidebar-mount, not a direct DOM sibling of .main. */
body.sidebar-is-collapsed .main { margin-left: 48px; }

/* ── Sidebar hover expand — overlay on top of content ────────
   Content does not move. Sidebar expands over it.              */
.sidebar-collapsed.sidebar-hover {
    width:      var(--sidebar-width);
    position:   fixed;
    z-index:    101; /* above topbar z-index:50, below modal z-index:200 */
    box-shadow: 4px 0 24px rgba(0,0,0,0.25);
}

/* Restore all hidden text on hover */
.sidebar-collapsed.sidebar-hover .brand-text,
.sidebar-collapsed.sidebar-hover .signout-label { display: revert; }
/* #bf-theme-btn's base rule sets display:flex to centre its icon —
   revert would fall through to the UA button default (inline-block)
   and break that centring, so restore the real value explicitly. */
.sidebar-collapsed.sidebar-hover #bf-theme-btn { display: flex; }

/* Restore nav-section on hover — font-size and padding back, divider gone */
.sidebar-collapsed.sidebar-hover .nav-section {
    padding:     0 20px;
    font-size:   10px;
    align-items: flex-end;
}
.sidebar-collapsed.sidebar-hover .nav-section::after { display: none; }

.sidebar-collapsed.sidebar-hover .sidebar-brand {
    padding: 0 20px;
    justify-content: space-between;
    gap: 10px;
}
.sidebar-collapsed.sidebar-hover .sidebar-brand-left {
    gap: 10px;
}
.sidebar-collapsed.sidebar-hover .brand-icon { width: 36px; height: 36px; font-size: 18px; }
.sidebar-collapsed.sidebar-hover .nav-item {
    padding:         0 20px;
    justify-content: flex-start;
    gap:             10px;
    font-size:       14px;
}
.sidebar-collapsed.sidebar-hover .nav-icon { width: 18px; height: 18px; }
.sidebar-collapsed.sidebar-hover .sidebar-footer {
    padding:     8px;
    align-items: stretch;
}
.sidebar-collapsed.sidebar-hover .btn-signout {
    padding:         0 20px;
    justify-content: flex-start;
    gap:             10px;
    font-size:       14px;
    border-radius:   var(--radius);
}
.sidebar-collapsed.sidebar-hover .signout-label { display: inline; }


/* ── User info (sidebar footer) ───────────────────────────── */

.user-info {
    display:       flex;
    align-items:   center;
    gap:           10px;
    cursor:        pointer;
    border-radius: 8px;
    padding:       8px;
    margin:        -8px -8px 8px;
    transition:    background 0.15s;
}
.user-info:hover { background: var(--tint-hover); }

.user-avatar {
    width:           34px;
    height:          34px;
    background:      var(--surface-raised);
    border-radius:   50%;
    display:         flex;
    align-items:     center;
    justify-content: center;
    font-size:       14px;
    font-weight:     700;
    color:           var(--brand-primary);
    flex-shrink:     0;
}
.user-name { font-size: 14px; font-weight: 500; color: var(--text-primary); }
.user-role { font-size: 12px; color: var(--text-muted); }

.btn-logout {
    width:        100%;
    padding:      9px;
    background:   var(--status-error-bg);
    border:       1px solid var(--status-error-border);
    border-radius:6px;
    color:        var(--status-error-text);
    font-family:  var(--font-ui);
    font-size:    14px;
    cursor:       pointer;
    text-decoration: none;
    display:      block;
    text-align:   center;
}

/* ── Sign out — icon + label nav-style row (replaces .btn-logout on converted pages) ── */
.btn-signout {
    display:         flex;
    align-items:     center;
    gap:             10px;
    padding:         0 10px;
    height:          40px;
    color:           var(--text-muted);
    text-decoration: none;
    font-size:       14px;
    border-radius:   var(--radius);
    transition:      background 0.1s, color 0.1s;
    cursor:          pointer;
    white-space:     nowrap;
    overflow:        hidden;
}
.btn-signout:hover {
    color:      var(--text-primary);
}
.btn-signout:active {
    transform: scale(0.98);
}


/* ── Main layout ──────────────────────────────────────────── */

.main {
    margin-left:    var(--sidebar-width);
    display:        flex;
    flex-direction: column;
    min-width:      0;
}
.topbar {
    height:          var(--topbar-height); /* explicit — matches sidebar-brand exactly */
    padding:         0 20px;
    border-bottom:   1px solid var(--surface-border);
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    background:      var(--surface-card);
    position:        sticky;
    top:             0;
    z-index:         var(--z-sticky);
    gap:             8px;
}
.topbar-left {
    display:     flex;
    align-items: center;
    gap:         10px;
    min-width:   0;
    flex:        1;
}
/* Action buttons — sit between title and bell.
   Visibility controlled by page type (see mobile breakpoint below). */
.topbar-actions {
    display:     flex;
    gap:         8px;
    align-items: center;
    flex-shrink: 0;
}
/* Bell slot — always visible on all breakpoints.
   nav.js injects the notification bell here.
   Sits to the right of .topbar-actions. */
.topbar-bell {
    display:     flex;
    align-items: center;
    gap:         8px;
    flex-shrink: 0;
}
.page-title {
    font-size:   16px;
    font-weight: 700;
    color:       var(--text-primary);
}
.hamburger {
    display:    none;
    background: none;
    border:     none;
    cursor:     pointer;
    color:      var(--text-primary);
    font-size:  22px;
    min-width:  44px;      /* shown only up to 860px, where it is the way to the menu: a 44px touch target */
    min-height: 44px;
}
/* Gutter (Ra 2026-10-21): the space around the page is .content: 24px, 14px on a phone (the 860px rule below). A page uses it and does not set its own. */
.content {
    padding: 24px;
    flex:    1;
}

/* ── Breadcrumb — rendered by nav.js from BX.nav stack ─────── */
/* Trail = path user walked. Variable depth. Single line.
   Section › Parent › Parent › Current
   Parents are clickable. No back arrow — parents ARE the nav.  */

.page-crumb {
    display:        flex;
    align-items:    center;
    margin-bottom:  10px;
    min-height:     28px;
}

.page-crumb-trail {
    display:     flex;
    align-items: center;
    overflow:    hidden;
    min-width:   0;
    flex:        1;
    gap:         4px;
}

.page-crumb-section {
    font-size:       14px;
    font-weight:     500;
    color:           var(--text-muted);
    white-space:     nowrap;
    flex-shrink:     0;
}

.page-crumb-chev {
    color:       var(--text-muted);
    display:     inline-flex;
    align-items: center;
    flex-shrink: 0;
    opacity:     0.4;
}

.page-crumb-parent {
    font-size:       14px;
    font-weight:     500;
    color:           var(--text-muted);
    text-decoration: none;
    white-space:     nowrap;
    overflow:        hidden;
    text-overflow:   ellipsis;
    max-width:       200px;
    flex-shrink:     1;
    min-width:       40px;
}
.page-crumb-parent:hover { color: var(--brand-primary); }

.page-crumb-current {
    font-size:       14px;
    font-weight:     700;
    color:           var(--text-primary);
    white-space:     nowrap;
    overflow:        hidden;
    text-overflow:   ellipsis;
    max-width:       280px;
    flex-shrink:     0;
}

@media (max-width: 480px) {
    .page-crumb-parent  { max-width: 120px; }
    .page-crumb-current { max-width: 160px; }
}

@media (max-width: 320px) {
    .page-crumb-parent  { max-width: 80px; }
    .page-crumb-current { max-width: 120px; }
}

.back-link {
    font-size:       14px;
    color:           var(--text-muted);
    text-decoration: none;
    display:         inline-block;
    margin-bottom:   10px;
}
.back-link:hover { color: var(--text-primary); }


/* ── Page header — list pages ─────────────────────────────── */
/* Title + subtitle + action buttons row. Used by all list pages. */
.page-head         { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: var(--stack); flex-wrap: wrap; }
.page-head-info    { min-width: 0; }
.page-head h1      { font-size: 22px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.01em; line-height: 1.1; margin: 0; }
.page-head-sub     { font-size: 14px; font-weight: 500; color: var(--text-muted); margin-top: 2px; }
/* flex-shrink:0 keeps the buttons from being squashed next to a long title, but
   on its own it also let this box grow wider than the page — .page-head wraps it
   onto its own line, and it then sat there at max-content, overflowing. Measured
   on /inventory (3 actions: Record movement / Bulk initial stock / New): 438px
   inside a 414px viewport, scrolling the whole page sideways. The ceiling is what
   was missing: capped at the container, the row can finally wrap, and flex-end
   keeps the second line aligned with the first. No effect wherever the actions
   already fit — which is every other page, none of which has more than two. */
.page-head-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: flex-end; flex-shrink: 0; max-width: 100%; }
@media (max-width: 860px) {
    .page-head          { gap: 10px; }
}

/* ── Detail page layout (customerview, sampleview, etc.) ─── */

.detail-cols {
    display:               grid;
    grid-template-columns: 1fr 380px;
    gap:                   0;
    border-top:            1px solid var(--surface-border);
    margin-top:            14px;
}
/* A 1fr track keeps min-width:auto, so it refuses to shrink below its own
   min-content width and shoves the fixed 380px aside off-screen — the whole
   page then scrolls sideways while the .table-wrap inside never narrows
   enough to scroll. Measured on /inventory: 78px of page overflow at a 900px
   window, 239px at 1.5x text. Letting the columns shrink hands the overflow
   back to the wrap, which is what it is for. Inert at full width. */
.detail-cols > * { min-width: 0; }
.detail-main {
    padding:        20px;
    border-right:   1px solid var(--surface-border);
    display:        flex;
    flex-direction: column;
    gap:            14px;
}
/* Two spacing models exist on purpose, and they must not be mixed in one
   container. .content is block flow: each page-level component carries its own
   `margin-bottom: var(--stack)`, which lets spacing be uneven where the design
   wants grouping. .detail-main owns the rhythm itself via the gap above, which
   is right for a stack of evenly spaced cards and wrong for anything that needs
   grouping. A component carrying --stack placed in here would produce two gaps.
   Nothing does today: every .detail-main holds cards, and /inventory — the one
   list page inside this shell — opts out to block flow in its own stylesheet. */
.detail-aside { padding: 20px; }
.detail-sh { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.detail-sh-link { font-size: 14px; color: var(--brand-primary); text-decoration: none; font-weight: 500; }
.detail-sh-link:hover { text-decoration: underline; }

/* ── Detail page header ──────────────────────────────────── */

.detail-hdr     { padding: 14px 0 0; }
.detail-hdr-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.detail-name    { font-size: 20px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.3px; }
.detail-name a  { color: inherit; text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .detail-name a:hover { text-decoration: underline; text-decoration-color: var(--text-muted); } }
.detail-name-row { display: flex; align-items: center; gap: 10px; }
.detail-meta    { font-size: 14px; color: var(--text-muted); display: flex; gap: 10px; align-items: center; margin-top: 5px; flex-wrap: wrap; }
.detail-meta-sep   { color: var(--surface-border); }
.detail-meta-id    { font-family: var(--font-data); color: var(--brand-primary); font-weight: 700; overflow-wrap: anywhere; }
.detail-meta-val   { color: var(--text-secondary); }
.detail-meta-phone { font-family: var(--font-data); color: var(--brand-primary); }

/* ── DETAIL PAGE HEADER (extension) ─────────────────────────
   Desktop-only right-hand action column inside .detail-hdr-top */
.detail-hdr-actions {
    display:     flex;
    flex-wrap:   wrap;
    gap:         8px;
    flex-shrink: 0;
    align-items: flex-start;
}
@media (max-width: 768px) { .detail-hdr-actions { display: none; } }

/* ── Brief — text summary on detail pages ────────────────── */

.brief {
    margin:        14px 0 0;
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    padding:       16px 18px;
    line-height:   1.7;
    font-size:     14px;
    color:         var(--text-secondary);
    box-shadow:    var(--shadow-1);
}
.brief-line { margin-bottom: 3px; }
.brief-link { color: var(--brand-primary); text-decoration: none; border-bottom: 1px solid var(--brand-border); cursor: pointer; font-weight: 700; }
button.brief-link { background: none; border: none; border-bottom: 1px solid var(--brand-border); padding: 0; font: inherit; font-weight: 700; }
.brief-link:hover { border-bottom-color: var(--brand-primary); }
.brief-val   { font-family: var(--font-data); color: var(--text-primary); font-weight: 700; }
/* Weight was never declared, so it inherited 400 while every sibling in this
   family declares one. 14px carries 500. */
.brief-dim   { color: var(--text-muted); font-size: 14px; font-weight: 500; }
.brief-warn  { color: var(--status-error); font-weight: 700; }
.brief-ok    { color: var(--status-success); font-weight: 700; }
.brief-sep   { color: var(--surface-border); }


/* ── Showing count — filtered list result summary ─────────── */
/* Weight was never declared, so it inherited 400. 14px carries 500. This line
   is on every list page and is always visible since BOX-537. */
/* Sits outside the card it describes, so it carries the card's own inner text
   offset to line up with the first column beneath it: 1px card border plus one
   cell of horizontal padding. Expressed against --cell-px rather than a measured
   13px so it follows the table if that token ever changes. */
.showing-count {
    font-size:      14px;
    font-weight:    500;
    color:          var(--text-muted);
    margin-bottom:  4px;
    padding-left:   calc(var(--cell-px) + 1px);
}

/* ── Group pill — brand-tinted tag for any group/category ─── */
/* Used: customer groups. Reuse pattern for other modules that add groups. */
.group-pill {
    font-size:      14px;
    background:     var(--brand-subtle);
    color:          var(--brand-primary);
    padding:        2px 8px;
    border-radius:  10px;
    white-space:    nowrap;
    overflow:       hidden;
    text-overflow:  ellipsis;
    max-width:      140px;
    display:        inline-block;
    vertical-align: middle;
}


/* ── Buttons ──────────────────────────────────────────────── */

.btn {
    display:         inline-flex;
    align-items:     center;
    justify-content: center;
    gap:             6px;
    height:          30px;
    padding:         0 14px;
    border-radius:   var(--radius);
    border:          none;
    cursor:          pointer;
    font-family:     var(--font-ui);
    font-size:       14px;
    font-weight:     500;
    transition:      background 0.1s, border-color 0.1s, color 0.1s, transform 0.06s;
    text-decoration: none;
    white-space:     nowrap;
}
.btn svg { width: 13px; height: 13px; flex-shrink: 0; }
.btn:active { transform: translateY(0.5px); }
/* ⛔ DEPRECATED — do not use in new code.
   Use .action-btn.action-btn--primary instead.
   Kept for backward compatibility with existing pages. */
.btn-primary {
    background: linear-gradient(135deg, var(--brand-primary), var(--brand-secondary));
    color:      var(--surface-page);
}
.btn-primary:hover    { transform: translateY(-1px); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

.btn-ghost {
    background: var(--ghost-bg);
    border:     1px solid var(--surface-border);
    color:      var(--text-secondary);
}
.btn-ghost:hover { background: var(--ghost-bg-hover); }

/* ⛔ DEPRECATED — do not use in new code.
   Use .action-btn.action-btn--warning instead.
   Kept for backward compatibility with existing pages. */
.btn-danger {
    background: var(--status-error-bg);
    border:     1px solid var(--status-error-border);
    color:      var(--status-error-text);
}
/* ⛔ DEPRECATED — do not use in new code.
   Use .action-btn.action-btn--primary instead.
   Kept for backward compatibility with existing pages. */
.btn-success {
    background: var(--status-success-bg);
    border:     1px solid var(--status-success-border);
    color:      var(--status-success-text);
}
.btn-success:hover { background: var(--status-success-border); }
.btn-danger:hover  { background: var(--status-error-border);   }
.btn-sm { height: 32px; padding: 0 12px; font-size: 14px; }
/* ── Quick contact row: icon buttons for call / whatsapp / maps (each is .action-btn.action-btn--icon, see the BUTTONS section) ── */

.qc-row { display: flex; gap: 6px; flex-shrink: 0; }



/* ── Badges ───────────────────────────────────────────────── */

.badge {
    display:       inline-flex;
    align-items:   center;
    gap:           5px;
    padding:       2px 6px;      /* Ra 2026-10-20 */
    border-radius: var(--radius-lg);   /* 10px */
    font-size:     0.75rem;      /* 0.75rem is allowed only at weight 700 (type-scale floor, Ra 2026-10-11) */
    font-weight:   700;
    border:        1px solid transparent;
    line-height:   1.3;          /* the pill is 21.6px tall */
    white-space:   nowrap;
}
/* Auto-dot on every badge — inherits text color via currentColor */
.badge::before {
    content:       '';
    width:         5px;
    height:        5px;
    border-radius: 50%;
    background:    currentColor;
    flex-shrink:   0;
}
.badge.no-dot::before { display: none;
}
/* Manual dot class — kept for backward compatibility */
.badge-dot {
    width:         5px;
    height:        5px;
    border-radius: 50%;
    display:       inline-block;
    flex-shrink:   0;
    background:    currentColor;
}
.badge-active,
.badge-received,
.badge-approved,
.badge-dispatched,
.badge-ready      { background: var(--status-success-bg);   color: var(--status-success-text); border-color: var(--status-success-border); }
.badge-inactive,
.badge-overdue    { background: var(--status-error-bg);     color: var(--status-error-text);   border-color: var(--status-error-border); }
.badge-prospect,
.badge-draft,
.badge-nobom,
.badge-discussing { background: var(--color-slate-500-a12); color: var(--text-secondary); border-color: var(--color-slate-500-a20); }
.badge-submitted,
.badge-partial,
.badge-pending    { background: var(--status-warning-bg);   color: var(--status-warning-text); border-color: var(--status-warning-border); }
.badge-confirmed,
.badge-progress,
.badge-type,
.badge-converted  { background: var(--brand-subtle);        color: var(--brand-secondary); border-color: var(--brand-border); }
.badge-rejected   { background: var(--status-error-bg);     color: var(--status-error-text);   border-color: var(--status-error-border); }
.badge-closed,
.badge-superseded { background: var(--color-slate-500-a08); color: var(--text-muted); border-color: var(--color-slate-500-a20); }  /* was slate-500 at 50%: 1.6 to 2.2:1; the muted text token reaches 4.5:1 */
.badge-scheduled  { background: repeating-linear-gradient(-45deg, var(--status-warning-bg), var(--status-warning-bg) 3px, transparent 3px, transparent 7px); color: var(--status-warning-text); border-color: var(--status-warning-border); }
.badge-loaded     { background: var(--status-info-bg); color: var(--status-info-text); border-color: var(--status-info-border); }

/* ── Role badges (users, user page, profile): one definition, colours are the --role-* tokens above ─────────────────────────
   OWNER-ROLE (2026-09-07): Owner is distinct from Admin despite equivalent permissions: a shared colour would make the two rows indistinguishable at a glance,
   defeating the point of showing the role. Owner uses the amber family (the warning hue, otherwise unused as a role colour) as its own role token: the
   shared warning text is 4.34:1 on the off-white page, and a role badge needs 4.5:1. */
.badge-admin      { background: var(--brand-subtle);        color: var(--brand-secondary); }
.badge-owner      { background: var(--role-owner-bg);       color: var(--role-owner); }
.badge-supervisor { background: var(--role-supervisor-bg);  color: var(--role-supervisor); }
.badge-sales      { background: var(--role-sales-bg);       color: var(--role-sales); }
.badge-purchase   { background: var(--role-purchase-bg);    color: var(--role-purchase); }
.badge-store      { background: var(--role-store-bg);       color: var(--role-store); }


/* ── Page alerts ──────────────────────────────────────────── */

.page-alert {
    padding:       10px 14px;
    border-radius: var(--radius-lg);
    font-size:     14px;
    margin-bottom: 16px;
    display:       flex;
    align-items:   center;
    justify-content: space-between;
    gap:           10px;
}
/* Empty: no room taken, but still in the accessibility tree. A live region has to be on the page BEFORE its message arrives (display:none would remove it). */
.page-alert:not(.show)  { height: 0; min-height: 0; padding: 0; margin: 0; border-width: 0; overflow: hidden; }
.page-alert-success {
    background: var(--status-success-bg);
    border:     1px solid var(--status-success-border);
    color:      var(--status-success-text);
}
.page-alert-error {
    background: var(--status-error-bg);
    border:     1px solid var(--status-error-border);
    color:      var(--status-error-text);
}
.page-alert-warning {
    background: var(--status-warning-bg);
    border:     1px solid var(--status-warning-border);
    color:      var(--status-warning-text);
}
.page-alert-info {
    background: var(--status-info-bg);
    border:     1px solid var(--status-info-border);
    color:      var(--status-info-text);
}
.page-alert-msg   { flex: 1; font-weight: 500; }
/* Dismiss: a 24px target at least, 44px on a coarse pointer or a phone (the negative margin keeps the visual size). */
.page-alert-close {
    display:         inline-flex;
    align-items:     center;
    justify-content: center;
    min-width:       24px;
    min-height:      24px;
    background:      none;
    border:          none;
    color:           currentColor;
    cursor:          pointer;
    padding:         0;
    opacity:         1;   /* full strength: at 0.7 the glyph measured 2.88 to 2.96:1 on the light tints; now it equals the -text token colour (4.7:1 and up) */
    flex-shrink:     0;
}
.page-alert-close:hover { opacity: 1; }
@media (max-width: 860px), (pointer: coarse) { .page-alert-close { min-width: 44px; min-height: 44px; margin: -10px -8px -10px 0; } }
/* Fixed copy, shown when #pageAlert is out of the viewport. Its left edge is set from the real sidebar width in toast.js (16px by default). */
.page-alert.page-alert-fixed { position: fixed; left: 16px; right: 16px; margin-bottom: 0; z-index: var(--z-toast, 400); }
/* The fixed copy floats over the page, so its tint sits on a SOLID card colour: the tint alone is translucent and the page text showed through it (unreadable). */
.page-alert-fixed.page-alert-success { background: linear-gradient(var(--status-success-bg), var(--status-success-bg)), var(--surface-card); }
.page-alert-fixed.page-alert-error   { background: linear-gradient(var(--status-error-bg), var(--status-error-bg)), var(--surface-card); }
.page-alert-fixed.page-alert-warning { background: linear-gradient(var(--status-warning-bg), var(--status-warning-bg)), var(--surface-card); }
.page-alert-fixed.page-alert-info    { background: linear-gradient(var(--status-info-bg), var(--status-info-bg)), var(--surface-card); }
/* ── Urgency alert — overdue items on detail pages ─────── */

.urgency {
    margin:        10px 0 0;
    padding:       14px 16px;
    background:    var(--status-warning-bg);
    border:        1px solid var(--status-warning-border);
    border-radius: var(--radius-lg);
    display:       flex;
    align-items:   center;
    gap:           10px;
    font-size:     14px;
    color:         var(--status-warning-text);
    flex-wrap:     wrap;
}
.urgency-text   { font-weight: 700; }
.urgency-ctx    { color: var(--text-secondary); font-size: 14px; }
.urgency-action { margin-left: auto; font-size: 14px; color: var(--brand-primary); text-decoration: none; white-space: nowrap; font-weight: 700; }
.urgency-action:hover { text-decoration: underline; }

/* Urgency color modifiers */
.urgency--warn  { background: var(--status-warning-bg); border-color: var(--status-warning-border); color: var(--status-warning-text); }
.urgency--error { background: var(--status-error-bg);   border-color: var(--status-error-border);   color: var(--status-error-text); }
.urgency--info  { background: var(--brand-subtle);       border-color: var(--brand-border);          color: var(--brand-primary); }



/* ── Filters ──────────────────────────────────────────────── */

.filters            { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
.filters-search-row { width: 100%; }
.filters-selects-row{ display: flex; gap: 8px; flex-wrap: wrap; }

.search-input {
    width:       100%;
    padding:     9px 14px;
    background: var(--tint-hover);
    border:      1px solid var(--surface-border);
    border-radius: var(--radius);
    color:         var(--text-primary);
    font-family:   var(--font-ui);
    font-size:     14px;
    outline:       none;
}
.search-input::placeholder { color: var(--text-placeholder); }  /* was slate-500 at 50% (1.6 to 2.2:1); the placeholder token is now the muted colour */
.search-input:focus        { border-color: var(--brand-primary); }

.filter-select {
    flex:        0 1 220px;
    min-width:   160px;
    max-width:   280px;
    padding:     9px 12px;
    background: var(--tint-hover);
    border:      1px solid var(--surface-border);
    border-radius: var(--radius);
    color:         var(--text-secondary);
    font-family:   var(--font-ui);
    font-size:     14px;
    outline:       none;
    cursor:        pointer;
}
.filter-select option { background: var(--surface-card); }

/* Compact inline "Group by" control — a section-body toggle (2-4 options),
   not a list-page filter (.filter-select is sized for that role, 160-280px
   wide). Promoted 2026-07-29 from rmview.html's page-local .rv-inv-mode
   (same visual language, generic name) so a second page (jobcardview.html)
   didn't need its own duplicate copy. rmview.html's own .rv-inv-mode is
   unchanged — this is additive, not a migration of that page. */
.groupby-row   { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 0.75rem; color: var(--text-muted); }
.groupby-label { font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; }
.groupby-row select { width: auto; height: auto; font-size: 0.75rem; padding: 5px 8px; border-radius: var(--radius); border: 1px solid var(--surface-border); background: var(--surface-card); color: var(--text-primary); }


/* ── Modals ───────────────────────────────────────────────── */

.modal-overlay {
    display:         none;
    position:        fixed;
    inset:           0;
    background:      var(--surface-overlay);
    z-index:         var(--z-overlay);
    align-items:     center;
    justify-content: center;
    padding:         20px;
}
.modal-overlay.show { display: flex; }

.modal {
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    width:         100%;
    max-width:     480px;
    overflow:      hidden;
}
.modal-header {
    padding:       18px 20px 14px;
    border-bottom: 1px solid var(--surface-border);
    display:       flex;
    align-items:   center;
    justify-content: space-between;
}
.modal-title { font-size: 15px; font-weight: 700; color: var(--text-primary); }
.modal-close {
    background: none;
    border:     none;
    color:      var(--text-muted);
    font-size:  20px;
    cursor:     pointer;
    line-height:1;
    padding:    0 2px;
}
.modal-close:hover { color: var(--text-primary); }
.modal-body        { padding: 18px 20px; }
.modal-footer {
    padding:    14px 20px;
    border-top: 1px solid var(--surface-border);
    display:    flex;
    gap:        8px;
    justify-content: flex-end;
}
/* A footer with a text action on the LEFT and the buttons on the right: centre them on one line; the action takes the free space (BOX-593 profile dialog). */
.modal-footer--split { align-items: center; flex-wrap: wrap; }
.modal-footer-start  { margin-right: auto; }
/* On a phone the status text takes its own line above the buttons, so the buttons never have to share a narrow row with it (a 240px footer at 320px). */
@media (max-width: 860px) { .modal-footer--split .modal-footer-start { flex: 1 1 100%; } }
/* A long value (an e-mail address, a path) wraps instead of pushing the layout wider. */
.break-anywhere { overflow-wrap: anywhere; }
/* Opt-in (class on the .modal element): a dialog whose body can be taller than the window keeps its header and footer in view and scrolls the body (BOX-600). */
.modal--scroll { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
.modal--scroll .modal-body { overflow-y: auto; }
.modal--scroll .checkbox-label { min-height: 44px; } /* a tick box in a dialog is a 44px-high target */
@media (max-width: 860px), (pointer: coarse) { .list-item--flat .checkbox-label { min-height: 44px; } } /* the same for a tick box in a list row (a person's factories) */
/* The close control drawn as an icon (put the xmark icon inside, and give the button a title and an aria-label): a 44 x 44 target whose negative margin keeps the header the same height. */
.modal-close.modal-close--icon { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: -10px -12px -10px 0; padding: 0; }
.modal-close.modal-close--icon svg { width: 20px; height: 20px; }
/* Explanatory text inside a dialog body: 0.875rem, weight 500. Primary colour by default; --muted for the quieter second paragraph. A name inside it (<strong>) keeps the weight. */
.modal-text { font-size: 0.875rem; font-weight: 500; color: var(--text-primary); margin: 0 0 8px; }
.modal-text--muted { color: var(--text-muted); margin-bottom: 16px; }
.modal-text strong { font-weight: inherit; color: var(--text-primary); }


/* ── Empty states ─────────────────────────────────────────── */

.empty       { text-align: center; padding: 60px 20px; color: var(--text-muted); }
.empty-icon  { width: 40px; height: 40px; margin: 0 auto 12px; border: 1px dashed var(--surface-border-strong); border-radius: var(--radius); display: grid; place-items: center; color: var(--text-muted); }
/* 15px was not one of the three sizes and 700 was not its weight; the sub
   declared no weight at all and inherited 400. Both lines now sit at the body
   step, and the title is separated from the sub by colour — --text-primary
   against .empty's --text-muted — not by size or weight. */
.empty-title { font-size: 0.875rem; font-weight: 500; color: var(--text-primary); margin-bottom: 6px; }
.empty-sub   { font-size: 0.875rem; font-weight: 500; margin-bottom: 18px; }
/* ── Empty guide — guided empty state for new entities ──── */

.empty-guide { text-align: center; padding: 32px 20px; color: var(--text-muted); }
.empty-guide-title { font-size: 15px; font-weight: 500; color: var(--text-primary); margin-bottom: 8px; }
.empty-guide-sub { font-size: 14px; color: var(--text-secondary); margin-bottom: 16px; line-height: 1.6; max-width: 400px; margin-left: auto; margin-right: auto; }
.empty-guide-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }



/* ── Stats row ────────────────────────────────────────────── */

/* Container only — stat card internals are owned by statcards.js */
.stats-row {
    display:       grid;
    gap:           12px;
    margin-bottom: 20px;
}


/* ── Forms ────────────────────────────────────────────────── */

/* .field-label is the same caption for something that is NOT a form field —
   a computed value shown back to the reader. Using a <label> there is what
   the "no associated form field" audit flags, and it is right: there is no
   field to associate. Same rule, so the two read identically. */
label, .field-label {
    display:        block;
    font-size:      10px;
    font-weight:    700;
    color:          var(--text-muted);
    letter-spacing: 1.1px;
    text-transform: uppercase;
    margin-bottom:  5px;
}
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
    width:         100%;
    height:        34px;
    padding:       0 11px;
    background:    var(--surface-page);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius);
    color:         var(--text-primary);
    font-family:   var(--font-ui);
    font-size:     14px;
    outline:       none;
    transition:    border-color 0.1s, box-shadow 0.1s;
}
input:not([type="checkbox"]):not([type="radio"]):hover,
select:hover { border-color: var(--surface-border-strong); }
input:not([type="checkbox"]):not([type="radio"]):focus, select:focus, textarea:focus {
    border-color: var(--brand-primary);
    box-shadow:   0 0 0 3px var(--brand-subtle);
}
/* A field the page has marked wrong (aria-invalid="true") keeps its red border on focus. Its focus cue is the soft ring in the error tint, the same shape as the blue ring on
   other fields. Focus is shown by the line / border colour and this ring only: NEVER an outline rectangle (owner's design rule, 2026-10-22). */
input[aria-invalid="true"]:not([type="checkbox"]):not([type="radio"]):focus, select[aria-invalid="true"]:focus, textarea[aria-invalid="true"]:focus {
    border-color: var(--status-error);
    box-shadow: 0 0 0 3px var(--status-error-bg);
}
/* A field the page has judged wrong (aria-invalid="true") keeps its red border while hovered or focused, whether it is an input, a select or a textarea. The shared hover and focus rules above are (0,3,1); .modal-body makes this one at least (0,4,1), so it wins on specificity and not on order. */
.modal-body input[aria-invalid="true"]:not([type="checkbox"]):not([type="radio"]),
.modal-body select[aria-invalid="true"],
.modal-body textarea[aria-invalid="true"],
.fp-body input[aria-invalid="true"]:not([type="checkbox"]):not([type="radio"]),
.fp-body select[aria-invalid="true"],
.fp-body textarea[aria-invalid="true"] { border-color: var(--status-error); }
input::placeholder, textarea::placeholder { color: var(--text-placeholder); }
select option { background: var(--surface-card); }
textarea      { height: auto; padding: 8px 11px; resize: vertical; min-height: 72px; }

/* ── Checkboxes & radios ──────────────────────────────────── */

input[type="checkbox"],
input[type="radio"] {
    width:        16px;
    height:       16px;
    flex-shrink:  0;
    cursor:       pointer;
    accent-color: var(--brand-primary);
    padding:      0;
    background:   none;
    border:       none;
    transition:   none;
}

/* Wrapper label — checkbox/radio + text as one clickable unit.
   Use: <label class="checkbox-label"><input type="checkbox"><span>Text</span></label>
   Pattern: plain checkbox row. Gap between checkbox and text. Text stays muted.
   Used in: customer-form (GST checkbox), fg.html, customerview */
.checkbox-label {
    display:        flex;
    align-items:    center;
    gap:            8px;
    cursor:         pointer;
    font-size:      14px;
    color:          var(--text-secondary);
    font-weight:    normal;
    text-transform: none;
    letter-spacing: 0;
    margin-bottom:  0;
}

/* Group container — multiple checkboxes/chips side by side.
   Use: <div class="checkbox-group">...</div> */
.checkbox-group {
    display:   flex;
    flex-wrap: wrap;
    gap:       8px;
}

/* Pill chip — multi-select chip group. Full label colour changes on check.
   Use: <label class="type-chip"><input type="checkbox"><span>Text</span></label>
   inside a <div class="checkbox-group">.
   Pattern: pill shape. Small gap between checkbox and text. Both centred in pill height.
   Full label highlights on check. Used in: supplier-form (supplier type) */
.type-chip {
    display:      flex;
    align-items:  center;
    gap:          6px;
    border:       1px solid var(--surface-border);
    border-radius: 20px;
    padding:      4px 12px;
    font-size:    14px;
    cursor:       pointer;
    color:        var(--text-muted);
    transition:   all 0.15s;
}
.type-chip:has(input:checked) {
    background:   var(--brand-subtle);
    border-color: var(--brand-border);
    color:        var(--brand-secondary);
}


/* ── MACHINE CHIPS (Log Machine drawer — chip selector) ──────
   Slots after .type-chip section */
.machine-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.m-chip {
    padding:     6px 14px;
    border-radius: 20px;
    font-size:   13px;
    font-weight: 500;
    cursor:      pointer;
    border:      2px solid var(--surface-border);
    color:       var(--text-secondary);
    transition:  all 0.1s;
}
.m-chip.active   { border-color: var(--brand-primary); color: var(--brand-primary); background: var(--brand-subtle); }
.m-chip.selected { border-color: var(--brand-primary); color: var(--brand-primary); background: var(--brand-subtle); outline: 2px solid var(--brand-primary); outline-offset: 2px; }
.m-chip.done     { border-color: var(--status-success); color: var(--text-muted); background: var(--tint-hover); cursor: default; text-decoration: line-through; }
.m-chip.wait     { opacity: 0.5; cursor: not-allowed; }

.form-section {
    font-size:      11px;
    font-weight:    700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color:          var(--brand-primary);
    margin:         18px 0 10px;
    padding-bottom: 6px;
    border-bottom:  1px solid var(--brand-border);
    display:        flex;
    align-items:    center;
    gap:            8px;
}
.form-alert {
    padding:       9px 13px;
    border-radius: var(--radius);
    font-size:     14px;
    margin-bottom: 12px;
    display:       none;
}
.form-alert.show       { display: block; }
.form-alert-error {
    background: var(--status-error-bg);
    border:     1px solid var(--status-error-border);
    color:      var(--status-error-text);
}
.form-alert-success {
    background: var(--status-success-bg);
    border:     1px solid var(--status-success-border);
    color:      var(--status-success-text);
}


/* ── Form grid helpers ────────────────────────────────────── */

.fg    { margin-bottom: 13px; }
.fr    { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.fr-3  { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 11px; }
.fg-inline { display: flex; gap: 6px; align-items: center; }
.fg-inline > input,
.fg-inline > select  { flex: 1; }
.opt   { color: var(--text-muted); font-size: 14px; text-transform: none; letter-spacing: 0; }
/* Hint text below a field — use below any input/select/textarea.
   <div class="form-hint">Short code stored in all records.</div>
   DO NOT use .field-note — that is an alias being retired. */
.form-hint { font-size: 14px; color: var(--text-muted); margin-top: 4px; }
.form-hint--block  { display: block; margin: 12px 0 10px; }
.form-hint--italic { font-style: italic; }

/* Action row — between header card and tabs. Mobile only.
   Desktop hides this via @media min-width below.
   Mobile shows it and hides .topbar-actions instead.         */
.action-row { display: none; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
/* ── Action bar — body-level actions on detail pages ─────── */

.action-bar { padding: 12px 0 0; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.action-bar--mobile { display: none; }
@media (max-width: 768px) { .action-bar--mobile { display: flex; } }
.action-btn {
    padding: 8px 14px; border: 1px solid var(--surface-border); border-radius: var(--radius);
    font-size: 14px; font-family: var(--font-ui); font-weight: 500; color: var(--text-secondary);
    background: transparent; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
    text-decoration: none; white-space: nowrap; transition: border-color 0.15s, background 0.15s;
}
.action-btn svg, .action-btn i { font-size: 15px; color: var(--brand-primary); }
.action-btn:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { .action-btn:hover { border-color: var(--brand-border); background: var(--brand-subtle); } }
.action-btn--primary { background: var(--brand-subtle); border-color: var(--brand-border); color: var(--brand-primary); }
@media (hover: hover) and (pointer: fine) { .action-btn--primary:hover { border-color: var(--brand-primary); } }
/* No disabled state existed before — every disabled action-btn app-wide
   (dirty-state Save buttons, cooldown-gated Erase buttons) was visually
   indistinguishable from an enabled one. */
.action-btn:disabled { opacity: 0.5; cursor: not-allowed; }
@media (hover: hover) and (pointer: fine) {
    .action-btn:disabled:hover           { border-color: var(--surface-border); background: transparent; }
    .action-btn--primary:disabled:hover  { border-color: var(--brand-border); background: var(--brand-subtle); }
}

/* ── Segmented choice — one of a few views of the same thing ─────
   Each segment is an .action-btn: the chosen one wears the primary look (.action-btn--primary), the others the plain
   ghost look. Joined into one control. Mark the chosen segment with aria-pressed="true" (it is also what a screen reader
   announces); there is no separate class for it.
   <div class="segment"><button class="segment-btn" aria-pressed="true">Invoices</button><button class="segment-btn" aria-pressed="false">Statement</button></div> */
.segment { display: inline-flex; }
.segment-btn {
    padding: 8px 14px; border: 1px solid var(--surface-border);
    font-size: 0.875rem; font-family: var(--font-ui); font-weight: 500; color: var(--text-secondary);
    background: transparent; cursor: pointer; white-space: nowrap; transition: border-color 0.15s, background 0.15s;
}
.segment-btn + .segment-btn { margin-left: -1px; }
.segment-btn:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.segment-btn:last-child  { border-radius: 0 var(--radius) var(--radius) 0; }
.segment-btn:only-child  { border-radius: var(--radius); }
.segment-btn:active { transform: scale(0.97); }
.segment-btn[aria-pressed="true"] { background: var(--brand-subtle); border-color: var(--brand-border); color: var(--brand-primary); position: relative; }
@media (hover: hover) and (pointer: fine) {
    .segment-btn:hover { border-color: var(--brand-border); background: var(--brand-subtle); position: relative; }
    .segment-btn[aria-pressed="true"]:hover { border-color: var(--brand-primary); }
}
.segment-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Tally payments (components/tallypayments.js) ────────────────
   The component marks its two mounts: data-tp="desktop" (the card body above the tab breakpoint) and data-tp="phone" (the
   Payments tab's pane). The rules key on that, not on a page's ids, so any page that mounts it gets them.
   An entry's amount and balance sit level with its first line. */
[data-tp] tr[data-m] td, [data-tp] tr[data-split] td, [data-tp] tr[data-sub] td { vertical-align: top; }
/* A receipt and the invoices it paid (or an invoice and the receipts that paid it) read as one block: no divider between the
   parent and its first child, the same dashed line the shared .dl list uses between its rows (.dl > div) between children, a solid
   one after the last; the children are indented and tight. */
[data-tp] tr[data-split] td { border-bottom: none; }
[data-tp] tr[data-sub] td { height: auto; padding-top: 4px; padding-bottom: 4px; border-bottom: 1px dashed var(--surface-border); }
[data-tp] tr[data-sub] td:first-child { padding-left: calc(var(--cell-px) + 20px); }
[data-tp] tr[data-sub][data-last] td { padding-bottom: var(--cell-py); border-bottom: 1px solid var(--surface-border); }
/* A month's totals on the phone: tight rows, dashed between them (the same line as between a receipt's children), solid after the last. */
[data-tp] tr[data-total] td { height: auto; padding-top: 4px; padding-bottom: 4px; vertical-align: top; border-bottom: 1px dashed var(--surface-border); }
[data-tp] tr[data-total][data-last] td { padding-bottom: var(--cell-py); border-bottom: 1px solid var(--surface-border); }
/* Phone pane. The shared .table keeps a 500px floor so wide tables do not crush; the Statement here has two columns and fits.
   It sits on the card background (white, bordered, like the invoice cards beside it) with 8px of side padding in its cells,
   because every pixel of width counts. A statement entry's label side may be long: let it wrap beside the amount instead of
   pushing it off the edge (.dl's own dt does not shrink). */
[data-tp="phone"] .table { min-width: 0; --cell-px: 8px; }
[data-tp="phone"] .table-wrap { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: var(--radius); }
[data-tp="phone"] .dl dt { flex-shrink: 1; min-width: 0; }
/* The sync bar has the same space above (the tab bar, 10px from the pane's own padding) and below (the figures); the
   Invoices | Statement switch has 14px above (the flattened figures card leaves 14px of its own) and 14px below. */
[data-tp="phone"] #paySyncBar { margin-bottom: 10px; }
[data-tp="phone"] #paySegWrap { margin: 0 0 14px; }
.action-btn--warning { background: var(--status-warning-bg); border-color: var(--status-warning-border); color: var(--status-warning-text); }
/* Hover keeps the resting background (so the text stays at the resting contrast, 5.2:1 light / 6.5:1 dark) and strengthens the border instead (Ra 2026-10-21: the old amber-30% hover fill was 4.29:1 light, 4.46:1 dark). */
/* The resting background is restated because the shared .action-btn:hover (same specificity, earlier in the file) would otherwise turn it blue; inside the hover guard so a tap does not stick. */
@media (hover: hover) and (pointer: fine) { .action-btn--warning:hover { background: var(--status-warning-bg); border-color: var(--status-warning); } }
.action-btn--warning svg, .action-btn--warning i { color: var(--status-warning-text); }
/* SVG sizing for .action-btn (source sets font-size:15px for icon fonts; SVGs need explicit dimensions) */
/* 2026-07-22 fix: SVGs are inline REPLACED elements with a default
   vertical-align:baseline, which reserves invisible descender space
   below them (room for letters like "g"/"y") — that phantom space
   sits below the icon but not below sibling text/badge content, so
   even though .action-btn's align-items:center correctly centers each
   flex item's BOX, the icon's own icon-span box is taller than its
   visible glyph, and the glyph itself renders slightly above true
   center relative to text/badge siblings. Confirmed on the Filter
   button (icon visibly offset from its count badge, text/badge
   themselves correctly aligned) — the icon's own SVG coordinates are
   genuinely centered in its viewBox, so this was never a per-icon
   shape issue. display:block removes the icon from inline flow
   entirely (no baseline/descender concept applies to a block box), so
   its rendered box now exactly matches its visible bounds — every
   .action-btn icon app-wide, not just Filter, since this is the
   shared base rule every one of them uses. */
.action-btn svg { width: 14px; height: 14px; flex-shrink: 0; display: block; }

/* Touch target (Ra: 44px on a phone): every .action-btn and .segment-btn is at least 44px high up to 860px (the card-list switch) and on a touch screen. No class per button. */
@media (max-width: 860px), (pointer: coarse) { .action-btn, .segment-btn { min-height: 44px; } }

/* Borderless icon button (Users list rows): the box is gone, the touch target is not. A 20px glyph; 44 x 44 up to 860px and on a touch screen. Always carries a
   tooltip (title) and an accessible name (aria-label) that says who it is for. The row of them is .user-actions; a destructive icon is tinted in the icon itself (BX.icons.trashDanger). */
.action-btn.action-btn--bare { background: transparent; border-color: transparent; }
/* Icon-only button: .action-btn.action-btn--icon (Call / WhatsApp / Email / Maps, a row's Edit, a line's remove cross). Bordered by default; add .action-btn--bare for the borderless mode.
   Hover is a border-only change to --brand-primary (no fill), the same language as before the merge; the 44px phone height comes from the shared .action-btn rule. */
.action-btn--icon { padding: 8px 10px; transition: border-color 0.15s, background 0.15s, opacity 120ms ease; }
.action-btn--icon svg { width: 16px; height: 16px; }
@media (hover: hover) and (pointer: fine) { .action-btn--icon:not(.action-btn--bare):not(:disabled):hover { background: transparent; border-color: var(--brand-primary); } }
/* Inside a table row a bordered icon button is revealed on hover rather than drawn on every row (editing is low-frequency and the row already opens the record). :focus-within keeps it
   reachable from the keyboard; the reveal only applies to fine pointers, so a touch screen keeps it visible. The borderless mode is the row's own action and is always visible. */
@media (hover: hover) and (pointer: fine) {
    .table tbody tr .action-btn--icon:not(.action-btn--bare) { opacity: 0; }
    .table tbody tr:hover .action-btn--icon:not(.action-btn--bare):not(:disabled),
    .table tbody tr:focus-within .action-btn--icon:not(.action-btn--bare):not(:disabled) { opacity: 1; }
}
.table tbody tr .action-btn--icon:focus-visible { opacity: 1; }
@media (hover: hover) and (pointer: fine) {
    .action-btn.action-btn--bare:not(:disabled):hover { background: var(--tint-hover); border-color: transparent; }
    .action-btn.action-btn--bare:disabled:hover { background: transparent; border-color: transparent; }
}
.action-btn.action-btn--bare svg { width: 20px; height: 20px; }
/* Ink-coloured text on a status badge: the tint and the border carry the status, the text is --text-primary, because a status colour on its own tint is under 4.5:1 in the light theme. */
.badge.badge-ink { color: var(--text-primary); }
@media (max-width: 860px), (pointer: coarse) { .action-btn--bare { min-width: 44px; justify-content: center; } }
.user-actions { display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; }
.view-mobile .user-actions { flex-shrink: 0; gap: 4px; }

/* ── Small oval pill (2-state) — .pill-primary ─────────────────
   Same border/color/hover language as .action-btn above (not .mv-chip's
   square 6px-radius filter-chip look — a per-row inline state control,
   not a filter), just fully rounded and with much smaller padding since
   it sits inline next to a name/label instead of standing alone as a
   toolbar button. Reused across the app wherever a row needs a small
   "this one/promote this one" state pill — not customerview-specific,
   despite its first use being customerview.html's/customer-form.html's
   address & contact rows.
     .pill-primary--on           = active state (e.g. "Primary"), brand-tinted, static
     .pill-primary (as <button>) = promote action (e.g. "Set as primary"), clickable
     .pill-primary (as <span>)   = other static state (e.g. "Deactivated") — same
       neutral look as the button state, just non-interactive */
.pill-primary {
    display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0;
    padding: 2px 8px; border: 1px solid var(--surface-border); border-radius: 999px;
    font-size: 12px; font-family: var(--font-ui); font-weight: 700; color: var(--text-secondary);
    background: transparent; white-space: nowrap; transition: border-color 0.15s, background 0.15s;
}
button.pill-primary { cursor: pointer; }
@media (hover: hover) and (pointer: fine) {
    button.pill-primary:hover { border-color: var(--brand-border); background: var(--brand-subtle); }
}
.pill-primary--on { background: var(--brand-subtle); border-color: var(--brand-border); color: var(--brand-primary); }

/* Semantic color variants — absorbed from customers.html's page-local
   .sig-pill family (same static-info-badge role: pipeline/signal state on
   a row, not a click target like the base .pill-primary/--on toggle).
   Same 12px/2px 8px size as the base rule above (Ra's explicit call,
   overriding this file's own earlier reasoning to hold at 11px to avoid
   growing .sig-pill by 1px — 12px matches .pill-primary's original size
   instead, and .sig-pill grows by only 1px as a result). .badge is the
   same size (0.75rem, weight 700, Ra 2026-10-20); the two classes differ
   in role, not size: .badge is a primary status or role label, .pill-primary
   is a secondary marker or a two-state toggle. */
.pill-primary--po       { background: var(--brand-subtle);         color: var(--brand-primary);   border-color: var(--brand-border); }
.pill-primary--sample   { background: var(--color-purple-500-a12); color: var(--status-sample-text); border-color: rgba(184,127,232,0.25); }
.pill-primary--followup { background: var(--status-warning-bg);    color: var(--status-warning-text);   border-color: var(--status-warning-border); }
/* --overdue: suppliers.html's overdue-delivery signal — error tokens
   (not --followup's warning tokens) since a late shipment is a harder
   miss than a soft outreach reminder. */
.pill-primary--overdue  { background: var(--status-error-bg);      color: var(--status-error-text);     border-color: var(--status-error-border); }

/* Sample price quote status (samplecard.js scQuotePill()) — deliberately
   the pill shape, not .badge, so quote status is never visually confused
   with sample status (which uses .badge/scSbadge() right next to it on
   the same row) — same colour tokens as this codebase's other status
   indicators for the same semantic meaning (warning/success/error/brand/
   muted), just carried by the pill shape instead of the dot-badge shape. */
.pill-primary--pending             { background: var(--status-warning-bg);   color: var(--status-warning-text); border-color: var(--status-warning-border); }
.pill-primary--confirmed,
.pill-primary--progress            { background: var(--brand-subtle);        color: var(--brand-primary);  border-color: var(--brand-border); }
.pill-primary--approved            { background: var(--status-success-bg);   color: var(--status-success-text); border-color: var(--status-success-border); }
.pill-primary--rejected            { background: var(--status-error-bg);     color: var(--status-error-text);   border-color: var(--status-error-border); }
.pill-primary--closed,
.pill-primary--superseded          { background: var(--color-slate-500-a08); color: var(--text-muted); border-color: var(--color-slate-500-a20); }  /* was slate-500 at 50% (1.6 to 2.2:1); same fix as .badge-closed */



/* ── fp-overlay ───────────────────────────────────────────────
   ALL create/edit forms in BoxFlow use this pattern.
   Mobile  (≤768px): full-screen takeover
     — navy bg fills screen
     — fp-header: sticky top with back arrow + title
     — fp-body: scrollable middle
     — fp-footer: sticky bottom with Cancel + Save
   Desktop (≥769px): centred modal
     — dark backdrop
     — fp-title-bar: rounded top card with title + ×
     — fp-header: HIDDEN
     — fp-body: card body, scrollable, max 65vh
     — fp-footer: attached rounded bottom
   ─────────────────────────────────────────────────────────── */

/* Base — mobile full screen */
/* fp-overlay — base layout for forms (always visible).
   Pages that embed fp-overlay as a toggle use display:none in their
   own inline <style> to hide it initially, then add .show to open. */
.fp-overlay {
    display:        flex;
    flex-direction: column;
    position:       fixed;
    inset:          0;
    background:     var(--surface-page);
    z-index:        var(--z-fp);
    overflow-y:     auto;
    -webkit-overflow-scrolling: touch;
}

.fp-header {
    padding:       14px 18px;
    border-bottom: 1px solid var(--surface-border);
    display:       flex;
    align-items:   center;
    gap:           12px;
    background:    var(--surface-card);
    position:      sticky;
    top:           0;
    z-index:       10;
    flex-shrink:   0;
}
.fp-back {
    background: none;
    border:     none;
    color:      var(--text-muted);
    font-size:  20px;
    cursor:     pointer;
    padding:    0;
    line-height:1;
    display:    inline-flex;
    align-items:center;
    justify-content:center;
    min-width:  44px;
    min-height: 44px;
    margin:     -6px 0 -6px -10px;
}
.fp-title { font-size: 15px; font-weight: 700; color: var(--text-primary); }

.fp-body {
    padding:    18px;
    flex:       1;
    overflow-y: auto;
    background: var(--surface-card);
}
/* Context overrides for elements inside form body */
.fp-body .eyebrow     { margin-bottom: 14px; }
.fp-body textarea      { min-height: 80px; }
.fp-footer {
    padding:       14px 18px;
    border-top:    1px solid var(--surface-border);
    display:       flex;
    gap:           8px;
    justify-content: flex-end;
    background:    var(--surface-card);
    position:      sticky;
    bottom:        0;
    flex-shrink:   0;
}

/* ── fp-head / fp-foot — single header/footer for Option A forms ─
   Used by: dispatch-form, customer-form, customerpo-form, rm-receipt-form,
            sampleform, supplier-form, supplierpo-form
   One element visible on both mobile and desktop (no switching).
   Desktop constrains overlay to max-width card via @media in page style. */
.fp-head {
    padding:       16px 20px;
    border-bottom: 1px solid var(--surface-border);
    display:       flex;
    align-items:   center;
    justify-content: space-between;
    background:    var(--surface-card);
    position:      sticky;
    top:           0;
    z-index:       var(--z-sticky);
    flex-shrink:   0;
}
.fp-label {
    font-size:      10px;
    font-weight:    700;
    letter-spacing: 2px;
    text-transform: uppercase;
    color:          var(--brand-primary);
    margin-bottom:  2px;
}
.fp-sub {
    font-size:   14px;
    color:       var(--text-muted);
    font-family: var(--font-data);
    margin-top:  2px;
}
.fp-foot {
    padding:       14px 20px;
    border-top:    1px solid var(--surface-border);
    display:       flex;
    gap:           8px;
    justify-content: flex-end;
    background:    var(--surface-card);
    position:      sticky;
    bottom:        0;
    flex-shrink:   0;
}

/* ── Line cards — removable line items on form pages ──────────
   Used by: supplierpo-form, customerpo-form, rm-receipt-form
   Pattern: card wrapper + header row (label left, Remove right)
   Remove button uses .btn.btn-danger.btn-sm — no custom class needed. */
.line-card {
    background: var(--tint-subtle);
    border:        1px solid var(--surface-border);
    border-radius: 8px;
    padding:       12px;
    margin-bottom: 10px;
}
.line-card-header {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    margin-bottom:   10px;
}
.line-num {
    font-size:      10px;
    font-weight:    700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color:          var(--text-muted);
}

/* fp-close — base rule, all breakpoints */
.fp-close {
    background:  none;
    border:      1.5px solid var(--surface-border);
    color:       var(--text-muted);
    font-size:   22px;
    cursor:      pointer;
    padding:     4px 8px;
    line-height: 1;
    flex-shrink: 0;
}
.fp-close:hover { color: var(--text-primary); }
@media (max-width: 860px), (pointer: coarse) { .fp-close { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; } }

/* Desktop title bar — hidden on mobile */
.fp-title-bar { display: none; }

/* Desktop overrides */
@media (min-width: 769px) {
    .fp-overlay,
    .fp-overlay.show {
        background:      var(--surface-overlay);
        flex-direction:  column;
        align-items:     center;
        justify-content: flex-start;
        padding:         40px 20px;
        overflow-y:      auto;
    }
    .fp-title-bar {
        display:         flex;
        align-items:     center;
        justify-content: space-between;
        background:      var(--surface-card);
        border:          1px solid var(--surface-border);
        border-bottom: 1px solid var(--tint-border-soft);
        border-radius:   14px 14px 0 0;
        padding:         14px 22px;
        width: 100%; max-width: 600px;
        flex-shrink:     0;
    }
    .fp-title-bar-text { font-size: 15px; font-weight: 700; color: var(--text-primary); }
    .fp-subtitle       { font-size: 14px; color: var(--text-muted); margin-top: 2px; }
    /* fp-close already defined globally above — desktop override for context */
    .fp-close { font-size: 22px; }

    /* fp-head / fp-foot — Option A form card centering on desktop */
    .fp-head {
        width:         100%;
        max-width:     680px;
        border-radius: 14px 14px 0 0;
        border:        1px solid var(--surface-border);
        border-bottom: 1px solid var(--tint-border-soft);
        position:      static;
    }
    .fp-body {
        width:         100%;
        max-width:     680px;
        flex:          unset;
        position:      static;
        background:    var(--surface-card);
        max-height:    65vh;
        overflow-y:    auto;
    }
    .fp-foot {
        width:         100%;
        max-width:     680px;
        border-radius: 0 0 14px 14px;
        border:        1px solid var(--surface-border);
        border-top: 1px solid var(--tint-border-soft);
        position:      static;
    }
    .fp-header { display: none; }  /* hidden on desktop — fp-title-bar takes over */
    .fp-footer {
        background:    var(--surface-card);
        border:        1px solid var(--surface-border);
        border-top: 1px solid var(--tint-border-soft);
        border-radius: 0 0 14px 14px;
        width: 100%; max-width: 600px;
        padding:       14px 22px;
        justify-content: flex-end;
        position:      static;
        flex-shrink:   0;
    }
    .action-row { display: none !important; } /* topbar-actions handles desktop */
}


/* ── Prompt overlays ──────────────────────────────────────────
   Post-action prompts (e.g. "Activity logged — what next?")
   Always centred, always above fp-overlay (z-index 400).     */

.prompt-overlay {
    display:         none;
    position:        fixed;
    inset:           0;
    background:      var(--surface-overlay);
    z-index:         400;
    align-items:     center;
    justify-content: center;
    padding:         20px;
}
.prompt-overlay.show { display: flex; }

.prompt-box {
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    width:         100%;
    max-width:     420px;
    padding:       28px 24px;
    text-align:    center;
}
.prompt-icon  { font-size: 28px; margin-bottom: 12px; }
.prompt-title { font-size: 16px; font-weight: 700; color: var(--text-primary); margin-bottom: 6px; }
.prompt-sub   { font-size: 14px; color: var(--text-muted); margin-bottom: 20px; }

.prompt-option {
    width:          100%;
    text-align:     left;
    padding:        12px 16px;
    display:        flex;
    align-items:    center;
    gap:            12px;
    background: var(--tint-hover);
    border:         1px solid var(--surface-border);
    border-radius: var(--radius);
    color:         var(--text-secondary);
    cursor:        pointer;
    font-family:   var(--font-ui);
    margin-bottom: 8px;
}
.prompt-option:hover       { background: var(--tint-border-soft); }
.prompt-option-icon        { font-size: 20px; }
.prompt-option-title       { font-weight: 700; color: var(--text-primary); font-size: 14px; }
.prompt-option-sub         { font-size: 14px; color: var(--text-muted); }
.prompt-dismiss {
    width:         100%;
    padding:       10px;
    background:    none;
    border:        1px solid var(--surface-border);
    border-radius: var(--radius);
    color:         var(--text-muted);
    cursor:        pointer;
    font-family:   var(--font-ui);
    font-size:     14px;
    margin-top:    4px;
}

/* ── FP-overlay toggle modifier ─────────────────────────── */
/* Add to fp-overlay elements that are hidden by default.     */
.fp-overlay--toggle { display: none; }
.fp-overlay--toggle.show { display: flex; }

/* Toggle overlays: .fp-title-bar/.fp-header/.fp-body/.fp-footer are
   SIBLINGS (all direct children of .fp-overlay) in every real toggle
   drawer — never fp-footer nested inside fp-body. .fp-body gets no
   rounding of its own (title-bar and footer own the top/bottom corners
   as top-level siblings); overflow-y:auto (not overflow:hidden) so
   content can actually scroll on mobile — overflow:hidden squeezes
   fp-body into its flex allotment and clips instead of growing, since
   .fp-overlay's own outer scroll never engages once a descendant reports
   non-visible overflow. .fp-footer needs NO override here at all — the
   plain .fp-footer rule above (mobile: solid background + border-top +
   sticky; desktop: full card border + border-radius) is already correct
   once nothing clobbers it, which the old version of this rule did
   (transparent background + negative-margin bleed, both written for the
   nested structure this never actually has) — that's what made toggle
   drawer footers render as if detached from their own card, with no
   visible background. Promoted here from a page-local fix that used to
   live only in jobcardview.html (5 hardcoded IDs) so every current and
   future toggle drawer gets it for free, not just those five. */
.fp-overlay--toggle .fp-body { border-radius: 0; overflow-y: auto; }
@media (min-width: 769px) {
    .fp-overlay--toggle .fp-body { max-height: 80vh; width: 100%; max-width: 600px; }
}

/* Clickable checkbox + label row — promoted from jobcardview.html's
   page-local .jc-confirm-row (2026-08-29, receiptview.html's QC
   inspection drawer needed the identical pattern for a multi-row sample
   picker; same properties, generic name since it's no longer JC-specific).
   .jc-confirm-row itself stays as-is, unrenamed, to avoid an unrelated
   diff in jobcardview.html — this is the shared form going forward. */
.form-check-row { display: flex; align-items: center; gap: 8px; font-size: 0.875rem; cursor: pointer; }
.prompt-overlay--toggle { display: none; }
.prompt-overlay--toggle.show { display: flex; }
@media (min-width: 769px) { .fp-wide .fp-title-bar, .fp-wide .fp-body, .fp-wide .fp-footer { max-width: 680px; } }


/* ── Tabs ─────────────────────────────────────────────────── */

/* Base tab row — scroll.js sets position:sticky, top, z-index,
   and background as inline styles at runtime via SCROLL_CONFIG.
   Never set those properties here — scroll.js will override them. */
.tabs {
    /* Both the tab row and the search-scope row that replaces it are .tabs;
       one floor here keeps the swap from moving the page. */
    min-height:    44px;
    display:       flex;
    overflow-x:    auto;
    -webkit-overflow-scrolling: touch;
    margin-bottom: 20px;
    scroll-snap-type: x proximity;
    /* NO border-bottom here — scroll.js inline background would wipe any
       background-image trick. Border is on individual tabs + ::after spacer. */
}
/* Pseudo-element fills remaining width with the same border as inactive tabs.
   It is a flex child — inside the container, not clipped by overflow-x. */
.tabs::after {
    content:       '';
    flex:          1;
    min-width:     8px;
    align-self:    stretch;
    border-bottom: 1px solid var(--surface-border);
    pointer-events:none;
}
/* Overflow-edge fade — nav.js's _initTabScrollFade() toggles these classes
   per .tabs bar based on real scrollLeft/scrollWidth, so the fade only
   ever appears on the edge(s) that actually have more tabs scrolled past
   them (never applied to a bar that fits, so no regression there). */
/* A tab row can be hidden while a search is running. [hidden] is a
   user-agent rule, so .tabs{display:flex} overrides it without this. */
.tabs[hidden] { display: none; }

/* Takes the tab row's place while a query is live, in the same slot and at the
   same height, so the row never collapses under the reader. Height is NOT
   pinned to a number: both rows are .tabs, so the min-height above keeps them
   identical however the tab's own type changes. Pinning it to a measured
   pixel value broke twice when .tab and .tab-count were resized.
   Weight 500, not .tab's: this reports, it is not a control. */
.tab-scope {
    display:       flex;
    align-items:   center;
    padding:       0 16px;
    font-size:     0.875rem;
    font-weight:   500;
    color:         var(--text-primary);
    border-bottom: 1px solid var(--surface-border);
    white-space:   nowrap;
    flex-shrink:   0;
}
/* De-emphasis by colour, never by a smaller size. */
.tab-scope .scope-split { color: var(--text-secondary); }

.tabs.tabs-scroll-left {
    -webkit-mask-image: linear-gradient(to right, transparent, black 24px);
            mask-image: linear-gradient(to right, transparent, black 24px);
}
.tabs.tabs-scroll-right {
    -webkit-mask-image: linear-gradient(to left, transparent, black 24px);
            mask-image: linear-gradient(to left, transparent, black 24px);
}
.tabs.tabs-scroll-left.tabs-scroll-right {
    -webkit-mask-image: linear-gradient(to right, transparent, black 24px, black calc(100% - 24px), transparent);
            mask-image: linear-gradient(to right, transparent, black 24px, black calc(100% - 24px), transparent);
}
.tab {
    padding:        12px 16px;
    font-size:      0.875rem;
    font-weight:    500;
    color:          var(--text-muted);
    cursor:         pointer;
    /* All tabs have the same 1px bottom border — active overrides to brand 2px.
       Height is identical for all tabs so no layout shift. */
    border-bottom:  1px solid var(--surface-border);
    white-space:    nowrap;
    flex-shrink:    0;
    scroll-snap-align: start;
    transition:     color 0.1s, border-color 0.1s, border-width 0.1s;
    display:        flex;
    align-items:    center;
    gap:            6px;
}
.tab:hover  { color: var(--text-primary); }
.tab.active { color: var(--brand-primary); border-bottom: 2px solid var(--brand-primary); padding-bottom: 11px; }
.tab-count     { font-family: var(--font-data); font-size: 12px; font-weight: 700; color: var(--text-muted); background: var(--tint-hover); padding: 1px 6px; border-radius: 8px; }
.tab.active .tab-count { background: var(--brand-subtle); color: var(--brand-primary); }
/* ── Detail tabs — mobile column switcher ────────────────── */
.detail-tabs { display: none; }
.detail-tabs::after { content: ''; flex: 1; min-width: 8px; align-self: stretch; border-bottom: 1px solid var(--surface-border); }

.tab-panel        { display: none; }
.tab-panel.active { display: block; }




/* ── Info cards (shared: customerview, customerpoview, sampleview) ── */

.info-card       { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: 10px; padding: 16px; }
.info-card-title { font-size: 11px; font-weight: 700; letter-spacing: 1.1px; text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px; }
.info-row        { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed var(--surface-border); font-size: 14px; gap: 12px; }
.info-row:last-child { border-bottom: none; }
.info-label      { color: var(--text-muted); flex-shrink: 0; }
.info-value      { color: var(--text-primary); font-weight: 500; text-align: right; font-size: 14px; }


/* ── JOB CARD DETAIL PANEL ───────────────────────────────────
   Shared: jobcardview detail aside. Slots after info-card section. */

/* Section heading (uppercase label with top border) */
.det-sh { font-size: 14px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-muted); padding: 16px 0 8px; border-top: 1px solid var(--surface-border); margin-top: 12px; }
.det-sh:first-child { border-top: none; margin-top: 0; }

/* Key-value rows */
.det-row { display: flex; justify-content: space-between; gap: 8px; padding: 5px 0; font-size: 14px; }
.det-key { color: var(--text-muted); flex-shrink: 0; }
.det-val { color: var(--text-primary); text-align: right; }

/* RM accordion card */
.det-rm-card { border: 1px solid var(--surface-border); border-radius: var(--radius); margin-bottom: 8px; overflow: hidden; }
.det-rm-hdr  { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; background: var(--tint-hover); }
.det-rm-hdr:hover { background: var(--tint-active); }
.det-rm-body { padding: 8px 12px; display: none; }
.det-rm-card.open .det-rm-body { display: block; }
.det-reel     { font-size: 12px; color: var(--text-muted); padding: 4px 0; border-bottom: 1px dashed var(--surface-border); }
.det-reel:last-child { border-bottom: none; }
.det-rm-chev     { color: var(--text-muted); margin-left: 8px; display: inline-flex; align-items: center; }
.det-rm-chev svg { width: 14px; height: 14px; }

/* Quality stats (Pre-QC summary) */
.det-quality-stats    { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 6px; }
.det-quality-stat-lbl { font-size: 14px; color: var(--text-muted); margin-bottom: 2px; }
.det-quality-stat-val { font-size: 16px; font-weight: 500; font-family: var(--font-mono); }

/* ── Comm log timeline (shared: customerview, customerpoview, sampleview) ── */

.timeline        { display: flex; flex-direction: column; }
/* ── Timeline v1 — grid layout for customerview ─────────────
   Text sizing aligned with tl2-* (14px floor).
   TODO: migrate customerview HTML to tl2-* classes, then remove tl-* block. */

.tl-item         { display: grid; grid-template-columns: 80px 1fr; gap: 10px; position: relative; }
.tl-item::before { content: ''; position: absolute; left: 79px; top: 18px; bottom: 0; width: 1px; background: var(--surface-border); }
.tl-item:last-child::before { display: none; }
.tl-date-col     { text-align: right; padding-top: 4px; }
.tl-date         { font-size: 14px; font-weight: 700; color: var(--text-primary); line-height: 1.3; }
.tl-by           { font-size: 14px; color: var(--text-muted); margin-top: 1px; }
.tl-card         { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: 10px; padding: 11px 13px; margin-bottom: 10px; position: relative; }
.tl-card-auto    { border-color: var(--tint-hover); background: var(--tint-subtle); }
.tl-dot          { position: absolute; left: -17px; top: 11px; width: 8px; height: 8px; border-radius: 50%; border: 2px solid var(--surface-border); }
.tl-type         { font-size: 14px; font-weight: 700; }
.tl-notes        { font-size: 14px; color: var(--text-secondary); line-height: 1.55; }
.tl-meta         { margin-top: 6px; font-size: 14px; color: var(--text-muted); font-family: var(--font-data); }
.tl-edited       { font-size: 14px; color: var(--text-muted); margin-top: 4px; }

/* ── Timeline v2 — detail page timelines ─────────────────
   Used by: customerview, customerpoview, sampleview, dispatchview.
   Font floor: 14px. Dot: 10px. Ghost: color demotion, never opacity.
   ────────────────────────────────────────────────────────── */

/* Layout — dot + content (2-col). Date is inline in header. */
.tl2-item     { display: flex; gap: 12px; padding: 14px 0; border-bottom: 1px solid var(--surface-border); }
.tl2-item:last-child { border-bottom: none; }
.tl2-dot      { width: 10px; height: 10px; border-radius: 50%; margin-top: 5px; flex-shrink: 0; }
.tl2-dot--hollow { background: transparent; border: 2px solid var(--text-muted); width: 8px; height: 8px; margin: 6px 1px 0; }
.tl2-content  { flex: 1; min-width: 0; }

/* Header — Type · Contact · Date · Author, all inline */
.tl2-head     { display: flex; align-items: baseline; gap: 0; margin-bottom: 4px; flex-wrap: wrap; }
.tl2-type     { font-size: 0.875rem; font-weight: 700; margin-right: 4px; }
.tl2-sep      { color: var(--text-muted); margin: 0 4px; font-size: 0.875rem; }
.tl2-contact  { font-size: 0.875rem; color: var(--text-secondary); }
.tl2-meta     { font-size: 0.875rem; color: var(--text-secondary); }
.tl2-edit     { display: inline-flex; align-items: center; color: var(--text-muted); margin-left: auto; cursor: pointer; opacity: 0.7; transition: opacity 0.15s; }
@media (hover: hover) and (pointer: fine) { .tl2-edit { opacity: 0.4; } .tl2-edit:hover { opacity: 1; } }
.tl2-link     { cursor: pointer; }
.tl2-link:hover { text-decoration: underline; }

/* Body — notes, follow-up, entity refs */
.tl2-notes    { color: var(--text-primary); font-size: 0.875rem; line-height: 1.55; }
.tl2-followup     { font-size: 0.875rem; color: var(--text-muted); margin-top: 5px; }
.tl2-followup--due { font-size: 0.875rem; color: var(--status-warning); font-weight: 500; margin-top: 5px; }
.tl2-ref { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; padding: 4px 10px; background: var(--brand-subtle); border: 1px solid var(--brand-border); border-radius: 5px; font-size: 0.875rem; color: var(--brand-primary); cursor: pointer; }
.tl2-ref:hover { background: var(--brand-subtle-hover); }
.tl2-ref-expand { display: none; margin-top: 8px; padding: 10px; background: var(--tint-hover); border-radius: var(--radius); font-size: 0.875rem; }
.tl2-ref-expand.open { display: block; }

/* Ghost — system events. Color demotion on notes/meta, NEVER on type labels.
   Type labels keep their colors (PO=blue, Dispatch=amber, Sample=purple).
   Distinction: hollow dot + muted notes + no edit button. .tl2-ghost itself sets nothing (no opacity: fully legible). */
.tl2-ghost .tl2-notes  { color: var(--text-secondary); }
.tl2-ghost .tl2-meta   { color: var(--text-muted); }
.tl2-ghost .tl2-sep    { color: var(--text-muted); }

/* Entity chips — colored background pills on timeline entries */
.tl2-ents { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
.tl2-ent {
    display: inline-flex; align-items: center;
    font-size: 0.75rem; font-weight: 500; font-family: var(--font-data); /* 12px floor, rem; weight >=500 */
    padding: 2px 7px; border-radius: 4px; cursor: pointer;
}
.tl2-ent:hover  { filter: brightness(0.95); }
/* Shared entity palette (see --ent-* tokens) — same colours as the activity form. */
.tl2-ent--sample   { background: var(--ent-sample-bg);   color: var(--ent-sample-fg); }
.tl2-ent--po       { background: var(--ent-po-bg);       color: var(--ent-po-fg); }
.tl2-ent--receipt  { background: var(--ent-receipt-bg);  color: var(--ent-receipt-fg); }
.tl2-ent--dispatch { background: var(--ent-dispatch-bg); color: var(--ent-dispatch-fg); }
.tl2-ent--fg       { background: var(--ent-fg-bg);       color: var(--ent-fg-fg); }
.tl2-ent--rm       { background: var(--ent-rm-bg);       color: var(--ent-rm-fg); }

/* Milestones — centered tag between events */
.tl2-mile { display: flex; align-items: center; gap: 8px; padding: 12px 0; }
.tl2-mile-bar { flex: 1; height: 1px; background: var(--surface-border); }
.tl2-mile-tag {
    font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px;
    color: var(--text-muted); white-space: nowrap;
    padding: 2px 8px; border-radius: 3px; background: var(--tint-hover);
}

/* Courier tracking — structured data on timeline entries */
.tl2-courier {
    display: flex; align-items: center; gap: 5px; margin-top: 4px;
    font-size: 0.875rem; color: var(--text-secondary);
}
.tl2-courier svg { flex-shrink: 0; }
.tl2-courier-awb { font-family: var(--font-data); font-weight: 500; }
.tl2-courier-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.tl2-mini-btn {
    border: 1px solid var(--surface-border);
    background: var(--surface-card);
    color: var(--text-secondary);
    border-radius: 4px;
    padding: 4px 8px;
    font-size: 0.75rem;
    font-family: var(--font-ui);
    cursor: pointer;
}
.tl2-mini-btn:hover { background: var(--tint-hover); color: var(--text-primary); }

/* Month headers — collapsible */
.tl2-month    { padding: 14px 0 8px; font-size: 0.875rem; font-weight: 700; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid var(--surface-border); cursor: pointer; user-select: none; }
.tl2-month-chev { font-size: 0.875rem; }
.tl2-month-ct { font-weight: 400; font-size: 0.875rem; color: var(--text-muted); }
.tl2-month-body--hidden { display: none; }

/* Filter chips */
.tl2-chips { display: flex; gap: 6px; padding: 0 0 12px; flex-wrap: wrap; }
.tl2-chip {
    font-size: 0.875rem; font-weight: 500; font-family: var(--font-ui);
    padding: 6px 14px; border-radius: 20px;
    border: 1px solid var(--surface-border);
    background: transparent; color: var(--text-secondary);
    cursor: pointer;
}
.tl2-chip:active { transform: scale(0.97); }
.tl2-chip--on { background: var(--text-primary); color: var(--surface-card); border-color: var(--text-primary); }

/* Load more (legacy — replaced by collapsible months) */
.tl2-action-wrap { margin-bottom: 12px; }
.tl2-more     { text-align: center; padding: 16px 0; }
.tl2-more-btn { font-size: 0.875rem; color: var(--brand-primary); cursor: pointer; background: none; border: none; font-family: var(--font-ui); font-weight: 500; }
.tl2-more-count { font-size: 0.875rem; color: var(--text-muted); margin-top: 4px; }

/* Deprecated — date column layout. Kept for pages not yet migrated. */
.tl2-date     { width: 48px; flex-shrink: 0; text-align: right; padding-top: 1px; }
.tl2-day      { font-family: var(--font-data); font-size: 0.875rem; color: var(--text-secondary); }
.tl2-by       { font-size: 0.875rem; color: var(--text-muted); margin-top: 1px; }

/* ── Timeline comm-type colors (shared across all detail pages) ── */
.tl2-dot--visit, .tl2-dot--whatsapp           { background: var(--status-success); }
.tl2-dot--call, .tl2-dot--meeting              { background: var(--brand-primary); }
.tl2-dot--sample                               { background: var(--brand-primary); }
.tl2-dot--email, .tl2-dot--other               { background: var(--text-muted); }
.tl2-dot--dispatch                             { background: var(--status-warning); }
.tl2-dot--system                               { background: var(--text-muted); }
.tl2-dot--po                                   { background: var(--brand-primary); }
.tl2-type--visit, .tl2-type--whatsapp          { color: var(--status-success); }
.tl2-type--call, .tl2-type--meeting            { color: var(--brand-primary); }
.tl2-type--sample                              { color: var(--brand-primary); }
.tl2-type--email, .tl2-type--other             { color: var(--text-secondary); }
.tl2-type--dispatch                            { color: var(--status-warning); }
.tl2-type--system                              { color: var(--text-secondary); }
.tl2-type--po                                  { color: var(--brand-primary); }


/* ── MOVEMENT FEED ───────────────────────────────────────────
   Shared movement timeline component.
   Used by: inventory.html, fgview.html, rmview.html.
   Container + heading + filter chips + date-grouped entries.
   ─────────────────────────────────────────────────────────── */

/* Container + heading */
.mv-box     { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: var(--radius-lg); padding: 1rem 1.25rem; box-shadow: var(--shadow-1); }
.mv-heading { font-size: 1rem; font-weight: 700; color: var(--text-primary); margin-bottom: 0.75rem; }

/* Filter chips — square (6px radius), not pill-shaped */
.mv-chips        { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.375rem; }
.mv-chips:last-of-type { margin-bottom: 0.75rem; }
.mv-chip {
    font-size:      0.875rem;
    font-weight:    500;
    font-family:    var(--font-ui);
    padding:        0.375rem 0.75rem;
    border-radius:  var(--radius);
    border:         1px solid var(--surface-border);
    background:     transparent;
    color:          var(--text-secondary);
    cursor:         pointer;
}
.mv-chip:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
    .mv-chip:hover { border-color: var(--surface-border-strong); color: var(--text-primary); }
}
.mv-chip--on { background: var(--text-primary); color: var(--surface-card); border-color: var(--text-primary); }
@media (hover: hover) and (pointer: fine) {
    /* 2026-07-22 fix: .mv-chip:hover (above) has higher specificity
       than .mv-chip--on's plain class selector (one class + one
       pseudo-class vs. one class alone), so it always won the `color`
       conflict on an already-selected chip, regardless of source
       order — dark text (--text-primary) on the selected state's own
       dark background (--text-primary), invisible. Confirmed live.
       Restates the selected state's correct light-on-dark colors at
       matching specificity (.mv-chip--on:hover — one class + one
       pseudo-class, same as .mv-chip:hover) so it wins on the tie:
       hovering a selected chip now looks identical to its resting
       selected state (stable, readable) instead of inheriting the
       unselected chip's hover treatment. */
    .mv-chip--on:hover { color: var(--surface-card); border-color: var(--text-primary); }
}

/* Day group header — last 14 days shown as individual days */
.mv-grp {
    display:     flex;
    align-items: center;
    gap:         0.5rem;
    padding:     0.75rem 0 0.375rem;
    cursor:      pointer;
}
.mv-grp-dot  { width: 8px; height: 8px; border-radius: 50%; background: var(--brand-primary); flex-shrink: 0; }
.mv-grp-lbl  { font-family: var(--font-ui); font-size: 0.875rem; font-weight: 700; color: var(--text-primary); white-space: nowrap; }
.mv-grp-ct   { font-family: var(--font-data); font-size: 0.875rem; font-weight: 400; color: var(--text-muted); white-space: nowrap; }
.mv-grp-line { flex: 1; height: 1px; background: var(--surface-border); }
.mv-grp-chev { width: 14px; height: 14px; flex-shrink: 0; }
.mv-grp-chev svg { width: 14px; height: 14px; stroke: var(--text-muted); fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.15s ease-out; }
.mv-grp[aria-expanded="false"] .mv-grp-chev svg { transform: rotate(-90deg); }
.mv-grp[aria-expanded="false"] + .mv-grp-body { display: none; }

/* Month/year group header — older entries, muted, no dot */
.mv-mo {
    display:     flex;
    align-items: center;
    gap:         0.5rem;
    padding:     0.75rem 0 0.375rem;
    cursor:      pointer;
}
.mv-mo-lbl { font-family: var(--font-ui); font-size: 0.875rem; font-weight: 700; color: var(--text-muted); white-space: nowrap; }
.mv-mo-ct  { font-family: var(--font-data); font-size: 0.875rem; font-weight: 400; color: var(--text-placeholder); white-space: nowrap; }
.mv-mo[aria-expanded="false"] + .mv-grp-body { display: none; }

/* Movement entry row */
.mv-entry { padding: 0.5rem 0; border-bottom: 1px solid var(--surface-border); }
.mv-entry:last-child { border-bottom: 0; }

/* Top line (2026-08-17): dot + verb (+ [reversed]) + code | N items ▾ (right). Qty removed. */
.mv-entry-top { display: flex; align-items: center; gap: 0.375rem; }
/* Toggle sits directly on line 1 now (qty moved to .mv-entry-mid), so right-align it here
   — previously it was inside .mv-qty, which carried the margin-left:auto. */
.mv-entry-top .mv-cluster-toggle { margin-left: auto; }

/* Mid line (2026-08-17): date (left) + qty·unit (right). Keeps a long verb/tag from
   crowding the number, and pairs the movement's own date with its quantity. */
.mv-entry-mid { display: flex; align-items: baseline; gap: 0.375rem; padding-left: 1rem; margin-top: 0.125rem; }

/* Entry dot — 8px, colored by direction */
.mv-dot     { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.mv-dot--in   { background: var(--status-success); }
.mv-dot--out  { background: var(--status-error); }
.mv-dot--gate { background: var(--status-info); }
.mv-dot--adj  { background: var(--status-warning); }

/* Verb text — Sans, colored by direction */
.mv-verb       { font-family: var(--font-ui); font-size: 0.875rem; font-weight: 500; white-space: nowrap; }
.mv-verb--in   { color: var(--status-success); }
.mv-verb--out  { color: var(--status-error); }
.mv-verb--gate { color: var(--status-info); }
.mv-verb--adj  { color: var(--status-warning); }
.mv-verb--reversal { font-style: italic; } /* 2026-08-17: a reversal ("Released"/"Reversed") reads distinct from a fresh forward verb */

/* Item code — Mono, brand, clickable */
.mv-code { font-family: var(--font-data); font-size: 0.875rem; font-weight: 500; color: var(--brand-primary); cursor: pointer; }

/* Quantity — Mono, right-aligned */
.mv-qty { font-family: var(--font-data); font-size: 0.875rem; font-weight: 500; color: var(--text-primary); margin-left: auto; white-space: nowrap; }

/* Gate-effect qty (2026-07-19) — QC Passed and other on-hand-to-on-hand
   moves. No +/- sign (nothing was actually added or removed, see
   _mvEffect() in utils.js), and deliberately not colored like a real
   in/out — muted, same tone as de-emphasized data elsewhere (.mv-time). */
.mv-qty--gate { color: var(--text-muted); font-weight: 400; }

/* Bottom line: time/date + context */
.mv-entry-bot { display: flex; align-items: baseline; gap: 0.375rem; flex-wrap: wrap; padding-left: 1rem; margin-top: 0.125rem; }

/* Timestamp (under day header) or date (under month header) — Mono */
.mv-time { font-family: var(--font-data); font-size: 0.875rem; font-weight: 400; color: var(--text-placeholder); white-space: nowrap; }

/* Context — Sans, muted */
.mv-ctx       { font-size: 0.875rem; font-weight: 400; color: var(--text-muted); }
.mv-ctx strong { font-weight: 500; color: var(--text-secondary); }
.mv-ctx em    { font-style: italic; color: var(--text-muted); } /* explicit, not relying on browser default — reversal/reason asides */

/* Empty state inside movement box */
.mv-empty { padding: 2rem 0.5rem; text-align: center; color: var(--text-muted); font-size: 0.875rem; }

/* Verb-paired context (2026-07-19) — inline right after .mv-verb, whichever
   of location/reference answers that verb's own natural question. Same
   muted weight as .mv-ctx; it's still supporting detail, just relocated
   next to the verb instead of always sitting on the line below. */
.mv-paired { font-size: 0.875rem; font-weight: 400; color: var(--text-muted); margin-left: 2px; }

/* Reversal marker — was an inline style, now a real class */
.mv-reversed { color: var(--text-muted); font-weight: 400; margin-left: 4px; }

/* Cluster expand/collapse (2026-07-19) — a receipt is typically 20-40
   reels, a job-card issue typically 3-4; this collapses same-document
   rows into one summary with the individual rows revealed on demand.
   No-chrome button so it reads as part of the .mv-qty line it sits in,
   not as a separate UI control. */
.mv-cluster-toggle {
    font-family: var(--font-data); font-size: 0.875rem; font-weight: 500;
    color: var(--brand-primary); background: none; border: none; padding: 0;
    cursor: pointer;
}
.mv-cluster-toggle:hover { text-decoration: underline; }

.mv-cluster-children { padding-left: 1.5rem; margin-top: 0.375rem; display: flex; flex-direction: column; gap: 0.25rem; }
.mv-cluster-child    { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }
.mv-cluster-child .mv-code { font-size: 0.8125rem; }
.mv-cluster-child .mv-qty  { font-size: 0.8125rem; margin-left: auto; }


/* ── FILTER DROPDOWN ─────────────────────────────────────────
   Shared filter button + dropdown panel + active filter tags.
   Used by: inventory.html, fg.html, rm.html, customerpo.html.
   Panel contents differ per page; shell is shared.
   ─────────────────────────────────────────────────────────── */

.filter-wrap { position: relative; }
.filter-panel {
    position:      absolute;
    top:           calc(100% + 4px);
    left:          0;
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    padding:       0.875rem;
    width:         260px;
    box-shadow:    var(--shadow-dropdown);
    z-index:       30;
    display:       none;
}
.filter-panel.open { display: block; }
.filter-lbl {
    font-family:    var(--font-data);
    font-size:      0.75rem;
    font-weight:    700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color:          var(--text-placeholder);
    margin-bottom:  0.375rem;
}
.filter-opts { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.75rem; }
/* Pinned to the bottom of the panel's scrollport, not parked at the end of the
   content. The panel scrolls at 480px, so with a static footer the Apply button
   was simply below the fold on any panel with a few sections — picks made, no
   visible way to commit them. Sticky keeps it in view at every scroll position.
   The border and the shadow above it are also the scroll affordance: content
   visibly slides UNDER the footer, which is what tells you there is more of it.
   Negative margins + padding let the bar span the panel's full width and sit
   flush with its bottom edge, inside the panel's own 0.875rem padding. */
.filter-foot {
    display:      flex;
    gap:          0.5rem;
    position:     sticky;
    bottom:       -0.875rem;
    margin:       0.5rem -0.875rem -0.875rem;
    padding:      0.5rem 0.875rem 0.875rem;
    background:   var(--surface-card);
    border-top:   1px solid var(--surface-border);
    box-shadow:   0 -6px 8px -6px rgb(0 0 0 / 0.12);
    /* Match the panel's own bottom corners. Square ones leave two small arcs
       at the foot of the panel where scrolled content shows through beside the
       bar, which reads as a rendering fault rather than as depth. */
    border-bottom-left-radius:  var(--radius-lg);
    border-bottom-right-radius: var(--radius-lg);
}
.filter-tags { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.5rem; }
/* With no chips the row is 0px tall but still a flex ITEM, so it keeps its own
   margin AND a full gap slot from the column that holds it — 22px of blank
   space above the list whenever no filter is on, which is most of the time.
   Height zero is not the same as gone. Same reason .filter-badge:empty exists. */
.filter-tags:empty { display: none; }
/* --font-ui, not --font-data: a chip holds a criterion name ("5 ply",
   "TegaPack Industries", "Stock discrepancy"), which is label text, not the
   "IDs, codes, numbers" --font-data is defined for. It was mono, which also
   put it out of step with the "Clear all" beside it — that has always been
   --font-ui, and the two are meant to read as one row (Ra, 2026-09-25). */
.filter-tag {
    display:       inline-flex;
    align-items:   center;
    gap:           0.25rem;
    padding:       0.25rem 0.5rem;
    font-family:   var(--font-ui);
    font-size:     0.875rem;
    font-weight:   500;
    line-height:   1.35;
    background:    var(--brand-subtle);
    color:         var(--brand-primary);
    border:        1px solid var(--brand-border);
    border-radius: var(--radius-sm);
}
/* Names which criterion a chip belongs to. Needed once a panel offers more
   than one: "Idle" alone does not say whether it is a status or a state. */
.filter-tag-label { color: var(--text-muted); }
/* Written originally for inventory's <span>, so it never reset anything. On a
   <button> — which is what the other pages correctly use, because a remove
   control has to be reachable from the keyboard — the UA's own chrome came
   through: grey fill and a 2px outset border, which read as a tiny button
   sitting inside the chip. Everything the UA supplies is reset here so the
   element type no longer changes how the chip looks. */
.filter-tag-x {
    appearance:  none;
    background:  none;
    border:      none;
    padding:     0;
    margin:      0;
    color:       inherit;
    font:        inherit;
    font-size:   1rem;
    line-height: 1;
    cursor:      pointer;
    opacity:     0.6;
    /* 44px target without changing the glyph's size */
    position:    relative;
}
.filter-tag-x::after { content: ''; position: absolute; inset: -11px -8px; }
.filter-tag-x:focus-visible { opacity: 1; outline: 2px solid var(--brand-primary); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) { .filter-tag-x:hover { opacity: 1; } }

/* ── Filter panel, the rest of it ────────────────────────────────
   The shell above (.filter-wrap/.filter-panel/.filter-lbl/.filter-opts/
   .filter-foot/.filter-tags) was only ever half the component, so five pages
   grew their own copy of the other half: receipt (.rcp-filter-*), supplierpo
   (.spo-filter-*), samples (.samp-filter-*), fg (.fg-filter-*) and inventory
   (its own unprefixed extensions). These are those parts, taken from the
   receipt/supplierpo/samples lineage they shared, and put on the type scale:
   the section label, the checkbox row and Apply were all off it.

   The trigger is the existing .action-btn — inventory already used it for
   exactly this, so there is no .filter-btn here on purpose. Apply is
   .action-btn--primary inside .filter-foot. ── */

.filter-wrap { flex-shrink: 0; }

/* Trigger sized to sit in a .filterbar next to .field (30px), not as a
   free-standing action button. */
.filterbar .filter-wrap > .action-btn {
    height:         30px;
    padding:        0 10px;
    padding-top:    0;
    padding-bottom: 0;
}
.action-btn.has-active { border-color: var(--brand-primary); color: var(--brand-primary); }

/* Count of filters currently on, on the trigger. */
.filter-badge {
    min-width:       18px;
    height:          18px;
    padding:         0 5px;
    display:         inline-flex;
    align-items:     center;
    justify-content: center;
    border-radius:   9px;
    background:      var(--brand-primary);
    color:           var(--surface-card);
    font-family:     var(--font-data);
    font-size:       0.75rem;
    font-weight:     700;
}
/* No filters on, nothing to say. Pages set textContent and stop thinking
   about visibility — three of them were each doing this differently, and
   supplierpo's markup and its JS had already drifted apart. */
.filter-badge:empty { display: none; }

/* Panels anchored to a right-aligned trigger. inventory carried this
   page-locally; every other panel in this family wanted it too. */
.filter-panel--align-right { left: auto; right: 0; }
/* Opened downwards the panel can run off a short viewport, so it is capped and
   scrolls; --filter-panel-max-h is set by the page after measuring the space
   actually left below (or above) the trigger, and --flip-up opens it upwards
   when there is more room there. Came from inventory.html, which was the only
   page that had solved this. */
.filter-panel          { max-height: var(--filter-panel-max-h, 480px); max-width: calc(100vw - 32px); overflow-y: auto; }
.filter-panel--flip-up { top: auto; bottom: calc(100% + 4px); }
/* Several facet groups rather than one list of options (fg.html). */
.filter-panel--wide    { width: min(24rem, calc(100vw - 2rem)); }

/* ── Grouped zone + "Load more" ──────────────────────────────────
   A .grp-header and the block it collapses. rm.html called the wrapper
   .rm-zone and receipt.html .rcp-zone; it is the same 8px separation
   between one group and the next, so it lives here now. The body itself
   needs no rule — a div is already block, and NOT styling its display is
   what lets the [hidden] attribute collapse it. ── */
.grp-zone { margin-bottom: 8px; }

/* Tail of a paginated group: "Showing 10 of 34  Load more". customerpo and
   supplierpo currently do this with .showing-count plus an inline
   text-align/padding; they can drop that and use this. */
.load-more {
    padding:    10px 0;
    text-align: center;
    font-size:  0.875rem;
    font-weight: 500;
    color:      var(--text-muted);
}
.load-more button {
    margin-left: 4px;
    padding:     0;
    border:      none;
    background:  none;
    cursor:      pointer;
    font-family: inherit;
    font-size:   0.875rem;
    font-weight: 500;
    color:       var(--brand-primary);
}
@media (hover: hover) and (pointer: fine) { .load-more button:hover { text-decoration: underline; } }

.filter-sec                { margin-bottom: 10px; }
.filter-sec:last-of-type   { margin-bottom: 0; }

/* One selectable option. 0.875rem carries 500. */
.filter-chk {
    display:       flex;
    align-items:   center;
    gap:           8px;
    padding:       5px 8px;
    border-radius: var(--radius-sm);
    cursor:        pointer;
    font-size:     0.875rem;
    font-weight:   500;
    color:         var(--text-secondary);
    transition:    background 0.08s;
}
@media (hover: hover) and (pointer: fine) { .filter-chk:hover { background: var(--tint-hover); } }
/* filterpanel.js makes the option rows focusable (2026-10-18); show where focus is. */
.filter-chk:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: -2px; }
.filter-chk input[type="checkbox"] {
    width:        16px;
    height:       16px;
    accent-color: var(--brand-primary);
    cursor:       pointer;
    flex-shrink:  0;
    margin:       0;
}
.filter-chk--on { color: var(--brand-primary); }

/* Selected-state tick — BX.icons.check, the shared 16px glyph, not a bespoke
   one and not an <input type="checkbox">. The checkbox was doing two jobs at
   once: drawing a tick, and being a second copy of the selected state that the
   row's own colour already carries. This keeps the tick and drops the input.
   visibility rather than display, and the slot is always in the layout, so a
   row does not shift sideways the moment it is picked. The icon draws with
   stroke="currentColor", so it inherits the brand colour from --on for free. */
.filter-chk-tick {
    display:     inline-flex;
    align-items: center;
    flex-shrink: 0;
    width:       16px;
    height:      16px;
    visibility:  hidden;
}
.filter-chk--on .filter-chk-tick { visibility: visible; }

/* The count sits on the row, so it belongs to the row's state. .filter-chk-n
   pins its own muted colour, which meant a selected row turned brand blue
   everywhere except its number — the one part that reads as data. Inherit. */
.filter-chk--on .filter-chk-n { color: inherit; }

/* An option matching nothing is not rendered at all — see the row builders in
   samples.html / receipt.html / supplierpo.html. It was briefly shown-but-
   dimmed; an option you can see and cannot use is still something to read past,
   and with no count on the row there is nothing left for it to tell you. An
   option still renders while it is selected, even at zero, or there would be no
   way to clear it.
   (No .filter-chk--empty rule here on purpose: nothing emits that class now.)

   The same logic one level up: once every option in a section has dropped out,
   the section is a heading with nothing under it. Measured on /supplierpo,
   where no PO carries an edit request: "Needs attention" sat there as 16px of
   label and no rows. :has() on the section is what lets the rule live here
   rather than making each page remember to hide its own empty sections.
   .filter-empty is exempt: a section that deliberately explains its own
   emptiness ("No suppliers yet.") is saying something, unlike a bare label. */
/* .mv-chip counts too: a section whose options render as chips (fg's Ply and
   Ups, the short numeric facets) is just as empty as a row-based one when it
   has nothing to offer, and has to disappear the same way. Inventory's own
   chip facets are unaffected — its panel puts .filter-lbl/.filter-opts
   directly in the panel, not inside a .filter-sec. */
.filter-sec:not(:has(.filter-chk)):not(:has(.mv-chip)):not(:has(.filter-empty)) { display: none; }

/* Trailing count on an option ("Ballarpur  12"). */
.filter-chk-n { margin-left: auto; font-family: var(--font-data); font-size: 0.75rem; font-weight: 700; color: var(--text-placeholder); }

/* Nothing to choose from. Was 0.8125rem on receipt — off the scale. */
.filter-empty { padding: 5px 8px; font-size: 0.875rem; font-weight: 500; color: var(--text-placeholder); }

/* Apply fills the footer. */
.filter-foot .action-btn { flex: 1; justify-content: center; }

/* "Clear all", an action sitting among the tags. Same size as the tags it
   clears — it is separated from them by colour, not by being smaller. */
.filter-clear {
    display:       inline-flex;
    align-items:   center;
    /* Same box as .filter-tag — padding, radius and border width — so the
       chips and this sit on one line at one height. */
    padding:       0.25rem 0.5rem;
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-sm);
    background:    none;
    cursor:        pointer;
    font-family:   var(--font-ui);
    font-size:     0.875rem;
    font-weight:   500;
    line-height:   1.35;
    color:         var(--text-muted);
    white-space:   nowrap;
}
@media (hover: hover) and (pointer: fine) {
    .filter-clear:hover { border-color: var(--status-error); color: var(--status-error); }
}
.filter-clear:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }

@media (max-width: 860px) {
    /* Icon-only trigger, full-bleed panel. */
    .filter-btn-label { display: none; }
    .filterbar .filter-wrap > .action-btn { padding: 0 8px; }
    .filter-panel { width: calc(100vw - 24px); left: auto; right: 0; }
}


/* ── PRODUCTION PIPELINE ─────────────────────────────────────
   Shared: jobcardview pipeline column. Vertical step-by-step tracker. */

.pipe-list { position: relative; padding-left: 24px; }
.pipe-list::before {
    content:    '';
    position:   absolute;
    left:       7px; top: 12px; bottom: 12px;
    width:      2px;
    background: var(--surface-border);
}
/* End cap — the connector line terminates once the route's last machine
   is done, independent of the job card's own status (Completed/Closed/
   still open for closure sign-off all show it the same way). Without
   this the line just stops mid-air, reading as "more below" even when
   there isn't. */
.pipe-list--capped::after {
    content:    '';
    position:   absolute;
    left:       4px; bottom: 10px;
    width:      8px; height: 2px;
    background: var(--surface-border);
}
.pipe-item { position: relative; padding: 12px 0 12px 16px; border-bottom: 1px solid var(--surface-border); }
.pipe-item:last-child { border-bottom: none; }
.pipe-dot {
    position:      absolute;
    left:          -24px; top: 14px;
    width:         16px; height: 16px;
    border-radius: 50%;
    border:        2px solid var(--surface-border);
    background:    var(--surface-card);
    display:       flex; align-items: center; justify-content: center;
}
.pipe-dot--done   { background: var(--status-success); border-color: var(--status-success); }
.pipe-dot--active { border-color: var(--brand-primary); }
.pipe-dot--hold   { border-color: var(--status-warning); }

.pipe-pulse {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--brand-primary);
    animation: pipe-pulse 1.4s ease-in-out infinite;
}
.pipe-hold-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--status-warning);
}
@keyframes pipe-pulse {
    0%,100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.5; transform: scale(0.7); }
}

/* Type scale for this whole section, capped at three sizes (0.75 / 0.875
   / 1rem — 12 / 14 / 16px), 0.75rem used sparingly: only the machine-
   route pill, the Expected caption, and "Recorded by". Everything else
   that's real row data is 0.875rem; the machine name (the row's own
   headline) is 1rem. */
.pipe-machine      { font-size: 1rem; font-weight: 400; color: var(--text-primary); margin-bottom: 4px; }
.pipe-machine.done { color: var(--text-muted); }
.pipe-spb-pill { font-size: 0.75rem; font-weight: 500; padding: 1px 7px; border-radius: 4px; background: var(--tint-hover); color: var(--text-muted); border: 1px solid var(--surface-border); margin-left: 6px; vertical-align: middle; }

/* .pipe-row: mobile default is a wrapped flex line (each cell that needs
   its own line sets flex-basis:100%; Good/Rejected/Yield don't, so they
   sit inline together, wrapping only when they run out of room). At
   >=768px this becomes the real grid table — see the media query below. */
.pipe-row { display: flex; flex-wrap: wrap; column-gap: 16px; row-gap: 4px; }
.pipe-machine,
.pipe-cell-expected,
.pipe-run-time,
.pipe-recorded,
.pipe-waiting-cell { flex-basis: 100%; }

.pipe-stat-lbl { font-size: 0.875rem; color: var(--text-muted); }
.pipe-stat-val { font-size: 0.875rem; color: var(--text-primary); font-weight: 500; font-family: var(--font-data); }
.pipe-stat-val.jc-muted { color: var(--text-muted); font-weight: 500; font-family: var(--font-ui); }
.pipe-stat-caption { font-size: 0.75rem; color: var(--text-muted); margin-top: 2px; }
.pipe-cell-yield .pipe-stat-val { color: var(--status-success); }

.pipe-waiting-cell { font-size: 0.875rem; color: var(--text-placeholder); }

.pipe-run-time { font-size: 0.875rem; }
.pipe-run-time .pipe-stat-val { font-weight: 500; }
.pipe-run-time .pipe-dur { font-size: 0.875rem; color: var(--text-muted); font-weight: 500; }

.pipe-recorded { font-size: 0.75rem; color: var(--text-muted); }

.jc-pipe-paused { font-size: 0.875rem; color: var(--status-warning); margin-top: 6px; }

/* Production Run timeline — closure interruption marker, sits between the
   last machine that actually ran and the first one that never will. */
.pipe-interrupt { position: relative; padding: 8px 0 8px 16px; font-size: 0.75rem; font-weight: 500; color: var(--status-error); }
.pipe-interrupt-icon { position: absolute; left: -24px; top: 8px; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; }
.pipe-interrupt-icon svg { width: 14px; height: 14px; }

.pipe-corrug-card { margin-top: 10px; padding: 10px 12px; border-radius: var(--radius); background: var(--tint-hover); display: flex; gap: 20px; flex-wrap: wrap; }
.pipe-corrug-lbl  { font-size: 0.75rem; color: var(--text-muted); margin-bottom: 2px; }
.pipe-corrug-val  { font-size: 0.875rem; font-family: var(--font-mono); color: var(--text-primary); font-weight: 500; }
.pipe-corrug-val.ok { color: var(--status-success); }

.pipe-dot-icon     { color: var(--surface-page); display: flex; align-items: center; justify-content: center; }
.pipe-dot-icon svg { width: 9px; height: 9px; }

/* Column header row — only meaningful once the grid table exists, so it
   stays hidden until that breakpoint (see below). */
.pipe-head { display: none; }

/* ── Desktop table (>=768px) ──────────────────────────────────
   Structural breakpoint, not fluid type (PRODUCT.md: "design for the
   floor first, refine for the desk" — the stacked layout above is the
   baseline; this is the desktop refinement on TOP of it, not a
   replacement). Same markup both sides — only display/grid-column
   change. Every cell gets an EXPLICIT grid-column rather than relying on
   DOM-order auto-placement, so a row missing a cell (e.g. Yield on every
   row but the last, or Good/Rejected on a still-running machine) never
   shifts the cells after it out of their column. */
@media (min-width: 768px) {
    /* Defined on body, not .pipe-list--table — .pipe-head is that
       element's PRECEDING SIBLING, not a descendant, so a custom
       property set there would never be visible to it. body is a real
       ancestor of both, which is what inheritance actually needs. */
    body { --pipe-cols: minmax(130px,1fr) 1.3fr 0.7fr 0.7fr 0.7fr 1.15fr 1fr; }
    .pipe-list--table {
        padding-left: 28px;
    }
    /* .pipe-item's box starts 4px further right here (28px vs the 24px
       baseline .pipe-dot / .pipe-interrupt-icon's left:-24px assumes), so
       both need to shift 4px further left to keep the dot centered on
       the connector line (.pipe-list::before, unaffected by this padding
       change since it's positioned relative to .pipe-list itself). */
    .pipe-list--table .pipe-dot,
    .pipe-list--table .pipe-interrupt-icon {
        left: -28px;
    }
    .pipe-head {
        display: grid; grid-template-columns: var(--pipe-cols); column-gap: 16px;
        padding: 0 0 8px 28px; margin-bottom: 4px;
        border-bottom: 1px solid var(--surface-border);
    }
    .pipe-head span { font-size: 0.75rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }

    .pipe-list--table .pipe-row {
        display: grid; grid-template-columns: var(--pipe-cols); column-gap: 16px; align-items: start;
    }
    .pipe-list--table .pipe-machine          { grid-column: 1; margin-bottom: 0; }
    .pipe-list--table .pipe-cell-expected    { grid-column: 2; }
    .pipe-list--table .pipe-cell-good        { grid-column: 3; }
    .pipe-list--table .pipe-cell-rejected    { grid-column: 4; }
    .pipe-list--table .pipe-cell-yield       { grid-column: 5; }
    .pipe-list--table .pipe-run-time         { grid-column: 6; }
    .pipe-list--table .pipe-recorded         { grid-column: 7; }
    .pipe-list--table .pipe-waiting-cell     { grid-column: 2 / -1; }

    /* Column headers already label the data — the inline "Expected:" /
       "Good:" / "Run time:" prefixes would be redundant noise here. */
    .pipe-list--table .pipe-stat-lbl,
    .pipe-list--table .pipe-run-time .pipe-stat-lbl { display: none; }
}

/* ── APPROVAL DOTS (closure workflow) ────────────────────────
   Slots after PRODUCTION PIPELINE section */

.approval-dots  { display: flex; gap: 12px; align-items: center; margin: 12px 0; }
.approval-dot   { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.approval-dot-circle {
    width:         14px; height: 14px;
    border-radius: 50%;
    border:        2px solid var(--surface-border);
}
.approval-dot-circle.done { background: var(--status-success); border-color: var(--status-success); }
.approval-arrow     { color: var(--text-muted); display: inline-flex; align-items: center; }
.approval-arrow svg { width: 14px; height: 14px; }

/* ── Fulfillment trail — dispatch history within PO line cards ── */
/* Shared: customerpoview. Reusable on any page showing per-line dispatch trail. */

.fl-expand     { display: none; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--surface-border); }
.fl-expand.open { display: block; }

/* CRITICAL: text-decoration:none + color:inherit required — used as <a> tag */
.fl-dispatch   { display: flex; gap: 10px; padding: 8px 0; font-size: 14px; cursor: pointer; min-height: 44px; align-items: flex-start; text-decoration: none; color: inherit; border-radius: var(--radius); transition: background 0.1s; }
@media (hover: hover) and (pointer: fine) { .fl-dispatch:hover { background: var(--tint-hover); margin: 0 -6px; padding: 8px 6px; } }
.fl-dispatch-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--status-success); margin-top: 7px; flex-shrink: 0; }
.fl-dispatch-dot--delivered  { background: var(--status-success); }
.fl-dispatch-dot--dispatched { background: var(--brand-primary); }
.fl-dispatch-dot--draft      { background: transparent; border: 1.5px solid var(--text-muted); }
.fl-dispatch-body  { flex: 1; min-width: 0; }
.fl-dispatch-qty   { font-family: var(--font-data); font-weight: 700; color: var(--text-primary); }
.fl-dispatch-meta  { font-size: 14px; color: var(--text-muted); margin-top: 2px; }
.fl-dispatch-meta a { color: var(--brand-primary); text-decoration: none; font-weight: 700; }
@media (hover: hover) and (pointer: fine) { .fl-dispatch-meta a:hover { text-decoration: underline; } }
.fl-dispatch-chev  { color: var(--text-muted); margin-left: auto; flex-shrink: 0; margin-top: 5px; }
.fl-dispatch-remaining { padding: 6px 0 0 17px; font-size: 14px; color: var(--text-muted); }
/* text-align: left is not cosmetic here. This is a <button>, and a button
   defaults to center. While the label fit on one line nobody noticed; once the
   trail summary grew to four counts it wrapped, and the wrapped remainder
   centred itself mid-card. Buttons whose label is a sentence align like text. */
.fl-toggle     { font-size: 14px; color: var(--brand-primary); cursor: pointer; background: none; border: none; font-family: var(--font-ui); font-weight: 700; margin-top: 8px; padding: 0; text-align: left; }

/* .fl-count (one count and its own label, kept on a single line) shares the one no-wrap rule with .nowrap, under the table section. */
@media (hover: hover) and (pointer: fine) { .fl-toggle:hover { text-decoration: underline; } }
.fl-more       { font-size: 14px; color: var(--brand-primary); cursor: pointer; background: none; border: none; font-family: var(--font-ui); font-weight: 500; padding: 4px 0; }

/* Completed line — compressed row */
.fl-done       { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-radius: var(--radius); border: 1px solid var(--surface-border); opacity: 0.5; font-size: 14px; color: var(--text-muted); cursor: pointer; transition: opacity 0.15s, border-color 0.15s; }
@media (hover: hover) and (pointer: fine) { .fl-done:hover { opacity: 0.75; border-color: var(--surface-border-strong); } }
.fl-done-name   { font-weight: 700; color: var(--text-secondary); }
.fl-done-detail { font-family: var(--font-data); font-size: 14px; margin-left: auto; }
.fl-done-expanded        { display: none; }
.fl-done-expanded.open   { display: block; }
.fl-done.hidden          { display: none; }
.fl-done > svg           { color: var(--status-success); }

/* Closed line — compressed row with strikethrough */
.fl-closed     { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-radius: var(--radius); border: 1px solid var(--surface-border); opacity: 0.45; font-size: 14px; color: var(--text-muted); cursor: pointer; transition: opacity 0.15s; }
@media (hover: hover) and (pointer: fine) { .fl-closed:hover { opacity: 0.65; } }
.fl-closed > svg  { color: var(--text-placeholder); }
.fl-closed-name   { text-decoration: line-through; font-weight: 700; color: var(--text-muted); }
.fl-closed-reason { font-style: italic; margin-left: auto; }
.fl-closed-expanded      { display: none; }
.fl-closed-expanded.open { display: block; }
.fl-close-details        { margin-top: 10px; border-top: 1px dashed var(--surface-border); padding-top: 10px; }
.fl-closed.hidden        { display: none; }



/* ── Modal alerts ─────────────────────────────────────────── */

.m-alert       { padding:9px 13px; border-radius:7px; font-size:14px; margin-bottom:12px; display:none; }
.m-alert.show  { display:block; }
.m-alert-error { background:var(--status-error-bg); border:1px solid var(--status-error-border); color:var(--status-error-text); }
.m-alert-warning { background:var(--status-warning-bg); border:1px solid var(--status-warning-border); color:var(--status-warning-text); margin-bottom:0; margin-top:8px; }

/* Factory-context-mismatch notice (BOX-316) — rendered by BX.showFactoryMismatch.
   Plain box; message in warning colour, switch as an inline text+icon link. */
/* 2026-08-09 (Ra ruling): sticky, not scroll-to-top. Forcing the whole
   page to jump to top just to reveal an inline banner is jarring if the
   operator was scrolled deep into a long detail page — position:sticky
   keeps this pinned near the top of whatever's actually scrolling
   (the page, or a drawer's own .fp-body — see the override below) as
   they scroll past it, no viewport jump needed. Same --topbar-height/
   --z-sticky tokens .detail-tabs already uses for the identical
   stick-below-the-topbar pattern. */
.fac-alert  { display:none; align-items:center; justify-content:space-between; gap:12px; margin:14px 0 0; padding:10px 14px; background:var(--surface-card); border:1px solid var(--surface-border); border-radius:var(--radius-lg); font-size:14px; color:var(--text-secondary); position:sticky; top:var(--topbar-height); z-index:var(--z-sticky); }
/* Inside a drawer (.fp-body), there's no page topbar to clear — the
   drawer's own title-bar/header already sit outside .fp-body's scroll
   box (siblings, not scrolled with it), so this only needs to stick to
   the top of that scroll box itself. */
.fp-body .fac-alert { top: 0; }
.fac-msg    { color:var(--status-warning); font-weight:500; }
.fac-switch { display:inline-flex; align-items:center; gap:6px; flex-shrink:0; background:none; border:none; padding:0; font:inherit; font-weight:500; color:var(--brand-primary); cursor:pointer; white-space:nowrap; }
.fac-switch svg { width:16px; height:16px; }
/* 2026-08-09 (Ra ruling): re-attempting a blocked action while this
   strip is already visible re-triggers this — see showFactoryMismatch()
   (utils.js) for the remove/reflow/add logic that restarts it even on
   back-to-back clicks. A brief background pulse using tokens already in
   the palette, not a shake/bounce (PRODUCT.md motion rule). */
@keyframes fac-alert-attn {
    0%, 100% { background: var(--surface-card); }
    30%      { background: var(--tint-hover); }
}
.fac-alert--attn { animation: fac-alert-attn 0.6s ease; }
@media (prefers-reduced-motion: reduce) {
    .fac-alert--attn { animation: none; }
}


/* ── Rejection banner ─────────────────────────────────────── */

.rejection-banner { background:var(--status-error-bg); border:1px solid var(--status-error-border); border-radius:10px; padding:12px 16px; margin-bottom:16px; font-size:14px; color:var(--status-error-text); }


/* ── Type pill (supplier/customer category tag) ───────────── */

.type-pill { display:inline-block; padding:2px 8px; border-radius:20px; font-size:12px; font-weight:500; background:var(--brand-subtle); color:var(--brand-primary); margin-right:3px; }


/* ── Docket status chips (supplierview comm log) ──────────── */

.ds-booked                 { background:var(--color-slate-500-a12); color:var(--text-muted);      }
.ds-transit                { background:var(--brand-subtle-hover);  color:var(--brand-primary);   }
.ds-city                   { background:var(--status-success-bg);   color:var(--status-success-text);  }
.ds-delayed                { background:var(--status-error-bg);     color:var(--status-error-text);    }
.ds-received,.ds-delivered { background:var(--status-success-bg);   color:var(--status-success-text);  }


/* ── Form section title (bom, fg, shared modal forms) ─────── */

.form-section-title { font-size:14px; font-weight:700; letter-spacing:1.5px; text-transform:uppercase; color:var(--brand-primary); margin-bottom:14px; padding-bottom:8px; border-bottom:1px solid var(--brand-subtle-hover); }


/* Phone: sidebar fully hidden */
@media (max-width: 480px) {
    .sidebar               { transform: translateX(-100%); }
    .sidebar.open          { transform: translateX(0); }
    .main                  { margin-left: 0; }
    body.sidebar-is-collapsed .main { margin-left: 0; }
    .hamburger             { display: block; }
}

/* Mobile page layout: single column, tabs, compact spacing */
@media (max-width: 860px) {
    .content               { padding: 14px; }
    .topbar                { padding: 12px 14px; }
    body:has(.action-row) .topbar-actions { display: none; }
    .detail-cols           { grid-template-columns: 1fr; border: none; margin-top: 0; }
    /* Single column now — drop the 20px column padding too, not just the
       divider. Left in, it inset the main-column cards 20px further than the
       full-width brief above them (a direct .content child). Zeroing here lets
       every detail page's cards align to the same edge as the brief. Pages
       needing inner gutters set their own .detail-main override (customerview). */
    .detail-main           { padding: 0; border-right: none; order: 1; min-width: 0; }
    .detail-aside          { order: 2; }
    .detail-tabs           { display: flex;
                             padding: 0; margin-top: 10px; position: sticky; top: 56px;
                             z-index: 10; background: var(--surface-page); }
    .detail-main.tab-hidden,
    .detail-aside.tab-hidden { display: none; }
    /* Only a tabbed detail page (one column is always .tab-hidden) needs the visible column to fill the screen; a page without tabs (userview) would get blank gaps. */
    .detail-cols:has(> .tab-hidden) > .detail-main:not(.tab-hidden),
    .detail-cols:has(> .tab-hidden) > .detail-aside:not(.tab-hidden) { min-height: 70vh; }
    .detail-hdr            { padding: 12px 0 0; }
    .detail-meta-sep       { display: none; }   /* the bars dangle at wrap points on a phone: the 10px gap separates the items */
    .detail-hdr-top        { flex-direction: column; gap: 10px; }
    .action-bar            { padding: 10px 0 0; }
    .brief                 { margin: 10px 0 0; }
    .urgency               { margin: 8px 0 0; }
    .action-row            { display: flex; }
    .stats-row             { grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 14px; }
    .fr, .fr-3             { grid-template-columns: 1fr; }
    .hamburger             { display: block; }
}

/* ── Detail-page mobile card-flatten — opt-in via .detail-cols--flat ──
   Pages that put .detail-cols--flat on their .detail-cols get the
   .detail-main cards flattened from boxed .card cards into plain
   header + hairline-divider sections on mobile only (desktop keeps
   the boxed-card look). Opt-in — the other detail pages that use
   .detail-cols (customerpoview/supplierpoview/rmview/fgview/
   sampleview/inventory) stay at their current values until they
   adopt the class. Adopters: customerview, supplierview.
   :last-child (not a hardcoded #cardPayments id) drops the trailing
   divider whichever card renders last. */
@media (max-width: 768px) {
    .detail-cols--flat .detail-main { padding: 10px; }
    .detail-cols--flat .detail-main .card {
        background:     transparent;
        border:         none;
        border-radius:  0;
        box-shadow:     none;
        border-bottom:  1px solid var(--surface-border);
        padding-bottom: 14px;
        margin-bottom:  14px;
    }
    .detail-cols--flat .detail-main .card:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }
    .detail-cols--flat .detail-main .card-head { padding: 0 0 10px; }
    .detail-cols--flat .detail-main .card-body { padding: 8px 0 0 8px; }
}


/* ============================================================
   PART D — COMPONENT STANDARDS (BOX-141)
   Source: design_handoff_boxflow_redesign/prototype/styles.css

   Rules:
   - Every value uses var(--semantic-token)
   - These are the STANDARDS for Phase E pages
   - Old pages keep their inline styles until touched
   - When an old page is touched: replace inline table/card CSS
     with these classes and delete the inline rules

   D01  Card                 — .card general wrapper
   D02  Table                — .table-wrap + .table + cell modifiers + .row-urgent
   D03  Eyebrow label        — .eyebrow
   D04  Definition list      — .dl (view page info rows, new pages)
   D05  Filter bar           — .filterbar + .field (new pages only)
   D06  Stat tile            — .stat-tile (pure CSS, no JS)
   D07  List item            — .list-item + .list-item--urgent (mobile card layout)
   D08  Progress bar (legacy) — .bar, .bar-row, .bar-pct (with/without label)
   D08b Unified progress     — .progress, .progress--bar, .progress--ring, .progress-val
   D09  Group header         — .grp-header + .grp-dot + .grp-label + .grp-count
   D10  Period picker        — .period-btn, .period-popup, .period-opt
   D11  Archive section      — .archive, .archive-head, .archive-toggle, .archive-pagn
   D12  Responsive visibility— .view-desktop, .view-mobile
   ============================================================ */


/* ── D01  Card ────────────────────────────────────────────── */
/* General elevated container. Wraps tables, dl blocks, etc.  */
/* Usage: <div class="card"> ... </div>                        */

.card {
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    overflow:      hidden;
    box-shadow:    var(--shadow-1);
}
.card-head {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    padding:         12px 16px;
    border-bottom:   1px solid var(--surface-border);
}
.card-title {
    font-size:      14px;
    font-weight:    700;
    color:          var(--text-primary);
    letter-spacing: 0.2px;
    display:        flex;
    align-items:    center;
    gap:            8px;
}
.card-link {
    font-size:   14px;
    color:       var(--brand-primary);
    font-weight: 500;
    cursor:      pointer;
}
.card-link:hover { text-decoration: underline; }
.card-body { padding: 14px 16px; }


/* ── D02  Table ───────────────────────────────────────────── */
/* Standard data table. Always place inside .card or .table-wrap. */
/* Usage: <div class="card"><div class="table-wrap">           */
/*          <table class="table"> ... </table>                  */
/*        </div></div>                                          */

.table-wrap {
    width:                     100%;
    overflow-x:                auto;
    -webkit-overflow-scrolling: touch;
}
/* When directly inside a .card the border-radius is inherited */
.card > .table-wrap { border-radius: inherit; }

.table {
    width:           100%;
    border-collapse: collapse;
    font-size:       14px;
    min-width:       500px;  /* prevents columns collapsing too far */
}
/* The second selector is a column-label row INSIDE a body (<tr><th scope="col">), for a grouped table whose groups each
   carry their own labels where they give context (customerview Statement). Same look as the head, no extra class. */
.table thead th,
.table tbody th[scope="col"] {
    padding:        var(--cell-py) var(--cell-px);
    text-align:     left;
    font-size:      12px;
    font-weight:    700;
    letter-spacing: 1.1px;
    text-transform: uppercase;
    color:          var(--text-secondary);  /* 4.5:1+ contrast vs text-muted's 3.87:1 */
    background:     var(--surface-sunk);
    border-bottom:  1px solid var(--surface-border);
    height:         32px;
    white-space:    nowrap;
}
.table tbody td {
    padding:         var(--cell-py) var(--cell-px);
    height:          var(--row-h);
    color:           var(--text-secondary);
    border-bottom:   1px solid var(--surface-border);
    vertical-align:  middle;
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr { transition: background 0.08s; }
.table tbody tr:hover td { background: var(--tint-subtle); }
.table tbody tr.selected td { background: var(--brand-subtle); }

/* Cell modifier classes — add to <td> or <th> */
.table .col-num   { font-family: var(--font-data); text-align: right; font-variant-numeric: tabular-nums; }
.table .col-code  { font-family: var(--font-data); font-size: 14px; color: var(--text-primary); font-weight: 700; }
/* 14px carries weight 500. The row title is separated from the lines under it
   by colour — --text-primary against the --text-muted of .col-sub and
   .list-item-meta — not by a heavier weight, which is the same rule that
   stops a secondary line being shrunk instead of dimmed. */
.col-title        { font-size: 14px; color: var(--text-primary); font-weight: 500; }
/* Pipeline pills (customers.html, suppliers.html): two to a row, stacked, each column as wide as its widest pill, instead of one long row.
   Follow-up due | 1 PO
   2 samples
   Put on a <div> inside the cell: a table cell cannot itself be a grid. */
.col-pipeline     { display: grid; grid-template-columns: repeat(2, max-content); gap: 4px 6px; justify-items: start; align-items: center; }
/* The first column of a list table (the customer / supplier name): wide enough for a real company name and its ID and GST line without wrapping. */
.col-name         { min-width: 14rem; }
.col-actions      { width: 72px; text-align: right; white-space: nowrap; }
/* Numeric cell: size comes from the table, the data face and tabular figures
   come from .table .col-num above. Only the wrap behaviour is added here. */
/* One definition of "keep this on one line" (a count and its word, a date and its time): .nowrap is the general utility; .col-num and .fl-count are the same
   behaviour under the names their pages already use (one rule, not three copies). */
.nowrap, .col-num, .fl-count { white-space: nowrap; }

/* ── People list (Users page): the name cell and the muted rows ───────────────────────────────────────────────────────────────
   The name is a real link (Tab stop, Enter, middle-click); a click anywhere else on the row or card is handled by one delegated listener on the page.
   .user-name-line holds the name and, when there is one, its status tag side by side. .u-username is the @username in the data face. */
.user-name-line { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; }
.user-link      { text-decoration: none; }
/* The card's title already sets the colour; the link takes it. In a table cell the link is .col-title, which sets its own colour (--text-primary). */
.list-item-title .user-link { color: inherit; }
@media (hover: hover) and (pointer: fine) { .user-link:hover { text-decoration: underline; } }
.user-link:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; border-radius: var(--radius-sm); }
/* The card title clips its overflow (ellipsis) and so does the phone list: draw the ring inside the link's own box so it is never cut. */
.list-item-title .user-link:focus-visible { outline-offset: -2px; }
.u-username     { font-family: var(--font-data); }
/* Inactive and Deleted people are muted through colour, never whole-row opacity (it dropped the text to about 2.5:1 contrast). */
.table tbody tr.u-off td, .table tbody tr.u-off .col-title,
.list-item.u-off .list-item-title, .list-item.u-off .list-item-meta { color: var(--text-muted); }
/* A list table whose columns keep the same widths in every group, so the groups line up one under another. */
.users-table                  { table-layout: fixed; }
.users-table th:nth-child(1)  { width: 26%; }
.users-table th:nth-child(2)  { width: 16%; }
.users-table th:nth-child(3)  { width: 18%; }
.users-table th:nth-child(4)  { width: 24%; }
.users-table .col-actions     { width: 7.5rem; }
.users-table td               { overflow-wrap: anywhere; }   /* a long (e-mail style) username wraps inside its own cell instead of running into the Role column */
.col-num-sub      { font-size: 12px; font-weight: 700; color: var(--text-muted); }
/* Relative "last contact" date. Data face so the column lines up. */
.last-act         { font-size: 14px; font-weight: 500; color: var(--text-muted); font-family: var(--font-data); white-space: nowrap; }
.last-act-stale   { color: var(--status-warning); }
a.col-title       { text-decoration: none; display: block; }
/* Two-line cells. The second line is de-emphasised by colour, never by a
   smaller size. The identifier line keeps the 12px step because an id plus
   a GST number in the data face does not fit the column at 14px. */
.col-sub          { font-size: 12px; font-weight: 700; color: var(--text-muted); font-family: var(--font-data); margin-top: 2px; }
/* The customer / supplier code inside the ID line: the brand (primary) colour, the GST after it stays muted. Same size and weight as the line. */
.col-sub strong   { font-weight: 700; color: var(--brand-primary); }
.col-contact-name { font-size: 14px; font-weight: 500; color: var(--text-secondary); }
.col-contact-sub  { font-size: 14px; font-weight: 500; color: var(--text-secondary); margin-top: 2px; }
/* Phone in brand colour: a tap target on mobile, and de-emphasis by colour
   rather than by another size. */
.col-contact-phone { color: var(--brand-primary); }
.table .col-muted { color: var(--text-muted); font-size: 14px; }
.table th.col-num,
.table tbody th[scope="col"].col-num { text-align: right; }

/* Clickable row */
.table tbody tr.row-link { cursor: pointer; }

/* Semantic text color utilities — use on any element.
   Usage: <span class="col-danger">5 overdue</span>
          <div class="col-warning">Due today</div>              */
.col-danger  { color: var(--status-error-text); }
.col-warning { color: var(--status-warning-text); }
.col-info    { color: var(--brand-primary); }
.col-success { color: var(--status-success-text); }
.col-muted   { color: var(--text-muted); }
.col-primary { color: var(--text-primary); }

/* Text size utilities — the complete type scale, exactly 3 sizes.
   Each size has exactly ONE allowed weight (a pairing rule, not a
   floor) — 0.75rem is always 700, 0.875rem is always 500. Never set
   font-size below 0.75rem anywhere. font-weight:600 does not exist
   in this font stack (silently renders 700) — never use it.
   Usage: <span class="text-xs">CONFIRMED</span>
          <div class="text-sm">Supervisor</div>                  */
.text-xs   { font-size: 0.75rem;  font-weight: 700; }
.text-sm   { font-size: 0.875rem; font-weight: 500; }
.text-base { font-size: 1rem; }


/* ── OTIF Heatmap ─────────────────────────────────────────── */
/* Monthly OTIF % as a row of badge-pattern cells.
   Hidden by default — toggled open by OTIF link in status strip.
   Desktop/tablet: 12 cells. Mobile: 6 cells with H1/H2 nav.
   Usage:
     <div class="hm-panel" id="otifPanel">
       <div class="hm-row">
         <div class="hm-cell hm-cell--good">81</div>
         <div class="hm-cell hm-cell--mid">58</div>
         <div class="hm-cell hm-cell--bad">47</div>
         <div class="hm-cell hm-cell--empty">—</div>
       </div>
       <div class="hm-months"><span class="hm-mo">Apr</span>...</div>
       <div class="hm-avgs">This FY: <b>59%</b> · Last FY: <b>62%</b></div>
     </div>                                                      */

.hm-panel { display: none; margin-top: 10px; }
.hm-panel.open { display: block; }

.hm-row { display: inline-flex; gap: 3px; margin-bottom: 3px; }

.hm-cell {
    width: 40px; height: 28px; border-radius: 4px;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-data); font-size: 12px; font-weight: 500;
    line-height: 1;
}
.hm-cell--good  { color: var(--status-success-text);  background: var(--status-success-bg);  border: 1px solid var(--status-success-border); }
.hm-cell--mid   { color: var(--status-warning-text);  background: var(--status-warning-bg);  border: 1px solid var(--status-warning-border); }
.hm-cell--bad   { color: var(--status-error-text);    background: var(--status-error-bg);    border: 1px solid var(--status-error-border); }
.hm-cell--empty { color: var(--text-secondary);      background: var(--surface-border);     border: 1px solid transparent; }

.hm-months { display: inline-flex; gap: 3px; margin-bottom: 6px; }
.hm-mo {
    width: 40px; text-align: center;
    font-family: var(--font-data); font-size: 12px; font-weight: 400;
    line-height: 1; color: var(--text-muted);
}

.hm-avgs { font-size: 14px; color: var(--text-muted); margin-top: 2px; }
.hm-avgs b { font-weight: 500; color: var(--text-secondary); }

.hm-note { font-size: 14px; color: var(--text-muted); font-style: italic; margin: 4px 0; }

/* Mobile half-year nav — segmented toggle */
.hm-nav {
    display: none; /* shown via media query */
    border: 1px solid var(--surface-border); border-radius: 4px;
    overflow: hidden; margin-bottom: 8px;
}
.hm-nav-btn {
    padding: 4px 12px; border: none; background: none;
    font-family: var(--font-ui); font-size: 12px; font-weight: 500;
    color: var(--text-muted); cursor: pointer; line-height: 1;
}
.hm-nav-btn + .hm-nav-btn { border-left: 1px solid var(--surface-border); }
.hm-nav-btn--active { background: var(--surface-raised); color: var(--text-primary); }

/* Heatmap mobile breakpoint — content-driven, not global.
   12 cells need ~513px (12×40 + 11×3). Switch to 6-cell + nav below 550px. */
@media (max-width: 550px) {
    .hm-nav { display: inline-flex; }
    .hm-row--h2 { display: none; }
    .hm-months--h2 { display: none; }
}

/* Urgent row — side-stripe removed (impeccable: coloured side-stripe/inset
   accents are an anti-pattern). Urgency reads via the red group dot/label +
   the red ring. (.row-urgent is still a marker class on the row; it sets nothing here.) */


/* ── D03  Eyebrow label ───────────────────────────────────── */
/* Small all-caps label used as a section marker or card title. */
/* Usage: <span class="eyebrow">Section Name</span>            */

.eyebrow {
    font-size:      10px;
    font-weight:    700;
    letter-spacing: 1.1px;
    text-transform: uppercase;
    color:          var(--text-muted);
    display:        block;
}


/* ── D04  Definition list ─────────────────────────────────── */
/* Label + value rows for view pages (new pages in Phase E).   */
/* Use inside .card or .info-card.                              */
/* Usage: <dl class="dl">                                       */
/*          <div><dt>Label</dt><dd>Value</dd></div>             */
/*        </dl>                                                 */

.dl { display: grid; grid-template-columns: 1fr; }
.dl > div {
    display:         flex;
    justify-content: space-between;
    gap:             12px;
    padding:         8px 0;
    border-bottom:   1px dashed var(--surface-border);
    font-size:       14px;
}
.dl > div:last-child { border-bottom: none; }
.dl dt {
    color:      var(--text-muted);
    flex-shrink: 0;
    font-weight: 500;
}
.dl dd {
    color:       var(--text-primary);
    text-align:  right;
    font-weight: 500;
    min-width:   0;
    overflow-wrap: anywhere;   /* a long value (a username, an e-mail) wraps inside the card instead of overflowing it */
}
/* Monospace value variant — for IDs, codes, dates */
/* 2026-07-26: promoted from a page-local dead class — fgview.html/rmview.html
   both used class="mono" on display IDs expecting the data-font/tabular
   treatment every other ID on those pages gets (.stk-qty, .bom-meta-id,
   .ver-dates), but no bare .mono rule existed anywhere, so it silently fell
   back to the UI font. Compound rules below (.dl dd.mono etc.) keep winning
   on specificity where they already apply — this only fills the gap. */
.mono { font-family: var(--font-data); font-variant-numeric: tabular-nums; }
.dl dd.mono { font-family: var(--font-data); font-size: 14px; }
.dl dd a    { color: var(--brand-primary); text-decoration: none; font-weight: 700; }
@media (hover: hover) and (pointer: fine) { .dl dd a:hover { text-decoration: underline; } }


/* ── D05  Filter bar ──────────────────────────────────────── */
/* New filter pattern for Phase E pages.                       */
/* Old pages keep .filters / .search-input / .filter-select.  */
/* Usage: <div class="filterbar">                              */
/*          <div class="field grow"><svg/><input></div>         */
/*          <div class="field"><svg/><select></select></div>    */
/*        </div>                                                */

.filterbar {
    display:       flex;
    align-items:   center;
    gap:           8px;
    padding-top:   4px;
    margin-bottom: var(--stack);
    flex-wrap:     wrap;
}
/* Keeps the search box reachable on a long list instead of making the reader
   scroll back up to change the query. Same sticky arrangement .fac-alert
   uses — under the topbar, at the shared sticky layer. The page background
   is explicit because rows scroll underneath it, and the horizontal padding
   plus matching negative margin let that background reach the full width of
   the content column rather than stopping at the bar's own box. */
.filterbar--sticky {
    position:    sticky;
    top:         var(--topbar-height);
    z-index:     var(--z-sticky);
    background:  var(--surface-page);
    padding:     8px 12px;
    margin-left:  -12px;
    margin-right: -12px;
}
/* A filterbar nested inside a collapsible section sits tighter than one
   heading a page. Dispatch's archive is the only one today. */
.filterbar--tight { margin-bottom: 8px; }

.field {
    display:       flex;
    align-items:   center;
    gap:           6px;
    height:        30px;
    padding:       0 10px;
    border:        1px solid var(--surface-border);
    border-radius: var(--radius);
    background:    var(--surface-card);
    font-size:     14px;
    color:         var(--text-secondary);
    transition:    border-color 0.1s, box-shadow 0.1s;
}
.field:hover        { border-color: var(--brand-primary); }
.field:focus-within {
    border-color: var(--brand-primary);
    box-shadow:   0 0 0 3px var(--brand-subtle);
}
.field svg {
    width:      13px;
    height:     13px;
    color:      var(--text-muted);
    flex-shrink: 0;
}
/* Inputs/selects INSIDE .field reset the global input styles */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select {
    background:  none;
    border:      1.5px solid var(--surface-border);
    outline:     none;
    height:      100%;
    padding:     0;
    min-width:   80px;
    color:       var(--text-primary);
    font-size:   14px;
    font-weight: 500;
    font-family: var(--font-ui);
    box-shadow:  none;
}
.field input::placeholder { color: var(--text-placeholder); }
.field.grow { flex: 1; min-width: 200px; }

/* Segmented control — status/type toggle tabs */
.seg {
    display:       inline-flex;
    padding:       2px;
    border-radius: var(--radius);
    background:    var(--tint-hover);
    border:        1px solid var(--surface-border);
}
.seg button {
    height:        24px;
    padding:       0 10px;
    font-size:     14px;
    font-weight:   500;
    color:         var(--text-muted);
    border-radius: 4px;
    display:       flex;
    align-items:   center;
    gap:           5px;
    font-family:   var(--font-ui);
}
.seg button:hover { color: var(--text-primary); }
.seg button.on {
    background: var(--surface-card);
    color:      var(--text-primary);
    box-shadow: var(--shadow-1);
}


/* ── D06  Stat tile ───────────────────────────────────────── */
/* Pure CSS stat card. No JS required.                         */
/* For interactive filter stat cards use statcards.js instead. */
/* Usage: <div class="stat-tile">                              */
/*          <div class="stat-tile-label">Open POs</div>        */
/*          <div class="stat-tile-value">12</div>              */
/*          <div class="stat-tile-sub">↑ 3 this week</div>     */
/*        </div>                                                */

.stat-tile {
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    padding:       14px 16px;
    position:      relative;
    overflow:      hidden;
    transition:    border-color 0.1s;
}
.stat-tile:hover { border-color: var(--surface-border-strong); }
.stat-tile-label {
    font-size:      11px;
    font-weight:    700;
    letter-spacing: 1.1px;
    text-transform: uppercase;
    color:          var(--text-muted);
    margin-bottom:  10px;
}
.stat-tile-value {
    font-family:   var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size:     24px;
    font-weight:   700;
    color:         var(--text-primary);
    line-height:   1;
    letter-spacing: -0.02em;
}
.stat-tile-value .unit {
    font-size:   14px;
    color:       var(--text-muted);
    font-weight: 400;
    margin-left: 4px;
}
.stat-tile-sub {
    font-size:  14px;
    color:      var(--text-muted);
    margin-top: 6px;
}
/* Variant: alert state */
.stat-tile.alert { border-color: var(--status-error-border); }
.stat-tile.alert .stat-tile-value { color: var(--status-error); }
.stat-tile.warn  { border-color: var(--status-warning-border); }
.stat-tile.warn  .stat-tile-value { color: var(--status-warning); }


/* ── D07  List item ───────────────────────────────────────── */
/* Card-mode row for mobile views and card-based list pages.   */
/* Usage: <div class="list-item" onclick="...">                */
/*          <div class="list-item-head">                       */
/*            <span class="list-item-title">PO-001             */
/*              <span class="list-item-ref">· Ref</span>       */
/*            </span>                                          */
/*            <span class="badge">Status</span>                */
/*          </div>                                             */
/*          <div class="list-item-meta">                       */
/*            <span>City</span>                                */
/*            <span><b>12</b> POs</span>                       */
/*          </div>                                             */
/*        </div>                                               */

.list-item {
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius);
    padding:       14px var(--list-px);
    cursor:        pointer;
    transition:    border-color 0.1s, box-shadow 0.1s;
    box-shadow:    var(--shadow-1);
}
.list-item:hover {
    border-color: var(--brand-border);
    box-shadow:   var(--shadow-1);
}
.list-item-head {
    display:         flex;
    align-items:     flex-start;
    justify-content: space-between;
    gap:             12px;
    margin-bottom:   2px;
}
.list-item-body {
    display:     flex;
    align-items: center;
    gap:         10px;
}
.list-item-text { flex: 1; min-width: 0; }
.list-item-title { font-size: 15px; font-weight: 700; color: var(--text-primary); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-item-ref   { font-weight: 400; font-size: 14px; color: var(--text-muted); }
/* Factory-identity tag — shared display primitive for the factory code + icon
   (Decision #162). Used by supplierpo list/supplierview/supplierpoview (spoFacTag)
   and the dispatch board/filtered list (BOX-411). Inline after the display id,
   same size as the date ref (0.875rem), weight 500. Only table cells add a small
   top gap (block placement). Renamed from .spo-fac (2026-08-09, BOX-411) so the
   name no longer reads supplier-po-specific now that dispatch reuses it. */
.fac-tag { display: inline-flex; align-items: center; gap: 4px; line-height: 1; font-family: var(--font-data, monospace); font-size: 0.875rem; font-weight: 500; color: var(--text-muted); }
.fac-tag svg { width: 0.875rem; height: 0.875rem; }
.table .fac-tag { margin-top: 3px; }
/* Draft card head: keep PO id + factory tag together on the left, badge right. */
.list-item-head .li-head-main { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.list-item-code  { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); margin-bottom: 5px; }
.list-item-code a  { color: var(--brand-primary); text-decoration: none; font-weight: 700; }
@media (hover: hover) and (pointer: fine) { .list-item-code a:hover { text-decoration: underline; } }
.list-item-meta  { display: flex; gap: 14px; flex-wrap: wrap; font-size: 14px; font-weight: 500; color: var(--text-muted); }
.list-item-meta b { color: var(--text-primary); font-weight: 700; }
.list-item-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; gap: 8px; flex-wrap: wrap; }

/* Urgent card — left border accent for overdue items.
   Usage: <div class="list-item list-item--urgent">              */
/* Side-stripe removed (impeccable: coloured side-stripe borders are an
   anti-pattern). Urgency reads via the red fulfilment ring + red due text +
   the Overdue group header — no per-card colour accent. Selectors kept
   (markers .list-item--urgent and .list-item--due-today set nothing; they stay in the markup). */
/* Static card — no pointer cursor. For detail pages where sub-elements are clickable, not the card. */
.list-item--static { cursor: default; }

/* Action link — inline text link for primary actions within cards */
.action-link { font-size: 0.875rem; color: var(--brand-primary); font-weight: 700; text-decoration: none; cursor: pointer; }
/* The same text action drawn as a <button> (BOX-593: Change, Resend, Remove e-mail, Send a new code, Back). The look is .action-link's own; this only strips the
   button chrome, greys a disabled one and grows the target on a touch screen. Hover underlines only a button, so existing <a class="action-link"> are unchanged. */
button.action-link { background: none; border: 0; padding: 6px 0; min-height: 32px; font-family: inherit; }
button.action-link:disabled { color: var(--text-muted); cursor: default; }
@media (hover: hover) and (pointer: fine) { button.action-link:not(:disabled):hover { text-decoration: underline; } }
@media (pointer: coarse) { button.action-link { min-height: 44px; } }
/* An icon and a word on one line (a status such as "Confirmed" with its check). Size and weight come from .text-sm, the colour from .col-success / .col-warning. */
.state-inline { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.state-inline svg { flex: none; }
@media (hover: hover) and (pointer: fine) { .action-link:hover { text-decoration: underline; } }

/* Mini card modifier — compact sidebar cards (customerview PO sidebar) */
.list-item--mini { padding: 10px 12px; }
.list-item--mini .list-item-head { margin-bottom: 3px; }
.list-item--mini .list-item-meta { margin-bottom: 4px; }

/* Flat modifier — thin-line-separated row instead of a boxed card, for
   use inside a container that's already a .card (customerview.html's
   Open orders list sits inside #cardOrders — a card of cards is always
   wrong). Only a bottom border between rows; none on the last row, so a
   single-item list shows no line at all. */
.list-item--flat {
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 10px 0;
    border-bottom: 1px solid var(--surface-border);
}
.list-item--flat:last-child { border-bottom: none; }

/* Status badge plus any pipeline pills, on the row's bottom line. */
.row-signals { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; flex: 1; min-width: 0; }

/* Quick-filter chip row above a list page's filterbar. Both list pages had
   their own copy, plus a #filtersBar padding rule for the gap under it —
   that gap is this row's margin, so it lives with the row. */
.quick-filters { display: flex; gap: 6px; flex-wrap: wrap; padding: 8px 0 4px; margin-bottom: 6px; }
@media (max-width: 768px) { .quick-filters { padding: 4px 0 2px; } }

/* An amount owed, on a row. Error colour carries the meaning; the size and
   weight stay whatever the line they sit on uses. */
.row-due { font-family: var(--font-data); color: var(--status-error); }

@media (hover: hover) and (pointer: fine) {
    .list-item--flat:hover { background: var(--surface-hover); }
}

/* ── Sidebar PO row — REMOVED ──────────────────────────────
   Replaced by customerpocard.js renderPoMini() which uses
   .list-item, .bar-row, .badge classes instead.
   Old classes (.side-po, .side-po-top, etc.) deleted.       */

/* ── Sidebar sample row — mostly REMOVED ───────────────────
   Replaced by samplecard.js's scRenderMini() (customerview.html's Active
   samples), same migration as the PO row above. .side-samp-specs is kept
   — still used by customerview.html's separate renderSampMini()/
   toggleRefTag() timeline ref-tag preview, an unrelated feature. */

.side-samp-specs { font-size: 14px; color: var(--text-muted); }

/* ── Payment summary tiles ────────────────────────────────── */

.pay-grid  { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
.pay-tile  { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: var(--radius); padding: 12px 14px; box-shadow: var(--shadow-1); }
.pay-label { font-size: 14px; color: var(--text-muted); font-weight: 700; }
.pay-val   { font-family: var(--font-data); font-size: 18px; color: var(--text-primary); margin-top: 3px; font-weight: 700; }
.pay-sync  { font-size: 14px; color: var(--text-muted); margin-top: 8px; }
.pay-sync a { color: var(--brand-primary); text-decoration: none; }
.pay-sync a:hover { text-decoration: underline; }
.pay-val--warn { color: var(--status-warning); }
.pay-val--ok   { color: var(--status-success); }

/* pay-grid mobile override — MUST be after base definition for cascade */
@media (max-width: 860px) { .pay-grid { grid-template-columns: 1fr 1fr; } }


/* ── Address cards (customerview, shared) ───────────────── */
/* Same row/section/disclosure shell also used for the Contacts list below
   it in the same #cardContact card (customerview.html) — one visual
   language for both "default + list with disclosure" groups.

   Plain flexbox, deliberately NOT CSS Grid with shared/centered row-tracks
   (an earlier version used that and broke: when the name text wrapped to
   a second line, the grid row grew taller, and sibling cells — vertically
   centered in that now-taller track — landed on top of the wrapped text).
   Flex items can't overlap from wrapping: a flex-wrap container just grows
   in the cross-axis to fit whichever child wraps, so this is fluid at any
   card width (this card sits in the narrower .detail-main column, not the
   full viewport, so a viewport-width media query was never the right tool
   here either — no breakpoint at all is used below; it degrades
   continuously, which also covers a ~720px tablet-width column with no
   special case needed).

   .addr-topline: name + GST inline, close together (not stretched to a
   far edge, so they read as belonging together) — this line ONLY ever
   holds text, nothing pinned/right-aligned in it, so it can never make
   anything else appear to jump position depending on how much text
   precedes it. .addr-body: the address text (addresses only), its own
   line. .addr-footer: Primary/Set-as-primary/Deactivated pill + Edit/
   Deactivate actions, ALWAYS grouped together on one dedicated line —
   not split between the topline and a separate actions line — so the
   pill sits in exactly the same place relative to the actions every
   time, regardless of viewport width or how long the name/GST/role/
   phone/email text above it runs. */
.addr-sec       { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.addr-sec--ship { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--surface-border); }
.addr-row       { padding: 8px 0; }
.addr-row--off  { opacity: 0.45; }
.addr-topline      { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 10px; row-gap: 2px; }
.addr-topline-text { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 10px; row-gap: 2px; min-width: 0; }
.addr-name      { font-size: 14px; }
.addr-body      { font-size: 14px; margin-top: 3px; }
.addr-footer    { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.addr-actions   { display: flex; gap: 6px; flex-shrink: 0; }
.addr-more      { margin-top: 8px; }
.addr-more-link { font-size: 14px; }
.addr-chev      { display: inline-flex; color: var(--text-muted); }
.addr-overview  { margin-top: 12px; }
.addr-gst       { font-size: 14px; color: var(--text-muted); }

/* Row actions (Edit / Activate-Deactivate) reuse the shared .action-btn--icon
   icon-button (already used for Call/WhatsApp/Email/Maps in this same
   page's header) instead of a bespoke class — one icon-button language
   for the whole page. .pill-primary itself (Primary/Set-as-primary/
   Deactivated states) is a general shared component, not customerview-
   specific — its definition lives in the BUTTONS section next to
   .action-btn, not here. */

/* Contact row — "Name:" and "Role" as plain text (no chip), grouped so
   Role wraps to its own line as a whole word instead of breaking
   mid-character if the row is too narrow. Phone/email pair with a
   separator once there's room, split to their own lines when genuinely
   tight (this inner split is independent of the outer row grid above —
   it can still trigger at 768px+ if the detail column itself is narrow). */
.contact-namerole      { display: inline-flex; flex-wrap: wrap; align-items: baseline; column-gap: 4px; }
.contact-namerole .addr-name,
.contact-role-txt      { white-space: nowrap; }
.contact-role-txt      { font-size: 14px; color: var(--text-muted); }
.contact-line2      { font-size: 14px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.contact-line2 .sep { color: var(--surface-border-strong); }
@media (max-width: 480px) {
    .contact-line2      { flex-direction: column; align-items: flex-start; gap: 0; }
    .contact-line2 .sep { display: none; }
}



/* ── D08  Progress bar ────────────────────────────────────── */
/* Legacy linear bar — kept for backward compat.
   New code should use .progress (see D08b below).

   Bar only (no label):
     <div class="bar bar-ok"><span style="width:70%"></span></div>

   Bar + percentage label:
     <div class="bar-row">
       <div class="bar bar-ok"><span style="width:70%"></span></div>
       <span class="bar-pct">70%</span>
     </div>

   Color modifiers: .bar-ok (green), .bar-warn (amber), .bar-err (red).
   Default (no modifier) uses brand-primary.
   Width is set inline via JS — it is dynamic data, not style.  */

.bar {
    height:        5px;
    width:         100%;
    background:    var(--tint-hover);
    border-radius: 3px;
    overflow:      hidden;
}
.bar > span {
    display:       block;
    height:        100%;
    background:    var(--brand-primary);
    border-radius: 3px;
    min-width:     1px; /* visible even at 1% */
    transition:    width 500ms cubic-bezier(0.23,1,0.32,1);
}
.bar-ok   > span { background: var(--status-success); }
.bar-warn > span { background: var(--status-warning); }
.bar-err  > span { background: var(--status-error); }

/* Bar + label wrapper — places bar and percentage side by side */
.bar-row {
    display:     flex;
    align-items: center;
    gap:         10px;
}
.list-item .bar-row { margin: 6px 0 8px; }
.bar-row .bar {
    flex:      1;
    min-width: 40px;
}
.bar-pct {
    font-family:          var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size:            14px;
    font-weight:          700;
    color:                var(--text-muted);
    min-width:            28px;
    text-align:           right;
    flex-shrink:          0;
}
.bar-pct--ok  { color: var(--status-success); }
.bar-pct--err { color: var(--status-error); }
/* Label row above standalone progress bars */
.bar-label { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; font-size: 14px; color: var(--text-muted); font-weight: 500; }
.bar-section { margin-top: var(--section); }

/* ── D08b  Unified progress ──────────────────────────────── */
/* Two variants sharing color modifiers. Replaces .bar system.
   BOX-169 tracks migration of existing .bar usages.

   Linear bar:
     <div class="progress progress--bar progress--ok">
       <div class="progress-track"><span style="width:70%"></span></div>
       <span class="progress-val">70%</span>
     </div>

   Ring (circular):
     <div class="progress progress--ring progress--err">
       <svg>…</svg>
       <span class="progress-val">52</span>
     </div>

   Color modifiers: .progress--ok (green), .progress--warn (amber), .progress--err (red).
   Default (no modifier) uses brand-primary.                   */

/* — Shared modifiers — */
.progress { display: inline-flex; align-items: center; flex-shrink: 0; }

/* — Linear variant — */
.progress--bar { gap: 10px; }
.progress--bar .progress-track {
    flex:          1;
    height:        5px;
    background:    var(--tint-hover);
    border-radius: 3px;
    overflow:      hidden;
    min-width:     40px;
}
.progress--bar .progress-track > span {
    display:       block;
    height:        100%;
    background:    var(--brand-primary);
    border-radius: 3px;
    min-width:     1px;
    transition:    width 500ms cubic-bezier(0.23, 1, 0.32, 1);
}
.progress--ok  .progress-track > span { background: var(--status-success); }
.progress--warn .progress-track > span { background: var(--status-warning); }
.progress--err .progress-track > span { background: var(--status-error); }

.progress--bar .progress-val {
    font-family:          var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size:            14px;
    font-weight:          700;
    color:                var(--text-muted);
    min-width:            28px;
    text-align:           right;
    flex-shrink:          0;
}

/* — Ring variant — */
.progress--ring { position: relative; width: 28px; height: 28px; }
.progress--ring > svg { display: block; transform: rotate(-90deg); }
.progress--ring .progress-track { fill: none; stroke: var(--surface-border); stroke-width: 3; }
.progress--ring .progress-arc {
    fill:             none;
    stroke:           var(--brand-primary);
    stroke-width:     3;
    stroke-linecap:   round;
    transition:       stroke-dashoffset 300ms ease;
}
.progress--ok  .progress-arc { stroke: var(--status-success); }
.progress--warn .progress-arc { stroke: var(--status-warning); }
.progress--err .progress-arc { stroke: var(--status-error); }

.progress--ring .progress-val {
    position:         absolute;
    inset:            0;
    display:          flex;
    align-items:      center;
    justify-content:  center;
    font-family:      var(--font-data);
    font-size:        12px;
    font-weight:      500;
    color:            var(--text-primary);
    font-variant-numeric: tabular-nums;
}

/* — Shared modifier for value text — */
.progress--ok  .progress-val { color: var(--status-success); }
.progress--err .progress-val { color: var(--status-error); }

/* Section eyebrow — uppercase label for grouped sections */
.section-label  { margin-bottom: 8px; font-size: 14px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.8px; }
.section-count  { font-weight: 500; color: var(--text-placeholder); }

/* Stacked card list — flex column with consistent gap between cards */
.card-stack { display: flex; flex-direction: column; gap: 12px; }


/* ── D09  Group header ───────────────────────────────────── */
/* Section divider for grouped lists (urgency zones, status groups).
   Pairs with .card > .table-wrap tables or .list-item cards below.

   Usage:
     <div class="grp-header">
       <span class="grp-dot grp-dot--danger"></span>
       <span class="grp-label grp-label--danger">Overdue</span>
       <span class="grp-count">5 orders</span>
       <span class="grp-line"></span>
       <button class="grp-toggle" aria-expanded="true">▾</button>
     </div>
     <div class="card"><div class="table-wrap">...</div></div>  */

.grp-header {
    display:     flex;
    align-items: center;
    gap:         8px;
    padding:     6px 0 4px;
    cursor:      pointer;
}
.grp-dot {
    width:         7px;
    height:        7px;
    border-radius: 50%;
    flex-shrink:   0;
    background:    var(--text-muted);
}
.grp-dot--danger  { background: var(--status-error); }
.grp-dot--warning { background: var(--status-warning); }
.grp-dot--info    { background: var(--brand-primary); }
.grp-dot--success { background: var(--status-success); }

.grp-label {
    font-size:      12px;
    font-weight:    700;
    letter-spacing: 1.1px;
    text-transform: uppercase;
    color:          var(--text-secondary);
}
.grp-label--danger  { color: var(--status-error); }
.grp-label--warning { color: var(--status-warning-text); }
.grp-label--info    { color: var(--brand-primary); }
.grp-label--success { color: var(--status-success); }

.grp-count {
    font-family: var(--font-data);
    font-size:   0.875rem;
    font-weight: 500;
    color:       var(--text-muted);
}
.grp-line {
    flex:       1;
    height:     1px;
    background: var(--surface-border);
}
.grp-toggle {
    position:      relative;
    background:    none;
    border:        none;
    color:         var(--text-muted);
    font-size:     16px;
    cursor:        pointer;
    padding:       2px 4px;
    border-radius: var(--radius-sm);
    transition:    transform 0.15s ease, background 0.1s, color 0.1s;
    flex-shrink:   0;
}
.grp-toggle::after { content: ''; position: absolute; inset: -11px; }
.grp-toggle[aria-expanded="false"] {
    transform: rotate(-90deg);
}
@media (hover: hover) and (pointer: fine) {
    .grp-toggle:hover { background: var(--tint-hover); color: var(--text-primary); }
}


/* ── D10  Period picker ──────────────────────────────────── */
/* Date range selector button + dropdown popover.
   Presets: This month, This quarter, This FY, Last FY, Custom.
   FY boundaries from BF_CONFIG.fyStartMonth.

   Usage:
     <div class="period-btn" onclick="togglePeriod(this)">
       <svg>cal icon</svg> This FY ▾
       <div class="period-popup"> ... </div>
     </div>                                                     */

.period-btn {
    display:       inline-flex;
    align-items:   center;
    gap:           5px;
    padding:       6px 10px;
    border:        1px solid var(--surface-border);
    border-radius: var(--radius);
    background:    var(--tint-hover);
    color:         var(--text-secondary);
    font-size:     14px;
    font-family:   var(--font-ui);
    cursor:        pointer;
    white-space:   nowrap;
    position:      relative;
    transition:    border-color 0.12s, color 0.12s;
}
.period-btn svg { color: var(--text-muted); flex-shrink: 0; }
@media (hover: hover) and (pointer: fine) {
    .period-btn:hover { border-color: var(--brand-primary); color: var(--text-primary); }
}
.period-btn:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }

.period-popup {
    position:      absolute;
    top:           calc(100% + 4px);
    right:         0;
    background:    var(--surface-card);
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-lg);
    padding:       6px 0;
    min-width:     240px;
    z-index:       var(--z-search);
    display:       none;
    box-shadow:    0 8px 24px rgba(0,0,0,0.35);
}
.period-popup.open { display: block; }

/* Shared period picker (periodpicker.js, 2026-10-18). The trigger is a real
   <button> and the popup its sibling inside .period-wrap, which is what the
   popup now positions against (it used to sit inside the trigger <div>). The
   flip modifiers replace the inline top/bottom/left/right the pages wrote. */
.period-wrap                 { position: relative; display: inline-flex; }
.period-popup--flip-up       { top: auto; bottom: calc(100% + 4px); }
.period-popup--align-left    { right: auto; left: 0; }

.period-opt {
    padding:         8px 14px;
    font-size:       14px;
    color:           var(--text-secondary);
    cursor:          pointer;
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    transition:      background 0.08s;
    /* An option may be a <button> (periodpicker.js): these make it draw like the <div> form. */
    width:           100%;
    border:          0;
    background:      transparent;
    font-family:     inherit;
    text-align:      left;
}
@media (hover: hover) and (pointer: fine) {
    .period-opt:hover { background: var(--tint-hover); }
}
.period-opt:active { background: var(--tint-hover-strong); }
.period-opt:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: -2px; }
.period-opt.active { color: var(--brand-primary); font-weight: 500; }
.period-opt-sub {
    font-family: var(--font-data);
    font-size:   14px;
    color:       var(--text-placeholder);
}
.period-divider {
    height:     1px;
    background: var(--surface-border);
    margin:     4px 0;
}
.period-custom { padding: 10px 14px; }
.period-custom-label {
    font-size:      14px;
    font-weight:    700;
    color:          var(--text-placeholder);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom:  6px;
}
.period-custom-row {
    display:     flex;
    gap:         6px;
    align-items: center;
}
.period-custom-row input[type="date"] {
    flex:          1;
    padding:       6px 8px;
    border:        1px solid var(--surface-border);
    border-radius: var(--radius);
    background:    var(--tint-hover);
    color:         var(--text-secondary);
    font-size:     14px;
    font-family:   var(--font-data);
    outline:       none;
}
.period-custom-row input[type="date"]:focus { border-color: var(--brand-primary); }
.period-custom-row span { font-size: 14px; color: var(--text-placeholder); }
.period-custom-apply {
    margin-top:    8px;
    width:         100%;
    padding:       6px;
    border:        none;
    border-radius: var(--radius);
    background:    var(--brand-primary);
    color:         var(--surface-page);
    font-size:     14px;
    font-weight:   700;
    font-family:   var(--font-ui);
    cursor:        pointer;
    transition:    opacity 0.1s;
}
.period-custom-apply:hover { opacity: 0.9; }
.period-custom-apply:active { transform: scale(0.98); }


/* ── D11  Archive section ────────────────────────────────── */
/* Completed/closed items with inline search + period filter.
   Expandable — collapsed by default, toggle reveals content.

   Usage:
     <div class="archive">
       <div class="archive-head">
         <span class="archive-title">Completed & closed</span>
         <span class="archive-count">175</span>
         <span class="archive-spacer"></span>
         <div class="field">search</div>
         <div class="period-btn">...</div>
       </div>
       <div class="card" id="archiveBody">
         <div class="table-wrap"><table class="table">...</table></div>
         <div class="archive-pagn">...</div>
       </div>
     </div>                                                     */

.archive {
    margin-top:  var(--section);
    padding-top: var(--section);
    border-top:  1px solid var(--surface-border);
}
.archive-head {
    display:       flex;
    align-items:   center;
    gap:           10px;
    margin-bottom: 10px;
    flex-wrap:     wrap;
}
.archive-title {
    font-size:      14px;
    font-weight:    700;
    color:          var(--text-primary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.archive-count {
    font-family: var(--font-data);
    font-size:   14px;
    color:       var(--text-placeholder);
}
.archive-spacer { flex: 1; }
.archive-toggle {
    font-size:   14px;
    font-weight: 500;
    color:       var(--brand-primary);
    background:  none;
    border:      1.5px solid var(--surface-border);
    cursor:      pointer;
    font-family: var(--font-ui);
    padding:     4px 0;
}
@media (hover: hover) and (pointer: fine) {
    .archive-toggle:hover { text-decoration: underline; }
}
.archive-toggle:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }

.archive-pagn {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
    padding:         8px var(--cell-px);
    border-top:      1px solid var(--surface-border);
    background:      var(--surface-sunk);
}
.archive-pagn-info {
    font-size: 14px;
    color:     var(--text-placeholder);
}
.archive-pagn-btns { display: flex; gap: 3px; }
.archive-pagn-btn {
    padding:       3px 8px;
    border:        1px solid var(--surface-border);
    border-radius: var(--radius-sm);
    background:    none;
    color:         var(--text-muted);
    font-size:     14px;
    font-family:   var(--font-ui);
    cursor:        pointer;
    transition:    border-color 0.1s, color 0.1s;
}
@media (hover: hover) and (pointer: fine) {
    .archive-pagn-btn:hover { border-color: var(--brand-primary); color: var(--brand-primary); }
}
.archive-pagn-btn.active {
    background:   var(--brand-subtle);
    border-color: var(--brand-border);
    color:        var(--brand-primary);
}


/* ── D12  Responsive visibility ──────────────────────────── */
/* Table/card switching for list pages.
   Usage: <div class="view-desktop">table</div>
          <div class="view-mobile">cards</div>               */

.view-desktop { display: block; }
.view-mobile  { display: none; }


/* ── Mobile breakpoints for D-section ────────────────────── */

@media (max-width: 860px) {
    .filterbar         { gap: 6px; }
    .field.grow        { min-width: 0; }
    .table             { min-width: 400px; }
    .stat-tile-value   { font-size: 20px; }
    .card-head         { padding: 10px 14px; }
    /* D12 responsive visibility */
    .view-desktop      { display: none; }
    .view-mobile       { display: block; }
    /* Mobile list group = ONE card with hairline-separated rows (not stacked
       boxed cards). Mirrors the desktop table-in-a-card. Scoped to .view-mobile
       so it only affects the customerpo/supplierpo list-page group renderers. */
    .view-mobile { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: var(--radius); overflow: hidden; }
    .view-mobile .list-item { background: transparent; border: none; border-radius: 0; box-shadow: none; }
    .view-mobile .list-item + .list-item { border-top: 1px solid var(--surface-border); }
    /* Rendered nothing — the search component is speaking instead — so the
       container must not be left as an empty bordered box. */
    .view-mobile:empty { display: none; }

    /* The count aligns to whichever card is beneath it — the table on desktop,
       the list card here, which insets by --list-px rather than --cell-px. */
    .showing-count { padding-left: calc(var(--list-px) + 1px); }
    /* D09 group header tighter on mobile */
    .grp-header        { padding: 8px 0 4px; }
    /* D11 archive search to own row on mobile */
    .archive-head .field { min-width: 100%; order: 10; }
}


/* ============================================================
   PART E — SEARCH PALETTE (BOX-141 / Universal Search)
   All .sp-* classes + #bf-search-btn + body.search-open.
   No inline styles — search-palette.js and nav.js reference
   these classes only.
   ============================================================ */

/* ── Topbar search trigger ────────────────────────────────── */
#bf-search-btn {
    width:        36px;
    height:       36px;
    border-radius:var(--radius);
    background:   transparent;
    border:       1px solid transparent;
    display:      flex;
    align-items:  center;
    justify-content: center;
    cursor:       pointer;
    color:        var(--text-muted);
    transition:   background 0.15s, border-color 0.15s, color 0.15s;
    flex-shrink:  0;
}
#bf-search-btn:hover {
    background:   var(--ghost-bg);
    border-color: var(--surface-border);
    color:        var(--text-primary);
}
#bf-search-btn.active {
    background:   var(--brand-subtle-hover);
    border-color: var(--brand-border);
    color:        var(--brand-primary);
}

/* ── Theme + Bell topbar buttons — match search button hover ── */
#bf-theme-btn,
#bf-bell-btn {
    width:        36px;
    height:       36px;
    border-radius:var(--radius);
    background:   transparent;
    border:       1px solid transparent;
    display:      flex;
    align-items:  center;
    justify-content: center;
    cursor:       pointer;
    color:        var(--text-muted);
    transition:   background 0.15s, border-color 0.15s, color 0.15s;
    flex-shrink:  0;
}
#bf-theme-btn:hover,
#bf-bell-btn:hover {
    background:   var(--ghost-bg);
    border-color: var(--surface-border);
    color:        var(--text-primary);
}
#bf-bell-btn.active {
    background:   var(--brand-subtle-hover);
    border-color: var(--brand-border);
    color:        var(--brand-primary);
}

/* ── User identity + factory switcher — main topbar (after search, bell).
   #userAvatar is the ID nav.js builds either shape into — plain circle for
   single-factory users (base .user-avatar rule, unchanged), or the wider
   pill below for multi-factory users. Dropdown trigger is #userAvatar
   itself in the multi-factory case. ──────────────────────────────────── */
.bf-factory-wrap {
    position:    relative;
    display:     flex;
    align-items: center;
    flex-shrink: 0;
}
#userAvatar {
    cursor:          pointer;
    justify-content: center;
    transition:      border-color 0.15s, background 0.15s;
}
/* No permanent pill — at rest this shows factory icon + code (not
   initials), sized to its content rather than a fixed circle. Initials
   stay collapsed (see .ua-initials below) until hovered or the dropdown
   is open, matching the bell/theme buttons' own hover-only affordance
   instead of a permanently-expanded chip. Single-factory users are
   unaffected — they still get the plain circle from the base .user-avatar
   rule, this block only ever applies to .user-avatar-multi. */
#userAvatar.user-avatar-multi {
    width:         auto;
    border-radius: 999px;
    padding:       0 10px;
    border:        1.5px solid transparent;
    overflow:      hidden;
}
.ua-initials {
    color:       var(--brand-primary);
    font-size:   12px;
    font-weight: 700;
    flex-shrink: 0;
    /* max-width alone, not width — width:0 would stay a hard 0px even
       once max-width relaxes on hover, since max-width only caps a
       width that's otherwise free to grow (auto), it doesn't override
       an explicit width. That was the bug: opacity flipped to 1 but the
       box stayed 0px wide, so nothing ever became visible. */
    max-width:   0;
    margin-right: 0;
    opacity:     0;
    overflow:    hidden;
    white-space: nowrap;
    transition:  max-width 0.2s ease, margin-right 0.2s ease, opacity 0.15s ease;
}
.ua-factory-icon {
    display:         flex;
    align-items:     center;
    justify-content: center;
    flex-shrink:     0;
    width:           14px;
    color:           var(--text-muted);
    transition:      color 0.15s ease;
}
.ua-factory-code {
    color:          var(--text-muted);
    font-size:      12px;
    font-weight:    700;
    letter-spacing: 0.02em;
    margin-left:    4px;
    white-space:    nowrap;
    transition:     color 0.15s ease;
}
/* Reveal initials on hover OR while the dropdown is open (.active) — not
   permanently. Icon + code stay visible the whole time; only the
   identity part (initials) is the hover-only reveal now. */
#userAvatar.user-avatar-multi:hover,
#userAvatar.user-avatar-multi.active {
    background:   var(--ghost-bg);
    border-color: var(--surface-border);
}
#userAvatar.user-avatar-multi:hover .ua-initials,
#userAvatar.user-avatar-multi.active .ua-initials {
    max-width:    40px;
    margin-right: 5px;
    opacity:      1;
}
#userAvatar.user-avatar-multi.active {
    background:   var(--brand-subtle-hover);
    border-color: var(--brand-border);
}
#userAvatar.user-avatar-multi.active .ua-factory-icon,
#userAvatar.user-avatar-multi.active .ua-factory-code {
    color: var(--brand-primary);
}
.bf-factory-dropdown {
    position:      absolute;
    top:           calc(100% + 8px);
    right:         0;
    min-width:     200px;
    background:    var(--surface-card);
    border:        1px solid var(--brand-border);
    border-radius: 10px;
    overflow:      hidden;
    box-shadow:    var(--shadow-dropdown);
    transform:     translateY(-6px);
    opacity:       0;
    pointer-events: none;
    transition:    transform 0.18s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.15s ease;
    z-index:       1000;
}
/* Space-aware fallback — flipped by JS (_positionFactoryDropdown) when the
   default right:0 anchor would overflow the viewport's left edge instead
   of trusting a fixed anchor that assumes room is always there. */
.bf-factory-dropdown.flip-left {
    right: auto;
    left:  0;
}
.bf-factory-dropdown.open {
    transform:      translateY(0);
    opacity:        1;
    pointer-events: all;
}
.bf-factory-item {
    display:       flex;
    align-items:   center;
    justify-content: space-between;
    gap:           8px;
    width:         100%;
    padding:       10px 14px;
    background:    none;
    border:        none;
    border-bottom: 1px solid var(--surface-border);
    cursor:        pointer;
    color:         var(--text-secondary);
    font-family:   var(--font-ui);
    font-size:     14px;
    text-align:    left;
    transition:    background 0.12s, color 0.12s;
}
.bf-factory-item:last-child { border-bottom: none; }
.bf-factory-item:hover {
    background: var(--ghost-bg);
    color:      var(--text-primary);
}
/* Active is said once, in colour. A weight step on top would be a second
   signal for one state, and the size here has only one legal weight. */
.bf-factory-item.active {
    color: var(--brand-primary);
}
.bf-factory-item-label {
    display:     flex;
    align-items: baseline;
    gap:         6px;
    overflow:    hidden;
}
.bf-factory-item-code {
    color:          var(--text-muted);
    font-family:    var(--font-data);
    font-size:      11px;
    letter-spacing: 0.04em;
}
/* Full-width rule, not a side accent — separates factories from My Profile */
.bf-factory-divider {
    height:     1px;
    background: var(--surface-border);
}
.bf-factory-item-profile {
    color:       var(--text-muted);
    font-weight: 500;
}
.bf-factory-item-profile:hover {
    color: var(--text-primary);
}
.bf-factory-item-check {
    display:     flex;
    width:       14px;
    height:      14px;
    flex-shrink: 0;
    color:       var(--brand-primary);
}

/* ── Sign-out loader — /auth/logout's Clear-Site-Data header can take
   several seconds to wipe the SW cache before the redirect completes;
   this replaces the confirm modal's buttons so the wait reads as "in
   progress", not frozen. Loading is an endorsed motion purpose. ──── */
@keyframes bf-spin { to { transform: rotate(360deg); } }
.bf-spinner {
    width:         20px;
    height:        20px;
    border:        2.5px solid var(--surface-border);
    border-top-color: var(--brand-primary);
    border-radius: 50%;
    animation:     bf-spin 0.7s linear infinite;
}

/* ⌘K hint badge beside the search button — hidden on mobile */
.sp-topbar-kbd {
    font-family:  var(--font-data);
    font-size:    9px;
    color:        var(--text-muted);
    background:   var(--tint-hover);
    border:       1px solid var(--surface-border);
    border-radius:var(--radius-sm);
    padding:      2px 5px;
    white-space:  nowrap;
}

/* ── Scroll lock when palette is open ─────────────────────── */
body.search-open { overflow: hidden; }

/* ── Backdrop ─────────────────────────────────────────────── */
.sp-backdrop {
    position:        fixed;
    inset:           0;
    background:      var(--surface-overlay);
    z-index:         var(--z-search);
    display:         none;
    align-items:     flex-start;
    justify-content: center;
    padding-top:     80px;
}
.sp-backdrop.sp-open {
    display:         flex;
}

/* ── Palette container ─────────────────────────────────────── */
.sp-palette {
    background:    var(--surface-card);
    border:        1px solid var(--brand-border);
    border-radius: var(--radius-lg);
    width:         580px;
    max-width:     calc(100vw - 32px);
    max-height:    480px;
    display:       flex;
    flex-direction:column;
    overflow:      hidden;
    box-shadow:    0 20px 60px rgba(0,0,0,0.5);
}

/* ── Input row ─────────────────────────────────────────────── */
.sp-input-row {
    display:       flex;
    align-items:   center;
    gap:           10px;
    padding:       12px 16px;
    border-bottom: 1px solid var(--surface-border);
    flex-shrink:   0;
}
.sp-input-icon {
    color:      var(--text-muted);
    flex-shrink:0;
    display:    flex;
    align-items:center;
}
.sp-input {
    flex:       1;
    background: none;
    border:     none;
    outline:    none;
    font-family:var(--font-ui);
    font-size:  15px;
    color:      var(--text-primary);
    min-width:  0;
}
.sp-input::placeholder { color: var(--text-placeholder); }

/* ⌘K / Esc keyboard badges */
.sp-kbd {
    font-family:  var(--font-data);
    font-size:    10px;
    color:        var(--text-muted);
    background:   var(--tint-hover);
    border:       1px solid var(--surface-border);
    border-radius:var(--radius-sm);
    padding:      2px 6px;
    white-space:  nowrap;
    flex-shrink:  0;
}

/* Back arrow (mobile close) */
.sp-back-btn {
    background:   none;
    border:       none;
    color:        var(--text-muted);
    cursor:       pointer;
    display:      none;
    align-items:  center;
    padding:      0;
    flex-shrink:  0;
}

/* ── Results list ──────────────────────────────────────────── */
.sp-results {
    flex:       1;
    overflow-y: auto;
    position:   relative;  /* offsetParent for items — keyboard scroll needs this */
    scrollbar-width: thin;
    scrollbar-color: var(--tint-hover) transparent;
}

/* Section eyebrow label */
.sp-section-label {
    font-family:    var(--font-data);
    font-size:      10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color:          var(--text-muted);
    padding:        10px 16px 4px;
    display:        block;
}

/* Result row */
.sp-item {
    display:     flex;
    align-items: center;
    gap:         12px;
    padding:     8px 16px;
    cursor:      pointer;
    transition:  background 0.08s;
    text-decoration: none;
    color:       inherit;
}
.sp-item:hover,
.sp-item.highlighted { background: var(--tint-hover); }

/* Icon container */
.sp-item-icon {
    width:        28px;
    height:       28px;
    border-radius:var(--radius-sm);
    background:   var(--tint-hover);
    border:       1px solid var(--surface-border);
    display:      grid;
    place-items:  center;
    flex-shrink:  0;
    color:        var(--text-muted);
}
.sp-item.highlighted .sp-item-icon {
    background:   var(--brand-subtle);
    border-color: var(--brand-border);
    color:        var(--brand-primary);
}

/* Text body */
.sp-item-body {
    flex:           1;
    min-width:      0;
    display:        flex;
    flex-direction: column;
}
.sp-item-label {
    font-size:     14px;
    color:         var(--text-primary);
    font-weight:   500;
    white-space:   nowrap;
    overflow:      hidden;
    text-overflow: ellipsis;
    line-height:   1.3;
}
.sp-item-code {
    font-family: var(--font-data);
    font-size:   14px;
    color:       var(--text-muted);
    line-height: 1.3;
}

/* Traceability sub-line (item.sub — "-> REEL-0042", "-> used in 3 job
   cards"). 2026-07-21: same slot .sp-item-code occupies (only one or the
   other ever renders — see _renderRecordItem), same visual weight, own
   class since it's a distinct kind of information (a lineage fact, not
   an id/attribution). */
.sp-item-sub {
    font-family: var(--font-data);
    font-size:   14px;
    color:       var(--text-muted);
    line-height: 1.3;
}

/* Meta badge (entity type) */
.sp-item-meta {
    font-family:    var(--font-data);
    font-size:      10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding:        2px 6px;
    border-radius:  8px;
    background:     var(--tint-hover);
    color:          var(--text-muted);
    flex-shrink:    0;
    white-space:    nowrap;
}
.sp-item.highlighted .sp-item-meta {
    background: var(--brand-subtle);
    color:      var(--brand-primary);
}

/* Stacked item layout (item.state present — currently reel, job card).
   2026-07-21: replaces an earlier chip-badge treatment for state — a
   live mobile screenshot showed the old single-row layout (label + up
   to 3 chips: meta, state, factory) truncating the label entirely on
   narrow screens. Label+chips now get their own wrappable row; state
   and traceability become plain stacked text lines beneath it instead
   of competing chips. */
.sp-item-stacked {
    align-items: flex-start;
}
.sp-item-stacked .sp-item-icon {
    margin-top: 2px; /* optical alignment against the now-taller multi-line body */
}
.sp-item-stacked .sp-item-body {
    gap: 2px;
}
.sp-item-label-row {
    display:     flex;
    flex-wrap:   wrap;
    align-items: center;
    gap:         8px;
    row-gap:     4px;
}
.sp-item-stacked .sp-item-label {
    white-space:   normal;
    overflow:      visible;
    text-overflow: clip;
}

/* Plain-text state line (item.state — e.g. reel "In_Use · 690 kg", job
   card "Queued"). Deliberately NOT a chip — reads as ordinary
   descriptive text under the label, same visual weight as
   .sp-item-code, not another badge competing with meta/factory for
   attention. No color-coding by value — PRODUCT.md's "never encode
   meaning in color alone" rule means a real color treatment would need
   an icon/shape per state too, a bigger design pass than this one. */
.sp-item-state-text {
    font-family: var(--font-data);
    font-size:   13px;
    color:       var(--text-muted);
    line-height: 1.4;
}

/* Reel-width grouped search (item.groupLines — "N reels available in
   <factory>", one line per factory under an RM name). Same plain-text
   treatment as .sp-item-state-text, not a chip — this is a count of
   available stock, not a status. Factory icon+code renders inline
   within the line (not the pill-badge .sp-item-factory treatment used
   for the header row) since it reads as part of the sentence here,
   not a separate badge competing for attention. */
.sp-item-group-line {
    font-family: var(--font-data);
    font-size:   13px;
    color:       var(--text-muted);
    line-height: 1.4;
}
.sp-item-group-line-factory {
    display:      inline-flex;
    align-items:  center;
    gap:          3px;
    color:        var(--text-primary);
    font-weight:  500;
}

/* Factory badge (item.factory_code — multi-factory users only, see
   _tagFactory in search.js; single-factory users never receive this
   field at all). Icon + text, not color alone, per PRODUCT.md — the
   icon is what actually distinguishes this from .sp-item-meta, not a
   different color (same neutral treatment intentionally). */
.sp-item-factory {
    display:        inline-flex;
    align-items:    center;
    gap:            4px;
    font-family:    var(--font-data);
    font-size:      10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding:        2px 6px;
    border-radius:  8px;
    background:     var(--tint-hover);
    color:          var(--text-muted);
    flex-shrink:    0;
    white-space:    nowrap;
}
.sp-item-factory-icon {
    width:   10px;
    height:  10px;
    display: inline-flex;
}
.sp-item-factory-icon svg { width: 100%; height: 100%; }
.sp-item.highlighted .sp-item-factory {
    background: var(--brand-subtle);
    color:      var(--brand-primary);
}

/* Synthetic "View all" link row */
.sp-item-link-label {
    font-size:   14px;
    color:       var(--brand-primary);
    font-weight: 500;
}

/* Reel-width search — result count ("40 reels found"). Plain text
   header above the paged RM groups, not a row/link — same slot as
   .sp-section-label visually but reports a fact about THIS query's
   results rather than labeling a section. 2026-07-21. */
.sp-item-summary-note {
    font-family: var(--font-data);
    font-size:   12px;
    color:       var(--text-muted);
    padding:     2px 16px 8px;
}

/* Reel-width search — "Load more" pagination control. Full-width,
   borderless, quiet — matches .sp-back-link's "ordinary navigation,
   not a special action" treatment rather than a bordered button, since
   this is routine pagination, not a rare/destructive action like
   .sp-clear-btn. Disabled state covers the in-flight fetch window
   (search-palette.js sets/clears it, no separate spinner). 2026-07-21. */
.sp-load-more {
    display:     block;
    width:       100%;
    text-align:  center;
    font-size:   13px;
    color:       var(--brand-primary);
    background:  none;
    border:      none;
    border-top:  1px solid var(--surface-border);
    cursor:      pointer;
    font-family: var(--font-ui);
    padding:     10px 16px;
}
.sp-load-more:hover:not(:disabled) { background: var(--tint-hover); }
.sp-load-more:disabled {
    color:  var(--text-muted);
    cursor: default;
}

/* Recent-list header row — back link (left) + clear button (right).
   2026-07-21: was a bare .sp-section-label (display:block), so
   .sp-clear-btn's margin-left:auto never actually pushed it right —
   margin:auto only pushes an element within a flex or block-centering
   context, not an inline-level child of a block parent. This modifier
   makes the row a real flex container so the split works as designed. */
.sp-recent-header {
    display:         flex;
    align-items:     center;
    justify-content: space-between;
}

/* Back to pages — quiet wayfinding, matches the palette's other
   borderless controls (sp-back-btn, nav/result rows). Not a bordered
   chip — this is ordinary navigation, not a special action. */
.sp-back-link {
    font-size:   13px;
    color:       var(--text-muted);
    background:  none;
    border:      none;
    cursor:      pointer;
    font-family: var(--font-ui);
    padding:     0;
    text-decoration: none;
}
.sp-back-link:hover { color: var(--brand-primary); }

/* Clear all — deliberately smaller/quieter than Back to pages. It's a
   rare, one-click, irreversible action (wipes the whole history) that
   used to sit at equal visual weight right next to ordinary navigation
   — a real misclick risk, not just a style mismatch. The two-step
   inline confirm (search-palette.js) backs this up functionally;
   .sp-clear-btn-confirm makes the danger visible for the whole confirm
   window, not just on :hover — color is never the only signal here,
   the button's own text changes too ("Clear all" -> "Confirm clear?"). */
.sp-clear-btn {
    font-size:      11px;
    letter-spacing: 0.02em;
    color:          var(--text-muted);
    background:     none;
    border:         none;
    cursor:         pointer;
    font-family:    var(--font-ui);
    padding:        0;
}
.sp-clear-btn:hover { color: var(--status-error); }
/* Same: the confirm state is carried by error colour alone. */
.sp-clear-btn-confirm {
    color: var(--status-error);
}

/* ── Empty / loading state ─────────────────────────────────── */
.sp-empty {
    padding:    40px 16px;
    text-align: center;
}
.sp-empty-msg {
    font-family:    var(--font-data);
    font-size:      14px;
    color:          var(--text-muted);
    letter-spacing: 0.06em;
}

/* ── Footer — keyboard hints (desktop only) ────────────────── */
.sp-footer {
    padding:       8px 16px;
    border-top:    1px solid var(--surface-border);
    display:       flex;
    align-items:   center;
    gap:           16px;
    flex-shrink:   0;
}
.sp-footer-hint {
    font-family:    var(--font-data);
    font-size:      10px;
    color:          var(--text-muted);
    letter-spacing: 0.04em;
    display:        flex;
    align-items:    center;
    gap:            5px;
}

/* ── Mobile — full-page layout ─────────────────────────────── */
@media (max-width: 599px) {
    .sp-topbar-kbd { display: none; }

    .sp-backdrop {
        padding-top: 0;
        align-items: stretch;
    }
    .sp-palette {
        width:         100%;
        max-width:     100%;
        max-height:    100dvh;
        border-radius: 0;
        border-left:   none;
        border-right:  none;
        border-top:    none;
    }
    .sp-footer  { display: none; }
    .sp-kbd     { display: none; }
    .sp-back-btn { display: flex; }

    /* Taller rows on mobile — two-line layout */
    .sp-item    { padding: 10px 16px; align-items: flex-start; }
    .sp-item-icon { margin-top: 2px; }
}


/* ── Dispatch — urgency group ────────────────────────────────
   Collapsible section with header + body. Used in dispatch.html
   operations board for Load Today, In Transit, Scheduled, Delivered groups.
   ──────────────────────────────────────────────────────────── */

.dispatch-group        { margin-bottom: var(--section); }
.dispatch-group-body          { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.dispatch-group-body.list-layout { display: flex; flex-direction: column; gap: 0; }
.dispatch-group-body.collapsed   { display: none; }
/* The group is a card grid; an empty state is a message about the whole
   group, so it spans rather than sitting in the first 280px cell. */
.dispatch-group-body > .empty    { grid-column: 1 / -1; }


/* ── Dispatch — packing slip card (Load Today) ───────────────
   Card: customer → address → FG items → ref pills → signal footer.
   ──────────────────────────────────────────────────────────── */

.slip-card {
    background: var(--surface-card); border: 1px solid var(--surface-border);
    border-radius: var(--radius-lg); overflow: hidden;
    display: flex; flex-direction: column; cursor: pointer;
    transition: border-color 0.15s, transform 0.1s;
    text-decoration: none; color: inherit;
}
.slip-card:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { .slip-card:hover { border-color: var(--brand-border); } }
.slip-card-header {
    padding: 12px 14px 0;
    display: flex; flex-direction: column; gap: 3px;
}
/* Dispatch board cards — the slip cards and the compact rows under them — put
   the customer name one step up: it is the card's subject and the board is
   scanned at a distance. 16px carries weight 400 (Ra, 2026-09-23). Lists that
   are not board cards keep .col-title's 14/500.
   Before this, the slip card was 16px, the badge row 14px, and the third
   emitter omitted .col-title entirely and fell to 14/400 — one field, three
   renderings on one page. .sc-customer had no rule left and is gone with it. */
.slip-card .col-title,
.dispatch-compact-row .col-title,
.transit-card .col-title,
.dispatcher-load-card .col-title { font-size: 16px; font-weight: 400; }
.slip-card-header .sc-address   { font-size: 14px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.slip-card-items { padding: 8px 14px 0; display: flex; flex-direction: column; gap: 0; flex: 1; }
/* Stale-assignment flag above the card body. Warning, not error: the batch
   assignment wants revisiting, it does not block the dispatch. */
.slip-stale   { display: flex; align-items: center; gap: 5px; padding: 2px 14px 4px; }
/* Icon-and-text signal on the card footer (overdue, due today). */
.slip-signal  { display: flex; align-items: center; gap: 5px; }
/* Vehicle and driver line under the card body. */
.slip-vehicle { padding: 4px 14px 0; }
.slip-card-item-row {
    display: flex; align-items: baseline; justify-content: space-between;
    padding: 6px 0; border-bottom: 1px solid var(--tint-border-soft); gap: 8px;
}
.slip-card-item-row:last-child  { border-bottom: none; }
.slip-card-item-row .si-name    { font-size: 14px; color: var(--text-secondary); flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.slip-card-item-row .si-qty     { font-size: 14px; font-weight: 700; font-family: var(--font-data); color: var(--text-primary); white-space: nowrap; }
.slip-refs { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 14px; }
.slip-ref-pill { font-size: 14px; font-family: var(--font-data); color: var(--text-muted); background: var(--tint-hover); padding: 2px 8px; border-radius: var(--radius-sm); }
.slip-card-footer { padding: 8px 14px; border-top: 1px solid var(--surface-border); display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.slip-card-footer .slip-arrow { color: var(--text-muted); margin-left: auto; }


/* ── Dispatch — transit card (In Transit) ────────────────────
   Card showing customer, vehicle/driver, days-since counter.
   ──────────────────────────────────────────────────────────── */

.transit-card {
    background: var(--surface-card); border: 1px solid var(--surface-border);
    border-radius: var(--radius-lg); padding: 14px;
    display: flex; flex-direction: column; gap: 10px;
    cursor: pointer; text-decoration: none; transition: border-color 0.15s, transform 0.1s;
}
.transit-card:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { .transit-card:hover { border-color: var(--brand-border); } }
.transit-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
/* Type comes from the board-card rule beside .col-title. */
.transit-card .tc-ref      { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); margin-top: 2px; }
.transit-card-meta  { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.transit-card .tc-vehicle  { font-size: 14px; color: var(--text-secondary); }
.transit-card .tc-driver   { font-size: 14px; color: var(--text-muted); }
.transit-card .tc-qty      { font-family: var(--font-data); font-size: 14px; color: var(--text-secondary); margin-top: auto; }

.transit-days       { font-family: var(--font-data); font-size: 14px; font-weight: 700; color: var(--text-secondary); background: var(--tint-hover); border: 1px solid var(--surface-border); border-radius: var(--radius-sm); padding: 2px 8px; display: inline-block; }
.transit-days--stale { background: var(--status-warning-bg); color: var(--status-warning-text); border-color: var(--status-warning-border); }


/* ── Dispatch — compact row (Scheduled + Delivered groups) ───
   Single-line row for collapsed list groups.
   ──────────────────────────────────────────────────────────── */

.dispatch-compact-row {
    display: flex; align-items: center; gap: 12px;
    padding: 9px 14px; border-bottom: 1px solid var(--tint-border-soft);
    cursor: pointer; text-decoration: none; transition: background 0.1s;
}
.dispatch-compact-row:last-child { border-bottom: none; }
.dispatch-compact-row:active     { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { .dispatch-compact-row:hover { background: var(--tint-hover); } }
.dispatch-compact-row .cr-date     { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); width: 72px; flex-shrink: 0; }
.dispatch-compact-row .cr-body     { flex: 1; min-width: 0; }
.dispatch-compact-row > .cr-customer { flex: 1; min-width: 0; }
/* Size and colour come from the board-card rule next to .col-title, so the
   slip cards and these rows cannot drift apart again. What stays here is the
   row's own truncation behaviour. */
.dispatch-compact-row .cr-customer { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dispatch-compact-row .cr-sub      { font-size: 14px; color: var(--text-muted); margin-top: 2px; }
.dispatch-compact-row .cr-summary  { font-size: 14px; color: var(--text-muted); white-space: nowrap; }
.dispatch-compact-row .cr-qty      { font-family: var(--font-data); font-size: 14px; font-weight: 700; color: var(--text-secondary); white-space: nowrap; min-width: 80px; text-align: right; }
.dispatch-compact-row .cr-status    { width: 20px; flex-shrink: 0; text-align: center; font-size: 16px; }
.dispatch-compact-row .cr-status--ok { color: var(--status-success); }
.dispatch-compact-row .cr-status--cancel { color: var(--text-muted); }


/* ── Dispatch — dispatcher mobile ────────────────────────────
   Simplified loading cards for dispatcher role on Android.
   ──────────────────────────────────────────────────────────── */

.dispatcher-greeting  { font-size: 15px; color: var(--text-muted); margin-bottom: 4px; }
.dispatcher-headline  { font-size: 22px; font-weight: 700; color: var(--text-primary); margin-bottom: var(--section); font-family: var(--font-heading); }
.dispatcher-load-card {
    background: var(--surface-card); border: 1px solid var(--surface-border);
    border-radius: var(--radius-lg); padding: 14px;
    display: flex; align-items: center; gap: 12px;
    cursor: pointer; text-decoration: none; margin-bottom: 8px;
    transition: border-color 0.15s;
}
.dispatcher-load-card:active    { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { .dispatcher-load-card:hover { border-color: var(--brand-border); } }
.dispatcher-load-card.is-loaded { opacity: 0.5; }
.dispatcher-load-card .dlc-body     { flex: 1; min-width: 0; }
/* Type comes from the board-card rule; only truncation is the card's own. */
.dispatcher-load-card .dlc-customer { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dispatcher-load-card .dlc-summary  { font-size: 14px; color: var(--text-muted); margin-top: 2px; }
.dispatcher-load-card .dlc-arrow    { color: var(--text-muted); font-size: 16px; flex-shrink: 0; }

.search-match-hidden { display: none !important; }
/* .dispatcher-view is shown and hidden with the [hidden] attribute (dispatch.html). */

@media (max-width: 768px) {
    .dispatch-group-body { grid-template-columns: 1fr; }
}


/* ═══════════════════════════════════════════════════════════════
   § DISPATCH VIEW — dispatchview.html
   Lifecycle view + create mode for dispatches.
   Progress dots, envelope, PO line picker, loading entry,
   gate checklist, info grid, summary bar.
   Uses: BX.icons.check, BX.icons.xmark, BX.icons.chevDown from utils.js
   ═══════════════════════════════════════════════════════════════ */

/* ── Progress dots — 4-step lifecycle ─────────────────────────── */
.dv-progress       { display: flex; align-items: stretch; margin-bottom: var(--section); padding: 0 8px; }
.dv-step            { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; position: relative; }
.dv-step::before    { content: ''; position: absolute; top: 9px; left: -50%; right: 50%; height: 2px; background: var(--surface-border); }
.dv-step:first-child::before { display: none; }
.dv-dot             { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--surface-border); background: var(--surface-card); z-index: 1; flex-shrink: 0; }
.dv-step-label      { font-size: 14px; color: var(--text-muted); text-align: center; }
.dv-step.done .dv-dot          { background: var(--status-success); border-color: var(--status-success); }
.dv-step.done .dv-step-label   { color: var(--status-success); }
.dv-step.done::before          { background: var(--status-success); }
.dv-step.now .dv-dot           { background: var(--brand-primary); border-color: var(--brand-primary); }
.dv-step.now .dv-step-label    { color: var(--brand-primary); font-weight: 700; }
.dv-step.now::before           { background: var(--status-success); } /* user traveled here */

/* ── View header ──────────────────────────────────────────────── */
.dv-head            { margin-bottom: 16px; }
.dv-ref             { font-family: var(--font-data); font-size: 14px; color: var(--brand-primary); font-weight: 700; margin-bottom: 2px; }
.dv-customer        { font-size: 22px; font-weight: 700; color: var(--text-primary); margin-bottom: 6px; }
.dv-meta            { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 14px; color: var(--text-muted); }
.dv-meta-transit    { font-family: var(--font-data); color: var(--status-warning); }

/* ── Action bar ───────────────────────────────────────────────── */
.dv-actions         { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; align-items: center; }

/* ── Stage card ───────────────────────────────────────────────── */
.dv-card            { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: var(--radius-lg); margin-bottom: var(--stack); overflow: hidden; }
.dv-card-head       { padding: 12px 16px; border-bottom: 1px solid var(--surface-border); }
.dv-card-title      { font-size: 14px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase; color: var(--brand-primary); }
.dv-card.past       { opacity: 0.55; }

/* ── View lines — PO subheader + item rows ────────────────────── */
.dv-vpo             { display: flex; align-items: center; gap: 8px; padding: 9px 16px; background: var(--tint-subtle); border-bottom: 1px solid var(--surface-border); }
.dv-vpo-ref         { font-family: var(--font-data); font-size: 14px; font-weight: 700; color: var(--text-primary); }
.dv-vpo-cpo         { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.dv-vpo-due         { font-size: 14px; color: var(--text-muted); margin-left: auto; }
.dv-vline           { display: flex; align-items: baseline; padding: 10px 16px; border-bottom: 1px solid var(--surface-border); gap: 12px; }
.dv-vline:last-child { border-bottom: none; }
.dv-vline-fg        { flex: 1; min-width: 0; }
.dv-vline-name      { font-size: 14px; color: var(--text-primary); }
.dv-vline-code      { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.dv-vline-qty       { font-family: var(--font-data); font-size: 14px; text-align: right; white-space: nowrap; color: var(--text-primary); }
.dv-vline-unit      { color: var(--text-muted); }

/* ── Planned vs loaded qty columns ────────────────────────────── */
.dv-qtys            { display: flex; gap: 16px; text-align: right; }
.dv-q-col           { display: flex; flex-direction: column; gap: 1px; align-items: flex-end; }
.dv-q-label         { font-size: 14px; color: var(--text-muted); }
.dv-q-val           { font-family: var(--font-data); font-size: 14px; font-weight: 700; color: var(--text-primary); }

/* ── Info grid (dispatched/delivered) ─────────────────────────── */
.dv-info            { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; padding: 16px; }
.dv-info-item       { display: flex; flex-direction: column; gap: 2px; }
.dv-info-label      { font-size: 14px; color: var(--text-muted); }
.dv-info-val        { font-size: 14px; font-weight: 700; color: var(--text-primary); }
.dv-info-val.mono   { font-family: var(--font-data); }

/* ── Footer — created date ────────────────────────────────────── */
.dv-footer          { padding: 12px 16px; font-size: 14px; color: var(--text-muted); border-top: 1px solid var(--surface-border); }

/* ── Status banners ───────────────────────────────────────────── */
.dv-delivered       { display: flex; align-items: center; gap: 8px; padding: 14px 16px; background: var(--status-success-bg); border: 1px solid var(--status-success-border); border-radius: var(--radius-lg); font-size: 14px; font-weight: 700; color: var(--status-success-text); margin-bottom: var(--stack); }
.dv-cancelled       { display: flex; flex-direction: column; gap: 6px; padding: 14px 16px; background: var(--status-error-bg); border: 1px solid var(--status-error-border); border-radius: var(--radius-lg); font-size: 14px; color: var(--status-error-text); margin-bottom: var(--stack); }
.dv-cancelled-reason { color: var(--text-secondary); font-weight: 400; }
/* BOX-413 Part 1 — cancellation awaiting an Admin decision.
   WARNING, not error: nothing has failed and nothing is blocked. The goods are
   still in transit and the dispatch is still fully valid; someone has asked for
   a second pair of eyes. Error styling here would read as "this shipment broke"
   and would be indistinguishable from .dv-cancelled directly above, which is
   the genuinely terminal state.
   One size, one weight (14px/500) across both lines — the meta and reason are
   de-emphasised with COLOUR alone, never a smaller size or a lighter weight
   (the 600 in the font stack does not exist and silently renders as 700). */
.dv-cancel-req      { display: flex; flex-direction: column; gap: 6px; padding: 14px 16px; background: var(--status-warning-bg); border: 1px solid var(--status-warning-border); border-radius: var(--radius-lg); font-size: 14px; font-weight: 500; color: var(--status-warning-text); margin-bottom: var(--stack); }
.dv-cancel-req-head { display: flex; align-items: center; gap: 8px; }
.dv-cancel-req-meta { color: var(--text-secondary); }
.dv-cancel-req-why  { color: var(--text-secondary); }

/* ── Create mode — envelope ───────────────────────────────────── */
.dv-envelope        { margin-bottom: 28px; }
.dv-env-row         { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.dv-env-label       { font-size: 14px; color: var(--text-muted); flex-shrink: 0; }
.dv-env-pick        { font-size: 22px; font-weight: 700; color: var(--text-primary); background: none; border: none; border-bottom: 2px dashed var(--surface-border); padding: 0 0 2px; font-family: var(--font-ui); cursor: pointer; outline: none; appearance: none; -webkit-appearance: none; max-width: 500px; }
.dv-env-pick:focus  { border-bottom-color: var(--brand-primary); }
.dv-env-pick option { font-size: 16px; background: var(--surface-card); color: var(--text-primary); }
.dv-env-date        { font-size: 14px; color: var(--text-secondary); background: none; border: none; border-bottom: 1px dashed var(--surface-border); padding: 0 0 1px; font-family: var(--font-ui); cursor: pointer; outline: none; min-height: 40px; max-width: 180px; }
.dv-env-date:focus  { border-bottom-color: var(--brand-primary); }
.dv-env-addr        { font-size: 14px; color: var(--text-muted); line-height: 1.5; display: flex; align-items: baseline; gap: 8px; }
.dv-env-change      { font-size: 14px; color: var(--brand-primary); cursor: pointer; background: none; border: none; font-family: var(--font-ui); font-weight: 700; white-space: nowrap; min-height: 40px; }
.dv-env-change:active { transform: scale(0.97); }

/* ── Create mode — items heading ──────────────────────────────── */
.dv-items-head      { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--stack); padding-bottom: 8px; border-bottom: 1px solid var(--surface-border); }
.dv-items-title     { font-size: 14px; font-weight: 700; color: var(--text-muted); }
.dv-items-count     { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }

/* ── Create mode — PO group (collapsible, uses BX.icons.chevDown) ── */
.dv-po              { margin-bottom: 12px; border: 1px solid var(--surface-border); border-radius: var(--radius-lg); }
.dv-po-head         { display: flex; align-items: center; gap: 10px; padding: 10px 16px; background: var(--surface-card); cursor: pointer; user-select: none; min-height: 44px; }
.dv-po-head:active  { opacity: 0.9; }
.dv-po-chev         { color: var(--text-muted); font-size: 14px; transition: transform 0.2s; flex-shrink: 0; display: inline-flex; }
.dv-po.open .dv-po-chev { transform: rotate(90deg); }
.dv-po-ref          { font-family: var(--font-data); font-size: 14px; font-weight: 700; color: var(--text-primary); }
.dv-po-cpo          { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.dv-po-due          { font-size: 14px; color: var(--text-muted); }
.dv-po-fill         { font-size: 14px; color: var(--brand-primary); background: none; border: none; cursor: pointer; font-weight: 700; font-family: var(--font-ui); padding: 4px 8px; min-height: 40px; margin-left: auto; }
.dv-po-fill:active  { transform: scale(0.97); }
.dv-po-body         { display: none; }
.dv-po.open .dv-po-body { display: block; }

/* ── Create mode — line (tap to activate) ─────────────────────── */
.dv-line            { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 12px; padding: 12px 16px; border-top: 1px solid var(--surface-border); min-height: 52px; cursor: pointer; transition: background 0.15s; }
.dv-line:active     { transform: scale(0.99); }
.dv-line.on         { background: var(--brand-subtle); cursor: default; }
.dv-line.unavailable { opacity: 0.35; cursor: not-allowed; }
.dv-line-fg         { min-width: 0; display: flex; align-items: center; gap: 8px; }
.dv-line-dot        { width: 8px; height: 8px; border-radius: 50%; background: var(--surface-border); flex-shrink: 0; transition: background 0.15s; }
.dv-line.on .dv-line-dot { background: var(--brand-primary); }
.dv-line-info       { min-width: 0; }
.dv-line-name       { font-size: 14px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dv-line-code       { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.dv-line-avail      { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); text-align: right; white-space: nowrap; }
.dv-line-qty        { background: var(--tint-hover); border: 1px solid var(--surface-border); border-radius: var(--radius); color: var(--text-primary); padding: 6px 8px; font-size: 14px; font-family: var(--font-data); width: 80px; text-align: right; outline: none; min-height: 40px; opacity: 0; pointer-events: none; transition: opacity 0.15s; }
.dv-line.on .dv-line-qty { opacity: 1; pointer-events: auto; }
.dv-line-qty:focus  { border-color: var(--brand-primary); }

/* ── Create mode — sticky summary bar ─────────────────────────── */
.dv-summary         { position: fixed; bottom: 0; left: var(--sidebar-width); right: 0; background: var(--surface-card); border-top: 1px solid var(--surface-border); padding: 12px 20px; display: flex; align-items: center; gap: 16px; z-index: var(--z-sticky); padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
body.sidebar-is-collapsed .dv-summary { left: 48px; }
/* D7d (Ra 2026-10-18): .dv-sum-count is 0.875rem/700 on the scale (was 16px). */
.dv-sum-count       { font-family: var(--font-data); font-size: 0.875rem; font-weight: 700; white-space: nowrap; color: var(--text-primary); }
.dv-sum-detail      { font-size: 14px; color: var(--text-muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dv-sum-detail.has  { color: var(--brand-primary); }
.dv-sum-actions     { display: flex; gap: 8px; flex-shrink: 0; }

/* ── Loading entry — per-line qty input ───────────────────────── */
.dv-lline           { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--surface-border); min-height: 56px; }
.dv-lline:last-child { border-bottom: none; }
.dv-lline-fg        { min-width: 0; }
.dv-lline-name      { font-size: 14px; font-weight: 700; color: var(--text-primary); }
.dv-lline-code      { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.dv-lline-planned   { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); text-align: right; white-space: nowrap; }
.dv-lline-loaded    { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.dv-lline-input     { background: var(--tint-hover); border: 1px solid var(--surface-border); border-radius: var(--radius); color: var(--text-primary); padding: 6px 8px; font-size: 14px; font-family: var(--font-data); width: 90px; text-align: right; outline: none; min-height: 40px; }
.dv-lline-input:focus { border-color: var(--brand-primary); }
.dv-lline-match     { font-size: 14px; min-height: 18px; }
.dv-lline-match.ok  { color: var(--status-success); }
.dv-lline-match.diff { color: var(--status-warning); }

/* ── Loading entry — vehicle/driver grid ──────────────────────── */
.dv-logistics       { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 16px; border-top: 1px solid var(--surface-border); }
.dv-logistics-field { display: flex; flex-direction: column; gap: 4px; }
.dv-logistics-field label { font-size: 14px; color: var(--text-muted); text-transform: none; letter-spacing: 0; }
.dv-logistics-field input { background: var(--tint-hover); border: 1px solid var(--surface-border); border-radius: var(--radius); color: var(--text-primary); padding: 8px 10px; font-size: 14px; font-family: var(--font-data); outline: none; min-height: 40px; }
.dv-logistics-field input:focus { border-color: var(--brand-primary); }

/* ── Gate checklist ───────────────────────────────────────────── */
.dv-gate            { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: var(--radius-lg); padding: 16px; margin-bottom: var(--stack); }
.dv-gate-title      { font-size: 14px; font-weight: 700; color: var(--text-primary); margin-bottom: 12px; }
.dv-gate-row        { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--surface-border); }
.dv-gate-row:last-child { border-bottom: none; }
.dv-gate-row--pick  { cursor: pointer; }
.dv-gate-row--pick:active { transform: scale(0.97); }
.dv-gate-icon       { width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; }
.dv-gate-icon.ok    { background: var(--status-success-bg); color: var(--status-success-text); }
.dv-gate-icon.no    { background: var(--status-error-bg); color: var(--status-error-text); }
.dv-gate-label      { font-size: 14px; color: var(--text-primary); flex: 1; }
.dv-gate-val        { font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.dv-gate-input      { padding: 6px 10px; border: 1px solid var(--surface-border); border-radius: var(--radius); background: var(--tint-hover); color: var(--text-primary); font-family: var(--font-data); font-size: 14px; width: 180px; min-height: 40px; outline: none; }
.dv-gate-input:focus { border-color: var(--brand-primary); }
.dv-gate-save       { font-size: 14px; color: var(--brand-primary); background: none; border: none; cursor: pointer; font-weight: 700; padding: 4px 8px; min-height: 40px; font-family: var(--font-ui); }
.dv-gate-save:active { transform: scale(0.97); }

/* ── Modal form system — self-contained, zero global inheritance ──
   Structure: fp-body > fp-form-title + fp-form-text/fp-form-group + fp-footer
   Labels use <div> not <label> to avoid global label{} 10px cascade.
   Inputs override global input{} at matching specificity (0,2,1).
   ──────────────────────────────────────────────────────────────── */
/* D7d (Ra 2026-10-18): .fp-form-title is 0.875rem/700 (was 16px); also used by the jobcardview drawers. */
.fp-form-title      { font-size: 0.875rem; font-weight: 700; color: var(--text-primary); padding-bottom: 12px; margin-bottom: 16px; border-bottom: 1px solid var(--surface-border); }
.fp-form-text       { font-size: 14px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 16px; }
/* Modifiers (2026-07-29, additive): --muted for text-secondary -> text-muted
   (a fainter shade than the default, for secondary/optional explanatory
   text); --tight for a smaller bottom margin when .fp-form-text directly
   precedes a tightly-related list/group rather than the next unrelated
   field. Promoted from jobcardview.html, which needed the same inline
   override in 3 places across its QC/Hold drawers. */
.fp-form-text--muted { color: var(--text-muted); }
.fp-form-text--tight { margin-bottom: 8px; }
.fp-form-text--warning { color: var(--status-warning); }
.fp-form-group      { margin-bottom: 16px; }
.fp-form-label      { font-size: 14px; font-weight: 500; color: var(--text-secondary); margin-bottom: 6px; }
.fp-form-hint       { font-size: 14px; color: var(--text-muted); margin-top: 4px; }

/* Input/textarea/select — full override of global input{} rule.
   Specificity (0,2,1) matches global, wins by source order.
   44px height meets touch target minimum for factory Android.
   BOX-424 (2026-08-23): also matches under .content — the standard
   page-shell wrapper — not just .fp-overlay's .fp-body, so a form
   converted from .fp-overlay to the page shell (fg-form.html/
   jobcard-form.html's pattern) doesn't silently lose this styling.
   Either ancestor class reaches the same (0,2,1) specificity; nothing
   about the values below is .fp-body-specific. */
.fp-body input.fp-form-input,
.fp-body select.fp-form-input,
.content input.fp-form-input,
.content select.fp-form-input    { width: 100%; height: 44px; padding: 0 12px; background: var(--tint-hover); border: 1px solid var(--surface-border); border-radius: var(--radius); color: var(--text-primary); font-family: var(--font-ui); font-size: 14px; outline: none; box-sizing: border-box; transition: border-color 0.15s; }
.fp-body textarea.fp-form-input,
.content textarea.fp-form-input  { width: 100%; height: auto; min-height: 88px; padding: 10px 12px; background: var(--tint-hover); border: 1px solid var(--surface-border); border-radius: var(--radius); color: var(--text-primary); font-family: var(--font-ui); font-size: 14px; outline: none; box-sizing: border-box; resize: vertical; transition: border-color 0.15s; }

.fp-body input.fp-form-input:focus,
.fp-body select.fp-form-input:focus,
.fp-body textarea.fp-form-input:focus,
.content input.fp-form-input:focus,
.content select.fp-form-input:focus,
.content textarea.fp-form-input:focus { border-color: var(--brand-primary); box-shadow: 0 0 0 3px var(--brand-subtle); }
/* A field marked wrong keeps its red border and red ring on focus inside the form overlay too (these focus rules above are (0,3,1); these are (0,4,1) and come after them). */
.fp-body input.fp-form-input[aria-invalid="true"]:focus,
.fp-body select.fp-form-input[aria-invalid="true"]:focus,
.fp-body textarea.fp-form-input[aria-invalid="true"]:focus { border-color: var(--status-error); box-shadow: 0 0 0 3px var(--status-error-bg); }
@media (hover: hover) and (pointer: fine) {
    .fp-body input.fp-form-input:hover,
    .fp-body select.fp-form-input:hover,
    .fp-body textarea.fp-form-input:hover,
    .content input.fp-form-input:hover,
    .content select.fp-form-input:hover,
    .content textarea.fp-form-input:hover { border-color: var(--surface-border-strong); }
}

/* ── Ruled input (shared, 2026-08-09) ────────────────────────────
   Underline-only alternative to .fp-form-input — no box, no fill, no
   border-radius. Promoted from customer-form.html's page-local
   .bf-ruled family (there scoped .bf-form input.bf-ruled) into a real
   shared component, same .fp-body-scoped pattern .fp-form-input above
   already uses to reach the same (0,2,1) specificity — same reason:
   the global input:not([type=checkbox]):not([type=radio]) reset above
   (line ~1576) carries that specificity itself (two :not([type=...])
   clauses each count as a class match), so anything meant to override
   it needs to match or exceed (0,2,1), not just be "a class." A bare
   single-class rule loses silently regardless of what it declares —
   confirmed live: an earlier page-scoped .jc-recon-qty rule with this
   exact CSS still rendered fully boxed, because it never had enough
   specificity to beat the reset in the first place.
   Use instead of .fp-form-input wherever a lighter, inline-feeling
   field fits better than a full boxed field — .fp-form-input remains
   the default for standard labeled fields; this is a deliberate
   lighter alternative, not a replacement.
   BOX-424 (2026-08-23): widened to also match under .content, same
   reasoning as .fp-form-input above. Verified against both existing
   page-local .content input.input-ruled workarounds (dispatchview.html,
   rm-receipt-form.html) — both are byte-identical copies of this exact
   block re-scoped under .content, so this change doesn't alter their
   rendering at all; it just makes their local copies redundant. Not
   removed here — that's an optional follow-up cleanup, not required
   for this fix. */
.fp-body input.input-ruled,
.fp-body select.input-ruled,
.modal-body input.input-ruled,
.modal-body select.input-ruled,
.content input.input-ruled,
.content select.input-ruled {
    border: none; border-bottom: 1px solid var(--surface-border); border-radius: 0;
    outline: none; width: 100%; height: auto; padding: 3px 0 5px;
    background: transparent; color: var(--text-primary);
    font-family: var(--font-ui); font-size: 0.9375rem;
    box-shadow: none; transition: border-color 0.1s;
}
.fp-body textarea.input-ruled,
.modal-body textarea.input-ruled,
.content textarea.input-ruled {
    border: none; border-bottom: 1px solid var(--surface-border); border-radius: 0;
    outline: none; width: 100%; padding: 3px 0 5px;
    background: transparent; color: var(--text-primary);
    font-family: var(--font-ui); font-size: 0.9375rem;
    box-shadow: none; resize: none; line-height: 1.45; min-height: 1.375rem;
    transition: border-color 0.1s;
}
.fp-body input.input-ruled:focus,
.fp-body select.input-ruled:focus,
.fp-body textarea.input-ruled:focus,
.modal-body input.input-ruled:focus,
.modal-body select.input-ruled:focus,
.modal-body textarea.input-ruled:focus,
.content input.input-ruled:focus,
.content select.input-ruled:focus,
.content textarea.input-ruled:focus { border-bottom-color: var(--brand-primary); box-shadow: none; }
/* A ruled field marked wrong keeps its red line on focus and shows NO ring: the line is the whole indicator (ruling 18). Same specificity as the generic invalid-focus ring above; it comes later. */
.fp-body input.input-ruled[aria-invalid="true"]:focus,
.fp-body select.input-ruled[aria-invalid="true"]:focus,
.modal-body input.input-ruled[aria-invalid="true"]:focus,
.modal-body select.input-ruled[aria-invalid="true"]:focus { border-bottom-color: var(--status-error); box-shadow: none; }
@media (hover: hover) and (pointer: fine) {
    .fp-body input.input-ruled:hover,
    .fp-body select.input-ruled:hover,
    .fp-body textarea.input-ruled:hover,
    .modal-body input.input-ruled:hover,
    .modal-body select.input-ruled:hover,
    .modal-body textarea.input-ruled:hover,
    .content input.input-ruled:hover,
    .content select.input-ruled:hover,
    .content textarea.input-ruled:hover { border-color: var(--surface-border-strong); }
}
.fp-body input.input-ruled::placeholder,
.fp-body textarea.input-ruled::placeholder,
.modal-body input.input-ruled::placeholder,
.modal-body textarea.input-ruled::placeholder,
.content input.input-ruled::placeholder,
.content textarea.input-ruled::placeholder { color: var(--text-placeholder); }
.fp-body input.input-ruled.input-ruled--data,
.modal-body input.input-ruled.input-ruled--data,
.content input.input-ruled.input-ruled--data { font-family: var(--font-data); font-size: 0.875rem; }
.fp-body input.input-ruled.input-ruled--error,
.fp-body textarea.input-ruled.input-ruled--error,
.modal-body input.input-ruled.input-ruled--error,
.modal-body textarea.input-ruled.input-ruled--error,
.content input.input-ruled.input-ruled--error,
.content textarea.input-ruled.input-ruled--error { border-bottom-color: var(--status-error); }
.fp-body input.input-ruled:disabled,
.modal-body input.input-ruled:disabled,
.content input.input-ruled:disabled { opacity: 0.4; cursor: not-allowed; }
.fp-body select.input-ruled,
.modal-body select.input-ruled,
.content select.input-ruled {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23A09D97' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 0 center;
}
/* Per-field validation message (2026-09-30) — markFieldError() in
   inventory.html (Adjust/Transfer/Bulk Initial Stock) appends one of
   these right after the invalid field. Matches the app's error-text
   convention (.status-error color), not a generic muted caption. */
.field-error { display: block; font-size: 0.875rem; font-weight: 500; color: var(--status-error); margin-top: 4px; }

/* ── Utility ──────────────────────────────────────────────────── */
.dv-bar-actions     { display: flex; justify-content: flex-end; padding: 12px 16px; gap: 8px; }
.dv-has-summary     { padding-bottom: 100px !important; } /* space above fixed summary bar */

/* ── Print slip overlay ───────────────────────────────────────── */
/* z-index bumped 400->1001 and isolation added 2026-07-27 (Task #109):
   found while building the supplier PO print overlay that .bf-factory-
   dropdown sits at z-index 1000 — higher than this overlay's old 400 —
   and .prompt-overlay/.modal-overlay(--z-overlay:200) also share/undercut
   this same layer. Not confirmed as the exact cause of a reported
   "content behind looks transparent" issue (no browser access to verify
   live), but a real, findable stacking risk regardless, and isolation
   guarantees this overlay's own contents composite as one clean layer
   rather than depending on paint order versus siblings at equal/nearby
   z-index. Safe for every .print-overlay consumer (currently dispatch's
   slip and this PO print) — a print/modal overlay should always be
   topmost. */
.print-overlay        { display: none; position: fixed; inset: 0; background: var(--surface-overlay); z-index: 1001; isolation: isolate; align-items: flex-start; justify-content: center; padding: 40px 20px; overflow-y: auto; }
.print-overlay.show   { display: flex; }
.slip                 { background: #fff; color: #111; border-radius: 8px; width: 100%; max-width: 600px; padding: 32px; font-family: var(--font-ui); }
.slip-head            { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid #111; padding-bottom: 12px; margin-bottom: 16px; }
.slip-title           { font-size: 18px; font-weight: 700; }
.slip-ref             { font-family: var(--font-data); font-size: 16px; font-weight: 700; }
.slip-info            { display: flex; gap: 8px; margin-bottom: 6px; font-size: 14px; }
.slip-lbl             { min-width: 90px; color: #555; }
.slip-hr              { border: none; border-top: 1px solid #ccc; margin: 12px 0; }
.slip-tbl             { width: 100%; border-collapse: collapse; margin-bottom: 16px; }
.slip-tbl th          { text-align: left; padding: 8px 6px; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; border-bottom: 1px solid #222; color: #333; }
.slip-tbl th.r        { text-align: right; }
.slip-tbl td          { padding: 9px 6px; border-bottom: 1px solid #eee; font-size: 14px; }
.slip-tbl td.r        { text-align: right; font-family: var(--font-data); }
.slip-vehicle-driver  { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; font-size: 14px; margin-bottom: 12px; }
.slip-sign            { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 8px; }
.slip-sign-block      { border-top: 1px solid #999; padding-top: 8px; font-size: 14px; color: #555; }
.slip-sign-lbl        { font-size: 14px; color: #555; margin-bottom: 20px; }
.slip-actions         { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
.slip-actions .action-btn { background: var(--surface-card); }

/* ── Supplier PO print (Task #109, 2026-07-27) ──────────────────
   Shares the generic .print-overlay mechanism and the plain printed-
   document utility classes above (slip-hr, slip-tbl, slip-sign,
   slip-actions — none of those are dispatch-specific despite the name,
   just table/rule/signature-block styling with nothing dispatch-domain
   baked in, so this reuses them directly rather than duplicating). Only
   adds classes for structure a supplier PO needs that a dispatch slip
   doesn't: a wider container (900px, wide enough for a 6-column line-
   item table with HSN/GST columns — .slip's 600px is a narrower packing-
   slip width), a letterhead row, and a two-party block. */
/* v3 revision (2026-07-27, Ra rulings across several rounds): centered
   title, tenant identity grid (name/PAN/address/CIN/Udyam), Bill-To/
   Ship-To split, Supplier+PO-meta split, S.No item table, GST/HSN
   summary AFTER the grand total (not before), "Grand Total in words"
   left-aligned, "label always shown, value blank if missing" field
   rows throughout (.po-field), fixed A4 page for the actual print
   (@media print below) with a flex spacer so leftover page space
   always lands after the footer, never between sections. */
/* Density pass (2026-07-29, Ra report): "even 3 lines pushes to page 2".
   Root cause was never the item table — measured the full v3 layout
   (topgrid + Bill-To/Ship-To + Supplier/PO-meta + item table + totals +
   words + HSN table + terms/bank + footer/signature) at ~1087px against
   the A4 print box's actual usable height (297mm page - 2×16mm padding =
   265mm ≈ 1002px) — the surrounding sections alone were already over
   budget before any line items. Every value below is inter-section/inter-
   row SPACING only (margin/padding/gap) — no field, section, or font was
   removed; .slip-hr/.slip-tbl overrides are scoped to .po-print so the
   dispatch packing-slip's own spacing (shared classes) is untouched. */
/* 2026-07-29 (Ra report — traced by comparing against dispatch's .slip,
   which never had this problem): display:flex/flex-direction:column
   REMOVED from the base rule — confirmed via side-by-side comparison that
   .slip (plain block, no flex) renders correctly on screen while .po-print
   (identical otherwise, plus this flex) rendered fully transparent, in
   the same browser/session, ruling out caching/extensions/DevTools
   emulation (all would affect both equally; only .po-print was broken).
   The flex column only ever did anything useful under @media print, where
   min-height:297mm can make the box taller than its content and
   .po-flexspacer needs to push the footer down to fill it — on screen,
   .po-print has no min-height at all, so flex was providing zero visual
   benefit while apparently triggering this paint bug. Moved into the
   @media print override below, where it's actually needed. */
.po-print             { background: #fff; color: #111; border-radius: 8px; width: 100%; max-width: 900px; padding: 32px; font-family: var(--font-ui); }
.po-title             { text-align: center; font-size: 20px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase; margin-bottom: 8px; }
.po-topgrid           { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; padding-bottom: 8px; margin-bottom: 10px; border-bottom: 2px solid #111; }
/* 2026-07-29 (Ra ruling): re-paired into Entity Name/PAN, Address line 1/
   Udyam No, Address line 2/CIN — no longer a full-width name row over 4
   stacked fields. .po-tenant-name lost grid-column:1/-1 (now a normal
   left-column cell, row-paired with PAN via plain grid auto-flow, same as
   every other row here); .po-addr-line is its plain-text left-column
   counterpart for the two address rows — letterhead convention, no label,
   same as a printed return address. */
.po-topgrid .po-tenant-name { font-weight: 700; font-size: 16px; }
.po-addr-line         { font-size: 13px; color: #333; }
.po-addr-line:empty::after { content: '\00a0'; }
.po-2col              { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 8px; }
.po-2col-hd           { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #888; font-weight: 700; margin-bottom: 2px; }
.po-party-name        { font-weight: 500; font-size: 14px; margin-bottom: 2px; }
.po-field             { display: flex; gap: 6px; font-size: 13px; padding: 0; }
.po-field-lbl         { color: #888; min-width: 84px; flex-shrink: 0; }
.po-field-val         { color: #111; }
.po-field-val:empty::after { content: '\00a0'; }
.po-print .slip-hr    { margin: 6px 0; }
.po-print .slip-tbl   { margin-bottom: 8px; }
.po-print .slip-tbl th { padding: 5px 6px; font-size: 13px; }
.po-print .slip-tbl td { padding: 5px 6px; font-size: 13px; }
.po-print-totals      { max-width: 320px; margin-left: auto; margin-top: 4px; }
.po-print-totalsrow   { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; padding: 2px 0; }
.po-print-totalsrow.grand { border-top: 1px solid #111; margin-top: 3px; padding-top: 4px; font-weight: 700; font-size: 14px; }
.po-words             { text-align: left; font-size: 13px; color: #333; font-style: italic; margin-top: 6px; }
.hsn-tbl              { width: 100%; border-collapse: collapse; margin: 2px 0; }
.hsn-tbl th           { text-align: right; padding: 4px 6px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px; border-bottom: 1px solid #222; color: #333; }
.hsn-tbl th:first-child, .hsn-tbl th:nth-child(2) { text-align: left; }
.hsn-tbl td           { text-align: right; padding: 4px 6px; font-size: 13px; border-bottom: 1px solid #eee; font-family: var(--font-data); }
.hsn-tbl td:first-child, .hsn-tbl td:nth-child(2) { text-align: left; font-family: inherit; }
.po-efoot             { font-size: 12px; color: #888; font-style: italic; margin: 8px 0 6px; }
.po-signrow           { font-size: 13px; }
.po-signrow b         { color: #111; }
.po-flexspacer        { flex: 1; }
.item-spec            { color: #666; font-size: 12.5px; }

@media print {
    body > *:not(.print-overlay) { display: none !important; }
    .print-overlay { display: flex !important; position: static; padding: 0; background: none; }
    .slip { box-shadow: none; max-width: none; border-radius: 0; }
    /* A4 fixed page — only for the actual printed output. On-screen
       preview stays fluid (100%/max-width:900px above) so it fits a
       phone viewport without a separate mobile stylesheet; the table's
       own overflow-x:auto wrapper (in the JS template) covers a screen
       narrower than the table needs. Page padding tightened 16mm→10mm
       (2026-07-29 density pass) — still a normal business-document
       margin, frees ~45px of usable height on its own. */
    .po-print { width: 210mm; min-height: 297mm; max-width: none; padding: 10mm 12mm; box-shadow: none; border-radius: 0; display: flex; flex-direction: column; }
    @page { size: A4; margin: 0; }
    .slip-actions { display: none; }
}

/* ── Job Card print document (jc-print) ──────────────────────────
   /components/jobcardprint.js builds this into the shared print shell
   (print.html), registered as PRINT_DOCS.jobcard. Same pattern as
   .po-print above — moved out of jobcardview.html's own @media print
   block (2026-08-02) when its old hidden-div/window.print() page was
   replaced by the shared full-page print view. Base rules apply
   unconditionally (not just @media print) because the shell shows this
   as an on-screen "paper" preview before the user ever prints it — the
   old page never needed that since the div stayed display:none until
   the print dialog opened.

   True black-on-white throughout, deliberately outside the screen
   palette (tinted neutrals) — a factory printer needs ink-reliable
   borders and text, not a design compromise. Grounded in the real
   reference job card (KOSHAL COMPLETE JOB CARD 21.02.2026.pdf) — same
   block order operators already scan by position: header ->
   company/board line -> dimensions -> sheet size -> paper/BOM ->
   machine route -> quality -> closure signatures. */
.jc-print { background: #fff; color: #111; width: 100%; max-width: 900px; padding: 24px; }

.jcp-page { font-family: var(--font-ui); color: #000; font-size: 8.5pt; line-height: 1.35; }
.jcp-page table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.jcp-page table.jcp-table { border: 1pt solid #000; margin-bottom: 3mm; }
.jcp-page table.jcp-table td, .jcp-page table.jcp-table th { border: 0.5pt solid #000; padding: 1.2mm 2mm; vertical-align: top; word-wrap: break-word; }
.jcp-page thead th { font-size: 6.5pt; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; text-align: left; background: #f2f2f2; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.jcp-lbl { font-size: 6.5pt; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: #000; width: 38%; background: #f6f6f6; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.jcp-val { font-size: 8.5pt; font-weight: 500; }
.jcp-muted { color: #555; font-style: italic; font-weight: 400; }

.jcp-headtbl { border: none; margin-bottom: 2mm; }
.jcp-headtbl td { border: none; padding: 0; vertical-align: middle; }
.jcp-brand { font-size: 12pt; font-weight: 700; letter-spacing: 0.02em; width: 25%; }
.jcp-title { font-size: 16pt; font-weight: 700; text-align: center; letter-spacing: 0.06em; width: 40%; }
.jcp-idbox { width: 35%; }
.jcp-idbox table { margin-bottom: 0; }

.jcp-note { border: 0.5pt dashed #000; padding: 1.5mm 2mm; margin-bottom: 3mm; font-size: 8pt; font-style: italic; }

.jcp-2col { border: none; }
.jcp-2col td { border: none; padding: 0; vertical-align: top; }
.jcp-half { width: 50%; padding-right: 2mm !important; }
.jcp-half + .jcp-half { padding-right: 0 !important; padding-left: 2mm !important; }
.jcp-two-thirds { width: 62%; padding-right: 2mm !important; }
.jcp-third { width: 38%; padding-left: 2mm !important; }

.jcp-dims th, .jcp-sheetsize th { text-align: center; }
.jcp-dims td:not(.jcp-lbl), .jcp-sheetsize td { text-align: center; }
.jcp-finish .jcp-lbl { width: 22%; }

.jcp-notes-block { margin-bottom: 3mm; }
.jcp-ruled { border-bottom: 0.5pt solid #000; height: 6mm; }

.jcp-sign .jcp-lbl { background: none; width: auto; padding-bottom: 1mm; }
.jcp-sign .jcp-ruled { height: 10mm; }
.jcp-sign td { padding: 0 2mm 0 0; }

@media print {
    .jc-print { width: 210mm; max-width: none; padding: 0; box-shadow: none; }
    @page { size: A4; margin: 10mm; }
    /* page-break hygiene — keep each bordered block intact, never split a
       table row across a page boundary. Scoped to .jcp-page — this is
       shared components.css now, not a page with nothing else on it. */
    .jcp-page table.jcp-table, .jcp-page .jcp-notes-block, .jcp-page .jcp-sign { break-inside: avoid; }
    .jcp-page thead { display: table-header-group; }
    .jcp-page tr { break-inside: avoid; }
}

/* ── Responsive ───────────────────────────────────────────────── */
@media (max-width: 640px) {
    .dv-env-pick        { font-size: 18px; }
    .dv-line            { grid-template-columns: 1fr auto; }
    .dv-line-avail      { display: none; }
    .dv-sum-detail      { display: none; }
    .dv-lline           { grid-template-columns: 1fr auto; }
    .dv-lline-planned   { display: none; }
    .dv-logistics       { grid-template-columns: 1fr; }
    .dv-info            { grid-template-columns: 1fr 1fr; }
    .dv-summary         { left: 0; }
}


/* ═══════════════════════════════════════════════════════════════
   § PATCH — .field input ghost border
   The shared .field input rule adds border: 1.5px solid var(--surface-border)
   which creates a double-border ghost effect inside the .field container's
   own 1px border. The container IS the visual border — the input must have none.
   ═══════════════════════════════════════════════════════════════ */

.field input:not([type="checkbox"]):not([type="radio"]),
.field select {
    border: none;
}

/* ═══════════════════════════════════════════════════════════════
   § FINISHED GOODS MODULE
   fg.html (FG list), fgview.html (FG detail), fg-form.html (FG form)
   Prefixes: .fg-*, .fgv-*, .fgf-*
   ═══════════════════════════════════════════════════════════════ */

/* ── FG LIST (fg.html) ─────────────────────────────────────────── */
/* HARD RULE: no font-size below 14px anywhere in this section.     */

/* Customer context bar — shown when arriving from customer view (?customer=) */
.fg-customer-bar {
    display:       none;
    align-items:   center;
    gap:           8px;
    background:    var(--brand-subtle);
    border:        1px solid var(--brand-border);
    border-radius: var(--radius);
    padding:       7px 12px;
    margin-bottom: 14px;
    font-size:     14px;
    color:         var(--text-secondary);
}
.fg-customer-bar.visible { display: flex; }
.fg-customer-bar strong  { color: var(--text-primary); font-weight: 700; }
.fg-customer-bar-count   { font-family: var(--font-data); color: var(--text-muted); }
.fg-customer-bar-clear {
    background:  none;
    border:      none;
    color:       var(--brand-primary);
    cursor:      pointer;
    font-family: var(--font-ui);
    font-size:   14px;
    padding:     0;
    margin-left: auto;
}
.fg-customer-bar-clear:hover { opacity: 0.75; }

/* Light mode: match design intent — 12% alpha border, not the token's 22% */
[data-theme="light"] .fg-customer-bar { border-color: rgba(0, 100, 180, 0.12); }

/* ── FG table — same pattern as customers.html ──────────────────── */
/* <div class="card"><div class="table-wrap"><table class="table fg-table"> */

/* fg-table modifier: only what differs from the shared .table base. */
/* Column headers inherit .table thead th (14px, sunk bg — correct inside .card). */

/* No min-width constraint on mobile — we hide columns instead of scrolling. */
.fg-table { min-width: 0; }

/* Status-driven opacity */
.fg-table tr.fg-row--draft        { opacity: 0.55; }
.fg-table tr.fg-row--inactive,
.fg-table tr.fg-row--discontinued { opacity: 0.35; }
.fg-table tr.fg-row--inactive:hover td,
.fg-table tr.fg-row--discontinued:hover td { background: var(--tint-subtle); }
.fg-table tr.fg-row--discontinued .fg-product-name { text-decoration: line-through; }
.fg-table tr.fg-row--draft .fg-product-name        { color: var(--text-secondary); }

/* Light mode: delicate row borders and near-invisible hover */
[data-theme="light"] .fg-table tbody td          { border-bottom-color: rgba(0,0,0,0.06); }
[data-theme="light"] .fg-table tbody tr:hover td { background: rgba(0,0,0,0.018); }

/* FG ID column: monospace, white-space nowrap keeps column narrow */
.fg-table .fg-col-id {
    font-family: var(--font-data);
    font-weight: 700;
    color:       var(--text-primary);
    white-space: nowrap;
    width:       90px;
}

/* Product column — multi-line: name + customer + mobile sub-row */
.fg-product-cell { min-width: 0; }

.fg-product-name {
    font-size:     14px;
    font-weight:   500;
    color:         var(--text-primary);
    white-space:   nowrap;
    overflow:      hidden;
    text-overflow: ellipsis;
}
.fg-product-customer {
    font-size:     14px;
    color:         var(--text-muted);
    margin-top:    2px;
    white-space:   nowrap;
    overflow:      hidden;
    text-overflow: ellipsis;
}

/* Mobile-only: FG ID + specs collapsed under product name */
.fg-product-mobile-sub {
    display:     none;
    align-items: center;
    gap:         6px;
    margin-top:  2px;
    font-family: var(--font-data);
    font-size:   14px;
    color:       var(--text-muted);
    white-space: nowrap;
    overflow:    hidden;
}
.fg-mobile-id  { color: var(--brand-primary); font-weight: 700; flex-shrink: 0; }
.fg-mobile-sep { opacity: 0.4; flex-shrink: 0; }

/* Specification column: mono, muted, no wrap */
.fg-table .fg-col-specs {
    font-family: var(--font-data);
    color:       var(--text-muted);
    white-space: nowrap;
    width:       200px;
}

/* BOM column: version + status signalled by color only.
   Approved=green, Pending=amber, Draft=muted italic.
   No sub-label — color alone is sufficient at this scale. */
.fg-bom-version {
    font-family: var(--font-data);
    font-size:   14px;
    font-weight: 700;
    color:       var(--text-muted);
}
.fg-bom-version--approved { color: var(--status-success); }
.fg-bom-version--pending  { color: var(--status-warning); }
.fg-bom-version--draft    { color: var(--text-muted); font-style: italic; font-weight: 500; }

/* Status pills — uses shared .badge system (.badge-nobom, .badge-draft).
   Container provides right-alignment inside the table cell. */
.fg-pills-cell {
    display:         flex;
    flex-wrap:       wrap;
    gap:             4px;
    justify-content: flex-end;
    align-items:     center;
}

/* Mobile ≤768px: hide FG ID and Specs columns, fold into product cell */
@media (max-width: 768px) {
    .fg-table .fg-col-id,
    .fg-table .fg-col-specs { display: none; }
    .fg-product-mobile-sub  { display: flex; }
    .fg-product-customer    { display: none; }
}

/* ── FG VIEW (fgview.html) ─────────────────────────────────────── */

/* ── 60/40 Split layout ──────────────────────────────────── */

.fgv-wrap {
    display: grid;
    grid-template-columns: 60fr 40fr;
    min-height: calc(100vh - var(--topbar-height));
    gap: 0;
}

.fgv-left {
    padding: 24px 24px 60px;
    overflow-y: auto;
    border-right: 1px solid var(--surface-border);
}

.fgv-right {
    padding: 24px 18px 24px;
    background: var(--surface-card);
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--topbar-height));
    position: sticky;
    top: var(--topbar-height);
    overflow: hidden;
}

/* ── Identity block ──────────────────────────────────────── */

/* BOX-120: FG display ID — 11px, font-data, weight 700, brand-primary */
.fgv-display-id {
    font-size: 11px;
    font-family: var(--font-data);
    font-weight: 700;
    color: var(--brand-primary);
    letter-spacing: 0.05em;
    margin-bottom: 6px;
}

/* BOX-120: FG name — 24px, weight 300, letter-spacing -0.4px */
.fgv-name {
    font-size: 24px;
    font-weight: 300;
    letter-spacing: -0.4px;
    color: var(--text-primary);
    line-height: 1.2;
    margin-bottom: 8px;
    font-family: var(--font-ui);
}

/* BOX-120: Subtitle line — 13px, text-secondary */
.fgv-subtitle {
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 0;
    line-height: 1.5;
}

/* BOX-120: Customer link inside subtitle — brand-primary, weight 500 */
.fgv-customer-link {
    color: var(--brand-primary);
    font-weight: 500;
    text-decoration: none;
}
.fgv-customer-link:hover {
    text-decoration: underline;
}

/* ── Brief placeholder block ─────────────────────────────── */

/* BOX-120: Brief section wrapper — spacing above identity */
.fgv-brief-wrap {
    margin-top: 16px;
}

/* BOX-120: Column header right-align modifier */
.fgv-mvt-col-hdr--right {
    text-align: right;
}

/* BOX-120: Section label */
.fgv-section-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-bottom: 10px;
}

.fgv-brief-placeholder {
    padding: 12px 14px;
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    color: var(--text-muted);
    font-size: 13px;
    font-family: var(--font-ui);
}

/* ── Divider ─────────────────────────────────────────────── */

.fgv-divider {
    border: none;
    border-top: 1px solid var(--surface-border);
    margin: 16px 0;
}

/* ── Stock position bar ──────────────────────────────────── */

/* BOX-120: Segmented stock bar — 5px tall */
.fgv-stock-bar-track {
    height: 5px;
    border-radius: 3px;
    background: var(--surface-border);
    overflow: hidden;
    display: flex;
    margin-bottom: 12px;
}

.fgv-stock-seg {
    height: 100%;
    transition: width 0.3s ease;
}

.fgv-stock-seg--free       { background: var(--status-success); }
.fgv-stock-seg--reserved   { background: var(--brand-primary); }
.fgv-stock-seg--qc         { background: var(--status-warning); }
.fgv-stock-seg--rejected   { background: var(--status-error); } /* 2026-07-19: "production" was never a real FG balance state (confirmed against live inventory_balances — Free/QC_Hold/Reserved/Rejected are the real ones); replaced with Rejected */

/* BOX-120: 4 stat tiles below the bar */
.fgv-stock-tiles {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}

.fgv-stock-tile {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.fgv-stock-tile-header {
    display: flex;
    align-items: center;
    gap: 5px;
}

/* Shared status dot — small colored circle used to flag state at a glance.
   Consolidated 2026-07-26 from three near-duplicate versions: this file's
   own .fgv-stock-dot (despite living here, only ever used by fgview.html),
   fgview.html's page-local .stk-dot (identical purpose, different size —
   7px vs this rule's 6px, with .stk-dot silently winning the cascade since
   the page's own <style> loads after this file), and a fully inline dot
   in fgview.html's Job Cards row. One shared class, token-based color
   variants (not domain-specific ones like the old --free/--qc/--reserved/
   --rejected) so it's genuinely reusable for any at-a-glance flag, not
   just FG stock state. Standardized on 7px, the size that was actually
   rendering everywhere already.
   Round 2 (same day): also absorbed fgview.html's .deact-dot/--has/--nil
   and .wf-strip-dot/--pending/--draft/--changes/--deact — see the
   --outline and --muted variants below for the two real (small,
   deliberately accepted) deltas that came with those. rmview.html's
   .rv-dot absorbed with zero delta (identical size and colors already). */
.status-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.status-dot--success { background: var(--status-success); }
.status-dot--warning { background: var(--status-warning); }
.status-dot--error   { background: var(--status-error); }
.status-dot--brand   { background: var(--brand-primary); }
/* 2026-07-26: --text-secondary, not --text-muted — chosen when this
   variant absorbed fgview.html's .wf-strip-dot--draft (which used
   --text-secondary). Fainter vs darker doesn't matter enough here to
   justify a 6th variant just to keep two shades of "muted" apart. */
.status-dot--muted   { background: var(--text-secondary); }
/* 2026-07-26: absorbed fgview.html's .deact-dot--nil — a "zero count"
   indicator that was never a filled dot, a hollow outline circle. Not
   representable via a background-color swap like the others. */
.status-dot--outline { background: none; border: 1.5px solid var(--text-muted); }

/* ── Workflow strip — shared component (2026-07-26, Ra ruling) ────
   Consolidated from two independent copies: fgview.html's (BOM
   submit/approve/withdraw workflow) and supplierpoview.html's (PO
   draft/submit/approve/confirm workflow) — same class names, same
   structure, drifting apart in small but real ways: supplierpoview's
   .wf-btn was still 30px tall (pre touch-target-fix height; fgview's
   own copy was already raised to 38px on 2026-07-26, but that fix
   never propagated to its sibling since they were two separate style
   blocks), supplierpoview hardcoded border-radius:8px instead of the
   var(--radius) token, and supplierpoview's dots still used a private
   .wf-strip-dot--* set instead of the already-shared .status-dot--*
   system fgview had already migrated to above. This block is the
   union of every variant either page actually uses — not every page
   uses every variant, same as .status-dot's own --outline/--muted
   variants above. Both HTML files' own local copies are removed;
   their JS now emits these shared classes (and .status-dot instead of
   .wf-strip-dot) directly. */
.wf-strip { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-radius: var(--radius); margin: 2px 0 14px; flex-wrap: wrap; gap: 6px; }
.wf-strip--draft     { background: var(--surface-raised); border: 1px solid var(--surface-border); }
.wf-strip--pending   { background: var(--brand-subtle); border: 1px solid var(--brand-border); }
.wf-strip--changes   { background: var(--status-warning-bg); border: 1px solid var(--status-warning-border); }
.wf-strip--confirmed { background: var(--surface-raised); border: 1px solid var(--surface-border); }
.wf-strip--deact     { background: var(--status-warning-bg); border: 1px solid var(--status-warning-border); }
.wf-strip-left { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.wf-strip-label { font-size: 14px; font-weight: 500; white-space: nowrap; }
.wf-strip-actions { display: flex; gap: 6px; flex-shrink: 0; }
.wf-strip-context { width: 100%; padding-top: 6px; border-top: 1px solid rgba(0,0,0,0.06); margin-top: 6px; }
.wf-ctx-line { font-size: 14px; color: var(--text-secondary); line-height: 1.5; margin: 2px 0; }
.wf-ctx-line b { font-weight: 500; color: var(--text-primary); }
.wf-ctx-line--muted { opacity: 0.7; }
/* .wf-ctx-who: a distinct attribution/timestamp caption under a
   .wf-ctx-line (e.g. "Submitted · Jul 20, 2026") — not the same role as
   .wf-ctx-line--muted (a muted continuation of the main content line),
   so kept as its own class rather than forced into one. */
.wf-ctx-who { font-size: 14px; color: var(--text-muted); opacity: 0.7; }
.wf-ctx-prev { margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(0,0,0,0.06); opacity: 0.65; }
/* Height 38px (2026-07-26 fix, carried over from fgview.html's copy —
   supplierpoview.html's copy was still at the pre-fix 30px): these are
   real workflow action buttons (Submit/Approve/Withdraw/Revert...),
   well under the 44px touch-target guideline on a product whose own
   PRODUCT.md requires touch targets to hold up on shop-floor tablets/
   phones. 38px matches .action-btn's effective height so button
   families stay visually consistent within a page. */
.wf-btn { display: inline-flex; align-items: center; gap: 4px; height: 38px; padding: 0 10px; border: 1px solid var(--surface-border); border-radius: var(--radius); background: var(--surface-card); color: var(--text-secondary); font-size: 14px; font-weight: 500; cursor: pointer; font-family: var(--font-ui, inherit); white-space: nowrap; transition: border-color 0.15s, background 0.15s; }
.wf-btn:hover { border-color: var(--brand-border); background: var(--surface-2); }
.wf-btn:active { transform: scale(0.97); }
.wf-btn--primary { border-color: var(--brand-border); color: var(--brand-primary); }
.wf-btn--primary:hover { background: var(--brand-subtle); }
.wf-btn--warning { border-color: var(--status-warning-border); color: var(--status-warning-text); }
.wf-btn--warning:hover { background: var(--status-warning-bg); }
.wf-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
/* BOX-61: .wf-btn never had a :disabled treatment — BX.action() sets the
   native disabled attribute on click, but with no matching style it was
   invisible (no dimming, no not-allowed cursor), unlike .action-btn's own
   :disabled rule below. Mirrors that exactly for consistency. */
.wf-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.wf-btn:disabled:hover { border-color: var(--surface-border); background: var(--surface-card); }
.wf-btn--primary:disabled:hover { border-color: var(--brand-border); background: var(--surface-card); }

/* BOX-120: Uppercase label — 10px */
.fgv-stock-tile-label {
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

/* BOX-120: Monospace number */
.fgv-stock-tile-value {
    font-size: 14px;
    font-family: var(--font-data);
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1;
}

/* ── Tab bar ─────────────────────────────────────────────── */

/* BOX-120: Tab bar sticky wrapper — scroll.js will make sticky */
.fgv-tabs-wrap {
    margin-top: 16px;
    border-bottom: 1px solid var(--surface-border);
}

/* Mobile-only movements tab — hidden on desktop */
.fgv-tab-movements-mobile {
    display: none;
}

/* ── Tab panels ──────────────────────────────────────────── */

/* BOX-120: min-height prevents scroll snap on tab switch (Learning #29) */
.fgv-panel {
    padding-top: 20px;
    min-height: 400px;
}

/* ── Collapsible sections ────────────────────────────────── */

.fgv-collapsible {
    margin-bottom: 8px;
}

.fgv-coll-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 0;
    cursor: pointer;
    user-select: none;
    border-bottom: 1px solid var(--surface-border);
}

.fgv-coll-header:hover .fgv-coll-label {
    color: var(--text-primary);
}

/* BOX-120: Section label inside collapsible */
.fgv-coll-label {
    font-size: 14px;
    font-weight: 700;
    color: var(--text-secondary);
    flex: 1;
}

.fgv-coll-aside {
    font-size: 12px;
    font-family: var(--font-data);
    color: var(--text-muted);
    background: var(--tint-hover);
    border: 1px solid var(--surface-border);
    border-radius: 10px;
    padding: 1px 7px;
    min-width: 20px;
    text-align: center;
}

.fgv-coll-chevron {
    display: inline-flex;
    align-items: center;
    width: 14px;
    height: 14px;
    color: var(--text-muted);
    transition: transform 0.18s ease;
    flex-shrink: 0;
}

/* Rotate chevron when collapsed */
.fgv-collapsible.is-collapsed .fgv-coll-chevron {
    transform: rotate(-90deg);
}

.fgv-coll-body {
    padding: 12px 0 4px;
    overflow: hidden;
}

/* Hidden when collapsed */
.fgv-collapsible.is-collapsed .fgv-coll-body {
    display: none;
}

/* ── Orders table ────────────────────────────────────────── */

.fgv-orders-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.fgv-orders-table th {
    text-align: left;
    padding: 6px 10px;
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    border-bottom: 1px solid var(--surface-border);
    white-space: nowrap;
}

.fgv-orders-table td {
    padding: 9px 10px;
    border-bottom: 1px solid var(--surface-border);
    vertical-align: middle;
    font-size: 13px;
    color: var(--text-secondary);
}

.fgv-orders-table tr:last-child td {
    border-bottom: none;
}

.fgv-overdue-flag {
    color: var(--status-error);
    font-size: 11px;
    margin-left: 4px;
}

/* ── BOM Layer Stack ─────────────────────────────────────── */

.fgv-layer-stack {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 4px 0;
}

.fgv-layer-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    background: var(--tint-subtle);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    border-left: 3px solid var(--surface-border);
}

/* BOX-120: Liner accent — tan/muted */
.fgv-layer-row--liner {
    border-left-color: var(--text-muted);
}

/* BOX-120: Flute accent — brand-primary at 50% opacity, simulated via border */
.fgv-layer-row--flute {
    border-left-color: var(--brand-primary);
}

/* BOX-120: L/F position badge */
.fgv-layer-pos {
    font-size: 10px;
    font-family: var(--font-data);
    font-weight: 700;
    width: 18px;
    text-align: center;
    flex-shrink: 0;
}

.fgv-layer-pos--liner  { color: var(--text-muted); }
.fgv-layer-pos--flute  { color: var(--brand-primary); }

.fgv-layer-info {
    flex: 1;
    min-width: 0;
}

/* BOX-120: RM name — 13px, weight 500 */
.fgv-layer-rm-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.fgv-layer-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
}

/* BOX-120: GSM — 11px, font-data, muted */
.fgv-layer-gsm {
    font-size: 11px;
    font-family: var(--font-data);
    color: var(--text-muted);
}

/* BOX-120: Flute type chip — 10px, mono, brand */
.fgv-layer-flute-chip {
    font-size: 10px;
    font-family: var(--font-data);
    font-weight: 700;
    color: var(--brand-primary);
    background: var(--brand-subtle);
    padding: 0 5px;
    border-radius: var(--radius-sm);
}

/* BOX-120: RM code — 10px, font-data, muted */
.fgv-layer-rm-code {
    font-size: 10px;
    font-family: var(--font-data);
    color: var(--text-muted);
}

/* ── BOM version history rows ────────────────────────────── */

.fgv-bom-version-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid var(--surface-border);
}

.fgv-bom-version-row:last-child {
    border-bottom: none;
}

.fgv-bom-v-num {
    font-family: var(--font-data);
    font-size: 14px;
    color: var(--text-primary);
    font-weight: 700;
    min-width: 28px;
}

.fgv-bom-v-meta {
    font-size: 14px;
    color: var(--text-muted);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* BOX-120: BOM status badge classes — same pattern as existing bom-v-badge */
.fgv-bv-badge {
    font-size: 14px;
    padding: 2px 8px;
    border-radius: 8px;
    white-space: nowrap;
}
.fgv-bv-badge.s-approved   { background: var(--status-success-bg);    color: var(--status-success-text); }
.fgv-bv-badge.s-draft      { background: var(--status-warning-bg);    color: var(--status-warning-text); }
.fgv-bv-badge.s-superseded { background: var(--tint-hover);           color: var(--text-muted); }
.fgv-bv-badge.s-rejected   { background: var(--status-error-bg);      color: var(--status-error-text); }
.fgv-bv-badge.s-pending    { background: var(--brand-subtle);         color: var(--brand-primary); }


/* ── BOM layer grid (aligned columns) ─────────────────────────
   Grid layout: badge | paper name | gsm | flute | rm code.
   Liner rows: muted left border. Flute rows: brand left border.
   Used by: fgview.html, BOM version modal.                     */

.bom-grid {
    margin-bottom: var(--stack);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    overflow: hidden;
}

.bom-grid-hdr {
    display: grid;
    grid-template-columns: 40px 1fr 48px 50px 40px 70px 70px;
    gap: 6px;
    padding: 6px 10px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-muted);
    border-bottom: 1px solid var(--surface-border);
    background: var(--tint-subtle);
}

.bom-row {
    display: grid;
    grid-template-columns: 40px 1fr 48px 50px 40px 70px 70px;
    gap: 6px;
    padding: 10px 10px;
    font-size: 14px;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--tint-border-soft);
    border-left: 3px solid transparent;
    align-items: center;
}
.bom-row:last-child { border-bottom: none; }

.bom-row--liner { border-left-color: var(--text-muted); }
.bom-row--flute { border-left-color: var(--brand-primary); background: var(--brand-subtle); }

.bom-badge { font-family: var(--font-data); font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bom-row--liner .bom-badge { color: var(--text-muted); }
.bom-row--flute .bom-badge { color: var(--brand-primary); }

.bom-paper { font-weight: 500; color: var(--text-primary); }
.bom-gsm   { font-family: var(--font-data); font-variant-numeric: tabular-nums; text-align: right; }
.bom-flute { text-align: center; }
.bom-flute-chip { font-size: 12px; font-family: var(--font-data); font-weight: 700; color: var(--brand-primary); background: var(--brand-subtle); padding: 1px 6px; border-radius: var(--radius-sm); }
.bom-rm    { font-family: var(--font-data); font-variant-numeric: tabular-nums; color: var(--text-muted); }

/* ── BOM mobile: 2-line rows ─────────────────────────────────── */
.bom-grid--mobile  { display: none; }
.bom-mobile-hdr    { display: flex; align-items: baseline; gap: 6px; font-size: 14px; }
.bom-mobile-data   { padding-left: 32px; font-size: 14px; color: var(--text-muted); margin-top: 3px; }

@media (max-width: 600px) {
    .bom-grid--desktop { display: none; }
    .bom-grid--mobile  { display: block; }
    .bom-grid--mobile .bom-row { display: block; padding: 8px 10px; }
    .bom-grid--mobile .bom-badge { width: 32px; flex-shrink: 0; }
    .bom-grid--mobile .bom-paper { flex: 1; }
}

/* ── BOM meta helpers ─────────────────────────────────────────
   Used by: fgview.html BOM section.                            */
.bom-meta      { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.bom-meta-id   { font-family: var(--font-data); color: var(--text-muted); }
.bom-meta-ver  { font-weight: 700; }
.bom-note      { color: var(--text-muted); margin-bottom: 4px; }

/* ── Empty state modifier ─────────────────────────────────────
   Left-aligned empty for inline sections (not full-page).      */
.empty--left       { text-align: left; padding: 14px 0; }
.empty--left .empty-title,
.empty--left .empty-sub { text-align: left; }

/* ── DSP card section-label flex ──────────────────────────────
   Puts summary text alongside the label on mobile.             */
.dsp-card .section-label { display: flex; justify-content: space-between; align-items: center; }




/* ── Route flow (serpentine machine cards) ─────────────────────
   Wrapping card flow with arrows between machines.
   Matches fg-form route display for UI continuity.
   Used by: fgview.html route collapsible.                      */

.route-flow { display: flex; align-items: stretch; flex-wrap: wrap; gap: 8px; }

.route-card {
    padding: 10px 14px;
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    background: var(--tint-subtle);
    min-width: 120px;
    position: relative;
}
.route-card-name { font-size: 14px; font-weight: 700; color: var(--text-primary); display: block; }
.route-card-type { font-size: 14px; color: var(--text-muted); }
.route-card-seq  { position: absolute; top: 6px; right: 8px; font-family: var(--font-data); font-size: 14px; color: var(--text-muted); }
.route-arrow     { display: flex; align-items: center; color: var(--text-muted); font-size: 14px; flex-shrink: 0; }
.route-hint      { font-size: 14px; color: var(--text-muted); margin-top: 8px; }

@media (max-width: 600px) { .route-card { min-width: 100px; } }


/* ── DSP grid (demand / supply / production) ──────────────────
   3-column grid of .card boxes with .section-label + .dl inside.
   Used by: fgview.html.                                        */

.fgv-dsp { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin-bottom: var(--section); }
.dsp-summary { display: none; font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--text-muted); }
@media (max-width: 768px) {
    .fgv-dsp { grid-template-columns: 1fr; }
    .dsp-summary { display: inline; }
    .dsp-body { display: none; }
    .dsp-card.dsp-expanded .dsp-body { display: block; }
    .dsp-card.dsp-expanded .dsp-summary { display: none; }
    .dsp-card { cursor: pointer; }
}


/* ── Manufacturing param chips ────────────────────────────────
   Inline flex of label + bold value pairs below BOM grid.
   Used by: fgview.html.                                        */

.fgv-mfg   { display: flex; gap: 8px; flex-wrap: wrap; font-family: var(--font-data); font-size: 14px; color: var(--text-muted); padding: 10px 0 var(--stack); }
.fgv-mfg span { background: var(--tint-subtle); padding: 4px 10px; border-radius: var(--radius-sm); }
.fgv-mfg b { font-weight: 500; color: var(--text-secondary); }


/* ── Sticky aside (fgview right column) ───────────────────────
   Extends .detail-aside with sticky behavior + card background.
   Used by: fgview.html.                                        */

#fgvMovements .card { margin-top: 12px; }

.fgv-aside {
    background: var(--surface-card);
    position: sticky;
    top: var(--topbar-height);
    height: calc(100vh - var(--topbar-height) - 60px);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}
@media (max-width: 768px) { #fgvMovements .card { margin-top: 12px; }

.fgv-aside { position: static; height: auto; border-top: 1px solid var(--surface-border); } }


/* ── Coming soon placeholder ─────────────────────────────── */

.fgv-coming-soon {
    padding: 36px 20px;
    text-align: center;
    color: var(--text-muted);
}

.fgv-coming-soon-title {
    font-size: 14px;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

.fgv-coming-soon-sub {
    font-size: 14px;
    line-height: 1.6;
    max-width: 280px;
    margin: 0 auto;
}

/* ── Right column — Movements ledger ────────────────────── */

/* BOX-120: Movements section label */
.fgv-mvt-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-bottom: 12px;
    flex-shrink: 0;
}

/* BOX-120: Action buttons row — Receive / Dispatch / Reserve */
.fgv-mvt-actions {
    display: flex;
    gap: 6px;
    margin-bottom: 10px;
    flex-shrink: 0;
}

.fgv-mvt-action-btn {
    height: 28px;
    padding: 0 10px;
    font-size: 12px;
    font-family: var(--font-ui);
    font-weight: 500;
    background: var(--tint-hover);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    color: var(--text-secondary);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    transition: background 0.1s, border-color 0.1s, color 0.1s;
    white-space: nowrap;
}

.fgv-mvt-action-btn:hover {
    background: var(--brand-subtle);
    border-color: var(--brand-border);
    color: var(--brand-primary);
}

/* BOX-120: Active action button state */
.fgv-mvt-action-btn.is-active {
    background: var(--brand-subtle);
    border-color: var(--brand-primary);
    color: var(--brand-primary);
}

/* BOX-120: Inline action form — slides in */
.fgv-mvt-form-wrap {
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    padding: 14px;
    margin-bottom: 10px;
    flex-shrink: 0;
    /* Subtle slide-in animation */
    animation: fgvFormIn 0.15s ease;
}

@keyframes fgvFormIn {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

.fgv-form-hidden {
    display: none;
}

.fgv-mvt-form-title {
    font-size: 12px;
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 10px;
}

.fgv-form-field {
    margin-bottom: 10px;
}

.fgv-form-field label {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin-bottom: 4px;
    display: block;
}

/* BOX-120: Compact inputs inside action form */
.fgv-form-field input,
.fgv-form-field select,
.fgv-form-field textarea {
    width: 100%;
    height: 30px;
    padding: 0 9px;
    background: var(--surface-page);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    color: var(--text-primary);
    font-family: var(--font-ui);
    font-size: 12px;
    outline: none;
    transition: border-color 0.1s;
}

.fgv-form-field input:focus,
.fgv-form-field select:focus,
.fgv-form-field textarea:focus {
    border-color: var(--brand-primary);
}

.fgv-form-field textarea {
    height: auto;
    padding: 6px 9px;
    resize: none;
    min-height: 52px;
}

.fgv-form-field input::placeholder,
.fgv-form-field textarea::placeholder {
    color: var(--text-placeholder);
}

.fgv-form-note {
    font-size: 11px;
    color: var(--text-muted);
    margin-bottom: 10px;
    line-height: 1.4;
    padding: 6px 8px;
    background: var(--status-warning-bg);
    border-radius: var(--radius-sm);
    border: 1px solid var(--status-warning-border);
    color: var(--status-warning-text);
}

.fgv-mvt-form-footer {
    display: flex;
    gap: 6px;
    margin-top: 10px;
}

.fgv-form-submit {
    flex: 1;
    height: 28px;
    font-size: 12px;
    font-family: var(--font-ui);
    font-weight: 700;
    background: var(--brand-subtle);
    border: 1px solid var(--brand-primary);
    border-radius: var(--radius);
    color: var(--brand-primary);
    cursor: pointer;
    transition: background 0.1s;
}
.fgv-form-submit:hover { background: var(--brand-subtle-hover); }
.fgv-form-submit:disabled { opacity: 0.5; cursor: not-allowed; }

.fgv-form-cancel {
    height: 28px;
    padding: 0 12px;
    font-size: 12px;
    font-family: var(--font-ui);
    background: var(--tint-hover);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.1s;
}
.fgv-form-cancel:hover { background: var(--tint-hover-strong); }

/* ── Filter pills row ────────────────────────────────────── */

/* BOX-120: Filter pills — All / In / Out / Hold */
.fgv-mvt-filters {
    display: flex;
    gap: 5px;
    margin-bottom: 8px;
    flex-shrink: 0;
    flex-wrap: wrap;
}

.fgv-mvt-filter-pill {
    height: 24px;
    padding: 0 10px;
    border-radius: 12px;
    border: 1px solid var(--surface-border);
    background: transparent;
    font-size: 11px;
    font-family: var(--font-ui);
    font-weight: 500;
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.1s, border-color 0.1s, color 0.1s;
    white-space: nowrap;
}

.fgv-mvt-filter-pill:hover {
    background: var(--tint-hover);
    color: var(--text-secondary);
}

/* BOX-120: Active filter pill */
.fgv-mvt-filter-pill.is-active {
    background: var(--brand-subtle);
    border-color: var(--brand-primary);
    color: var(--brand-primary);
    font-weight: 700;
}

/* ── Movements table ─────────────────────────────────────── */

.fgv-mvt-table-wrap {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
    margin-bottom: 0;
}

/* BOX-120: Column headers row */
.fgv-mvt-col-headers {
    display: grid;
    grid-template-columns: 56px 100px 72px 1fr 52px 52px;
    padding: 4px 0;
    border-bottom: 1px solid var(--surface-border);
    position: sticky;
    top: 0;
    background: var(--surface-card);
    z-index: 1;
    flex-shrink: 0;
}

.fgv-mvt-col-hdr {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    padding: 4px 3px;
}

/* Movement rows list */
.fgv-mvt-rows {
    display: flex;
    flex-direction: column;
}

/* BOX-120: Individual movement row */
.fgv-mvt-row {
    display: grid;
    grid-template-columns: 56px 100px 72px 1fr 52px 52px;
    border-bottom: 1px solid var(--surface-border);
    align-items: center;
}

.fgv-mvt-row:last-child {
    border-bottom: none;
}

.fgv-mvt-row:hover {
    background: var(--tint-hover);
}

.fgv-mvt-cell {
    padding: 6px 3px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* BOX-120: Date cell — 10px, font-data, muted */
.fgv-mvt-date {
    font-size: 10px;
    font-family: var(--font-data);
    color: var(--text-muted);
}

/* BOX-120: Type cell — 11px, weight 600 with 4px dot */
.fgv-mvt-type {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 700;
}

.fgv-mvt-type-dot {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    flex-shrink: 0;
}

.fgv-mvt-type--in   { color: var(--status-success); }
.fgv-mvt-type--in .fgv-mvt-type-dot   { background: var(--status-success); }
.fgv-mvt-type--out  { color: var(--brand-primary); }
.fgv-mvt-type--out .fgv-mvt-type-dot  { background: var(--brand-primary); }
.fgv-mvt-type--lock { color: var(--status-warning); }
.fgv-mvt-type--lock .fgv-mvt-type-dot { background: var(--status-warning); }

/* BOX-120: Ref cell — 10px, font-data, weight 700, brand-primary */
.fgv-mvt-ref {
    font-size: 10px;
    font-family: var(--font-data);
    font-weight: 700;
    color: var(--brand-primary);
}

/* BOX-120: Note cell — 11px, muted */
.fgv-mvt-note {
    font-size: 11px;
    color: var(--text-muted);
}

/* BOX-120: Qty cell — 11px, font-data, weight 600 */
.fgv-mvt-qty {
    font-size: 11px;
    font-family: var(--font-data);
    font-weight: 700;
    text-align: right;
}
.fgv-mvt-qty--pos { color: var(--status-success); }
.fgv-mvt-qty--neg { color: var(--brand-primary); }

/* BOX-120: Balance cell — 10px, font-data */
.fgv-mvt-bal {
    font-size: 10px;
    font-family: var(--font-data);
    color: var(--text-secondary);
    text-align: right;
}

/* ── Pagination row ──────────────────────────────────────── */

/* BOX-120: Pagination — prev / count / next */
.fgv-mvt-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 8px 0 4px;
    flex-shrink: 0;
    border-top: 1px solid var(--surface-border);
}

.fgv-pg-btn {
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--tint-hover);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    font-size: 14px;
    transition: background 0.1s, color 0.1s;
}
.fgv-pg-btn:hover:not(:disabled) { background: var(--tint-hover-strong); color: var(--text-primary); }
.fgv-pg-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* BOX-120: Page counter — 11px, monospace */
.fgv-pg-count {
    font-size: 11px;
    font-family: var(--font-data);
    color: var(--text-muted);
    white-space: nowrap;
}

/* ── Empty state in movements ────────────────────────────── */

.fgv-mvt-empty {
    padding: 40px 16px;
    text-align: center;
    color: var(--text-muted);
    font-size: 13px;
}

/* ── Mobile responsive ───────────────────────────────────── */

@media (max-width: 760px) {
    /* BOX-120: Right column hides on mobile */
    .fgv-wrap {
        display: block;
    }

    .fgv-right {
        display: none;
        height: auto;
        position: static;
    }

    /* Show movements tab on mobile */
    .fgv-tab-movements-mobile {
        display: inline-flex;
    }

    /* Stock tiles collapse to 2×2 grid */
    .fgv-stock-tiles {
        grid-template-columns: repeat(2, 1fr);
    }

    .fgv-left {
        padding: 16px 16px 60px;
    }
}

/* ── FG FORM (fg-form.html) ────────────────────────────────────── */

/* ── Browser autofill — keep dark surface ─────────────────── */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 100px var(--surface-card) inset;
    -webkit-text-fill-color: var(--text-primary);
    caret-color: var(--text-primary);
}
select { color: var(--text-primary); background: var(--tint-hover); }
select option   { background: var(--surface-card); color: var(--text-primary); }
select optgroup { background: var(--surface-card); color: var(--text-muted); }

/* ── Mode context header — convert tag ────────────────────── */
/* BOX-001: Convert mode info tag shown in fp-sub */
.fgf-convert-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--brand-subtle);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius-sm);
    padding: 3px 8px;
    font-size: 12px;
    font-family: var(--font-data);
    color: var(--brand-secondary);
    margin-top: 4px;
}

/* ── Toggle group (ply, colour count, lamination, varnish) ── */
/* BOX-002: Toggle group replaces radio buttons */
.fgf-toggle-group {
    display: flex;
    align-items: center;
    gap: 3px;
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    padding: 3px;
    width: fit-content;
}
.fgf-toggle-btn {
    padding: 5px 14px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font-size: 14px;
    font-family: var(--font-ui);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
    white-space: nowrap;
    line-height: 1.4;
}
.fgf-toggle-btn:hover {
    background: var(--tint-hover-strong);
    color: var(--text-secondary);
}
.fgf-toggle-btn.active {
    background: var(--surface-raised);
    color: var(--text-primary);
    font-weight: 700;
}

/* ── Unit pair (value + unit select side by side) ─────────── */
/* BOX-003: Used for CS, BS, board thickness, box weight */
.fgf-unit-pair {
    display: flex;
    gap: 8px;
    align-items: flex-end;
}
.fgf-unit-pair .fg { margin-bottom: 0; }
.fgf-unit-pair .fg:first-child { flex: 2; }
.fgf-unit-pair .fg:last-child  { flex: 1; min-width: 80px; }

/* ── OD / ID dimension row ─────────────────────────────────── */
/* BOX-004: 3 mono inputs in a row for L × W × H */
.fgf-dim-row {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 8px;
}
.fgf-dim-row input {
    font-family: var(--font-data);
    text-align: center;
}

/* ── Locked summary (version mode section 1 replacement) ───── */
/* BOX-005: Shown in version mode instead of Section 1 fields */
.fgf-locked-summary {
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    padding: 12px 16px;
    font-size: 14px;
    font-family: var(--font-data);
    color: var(--text-secondary);
    line-height: 1.6;
    margin-bottom: var(--section);
}
.fgf-locked-summary-label {
    font-size: 11px;
    font-family: var(--font-ui);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
    margin-bottom: 6px;
}

/* ── Layer builder ─────────────────────────────────────────── */
/* BOX-006: Table-style layout for BOM layers */
.fgf-layer-builder {
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    overflow: hidden;
}
.fgf-layer-col-headers {
    display: grid;
    grid-template-columns: 4px 28px 1fr 80px 80px 80px 100px 80px;
    gap: 8px;
    align-items: center;
    padding: 0 12px;
    height: 32px;
    background: var(--surface-card);
    border-bottom: 1px solid var(--surface-border);
    font-size: 11px;
    font-family: var(--font-ui);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
}
.fgf-layer-row {
    display: grid;
    grid-template-columns: 4px 28px 1fr 80px 80px 80px 100px 80px;
    gap: 8px;
    align-items: center;
    padding: 8px 12px;
    border-bottom: 1px solid var(--tint-border-soft);
    transition: background 0.1s;
}
.fgf-layer-row:last-child { border-bottom: none; }
.fgf-layer-row:hover { background: var(--tint-hover); }

/* Left accent bar — liner vs flute */
.fgf-layer-accent {
    width: 3px;
    height: 28px;
    border-radius: 2px;
    flex-shrink: 0;
    align-self: center;
}
.fgf-layer-liner .fgf-layer-accent {
    background: var(--status-warning);
    opacity: 0.55;
}
.fgf-layer-flute .fgf-layer-accent {
    background: var(--brand-primary);
    opacity: 0.55;
}

/* Layer badge (L / F) */
.fgf-layer-badge {
    width: 22px;
    height: 22px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-family: var(--font-data);
    font-weight: 700;
    flex-shrink: 0;
}
.fgf-layer-liner .fgf-layer-badge {
    background: var(--status-warning-bg);
    color: var(--status-warning-text);
}
.fgf-layer-flute .fgf-layer-badge {
    background: var(--brand-subtle);
    color: var(--brand-secondary);
}

/* Layer name tooltip inside material column */
.fgf-layer-pos-name {
    font-size: 11px;
    color: var(--text-muted);
    margin-bottom: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Layer cells — no extra margin */
.fgf-layer-cell { margin: 0; }
.fgf-layer-cell input,
.fgf-layer-cell select {
    height: var(--row-h);
    padding: 0 8px;
}
.fgf-layer-cell input[readonly] {
    color: var(--text-muted);
    cursor: default;
}
.fgf-layer-gsm  { font-family: var(--font-data); font-size: 13px; color: var(--text-secondary); text-align: center; }
.fgf-layer-est  { font-family: var(--font-data); font-size: 13px; color: var(--text-muted); text-align: right; }
.fgf-layer-qty  input { font-family: var(--font-data); }

/* ── Calculated estimates block ────────────────────────────── */
/* BOX-007: Shows cut length, reel width, est. box weight */
.fgf-calc-block {
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    border-radius: var(--radius);
    padding: 14px 16px;
    margin-top: 16px;
}
.fgf-calc-block-title {
    font-size: 11px;
    font-family: var(--font-ui);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-muted);
    margin-bottom: 12px;
}
.fgf-calc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 14px;
}
.fgf-calc-label {
    font-size: 11px;
    color: var(--text-muted);
    margin-bottom: 3px;
}
.fgf-calc-value {
    font-family: var(--font-data);
    font-size: 18px;
    font-weight: 500;
    color: var(--text-primary);
    line-height: 1.2;
}
.fgf-calc-unit {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* ── Production preview block ──────────────────────────────── */
/* BOX-008: Blue-tinted preview showing RM amounts per batch */
.fgf-preview-block {
    background: var(--brand-subtle);
    border: 1px solid var(--brand-border);
    border-radius: var(--radius);
    padding: 14px 16px;
    margin-top: 16px;
}
.fgf-preview-title {
    font-size: 11px;
    font-family: var(--font-ui);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--brand-primary);
    margin-bottom: 12px;
}
.fgf-preview-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 5px 0;
    border-bottom: 1px solid var(--brand-border);
    font-size: 14px;
}
.fgf-preview-row:last-child { border-bottom: none; }
.fgf-preview-badge {
    font-family: var(--font-data);
    font-size: 10px;
    font-weight: 700;
    color: var(--brand-secondary);
    width: 14px;
    flex-shrink: 0;
}
.fgf-preview-rm-name {
    flex: 1;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.fgf-preview-qty {
    font-family: var(--font-data);
    font-size: 14px;
    color: var(--brand-primary);
    white-space: nowrap;
}
.fgf-preview-total-row {
    display: flex;
    justify-content: space-between;
    padding-top: 8px;
    font-size: 14px;
    font-weight: 700;
    color: var(--text-primary);
    border-top: 1px solid var(--brand-border);
    margin-top: 2px;
}
.fgf-preview-total-label { color: var(--text-muted); font-size: 12px; }
.fgf-preview-total-val { font-family: var(--font-data); color: var(--brand-primary); }

/* ── Machine routing section ───────────────────────────────── */
/* BOX-009: Route auto-hint and override hint */
.fgf-route-hint {
    font-size: 12px;
    margin-top: 4px;
    padding: 4px 0;
}
.fgf-route-hint--auto  { color: var(--status-success); }
.fgf-route-hint--over  { color: var(--status-warning); }
.fgf-reset-link {
    background: none;
    border: none;
    color: var(--brand-primary);
    font-size: 12px;
    font-family: var(--font-ui);
    cursor: pointer;
    padding: 0;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.fgf-reset-link:hover { color: var(--brand-secondary); }

/* Route sequence table */
.fgf-route-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 10px;
    font-size: 14px;
}
.fgf-route-table th {
    text-align: left;
    font-size: 11px;
    font-family: var(--font-ui);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
    padding: 6px 10px;
    border-bottom: 1px solid var(--surface-border);
    font-weight: 500;
}
.fgf-route-table td {
    padding: 8px 10px;
    border-bottom: 1px solid var(--tint-border-soft);
    color: var(--text-secondary);
    vertical-align: middle;
}
.fgf-route-table tr:last-child td { border-bottom: none; }
.fgf-route-seq-num {
    font-family: var(--font-data);
    font-size: 12px;
    color: var(--text-muted);
    width: 32px;
}
.fgf-route-machine { color: var(--text-primary); font-weight: 500; }
.fgf-route-stage   { font-size: 12px; color: var(--text-muted); }

/* ── Tooling rows ──────────────────────────────────────────── */
/* BOX-010: Linked tooling table */
.fgf-tooling-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}
.fgf-tooling-table th {
    text-align: left;
    font-size: 11px;
    font-family: var(--font-ui);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
    padding: 6px 10px;
    border-bottom: 1px solid var(--surface-border);
    font-weight: 500;
}
.fgf-tooling-row td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--tint-border-soft);
    vertical-align: middle;
}
.fgf-tooling-row:last-child td { border-bottom: none; }
.fgf-tooling-remove {
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 16px;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    transition: color 0.12s, background 0.12s;
    line-height: 1;
}
.fgf-tooling-remove:hover {
    color: var(--status-error-text);
    background: var(--status-error-bg);
}
.fgf-tooling-add-row {
    display: flex;
    gap: 12px;
    margin-top: 10px;
    align-items: center;
}
.fgf-tooling-add-btn {
    background: none;
    border: 1px dashed var(--surface-border);
    border-radius: var(--radius-sm);
    color: var(--brand-primary);
    font-size: 13px;
    font-family: var(--font-ui);
    padding: 6px 12px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.fgf-tooling-add-btn:hover {
    background: var(--brand-subtle);
    border-color: var(--brand-border);
}

/* ── Empty tooling state ───────────────────────────────────── */
.fgf-tooling-empty {
    font-size: 13px;
    color: var(--text-muted);
    font-style: italic;
    padding: 10px 0 4px;
}

/* ── Version note required note ────────────────────────────── */
.fgf-version-note-hint {
    font-size: 12px;
    color: var(--status-warning);
    margin-top: 3px;
}

/* ── Colour name inputs row ────────────────────────────────── */
.fgf-colour-inputs {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 6px;
}
.fgf-colour-inputs .fg {
    flex: 1;
    min-width: 120px;
    margin-bottom: 0;
}

/* ── Box weight override hint ───────────────────────────────── */
.fgf-bw-hint {
    font-size: 11px;
    margin-top: 4px;
}
.fgf-bw-hint--auto     { color: var(--text-muted); }
.fgf-bw-hint--override { color: var(--status-warning); }

/* ── Inactive customer warning ─────────────────────────────── */
.fgf-inactive-warn {
    display: none;
    margin-top: 5px;
    padding: 6px 10px;
    background: var(--status-warning-bg);
    border: 1px solid var(--status-warning-border);
    border-radius: var(--radius-sm);
    font-size: 12px;
    color: var(--status-warning-text);
}
.fgf-inactive-warn.show { display: block; }

/* ── Mono inputs (codes, numbers) ──────────────────────────── */
.fgf-mono { font-family: var(--font-data); }

/* ── Layer col header alignment helpers ────────────────────── */
/* BOX-011: Used in JS-rendered layer col headers */
.fgf-col-center { text-align: center; }
.fgf-col-right  { text-align: right; }

/* ── Layer — no-flute placeholder cell ─────────────────────── */
/* BOX-012: Shown in liner rows where flute col is not applicable */
.fgf-no-flute {
    font-size: 12px;
    color: var(--text-muted);
    padding-left: 4px;
}

/* ── Tooling table — column widths ─────────────────────────── */
/* BOX-013: Structural widths for tooling table columns */
.fgf-tooling-col-asset   { width: 40%; }
.fgf-tooling-col-remove  { width: 36px; }

/* ── Tooling table — type cell ──────────────────────────────── */
/* BOX-014: Type auto-fill cell in tooling table */
.fgf-tooling-type-cell {
    font-size: 13px;
    color: var(--text-muted);
    white-space: nowrap;
}

/* ── Tooling table — full-width inputs/selects ─────────────── */
/* BOX-015: Stretch interactive controls inside table cells */
.fgf-tooling-table td select,
.fgf-tooling-table td input[type="text"] {
    width: 100%;
}

/* ── Layer RM material select — fills column ────────────────── */
/* BOX-016: RM dropdown stretches to fill the material column */
.fgf-layer-rm-select {
    width: 100%;
}

@media (prefers-reduced-motion: reduce) {
    .dv-po-chev, .dv-line, .dv-line-dot, .dv-line-qty { transition: none; }
}

/* Reduced motion: the looping pulse and spinner stand still and the form's entrance does not slide. .pipe-pulse stays a solid dot (the active step is still marked);
   .bf-spinner stays a ring with one coloured arc (still reads as "working": its sign-out message is text); the form simply appears. */
@media (prefers-reduced-motion: reduce) {
    .pipe-pulse, .bf-spinner, .fgv-mvt-form-wrap { animation: none; }
}

@media (hover: hover) and (pointer: fine) {
    .dv-po-head:hover   { background: var(--tint-hover); }
    .dv-line:not(.on):not(.unavailable):hover { background: var(--tint-hover); }
    .dv-po-fill:hover   { text-decoration: underline; }
    .dv-env-change:hover { text-decoration: underline; }
    .dv-gate-save:hover  { text-decoration: underline; }
    .dv-gate-row--pick:hover { background: var(--tint-hover); }
}

/* ── Load-today readiness badge (availability service, 2026-08-20) ──
   Card-level badge on dispatch board Load-today cards. Colours are semantic
   status tokens; "available" uses a hollow dot to read as "free but not yet
   assigned" vs the filled "assigned & ready". Type ≥0.8125rem / weight ≥500. */
.readiness-badge { display: flex; align-items: center; gap: 6px; font-size: 0.875rem; font-weight: 500; line-height: 1.4; padding: 4px 14px 2px; margin: 0; }
.readiness-badge .rb-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.readiness-badge .rb-extra { color: var(--text-muted); font-weight: 500; }
.rb-ready     { color: var(--status-success); }
.rb-available { color: var(--status-success); }
.rb-available .rb-dot { background: transparent; border: 1.5px solid var(--status-success); box-sizing: border-box; }
.rb-qc        { color: var(--status-warning); }
.rb-prod      { color: var(--status-info); }
.rb-short     { color: var(--status-error); }
/* dispatch.js returns 'unknown' when a dispatch's readiness could not be worked out: muted, never shown as a shortage. */
.rb-unknown   { color: var(--text-muted); }


/* ── Payment cue (components/paycue.js): the ONE line a list shows for a party's payment state ──────────────────────────────
   Used by customers.html and suppliers.html. 0.875rem, in the type scale's pairing for that size: 500 for the cue, 700 for "Due" (the one
   state to act on). The state is always told by the WORD; "Due" adds colour (.col-danger) and weight, the quiet states a muted colour.
   It never wraps; the column grows to fit it. Tone classes: none for money held, .col-danger for "Due", .col-muted for the quiet states. */
.pay-cue { font-size: 0.875rem; font-weight: 500; white-space: nowrap; }
.pay-cue.col-danger { font-weight: 700; }

/* ── Text for assistive technology only (a cell that is deliberately empty on screen still says why) ─────────────────────────── */
.sr-only {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* A button that a page shows or hides by permission, or a meta line a page shows only when it has something to say: the [hidden] attribute, which the class's own display would otherwise override. */
.action-btn[hidden], .detail-meta[hidden] { display: none; }
/* Same for the page-head action bar, the filter bar and a block hint, which pages show or hide
   with [hidden] instead of style writes; their own display would override it. */
.page-head-actions[hidden],
.filterbar[hidden],
.filter-tags[hidden],
.form-hint--block[hidden] { display: none; }

/* ── Phone tap targets for the controls every list page shares (WCAG target-size guideline, 44px) ────────────────────────────
   The search and group fields grow to 44px. A quick-filter pill keeps a compact look (32px) and borrows the rest of its hit area from a
   transparent ::after, the way a transparent ::after can, so the rows of pills do not get taller than they need to be. */
@media (max-width: 768px) {
    .filterbar .field { height: 44px; }
    .pill-primary { position: relative; min-height: 32px; padding: 4px 10px; }
    .pill-primary::after { content: ''; position: absolute; inset: -6px -2px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   FRONT DOOR — zofbox's own pages carry <html data-surface="front"> (the central page, /signup, the not-found page). Only the colours change:
   surfaces, text, the primary action (ink on paper, bone on night) and a control-edge colour for inputs. Shapes, sizes, type and every other page
   are untouched. The company sign-in page and the app keep the blue brand. To rebrand the whole app later, point --brand-primary and the surface
   tokens at these primitives and delete this block.
   ═══════════════════════════════════════════════════════════════════════ */
html[data-surface="front"]:not([data-theme="light"]) {
    --surface-page:      var(--color-front-night-page);
    --surface-card:      var(--color-front-night-card);
    --surface-border:    var(--color-front-night-border);
    --field-border:      var(--color-front-night-field);
    --text-primary:      var(--color-front-bone);
    --text-secondary:    var(--color-front-bone-text-2);
    --text-muted:        var(--color-front-bone-text-3);
    --text-placeholder:  var(--text-muted);
    --brand-primary:     var(--color-front-bone);
    --brand-secondary:   var(--color-front-bone-hover);
    --brand-subtle:      var(--color-front-bone-a06);
    --brand-subtle-hover:var(--color-front-bone-a12);
    --brand-border:      var(--color-front-bone-a22);
    --tint-hover:        var(--color-front-bone-a05);
    --front-signal:      var(--color-front-signal);
    --front-signal-edge: var(--color-front-signal-edge);
    --front-signal-ink:  var(--color-front-ink);   /* the mark drawn on lime: ink in both themes (lime is light in both) */
    --front-spinner-track: var(--color-white-a25);
    --front-mark-bg:     var(--color-mark-green);
    --front-mark-glyph:  var(--color-mark-paper);
}
html[data-surface="front"][data-theme="light"] {
    --surface-page:      var(--color-front-paper-page);
    --surface-card:      var(--color-front-paper-card);
    --surface-border:    var(--color-front-paper-border);
    --field-border:      var(--color-front-paper-field);
    --text-primary:      var(--color-front-ink);
    --text-secondary:    var(--color-front-ink-text-2);
    --text-muted:        var(--color-front-ink-text-3);
    --text-placeholder:  var(--text-muted);
    --brand-primary:     var(--color-front-ink);
    --brand-secondary:   var(--color-front-ink-hover);
    --brand-subtle:      var(--color-front-ink-a05);
    --brand-subtle-hover:var(--color-front-ink-a10);
    --brand-border:      var(--color-front-ink-a20);
    --tint-hover:        var(--color-front-ink-a04);
    --front-signal:      var(--color-front-signal);
    --front-signal-edge: var(--color-front-signal-edge);
    --front-signal-ink:  var(--color-front-ink);   /* the mark drawn on lime: ink in both themes (lime is light in both) */
    --front-spinner-track: var(--color-white-a25);
    --front-mark-bg:     var(--color-mark-green);
    --front-mark-glyph:  var(--color-mark-paper);
}
