/* ────────────────────────────────────────────────────────────────────────
   THEME TOKENS. Every colour in this file comes from a token below; no rule
   further down carries a raw hex that ought to move with the theme. (The
   @media print block is the deliberate exception: paper is always white and
   ink is always black, whatever the screen is showing.)

   Themes are chosen with data-theme on <html>. The bare :root block is the
   COMPLETE light palette, so nothing is left undefined if no media query and
   no attribute ever matches. Each theme block below re-states the colours it
   changes; the shared non-colour tokens (radii, easing, fonts) live on :root
   only, since they do not vary.

   The legacy alias names are load-bearing and are kept exactly:
     --green* is the app's PRIMARY brand, which is BLUE (= --brand-3/--brand-2)
     --blue*  is its ORANGE accent (CNC role, selection)
   Success stays green - see .done-status and .toast.success.

   A colour used as INK and a colour used as a FILL cannot be the same token
   once themes exist: in a dark theme the readable brand is a light blue, and
   white text on that fails. So --green/--blue/--amber stay the INK shades
   (measured against the surface they sit on) and --brand-fill/--accent-fill/
   --warn-fill are the solid backgrounds, all of which carry --on-fill text.
   ──────────────────────────────────────────────────────────────────────── */
:root,:root[data-theme="light"]{
  /* Empire MENA Control Room tokens, verbatim. */
  --brand:#75A1C8;--brand-2:#4E7DAE;--brand-3:#2E5580;
  --accent:#E99F6B;--accent-2:#D4834A;
  --pos:#2E9E5B;--neg:#D4483B;--warn:#D9922B;
  --bg:#DBDFE3;--bg-2:#D2D8E0;
  --card:rgba(255,255,255,.74);--card-solid:#FFFFFF;--surface-raise:#FFFFFF;
  --hair:rgba(16,40,72,.10);
  --ink:#14243A;--ink-2:#4A5C74;--ink-3:#7C8CA3;
  --shadow:0 1px 2px rgba(16,40,72,.05),0 10px 30px rgba(16,40,72,.09);
  --shadow-lift:0 2px 6px rgba(16,40,72,.07),0 18px 46px rgba(16,40,72,.15);
  --brand-tint:rgba(117,161,200,.10);--accent-tint:rgba(233,159,107,.14);

  /* Legacy names the app's CSS/templates already depend on, pointed at the
     tokens above so nothing has to be renamed. --blue/--amber/--red are one
     shade darker than the raw Control Room --accent-2/--warn/--neg hex:
     measured (not eyeballed) contrast of the verbatim tokens as text came up
     short of 4.5:1, so the "ink" shade used for text is darkened just enough
     to clear it everywhere it is actually used, while every *-soft background
     stays the original, already-accessible tint. --green-soft was a
     translucent tint; it is a solid now so its contrast can be measured
     rather than guessed at through whatever happens to be behind it. */
  --muted:var(--ink-2);--canvas:var(--bg);--surface:var(--card-solid);--surface-2:var(--bg-2);
  --line:rgba(16,40,72,.11);--line-strong:rgba(16,40,72,.22);
  --green:var(--brand-3);--green-2:var(--brand-2);--green-soft:#E8EFF7;
  --blue:#975D35;--blue-soft:#FDF0E6;
  --amber:#94631D;--amber-soft:#FFF4DC;
  --red:#B23A2E;--red-soft:#FDECEA;

  /* Solid fills + the ink that sits on them. */
  --on-fill:#FFFFFF;
  --brand-fill:#2E5580;--brand-fill-hover:#24466B;--accent-fill:#975D35;--warn-fill:#94631D;

  /* Header. Its canvas is dark in every theme, but not the SAME dark. */
  --header-bg:#14243A;--header-ink:#F5F8FC;--header-ink-2:#A9B8CD;--header-ink-3:#C3CDDA;
  --header-surface:#1E3552;--header-active:#2E5580;--header-active-line:rgba(78,125,174,.55);
  --header-hair:#3A4E68;--header-sep:rgba(255,255,255,.22);--header-focus:#8DB6DC;
  --header-shadow:0 3px 16px rgba(16,40,72,.18);
  --role-halo:rgba(255,255,255,.08);
  --role-dot:#9AA5AD;--role-admin:#F0B84B;--role-cnc:#56A0E2;--role-warehouse:#72BD7D;

  /* Controls. */
  --btn-bg:#FFFFFF;--btn-hover-bg:#EDF2F8;--btn-hover-line:#AAB8CC;--danger-line:#E3B4B0;
  --input-bg:#FFFFFF;--focus-line:#4E7DAE;--focus-ring:rgba(78,125,174,.12);
  --check-bg:#FAFCFE;--check-locked-bg:#F1F4F9;--drop-bg:#F7FAFD;--drop-line:#AAB8CC;

  /* The grid. */
  --grid-bg:#FFFFFF;--grid-head-1:#2E5580;--grid-head-2:#14243A;
  --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.14);
  --cell-editable:#E3E6EA;--cell-editable-hover:#D7DFE7;--cell-locked:#E2E4E8;--cell-wh:#DCE0E6;
  --cell-new:#D6DDE4;--cell-new-line:#7FA4CC;
  --done-bg:#DFF1E4;--done-ink:#173A25;--done-bg-hover:#D4EADB;
  --zero-bg:#FFF7F6;--zero-bg-hover:#FDEEEC;--pending-hover:#FDECCD;
  --sticky-shadow:rgba(16,40,72,.07);
  --rownum-bg:#EDF1F5;--rownum-bg-hover:#E3EAF2;--rownum-ink:#54637A;
  --sel-ring:#2E5580;--sel-glow:rgba(46,85,128,.16);--sel-locked-bg:#C7DAF0;--sel-handle:#FFFFFF;
  --chip-ring:rgba(0,0,0,.10);

  /* Messages. */
  --flash-bg:#16243A;--flash-ink:#FFFFFF;--flash-line:#3C5F8C;
  --toast-bg:#14243A;--toast-ink:#FFFFFF;--toast-error:#7D2D28;--toast-success:#235E39;

  /* Login. The brand panel is dark in every theme, so the hairlines drawn on
     it with white at low alpha are theme-invariant by construction. */
  --login-1:#EDF2F8;--login-2:#F7FAFD;--login-3:#E7EDF6;
  --login-panel-1:#14243A;--login-panel-2:#2E5580;--login-shadow:0 24px 70px rgba(16,40,72,.15);
  --login-toggle-bg:rgba(255,255,255,.92);
  --login-ink:#FFFFFF;--login-ink-2:#CBD8E6;--login-ink-3:#D3DFEC;--login-eyebrow:#B6CAE1;
  --login-hair:rgba(255,255,255,.11);--login-glow-1:rgba(255,255,255,.035);
  --login-glow-2:rgba(255,255,255,.02);
  --login-step-bg:rgba(255,255,255,.06);--login-step-line:rgba(255,255,255,.2);
  --login-error-bg:#FFF0EE;--login-error-ink:#9F2D24;--login-error-line:#F2C6C1;

  /* The 16 value chips. Backgrounds and inks are paired, never mixed. */
  --vc-navy-bg:#12509E;--vc-navy-ink:#FFFFFF;
  --vc-blue-bg:#2C6CA8;--vc-blue-ink:#FFFFFF;
  --vc-red-bg:#B23A2E;--vc-red-ink:#FFFFFF;
  --vc-pink-bg:#F6C8C3;--vc-pink-ink:#8E1F14;
  --vc-amber-bg:#F5D699;--vc-amber-ink:#6B4A0A;
  --vc-lavender-bg:#E0CCEE;--vc-lavender-ink:#5E2C80;
  --vc-teal-bg:#B6E0D9;--vc-teal-ink:#1F5850;
  --vc-purple-bg:#C6BBE2;--vc-purple-ink:#3E2E68;
  --vc-orange-bg:#A85B12;--vc-orange-ink:#FFFFFF;
  --vc-lightblue-bg:#D6E6F6;--vc-lightblue-ink:#1F5C93;
  --vc-peach-bg:#FBD9B4;--vc-peach-ink:#7A400A;
  --vc-grey-bg:#E6E6E6;--vc-grey-ink:#5C5C5C;
  --vc-stone-bg:#EFEFEF;--vc-stone-ink:#616161;
  --vc-green-bg:#2F6B1C;--vc-green-ink:#FFFFFF;
  --vc-leaf-bg:#C3E3B6;--vc-leaf-ink:#335624;
  --vc-dark-bg:#3A3A3A;--vc-dark-ink:#FFFFFF;

  /* Non-colour tokens. These do not vary by theme. */
  --r:18px;--r-sm:12px;--r-pill:999px;
  --ease:cubic-bezier(.32,.72,0,1);--fast:.22s;--med:.42s;
  --fs:-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI Variable","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --fa:"SF Arabic","Geeza Pro","Segoe UI","Noto Naskh Arabic",Tahoma,Arial,sans-serif;
  --radius:var(--r);--header-h:68px;--rownum-w:46px;
  --font:var(--fs);

  /* UX pack 2 non-colour tokens. --date-col-w is the third pinned column's
     left offset ingredient: the DEFAULT rendered width of the "date" field
     (excel_width 14 -> max(72,min(320,round(14*7))) = 98px, app.py). Pure
     CSS cannot read a resized sibling's live width, so if a user drags the
     date column away from this default, the PO column's pinned edge drifts
     until reload - a known limit of CSS-only stacked sticky columns.
     Density row heights/padding/font-size live per-block below them. */
  --date-col-w:98px;

  /* UX pack 2, light: opaque (never rgba) washes for sticky-column hover and
     the crosshair - a translucent background on a pinned column lets the
     columns scrolling underneath show through. Pre-blended by hand against
     --grid-bg so the maths never has to run twice: --brand-tint rgba(117,
     161,200,.10) over #FFFFFF = #F1F6FA. Zebra and range reuse existing
     solid tokens (--check-locked-bg, --sel-locked-bg) instead of inventing
     new colours the audit can't already vouch for. */
  --sticky-wash:#F1F6FA;--crosshair-bg:#F1F6FA;
  --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);

  /* Row-audit pack: soft-delete rows, the 409 conflict dialog, the health
     panel, the import-preview diff and the revert button. Defined ONCE as
     aliases onto tokens already redefined per theme below - CSS custom
     properties resolve at computed-value time, so --deleted-ink here tracks
     whichever theme's --ink-2 is active without restating it in all seven
     blocks (same trick as --muted:var(--ink-2) above). Every pairing was
     measured, not assumed:
       --ink-3 on --check-locked-bg fails 4.5:1 in light/sand/forest (as low
       as 3.10:1) so the deleted row uses --ink-2 instead, which clears
       6.2:1 everywhere. --amber/--amber-soft and --red/--red-soft are the
       already-audited pairs the .badge rule above relies on, reused as-is
       for the warn/bad health states and the import-preview warning cells.
       --ink on --btn-bg (already the default .btn text/background pair
       used everywhere) clears 12.4:1+ in every theme, so revert-btn reads
       its label in plain ink with a coloured border+icon instead of a new
       brand-on-btn-bg pair, which measured as low as 3.04:1 in midnight. */
  --deleted-bg:var(--check-locked-bg);--deleted-ink:var(--ink-2);--deleted-line:var(--line-strong);
  --warn-soft:var(--amber-soft);--bad-soft:var(--red-soft);
  --scrim:rgba(10,14,20,.55);
}

/* Dark: the same brand on a dark canvas. Stated twice on purpose - once for
   an explicit choice, once for the "system" preference, which stores nothing
   and therefore leaves <html> with no data-theme at all. */
:root[data-theme="dark"]{
  --brand:#8FB6D8;--brand-2:#6E9FCB;--brand-3:#4E7DAE;
  --accent:#F0B183;--accent-2:#E29A62;
  --pos:#4CC47E;--neg:#F0705F;--warn:#E8AE4C;
  --bg:#101822;--bg-2:#16202C;
  --card:rgba(26,35,47,.74);--card-solid:#1A232F;--surface-raise:#2D394B;
  --hair:rgba(255,255,255,.10);
  --ink:#E8EEF6;--ink-2:#AFBFD2;--ink-3:#8496AC;
  --shadow:0 1px 2px rgba(0,0,0,.35),0 10px 30px rgba(0,0,0,.45);
  --shadow-lift:0 2px 6px rgba(0,0,0,.40),0 18px 46px rgba(0,0,0,.55);
  --brand-tint:rgba(143,182,216,.12);--accent-tint:rgba(240,177,131,.14);
  --line:rgba(255,255,255,.13);--line-strong:rgba(255,255,255,.26);
  --green:var(--brand);--green-2:var(--brand-2);--green-soft:#1B2A3B;
  --blue:#EFAE79;--blue-soft:#33220F;
  --amber:#F0C069;--amber-soft:#332711;
  --red:#FF9083;--red-soft:#3A1C19;
  --on-fill:#FFFFFF;
  --brand-fill:#2F5C87;--brand-fill-hover:#3A6E9E;--accent-fill:#A05C28;--warn-fill:#8A5C18;
  --header-bg:#0B121B;--header-ink:#EAF1F9;--header-ink-2:#A3B5CA;--header-ink-3:#B6C6D8;
  --header-surface:#182534;--header-active:#2A4C74;--header-active-line:rgba(110,159,203,.5);
  --header-hair:#2B3C51;--header-sep:rgba(255,255,255,.18);--header-focus:#9CC3E6;
  --header-shadow:0 3px 16px rgba(0,0,0,.55);
  --role-halo:rgba(255,255,255,.08);
  --role-dot:#6B7A88;--role-admin:#F0B84B;--role-cnc:#56A0E2;--role-warehouse:#72BD7D;
  --btn-bg:#1F2A38;--btn-hover-bg:#273546;--btn-hover-line:rgba(255,255,255,.34);--danger-line:#7A3A33;
  --input-bg:#141C27;--focus-line:#6E9FCB;--focus-ring:rgba(110,159,203,.22);
  --check-bg:#18212C;--check-locked-bg:#141C26;--drop-bg:#151E29;--drop-line:#3B4A5C;
  --grid-bg:#151E29;--grid-head-1:#22415F;--grid-head-2:#101822;
  --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.14);
  --cell-editable:#1F2A38;--cell-editable-hover:#26354A;--cell-locked:#1A2331;--cell-wh:#182231;
  --cell-new:#22314A;--cell-new-line:#4E7DAE;
  --done-bg:#123122;--done-ink:#A9E5C1;--done-bg-hover:#173C2A;
  --zero-bg:#2B1618;--zero-bg-hover:#361C1E;--pending-hover:#3A2C12;
  --sticky-shadow:rgba(0,0,0,.50);
  --rownum-bg:#111A24;--rownum-bg-hover:#182231;--rownum-ink:#9AACC2;
  --sel-ring:#8FB6D8;--sel-glow:rgba(143,182,216,.18);--sel-locked-bg:#24344A;--sel-handle:#0B121B;
  --chip-ring:rgba(255,255,255,.14);
  --flash-bg:#1B2A3D;--flash-ink:#FFFFFF;--flash-line:#3C5F8C;
  --toast-bg:#1B2A3D;--toast-ink:#FFFFFF;--toast-error:#7D2D28;--toast-success:#1D5233;
  --login-1:#0C131C;--login-2:#111A24;--login-3:#0A1119;
  --login-panel-1:#0B121B;--login-panel-2:#2E5580;--login-shadow:0 24px 70px rgba(0,0,0,.60);
  --login-toggle-bg:rgba(26,35,47,.92);
  --login-ink:#FFFFFF;--login-ink-2:#C3D2E2;--login-ink-3:#CBD9E9;--login-eyebrow:#9FBADA;
  --login-error-bg:#3A1C19;--login-error-ink:#FFB4A8;--login-error-line:#7A3A33;
  --vc-navy-bg:#1D4E96;--vc-navy-ink:#FFFFFF;
  --vc-blue-bg:#2C6CA8;--vc-blue-ink:#FFFFFF;
  --vc-red-bg:#B23A2E;--vc-red-ink:#FFFFFF;
  --vc-pink-bg:#5A1F1A;--vc-pink-ink:#F6C8C3;
  --vc-amber-bg:#4A3508;--vc-amber-ink:#F5D699;
  --vc-lavender-bg:#3A2350;--vc-lavender-ink:#E0CCEE;
  --vc-teal-bg:#14403A;--vc-teal-ink:#B6E0D9;
  --vc-purple-bg:#332552;--vc-purple-ink:#C6BBE2;
  --vc-orange-bg:#8A4A0E;--vc-orange-ink:#FFFFFF;
  --vc-lightblue-bg:#17364F;--vc-lightblue-ink:#CFE3F7;
  --vc-peach-bg:#4E2E10;--vc-peach-ink:#FBD9B4;
  --vc-grey-bg:#4A4A4A;--vc-grey-ink:#E6E6E6;
  --vc-stone-bg:#33383D;--vc-stone-ink:#D8DEE4;
  --vc-green-bg:#2F6B1C;--vc-green-ink:#FFFFFF;
  --vc-leaf-bg:#223A17;--vc-leaf-ink:#C3E3B6;
  --vc-dark-bg:#EDEDED;--vc-dark-ink:#1F1F1F;

  /* UX pack 2, dark: --brand-tint rgba(143,182,216,.12) over --grid-bg
     #151E29 = #24303E. */
  --sticky-wash:#24303E;--crosshair-bg:#24303E;
  --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme]){
    --brand:#8FB6D8;--brand-2:#6E9FCB;--brand-3:#4E7DAE;
    --accent:#F0B183;--accent-2:#E29A62;
    --pos:#4CC47E;--neg:#F0705F;--warn:#E8AE4C;
    --bg:#101822;--bg-2:#16202C;
    --card:rgba(26,35,47,.74);--card-solid:#1A232F;--surface-raise:#2D394B;
    --hair:rgba(255,255,255,.10);
    --ink:#E8EEF6;--ink-2:#AFBFD2;--ink-3:#8496AC;
    --shadow:0 1px 2px rgba(0,0,0,.35),0 10px 30px rgba(0,0,0,.45);
    --shadow-lift:0 2px 6px rgba(0,0,0,.40),0 18px 46px rgba(0,0,0,.55);
    --brand-tint:rgba(143,182,216,.12);--accent-tint:rgba(240,177,131,.14);
    --line:rgba(255,255,255,.13);--line-strong:rgba(255,255,255,.26);
    --green:var(--brand);--green-2:var(--brand-2);--green-soft:#1B2A3B;
    --blue:#EFAE79;--blue-soft:#33220F;
    --amber:#F0C069;--amber-soft:#332711;
    --red:#FF9083;--red-soft:#3A1C19;
    --on-fill:#FFFFFF;
    --brand-fill:#2F5C87;--brand-fill-hover:#3A6E9E;--accent-fill:#A05C28;--warn-fill:#8A5C18;
    --header-bg:#0B121B;--header-ink:#EAF1F9;--header-ink-2:#A3B5CA;--header-ink-3:#B6C6D8;
    --header-surface:#182534;--header-active:#2A4C74;--header-active-line:rgba(110,159,203,.5);
    --header-hair:#2B3C51;--header-sep:rgba(255,255,255,.18);--header-focus:#9CC3E6;
    --header-shadow:0 3px 16px rgba(0,0,0,.55);
    --role-halo:rgba(255,255,255,.08);
    --role-dot:#6B7A88;--role-admin:#F0B84B;--role-cnc:#56A0E2;--role-warehouse:#72BD7D;
    --btn-bg:#1F2A38;--btn-hover-bg:#273546;--btn-hover-line:rgba(255,255,255,.34);--danger-line:#7A3A33;
    --input-bg:#141C27;--focus-line:#6E9FCB;--focus-ring:rgba(110,159,203,.22);
    --check-bg:#18212C;--check-locked-bg:#141C26;--drop-bg:#151E29;--drop-line:#3B4A5C;
    --grid-bg:#151E29;--grid-head-1:#22415F;--grid-head-2:#101822;
    --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.14);
    --cell-editable:#1F2A38;--cell-editable-hover:#26354A;--cell-locked:#1A2331;--cell-wh:#182231;
    --cell-new:#22314A;--cell-new-line:#4E7DAE;
    --done-bg:#123122;--done-ink:#A9E5C1;--done-bg-hover:#173C2A;
    --zero-bg:#2B1618;--zero-bg-hover:#361C1E;--pending-hover:#3A2C12;
    --sticky-shadow:rgba(0,0,0,.50);
    --rownum-bg:#111A24;--rownum-bg-hover:#182231;--rownum-ink:#9AACC2;
    --sel-ring:#8FB6D8;--sel-glow:rgba(143,182,216,.18);--sel-locked-bg:#24344A;--sel-handle:#0B121B;
    --chip-ring:rgba(255,255,255,.14);
    --flash-bg:#1B2A3D;--flash-ink:#FFFFFF;--flash-line:#3C5F8C;
    --toast-bg:#1B2A3D;--toast-ink:#FFFFFF;--toast-error:#7D2D28;--toast-success:#1D5233;
    --login-1:#0C131C;--login-2:#111A24;--login-3:#0A1119;
    --login-panel-1:#0B121B;--login-panel-2:#2E5580;--login-shadow:0 24px 70px rgba(0,0,0,.60);
    --login-toggle-bg:rgba(26,35,47,.92);
    --login-ink:#FFFFFF;--login-ink-2:#C3D2E2;--login-ink-3:#CBD9E9;--login-eyebrow:#9FBADA;
    --login-error-bg:#3A1C19;--login-error-ink:#FFB4A8;--login-error-line:#7A3A33;
    --vc-navy-bg:#1D4E96;--vc-navy-ink:#FFFFFF;
    --vc-blue-bg:#2C6CA8;--vc-blue-ink:#FFFFFF;
    --vc-red-bg:#B23A2E;--vc-red-ink:#FFFFFF;
    --vc-pink-bg:#5A1F1A;--vc-pink-ink:#F6C8C3;
    --vc-amber-bg:#4A3508;--vc-amber-ink:#F5D699;
    --vc-lavender-bg:#3A2350;--vc-lavender-ink:#E0CCEE;
    --vc-teal-bg:#14403A;--vc-teal-ink:#B6E0D9;
    --vc-purple-bg:#332552;--vc-purple-ink:#C6BBE2;
    --vc-orange-bg:#8A4A0E;--vc-orange-ink:#FFFFFF;
    --vc-lightblue-bg:#17364F;--vc-lightblue-ink:#CFE3F7;
    --vc-peach-bg:#4E2E10;--vc-peach-ink:#FBD9B4;
    --vc-grey-bg:#4A4A4A;--vc-grey-ink:#E6E6E6;
    --vc-stone-bg:#33383D;--vc-stone-ink:#D8DEE4;
    --vc-green-bg:#2F6B1C;--vc-green-ink:#FFFFFF;
    --vc-leaf-bg:#223A17;--vc-leaf-ink:#C3E3B6;
    --vc-dark-bg:#EDEDED;--vc-dark-ink:#1F1F1F;

    /* UX pack 2, system-dark: identical to the explicit dark block above. */
    --sticky-wash:#24303E;--crosshair-bg:#24303E;
    --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);
  }
}

/* Midnight: the same shapes, a deeper and cooler canvas. Near-black paper,
   so the grid's own colour does the talking on a dim night shift. */
:root[data-theme="midnight"]{
  --brand:#8AB4EE;--brand-2:#6690D4;--brand-3:#4368A8;
  --accent:#F2B58C;--accent-2:#DE9A6A;
  --pos:#46C888;--neg:#F4796A;--warn:#EDB255;
  --bg:#05080F;--bg-2:#0A0F1A;
  --card:rgba(13,19,31,.76);--card-solid:#0D131F;--surface-raise:#26324A;
  --hair:rgba(255,255,255,.09);
  --ink:#DFE8F8;--ink-2:#A9B9D4;--ink-3:#7B8CAA;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 10px 30px rgba(0,0,0,.6);
  --shadow-lift:0 2px 6px rgba(0,0,0,.55),0 18px 46px rgba(0,0,0,.7);
  --brand-tint:rgba(138,180,238,.12);--accent-tint:rgba(242,181,140,.14);
  --line:rgba(255,255,255,.12);--line-strong:rgba(255,255,255,.24);
  --green:var(--brand);--green-2:var(--brand-2);--green-soft:#131E33;
  --blue:#F2B58C;--blue-soft:#2C1E10;
  --amber:#F2C56E;--amber-soft:#2C2210;
  --red:#FF9587;--red-soft:#331718;
  --on-fill:#FFFFFF;
  --brand-fill:#35619A;--brand-fill-hover:#4172B0;--accent-fill:#96552B;--warn-fill:#815417;
  --header-bg:#04070D;--header-ink:#E7EEFA;--header-ink-2:#9FB0CE;--header-ink-3:#B2C2DC;
  --header-surface:#101A2B;--header-active:#254776;--header-active-line:rgba(102,144,212,.5);
  --header-hair:#22304A;--header-sep:rgba(255,255,255,.16);--header-focus:#93BAEC;
  --header-shadow:0 3px 16px rgba(0,0,0,.7);
  --role-halo:rgba(255,255,255,.08);
  --role-dot:#5F6E86;--role-admin:#F0B84B;--role-cnc:#56A0E2;--role-warehouse:#72BD7D;
  --btn-bg:#141D2E;--btn-hover-bg:#1C2740;--btn-hover-line:rgba(255,255,255,.32);--danger-line:#75352F;
  --input-bg:#0A1120;--focus-line:#6690D4;--focus-ring:rgba(102,144,212,.22);
  --check-bg:#0E1524;--check-locked-bg:#0B111E;--drop-bg:#0A1120;--drop-line:#334158;
  --grid-bg:#0A0F1B;--grid-head-1:#1C3557;--grid-head-2:#05080F;
  --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.14);
  --cell-editable:#141D2E;--cell-editable-hover:#1B2740;--cell-locked:#0F1626;--cell-wh:#0D1524;
  --cell-new:#182742;--cell-new-line:#4368A8;
  --done-bg:#0D2A1D;--done-ink:#A2E3BC;--done-bg-hover:#123425;
  --zero-bg:#261316;--zero-bg-hover:#31191C;--pending-hover:#33270F;
  --sticky-shadow:rgba(0,0,0,.6);
  --rownum-bg:#080D18;--rownum-bg-hover:#0E1626;--rownum-ink:#93A5C4;
  --sel-ring:#8AB4EE;--sel-glow:rgba(138,180,238,.2);--sel-locked-bg:#1B2B47;--sel-handle:#05080F;
  --chip-ring:rgba(255,255,255,.14);
  --flash-bg:#132038;--flash-ink:#FFFFFF;--flash-line:#35538A;
  --toast-bg:#132038;--toast-ink:#FFFFFF;--toast-error:#75282A;--toast-success:#1A4E31;
  --login-1:#05080F;--login-2:#0A0F1A;--login-3:#04070D;
  --login-panel-1:#04070D;--login-panel-2:#2C4F82;--login-shadow:0 24px 70px rgba(0,0,0,.75);
  --login-toggle-bg:rgba(13,19,31,.92);
  --login-ink:#FFFFFF;--login-ink-2:#BECEE6;--login-ink-3:#C6D6EE;--login-eyebrow:#9BB4DC;
  --login-error-bg:#331718;--login-error-ink:#FFB0A4;--login-error-line:#75352F;
  --vc-navy-bg:#1D4E96;--vc-navy-ink:#FFFFFF;
  --vc-blue-bg:#2C6CA8;--vc-blue-ink:#FFFFFF;
  --vc-red-bg:#B23A2E;--vc-red-ink:#FFFFFF;
  --vc-pink-bg:#521B16;--vc-pink-ink:#F6C8C3;
  --vc-amber-bg:#432F06;--vc-amber-ink:#F5D699;
  --vc-lavender-bg:#341F49;--vc-lavender-ink:#E0CCEE;
  --vc-teal-bg:#103A34;--vc-teal-ink:#B6E0D9;
  --vc-purple-bg:#2E214B;--vc-purple-ink:#C6BBE2;
  --vc-orange-bg:#8A4A0E;--vc-orange-ink:#FFFFFF;
  --vc-lightblue-bg:#133048;--vc-lightblue-ink:#CFE3F7;
  --vc-peach-bg:#47290C;--vc-peach-ink:#FBD9B4;
  --vc-grey-bg:#454545;--vc-grey-ink:#E6E6E6;
  --vc-stone-bg:#2E3338;--vc-stone-ink:#D8DEE4;
  --vc-green-bg:#2F6B1C;--vc-green-ink:#FFFFFF;
  --vc-leaf-bg:#1D3413;--vc-leaf-ink:#C3E3B6;
  --vc-dark-bg:#EDEDED;--vc-dark-ink:#1F1F1F;

  /* UX pack 2, midnight: --brand-tint rgba(138,180,238,.12) over --grid-bg
     #0A0F1B = #192334. */
  --sticky-wash:#192334;--crosshair-bg:#192334;
  --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);
}

/* Sand: warm light. Paper rather than screen-white, and the brand walks over
   to the warm side with it so the chrome does not read as a blue sticker on
   a beige page. */
:root[data-theme="sand"]{
  --brand:#C2A07A;--brand-2:#96714A;--brand-3:#6B4A28;
  --accent:#D98C4A;--accent-2:#B96F2F;
  --pos:#3E7D4A;--neg:#B03A2C;--warn:#A0691A;
  --bg:#EFE7D9;--bg-2:#E6DACA;
  --card:rgba(255,251,244,.78);--card-solid:#FFFBF4;--surface-raise:#FFFFFF;
  --hair:rgba(74,52,30,.12);
  --ink:#2F2418;--ink-2:#5B4835;--ink-3:#8B7660;
  --shadow:0 1px 2px rgba(74,52,30,.06),0 10px 30px rgba(74,52,30,.10);
  --shadow-lift:0 2px 6px rgba(74,52,30,.08),0 18px 46px rgba(74,52,30,.16);
  --brand-tint:rgba(194,160,122,.14);--accent-tint:rgba(217,140,74,.14);
  --line:rgba(74,52,30,.14);--line-strong:rgba(74,52,30,.26);
  --green:var(--brand-3);--green-2:var(--brand-2);--green-soft:#F2E7D6;
  --blue:#8A4E1E;--blue-soft:#FBEBD9;
  --amber:#8A5A12;--amber-soft:#FBF0D5;
  --red:#A93327;--red-soft:#FBE7E3;
  --on-fill:#FFFFFF;
  --brand-fill:#6B4A28;--brand-fill-hover:#563A1E;--accent-fill:#8A4E1E;--warn-fill:#8A5A12;
  --header-bg:#33261A;--header-ink:#FBF4EA;--header-ink-2:#C9B69F;--header-ink-3:#D8C9B6;
  --header-surface:#453421;--header-active:#6B4A28;--header-active-line:rgba(194,160,122,.5);
  --header-hair:#5B4632;--header-sep:rgba(255,255,255,.22);--header-focus:#DFC49C;
  --header-shadow:0 3px 16px rgba(74,52,30,.24);
  --role-halo:rgba(255,255,255,.08);
  --role-dot:#9D8C77;--role-admin:#B07A12;--role-cnc:#3F72A6;--role-warehouse:#4E8B57;
  --btn-bg:#FFFBF4;--btn-hover-bg:#F4E9D8;--btn-hover-line:#C6B092;--danger-line:#DDB3AC;
  --input-bg:#FFFBF4;--focus-line:#96714A;--focus-ring:rgba(150,113,74,.16);
  --check-bg:#FFFCF6;--check-locked-bg:#F2EADD;--drop-bg:#FBF5EA;--drop-line:#C6B092;
  --grid-bg:#FFFBF4;--grid-head-1:#6B4A28;--grid-head-2:#33261A;
  --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.16);
  --cell-editable:#EFE6D6;--cell-editable-hover:#E7DAC5;--cell-locked:#EDE4D6;--cell-wh:#E9DECB;
  --cell-new:#E4D7C1;--cell-new-line:#B08A5C;
  --done-bg:#E1F0DF;--done-ink:#1F3A22;--done-bg-hover:#D6E9D4;
  --zero-bg:#FCEFEA;--zero-bg-hover:#F8E3DC;--pending-hover:#F6E3BE;
  --sticky-shadow:rgba(74,52,30,.10);
  --rownum-bg:#F3EADB;--rownum-bg-hover:#EBE0CD;--rownum-ink:#6B5741;
  --sel-ring:#6B4A28;--sel-glow:rgba(107,74,40,.16);--sel-locked-bg:#E0CDA8;--sel-handle:#FFFBF4;
  --chip-ring:rgba(0,0,0,.10);
  --flash-bg:#33261A;--flash-ink:#FFFFFF;--flash-line:#7A6046;
  --toast-bg:#33261A;--toast-ink:#FFFFFF;--toast-error:#7A2B22;--toast-success:#245C36;
  --login-1:#F5EEE1;--login-2:#FFFBF4;--login-3:#EFE3D0;
  --login-panel-1:#33261A;--login-panel-2:#6B4A28;--login-shadow:0 24px 70px rgba(74,52,30,.18);
  --login-toggle-bg:rgba(255,251,244,.92);
  --login-ink:#FFFFFF;--login-ink-2:#E4D7C5;--login-ink-3:#EADFCF;--login-eyebrow:#D9C3A4;
  --login-error-bg:#FBE7E3;--login-error-ink:#8E271D;--login-error-line:#E7BDB6;
  --vc-navy-bg:#12509E;--vc-navy-ink:#FFFFFF;
  --vc-blue-bg:#2C6CA8;--vc-blue-ink:#FFFFFF;
  --vc-red-bg:#B23A2E;--vc-red-ink:#FFFFFF;
  --vc-pink-bg:#F6C8C3;--vc-pink-ink:#8E1F14;
  --vc-amber-bg:#F5D699;--vc-amber-ink:#6B4A0A;
  --vc-lavender-bg:#E0CCEE;--vc-lavender-ink:#5E2C80;
  --vc-teal-bg:#B6E0D9;--vc-teal-ink:#1F5850;
  --vc-purple-bg:#C6BBE2;--vc-purple-ink:#3E2E68;
  --vc-orange-bg:#A85B12;--vc-orange-ink:#FFFFFF;
  --vc-lightblue-bg:#D6E6F6;--vc-lightblue-ink:#1F5C93;
  --vc-peach-bg:#FBD9B4;--vc-peach-ink:#7A400A;
  --vc-grey-bg:#E7E2D8;--vc-grey-ink:#5A5348;
  --vc-stone-bg:#F0ECE4;--vc-stone-ink:#5F584C;
  --vc-green-bg:#2F6B1C;--vc-green-ink:#FFFFFF;
  --vc-leaf-bg:#C3E3B6;--vc-leaf-ink:#335624;
  --vc-dark-bg:#3A342C;--vc-dark-ink:#FFFFFF;

  /* UX pack 2, sand: --brand-tint rgba(194,160,122,.14) over --grid-bg
     #FFFBF4 = #F6EEE3. */
  --sticky-wash:#F6EEE3;--crosshair-bg:#F6EEE3;
  --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);
}

/* Forest: low glare, for a screen that is on all day in a workshop. Nothing
   is pure white and nothing is pure black - the whole range is pulled in from
   both ends so the panel stops flaring under strip lighting - but every text
   pair is still measured at 4.5:1 or better. */
:root[data-theme="forest"]{
  --brand:#7FA894;--brand-2:#4E7D68;--brand-3:#2A5545;
  --accent:#D9A05B;--accent-2:#BE8036;
  --pos:#2E7D4F;--neg:#B33F30;--warn:#96681A;
  --bg:#D9E0D8;--bg-2:#CDD6CB;
  --card:rgba(240,244,239,.80);--card-solid:#F0F4EF;--surface-raise:#FFFFFF;
  --hair:rgba(20,40,30,.12);
  --ink:#15251C;--ink-2:#3F5648;--ink-3:#6C8175;
  --shadow:0 1px 2px rgba(20,40,30,.05),0 10px 30px rgba(20,40,30,.09);
  --shadow-lift:0 2px 6px rgba(20,40,30,.07),0 18px 46px rgba(20,40,30,.14);
  --brand-tint:rgba(127,168,148,.14);--accent-tint:rgba(217,160,91,.14);
  --line:rgba(20,40,30,.13);--line-strong:rgba(20,40,30,.24);
  --green:var(--brand-3);--green-2:var(--brand-2);--green-soft:#DDEADF;
  --blue:#8A5220;--blue-soft:#F7EADB;
  --amber:#7D5710;--amber-soft:#F5EED6;
  --red:#A93A2C;--red-soft:#F7E4E1;
  --on-fill:#FFFFFF;
  --brand-fill:#2A5545;--brand-fill-hover:#1F4335;--accent-fill:#8A5220;--warn-fill:#7D5710;
  --header-bg:#16261E;--header-ink:#EFF5EF;--header-ink-2:#AEC2B4;--header-ink-3:#C1D2C6;
  --header-surface:#20362A;--header-active:#2A5545;--header-active-line:rgba(127,168,148,.5);
  --header-hair:#33513F;--header-sep:rgba(255,255,255,.2);--header-focus:#A7CDB8;
  --header-shadow:0 3px 16px rgba(20,40,30,.22);
  --role-halo:rgba(255,255,255,.08);
  --role-dot:#8C9C90;--role-admin:#A87614;--role-cnc:#3D7398;--role-warehouse:#40865A;
  --btn-bg:#F0F4EF;--btn-hover-bg:#E2EAE0;--btn-hover-line:#A9BBAE;--danger-line:#D9B2AB;
  --input-bg:#F6F9F5;--focus-line:#4E7D68;--focus-ring:rgba(78,125,104,.16);
  --check-bg:#F6F9F5;--check-locked-bg:#E9EFE8;--drop-bg:#F3F7F2;--drop-line:#A9BBAE;
  --grid-bg:#F0F4EF;--grid-head-1:#2A5545;--grid-head-2:#16261E;
  --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.16);
  --cell-editable:#E0E7DE;--cell-editable-hover:#D5DED3;--cell-locked:#DFE5DD;--cell-wh:#DAE2D8;
  --cell-new:#D3DDD1;--cell-new-line:#7FA894;
  --done-bg:#DDEEDF;--done-ink:#183A24;--done-bg-hover:#D2E7D5;
  --zero-bg:#F8EDEA;--zero-bg-hover:#F2E0DC;--pending-hover:#F1E5C6;
  --sticky-shadow:rgba(20,40,30,.10);
  --rownum-bg:#E7EDE5;--rownum-bg-hover:#DDE5DB;--rownum-ink:#4C6153;
  --sel-ring:#2A5545;--sel-glow:rgba(42,85,69,.16);--sel-locked-bg:#C6DCC9;--sel-handle:#F0F4EF;
  --chip-ring:rgba(0,0,0,.10);
  --flash-bg:#16261E;--flash-ink:#FFFFFF;--flash-line:#3F6350;
  --toast-bg:#16261E;--toast-ink:#FFFFFF;--toast-error:#7A2C24;--toast-success:#215636;
  --login-1:#E4EBE2;--login-2:#F2F6F1;--login-3:#DCE5DA;
  --login-panel-1:#16261E;--login-panel-2:#2A5545;--login-shadow:0 24px 70px rgba(20,40,30,.18);
  --login-toggle-bg:rgba(240,244,239,.92);
  --login-ink:#FFFFFF;--login-ink-2:#CFDDD3;--login-ink-3:#D7E4DA;--login-eyebrow:#A9C7B5;
  --login-error-bg:#F7E4E1;--login-error-ink:#8E2A20;--login-error-line:#E0B8B2;
  --vc-navy-bg:#12509E;--vc-navy-ink:#FFFFFF;
  --vc-blue-bg:#2C6CA8;--vc-blue-ink:#FFFFFF;
  --vc-red-bg:#A93A2C;--vc-red-ink:#FFFFFF;
  --vc-pink-bg:#F0C7C2;--vc-pink-ink:#8A1F14;
  --vc-amber-bg:#EFD298;--vc-amber-ink:#68470A;
  --vc-lavender-bg:#DBC8E8;--vc-lavender-ink:#5B2B7C;
  --vc-teal-bg:#B2DCD5;--vc-teal-ink:#1D554E;
  --vc-purple-bg:#C1B7DC;--vc-purple-ink:#3B2C64;
  --vc-orange-bg:#A05712;--vc-orange-ink:#FFFFFF;
  --vc-lightblue-bg:#D2E2F1;--vc-lightblue-ink:#1D588D;
  --vc-peach-bg:#F5D5B1;--vc-peach-ink:#763E0A;
  --vc-grey-bg:#E1E4E0;--vc-grey-ink:#565B57;
  --vc-stone-bg:#EAEDE9;--vc-stone-ink:#5B605C;
  --vc-green-bg:#2C6519;--vc-green-ink:#FFFFFF;
  --vc-leaf-bg:#BFDFB2;--vc-leaf-ink:#315322;
  --vc-dark-bg:#35392F;--vc-dark-ink:#FFFFFF;

  /* UX pack 2, forest: --brand-tint rgba(127,168,148,.14) over --grid-bg
     #F0F4EF = #E0E9E2. */
  --sticky-wash:#E0E9E2;--crosshair-bg:#E0E9E2;
  --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);
}

/* Contrast: the shop floor, poor light, a screen seen from two metres away.
   Black hairlines, black ink, and every chip pushed well past 4.5:1. */
:root[data-theme="contrast"]{
  --brand:#0A66C2;--brand-2:#004B94;--brand-3:#003A75;
  --accent:#B35A00;--accent-2:#8A4300;
  --pos:#006B2D;--neg:#A80000;--warn:#7A4E00;
  --bg:#FFFFFF;--bg-2:#EDEDED;
  --card:#FFFFFF;--card-solid:#FFFFFF;--surface-raise:#DCDCDC;
  --hair:#000000;
  --ink:#000000;--ink-2:#1F1F1F;--ink-3:#3D3D3D;
  --shadow:0 0 0 1px #000000;--shadow-lift:0 0 0 2px #000000;
  --brand-tint:#E3EEF9;--accent-tint:#FBEBD8;
  --line:#000000;--line-strong:#000000;
  --green:var(--brand-3);--green-2:var(--brand-2);--green-soft:#DEEBF7;
  --blue:#8A4300;--blue-soft:#FDEEDD;
  --amber:#6B4300;--amber-soft:#FBF0D2;
  --red:#A80000;--red-soft:#FCE6E6;
  --on-fill:#FFFFFF;
  --brand-fill:#003A75;--brand-fill-hover:#002B57;--accent-fill:#8A4300;--warn-fill:#6B4300;
  --header-bg:#000000;--header-ink:#FFFFFF;--header-ink-2:#E6E6E6;--header-ink-3:#F2F2F2;
  --header-surface:#1F1F1F;--header-active:#003A75;--header-active-line:#FFFFFF;
  --header-hair:#FFFFFF;--header-sep:#FFFFFF;--header-focus:#FFD400;
  --header-shadow:0 3px 0 #000000;
  --role-halo:rgba(255,255,255,.25);
  --role-dot:#5C5C5C;--role-admin:#8A5A00;--role-cnc:#004B94;--role-warehouse:#006B2D;
  --btn-bg:#FFFFFF;--btn-hover-bg:#E3EEF9;--btn-hover-line:#000000;--danger-line:#A80000;
  --input-bg:#FFFFFF;--focus-line:#003A75;--focus-ring:rgba(0,58,117,.30);
  --check-bg:#FFFFFF;--check-locked-bg:#EDEDED;--drop-bg:#FFFFFF;--drop-line:#000000;
  --grid-bg:#FFFFFF;--grid-head-1:#00305F;--grid-head-2:#000000;
  --grid-head-ink:#FFFFFF;--grid-head-hair:rgba(255,255,255,.35);
  --cell-editable:#F2F2F2;--cell-editable-hover:#E4E4E4;--cell-locked:#EDEDED;--cell-wh:#E8E8E8;
  --cell-new:#DDEAF7;--cell-new-line:#003A75;
  --done-bg:#D8F0DD;--done-ink:#00341A;--done-bg-hover:#CBE9D2;
  --zero-bg:#FCE6E6;--zero-bg-hover:#F7D6D6;--pending-hover:#F7E6BE;
  --sticky-shadow:rgba(0,0,0,.35);
  --rownum-bg:#EDEDED;--rownum-bg-hover:#E0E0E0;--rownum-ink:#1F1F1F;
  --sel-ring:#000000;--sel-glow:rgba(0,0,0,.18);--sel-locked-bg:#BCD8F5;--sel-handle:#FFFFFF;
  --chip-ring:rgba(0,0,0,.55);
  --flash-bg:#000000;--flash-ink:#FFFFFF;--flash-line:#FFFFFF;
  --toast-bg:#000000;--toast-ink:#FFFFFF;--toast-error:#8A0000;--toast-success:#00461F;
  --login-1:#FFFFFF;--login-2:#FFFFFF;--login-3:#EDEDED;
  --login-panel-1:#000000;--login-panel-2:#003A75;--login-shadow:0 0 0 2px #000000;
  --login-toggle-bg:#FFFFFF;
  --login-ink:#FFFFFF;--login-ink-2:#FFFFFF;--login-ink-3:#FFFFFF;--login-eyebrow:#FFD400;
  --login-error-bg:#FCE6E6;--login-error-ink:#8A0000;--login-error-line:#A80000;
  --vc-navy-bg:#0A2F63;--vc-navy-ink:#FFFFFF;
  --vc-blue-bg:#0B4E85;--vc-blue-ink:#FFFFFF;
  --vc-red-bg:#8C1E12;--vc-red-ink:#FFFFFF;
  --vc-pink-bg:#FFE1DD;--vc-pink-ink:#7A1108;
  --vc-amber-bg:#FFE7B8;--vc-amber-ink:#4A3000;
  --vc-lavender-bg:#EFE0FA;--vc-lavender-ink:#4A1D6B;
  --vc-teal-bg:#D6F1EB;--vc-teal-ink:#0E3B34;
  --vc-purple-bg:#E2DAF5;--vc-purple-ink:#2E1F58;
  --vc-orange-bg:#7A3D00;--vc-orange-ink:#FFFFFF;
  --vc-lightblue-bg:#E1EFFC;--vc-lightblue-ink:#0F4472;
  --vc-peach-bg:#FFE8CE;--vc-peach-ink:#5C2E00;
  --vc-grey-bg:#EDEDED;--vc-grey-ink:#3D3D3D;
  --vc-stone-bg:#F5F5F5;--vc-stone-ink:#454545;
  --vc-green-bg:#17551A;--vc-green-ink:#FFFFFF;
  --vc-leaf-bg:#DCF2D2;--vc-leaf-ink:#20421A;
  --vc-dark-bg:#1F1F1F;--vc-dark-ink:#FFFFFF;

  /* UX pack 2, contrast: --brand-tint is already solid here (#E3EEF9), so
     no compositing is needed - reuse it verbatim. */
  --sticky-wash:#E3EEF9;--crosshair-bg:#E3EEF9;
  --zebra-bg:var(--check-locked-bg);--range-bg:var(--sel-locked-bg);
}

:lang(ar){font-family:var(--fa)}
*{box-sizing:border-box}
html,body{margin:0;min-height:100%;background:var(--canvas);color:var(--ink);font-family:var(--font);font-size:14px}
body{padding-top:var(--header-h)}
button,input,select{font:inherit}
button,a{transition:background var(--fast) var(--ease),border-color var(--fast) var(--ease),color var(--fast) var(--ease),opacity var(--fast) var(--ease)}
a{color:inherit}
.app-header{position:fixed;inset:0 0 auto 0;height:var(--header-h);z-index:50;display:grid;grid-template-columns:300px 1fr auto;align-items:center;gap:20px;padding:0 22px;background:var(--header-bg);color:var(--header-ink);border-bottom:3px solid var(--green-2);box-shadow:var(--header-shadow)}
.brand{display:flex;align-items:center;gap:11px;text-decoration:none;min-width:0}
.brand-mark{display:grid;place-items:center;width:38px;height:38px;border:1px solid var(--brand-2);border-radius:9px;background:var(--header-surface);color:var(--header-ink);font-weight:800;letter-spacing:-.5px}
.brand strong,.brand small,.account strong,.account small{display:block;white-space:nowrap}
.brand strong{font-size:14px}.brand small{font-size:10px;color:var(--header-ink-2);margin-top:2px;letter-spacing:.04em}
.app-nav{display:flex;align-items:center;justify-content:center;gap:5px;height:100%}
.app-nav a{display:flex;align-items:center;gap:7px;padding:10px 13px;border-radius:8px;color:var(--header-ink-3);text-decoration:none;font-weight:600;font-size:13px}
.app-nav a:hover{color:var(--header-ink);background:var(--header-surface)}.app-nav a.active{color:var(--header-ink);background:var(--header-active);box-shadow:inset 0 0 0 1px var(--header-active-line)}
.account{display:flex;align-items:center;gap:9px;font-size:12px}.account small{color:var(--header-ink-2);font-size:10px;text-transform:uppercase;margin-top:1px}
.lang-toggle{display:flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--line-strong);border-radius:999px;background:var(--surface)}
.lang-opt{padding:4px 9px;border-radius:999px;font-size:10px;font-weight:800;letter-spacing:.04em;color:var(--muted);text-decoration:none;line-height:1.4}
.lang-opt:hover{color:var(--ink)}
.lang-opt.is-on{background:var(--brand-fill);color:var(--on-fill)}
.lang-toggle.on-dark{border-color:var(--header-hair);background:transparent}
.lang-toggle.on-dark .lang-opt{color:var(--header-ink-2)}
.lang-toggle.on-dark .lang-opt:hover{color:var(--header-ink)}
.lang-toggle.on-dark .lang-opt.is-on{background:var(--header-active);color:var(--header-ink)}
.role-dot{width:9px;height:9px;border-radius:50%;background:var(--role-dot);box-shadow:0 0 0 3px var(--role-halo)}.role-admin{background:var(--role-admin)}.role-cnc{background:var(--role-cnc)}.role-warehouse{background:var(--role-warehouse)}
.logout{margin-inline-start:6px;padding:7px 10px;border:1px solid var(--header-hair);border-radius:7px;color:var(--header-ink-2);text-decoration:none}.logout:hover{background:var(--header-surface);color:var(--header-ink)}
.flash-stack{position:fixed;top:76px;inset-inline-end:18px;z-index:80;display:flex;flex-direction:column;gap:8px}.flash{max-width:520px;padding:11px 14px;border-radius:8px;background:var(--flash-bg);color:var(--flash-ink);box-shadow:var(--shadow);border:1px solid var(--flash-line)}
.app-main{min-height:calc(100vh - var(--header-h));padding:20px 22px 28px}
.page-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:flex-end;gap:14px 18px;margin-bottom:16px}
.page-head>div:first-child{flex:1 1 280px;min-width:0}
.page-head h1{margin:0;font-size:24px;letter-spacing:-.02em}.page-head p{margin:5px 0 0;color:var(--muted);font-size:13px}.page-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.metrics{display:flex;align-items:stretch;gap:8px;flex-wrap:wrap;flex:0 0 auto}.metric{min-width:92px;padding:9px 12px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface)}.metric strong{display:block;font-size:17px}.metric span{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em}
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden;margin-bottom:16px;transition:box-shadow var(--med) var(--ease)}.panel-body{padding:16px}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-bottom:1px solid var(--line);background:var(--surface-2)}.panel-head h2,.panel-head h3{margin:0;font-size:14px}.panel-head p{margin:3px 0 0;color:var(--muted);font-size:11px}
.toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:11px 14px;border-bottom:1px solid var(--line);background:var(--surface)}.toolbar .spacer{flex:1}.hint{color:var(--muted);font-size:11px}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;border:1px solid var(--line-strong);border-radius:var(--r-sm);padding:8px 12px;background:var(--btn-bg);color:var(--ink);cursor:pointer;text-decoration:none;font-weight:650;font-size:12px}.btn:hover{background:var(--btn-hover-bg);border-color:var(--btn-hover-line)}.btn.primary{background:var(--brand-fill);border-color:var(--brand-fill);color:var(--on-fill)}.btn.primary:hover{background:var(--brand-fill-hover);border-color:var(--brand-fill-hover)}.btn.blue{background:var(--accent-fill);border-color:var(--accent-fill);color:var(--on-fill)}.btn.danger{color:var(--red);border-color:var(--danger-line)}.btn:disabled{opacity:.45;cursor:not-allowed}
.badge{display:inline-flex;align-items:center;gap:5px;border-radius:var(--r-pill);padding:2px 10px;font-size:10.5px;font-weight:700;white-space:nowrap}.badge.cnc{background:var(--blue-soft);color:var(--blue)}.badge.warehouse{background:var(--green-soft);color:var(--green)}.badge.warning{background:var(--amber-soft);color:var(--amber)}.badge.error{background:var(--red-soft);color:var(--red)}
.field,.field label{display:flex;flex-direction:column;gap:5px}.field label,.form-label{font-size:11px;color:var(--muted);font-weight:650}.input,input[type=text],input[type=password],input[type=number],input[type=date],input[type=file],select{min-height:36px;border:1px solid var(--line-strong);border-radius:7px;background:var(--input-bg);color:var(--ink);padding:7px 9px;outline:none}.input:focus,input:focus,select:focus{border-color:var(--focus-line);box-shadow:0 0 0 3px var(--focus-ring)}input[type=color]{width:38px;height:34px;padding:2px;border:1px solid var(--line-strong);border-radius:7px;background:var(--input-bg)}.form-row{display:flex;align-items:flex-end;gap:9px;flex-wrap:wrap}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
.data-wrap{width:100%;overflow:auto;background:var(--grid-bg);max-height:calc(100vh - 230px)}
.data-table{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%;white-space:nowrap;font-size:12px;table-layout:fixed}.data-table th,.data-table td{height:36px;padding:7px 10px;text-align:start;border-inline-end:1px solid var(--line);border-bottom:1px solid var(--line);overflow:hidden;text-overflow:ellipsis;background:var(--grid-bg)}.data-table th{position:sticky;top:0;z-index:4;height:40px;background:linear-gradient(160deg,var(--grid-head-1),var(--grid-head-2));color:var(--grid-head-ink);font-size:11px;font-weight:750;border-top:1px solid var(--grid-head-hair);box-shadow:inset 0 -1px 0 var(--grid-head-hair);user-select:none}.data-table tr:hover td{background:var(--brand-tint)}.data-table td.editable{cursor:text;background:var(--cell-editable)}.data-table td.editable:hover{background:var(--cell-editable-hover)}.data-table td.locked{color:var(--muted);background:var(--cell-locked)}.data-table td.wh{background:var(--cell-wh)}.data-table td.cut{font-variant-numeric:tabular-nums}.data-table .done-status{background:var(--done-bg)!important;color:var(--done-ink)!important}.data-table tr:hover td.done-status{background:var(--done-bg-hover)!important}.data-table .new-row td{background:var(--cell-new);border-top:2px solid var(--cell-new-line)}.data-table .new-row input{width:100%;min-width:0;height:30px;min-height:30px;padding:4px 6px}.data-table .selected-color{outline:3px solid var(--blue);outline-offset:-3px;position:relative;z-index:2}.data-table td.negative{color:var(--red);font-weight:700;background:var(--red-soft)}
/* Every cell reads centred - the operator asked for it, and for short values
   (dates, order numbers, quantities, chips) a centred column is easier to scan
   down than a ragged left edge. The two long free-text columns are the
   exception: Item Code and Item Name are read as words, not scanned as values,
   so they stay aligned to the start of the line in whichever direction the
   language runs. Header cells follow their column so the two never disagree. */
.data-table th,.data-table td{text-align:center;vertical-align:middle}

/* ---- toolbar menus -------------------------------------------------------
   Four <details> acting as drop-downs, replacing a row of ~20 controls and six
   permanently open accordions. Anchored to the button, not the page, so the
   panel opens beside what was clicked; inline-start/end so it flips with the
   writing direction. */
.wf-menu{position:relative;display:inline-block}
.wf-menu>summary{list-style:none;cursor:pointer;user-select:none;display:inline-flex;
  align-items:center;gap:6px;white-space:nowrap}
.wf-menu>summary::-webkit-details-marker{display:none}
.wf-menu[open]>summary{background:var(--brand-tint);border-color:var(--brand-2)}
/* Setting display on a <details> child OVERRIDES the browser's own hiding of
   closed content, so all four panels rendered permanently - the exact thing
   this was meant to fix. Hide explicitly, and only lay out when open. */
.wf-menu-panel{display:none}
.wf-menu[open]>.wf-menu-panel{position:absolute;z-index:60;
  inset-block-start:calc(100% + 6px);
  inset-inline-start:0;min-width:230px;max-width:min(92vw,420px);
  display:flex;flex-direction:column;gap:8px;padding:10px;
  /* --surface sits within a shade of the page background in the dark themes -
     measured 1.13:1 against it, which reads as transparent with the grid showing
     through. --surface-raise is that theme's popover surface: it steps AWAY from
     the page - lighter on the dark themes, darker on contrast, whose page is
     already white. Deriving it instead (mixing ink into --surface) lifted the dark
     themes but CANCELLED the light ones, which were already correct. */
  background:var(--surface-raise,var(--surface));color:var(--ink);
  border:1px solid var(--line-strong);
  border-radius:var(--r-sm);
  box-shadow:0 0 0 1px rgba(0,0,0,.25),var(--shadow-lift)}
/* The last menu is pushed to the far edge by the spacer, so anchoring its panel
   to the START edge sent it off the side of the screen (measured left:-140 in
   RTL). Anchor it to the END edge so it opens back over the content. */
.wf-menu:last-of-type[open]>.wf-menu-panel{inset-inline-start:auto;inset-inline-end:0}
.wf-menu-item{display:block;width:100%;text-align:start;padding:7px 9px;
  font:inherit;font-size:12.5px;color:var(--ink);background:transparent;
  border:1px solid transparent;border-radius:7px;cursor:pointer}
.wf-menu-item:hover,.wf-menu-item:focus-visible{background:var(--brand-tint);
  border-color:var(--line)}
/* the module-mounted controls sit in a menu now, so they stack rather than
   fight for width in a single row */
.wf-menu-panel .wf-insight-mount,.wf-menu-panel .wf-assist-mount,
.wf-menu-panel .wf-production-mount{display:flex;flex-wrap:wrap;gap:6px 10px}
.wf-menu-panel .wf-scan,.wf-menu-panel .wf-views,.wf-menu-panel .wf-lowstock,
.wf-menu-panel .wf-export{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.wf-menu-panel .wf-scan input{width:100%}
@media(max-width:900px){
  .wf-menu[open]>.wf-menu-panel{position:fixed;inset-block-start:auto;
    inset-block-end:0;inset-inline:0;max-width:none;
    border-radius:var(--r) var(--r) 0 0;max-height:70vh;overflow:auto}
}
/* The Hijri equivalent, shown only when the operator asks for both calendars.
   Secondary by design: the Gregorian date is the stored value. */
.hijri-date{display:block;font-size:10px;color:var(--ink-2);margin-block-start:1px}
.data-table th[data-field="Code"],.data-table td[data-field="Code"],
.data-table th[data-field="material_name"],.data-table td[data-field="material_name"],
.data-table th[data-field="code"],.data-table td[data-field="code"],
.data-table th[data-field="name"],.data-table td[data-field="name"]{text-align:start}
.vchip{display:inline-flex;align-items:center;border-radius:11px;padding:2px 10px;font-size:11px;font-weight:700;line-height:1.3;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis;box-shadow:inset 0 0 0 1px var(--chip-ring);margin-block:1px}.vchip-navy{background:var(--vc-navy-bg);color:var(--vc-navy-ink)}.vchip-blue{background:var(--vc-blue-bg);color:var(--vc-blue-ink)}.vchip-red{background:var(--vc-red-bg);color:var(--vc-red-ink)}.vchip-pink{background:var(--vc-pink-bg);color:var(--vc-pink-ink)}.vchip-amber{background:var(--vc-amber-bg);color:var(--vc-amber-ink)}.vchip-lavender{background:var(--vc-lavender-bg);color:var(--vc-lavender-ink)}.vchip-teal{background:var(--vc-teal-bg);color:var(--vc-teal-ink)}.vchip-purple{background:var(--vc-purple-bg);color:var(--vc-purple-ink)}.vchip-orange{background:var(--vc-orange-bg);color:var(--vc-orange-ink)}.vchip-lightblue{background:var(--vc-lightblue-bg);color:var(--vc-lightblue-ink)}.vchip-peach{background:var(--vc-peach-bg);color:var(--vc-peach-ink)}.vchip-grey{background:var(--vc-grey-bg);color:var(--vc-grey-ink)}.vchip-stone{background:var(--vc-stone-bg);color:var(--vc-stone-ink)}.vchip-green{background:var(--vc-green-bg);color:var(--vc-green-ink)}.vchip-leaf{background:var(--vc-leaf-bg);color:var(--vc-leaf-ink)}.vchip-dark{background:var(--vc-dark-bg);color:var(--vc-dark-ink)}.vchip-none{}
.data-table td input.cell,.data-table .new-row input{background:var(--input-bg);color:var(--ink);caret-color:var(--ink);cursor:text}.data-table td input.cell:focus,.data-table .new-row input:focus{border-color:var(--focus-line);box-shadow:0 0 0 3px var(--focus-ring)}
.data-table td.just-saved{animation:just-saved-flash 1.2s ease-out}@keyframes just-saved-flash{0%{background:var(--brand-tint)}100%{background:transparent}}@media (prefers-reduced-motion:reduce){.data-table td.just-saved{animation:none}}
.resize-handle{position:absolute;top:0;inset-inline-end:-3px;width:7px;height:100%;cursor:col-resize;z-index:8}.resize-handle:hover{box-shadow:inset -2px 0 0 var(--brand-2)}body.resizing .resize-handle{box-shadow:inset -2px 0 0 var(--brand-3)}.resize-handle:focus-visible{outline:2px solid var(--blue);outline-offset:1px}.resizing{cursor:col-resize!important;user-select:none!important}
.code-pill{display:inline-block;padding:2px 6px;border:1px solid var(--line-strong);border-radius:5px;background:var(--brand-tint);font-family:"SFMono-Regular",Consolas,monospace}.match{color:var(--green);font-weight:800}.unmatch{color:var(--red);font-weight:800}
.color-tools{display:flex;align-items:center;gap:7px;padding-inline-start:10px;border-inline-start:1px solid var(--line)}.color-tools .target{max-width:180px;color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}body.color-mode td[data-colorable=true]{cursor:crosshair!important}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:12px;margin-bottom:16px}.kpi{padding:15px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);transition:box-shadow var(--med) var(--ease)}.kpi small{color:var(--muted)}.kpi strong{display:block;font-size:24px;margin-top:7px}.kpi.error strong{color:var(--red)}.kpi.warning strong{color:var(--amber)}.kpi.good strong{color:var(--green)}

/* Soft translucent cards, per the Control Room idiom - with an opaque
   fallback (the plain var(--surface) above) for browsers without
   backdrop-filter support. */
@supports (backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px)){
  .panel,.kpi{background:var(--card);-webkit-backdrop-filter:blur(16px) saturate(1.3);backdrop-filter:blur(16px) saturate(1.3)}
  .panel:hover,.kpi:hover{box-shadow:var(--shadow-lift)}
}
.section-stack{display:grid;gap:16px}.empty-state{padding:38px;text-align:center;color:var(--muted)}.empty-state strong{display:block;color:var(--green);font-size:17px;margin-bottom:5px}
.admin-grid{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(300px,.7fr);gap:16px}.admin-side{display:grid;align-content:start;gap:16px}.permission-role{padding:14px;border-bottom:1px solid var(--line)}.permission-role:last-child{border-bottom:0}.permission-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:7px;margin:10px 0}.check.is-locked{opacity:.55;background:var(--check-locked-bg);cursor:not-allowed}.check.is-locked .lock-mark{margin-inline-start:auto;font-size:10px;opacity:.7}
.check{display:flex;align-items:center;gap:7px;padding:7px 8px;border:1px solid var(--line);border-radius:7px;background:var(--check-bg);font-size:11px}.check input{accent-color:var(--brand-fill)}.note{font-size:11px;color:var(--muted);line-height:1.55}.import-drop{padding:22px;border:1.5px dashed var(--drop-line);border-radius:10px;background:var(--drop-bg);text-align:center}.import-drop input{width:100%;margin:12px 0}.import-report{margin-top:12px;padding:10px;border-radius:8px;background:var(--green-soft);color:var(--green)}
.simple-table{width:100%;border-collapse:collapse;font-size:12px}.simple-table th,.simple-table td{padding:9px 10px;border-bottom:1px solid var(--line);text-align:start}.simple-table th{background:var(--surface-2);color:var(--muted);font-size:10px;text-transform:uppercase}
.toast-region{position:fixed;inset-inline-end:18px;bottom:18px;z-index:100;display:grid;gap:8px}.toast{padding:10px 14px;border-radius:8px;background:var(--toast-bg);color:var(--toast-ink);box-shadow:var(--shadow);font-size:12px}.toast.error{background:var(--toast-error)}.toast.success{background:var(--toast-success)}
[hidden]{display:none!important}
/* A pinned row must stay on screen even when the filter has hidden it -
   that is the entire point of pinning. An inline style cannot beat the
   !important above, so this higher-specificity rule does it instead. */
#grid tbody tr[data-wfi-pinned="1"]{display:table-row!important}
.ltr{direction:ltr;unicode-bidi:isolate;text-align:left}
bdi{unicode-bidi:isolate}
@media(max-width:900px){.app-header{grid-template-columns:auto 1fr auto;padding:0 12px}.brand span:last-child{display:none}.app-nav{justify-content:flex-start;overflow:auto}.app-nav a{padding:9px}.app-nav a span+*{display:none}.account span:not(.role-dot){display:none}.app-main{padding:14px}.page-head{align-items:flex-start;flex-direction:column}.admin-grid{grid-template-columns:1fr}.data-wrap{max-height:calc(100vh - 260px)}}

.login-page .lang-toggle{position:fixed;top:16px;inset-inline-end:16px;z-index:60;background:var(--login-toggle-bg);box-shadow:var(--shadow)}

/* Public login uses the same operations design language. */
.login-page{min-height:100vh;background:linear-gradient(135deg,var(--login-1) 0%,var(--login-2) 58%,var(--login-3) 100%);display:grid;place-items:center;padding:28px}
.login-shell{width:min(1040px,100%);min-height:590px;display:grid;grid-template-columns:1.25fr .75fr;background:var(--surface);border:1px solid var(--line-strong);border-radius:20px;overflow:hidden;box-shadow:var(--login-shadow)}
.login-brand{background:linear-gradient(150deg,var(--login-panel-1),var(--login-panel-2));color:var(--login-ink);padding:42px;display:flex;flex-direction:column;justify-content:space-between;gap:38px;position:relative;overflow:hidden}.login-brand:after{content:"";position:absolute;width:340px;height:340px;border:1px solid var(--login-hair);border-radius:50%;inset-inline-end:-130px;bottom:-120px;box-shadow:0 0 0 45px var(--login-glow-1),0 0 0 90px var(--login-glow-2)}
.login-logo{color:var(--login-ink);position:relative;z-index:1}.login-logo .brand-mark{background:var(--login-ink);color:var(--login-panel-2);border-color:var(--login-hair)}.login-logo small{color:var(--login-ink-3)}.login-brand h1{font-size:clamp(30px,4vw,50px);line-height:1.14;max-width:620px;margin:12px 0 16px;letter-spacing:-.035em}.login-brand p{color:var(--login-ink-2);line-height:1.8;max-width:620px;font-size:15px}.eyebrow{text-transform:uppercase;letter-spacing:.13em;font-size:11px;font-weight:800;color:var(--login-eyebrow)}.login-steps{display:flex;gap:9px;flex-wrap:wrap;position:relative;z-index:1}.login-steps span{border:1px solid var(--login-step-line);background:var(--login-step-bg);padding:8px 10px;border-radius:999px;font-size:10px;color:var(--login-ink-3)}
.login-card{padding:46px 38px;display:flex;flex-direction:column;justify-content:center;gap:28px}.login-card h2{font-size:28px;margin:8px 0 4px}.login-card p{color:var(--muted);margin:0}.login-submit{width:100%;justify-content:center;padding:12px}.login-error{background:var(--login-error-bg);color:var(--login-error-ink);border:1px solid var(--login-error-line);border-radius:8px;padding:9px 11px;font-size:12px}.login-foot{color:var(--muted);text-align:center}
@media(max-width:760px){.login-page{padding:14px}.login-shell{grid-template-columns:1fr;min-height:auto}.login-brand{padding:28px;min-height:310px}.login-brand h1{font-size:32px}.login-card{padding:30px 24px}}
/* ── Stock page: Al-Sahli sync state ─────────────────────────────────── */
.sync-bar{display:flex;align-items:center;gap:10px;padding:9px 14px;background:var(--green-soft);border-bottom:1px solid var(--line)}
.sync-bar.is-bad{background:var(--red-soft)}
.sync-bar.is-warn{background:var(--amber-soft)}
.sync-bar .sync-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.sync-bar strong{font-size:12px;color:var(--green)}
.sync-bar.is-bad strong{color:var(--red)}
.sync-bar.is-warn strong{color:var(--amber)}
.sync-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--green-2)}
.sync-bar.is-bad .sync-dot{background:var(--red)}

.chips{display:flex;gap:6px;flex-wrap:wrap}
.chip{border:1px solid var(--line-strong);border-radius:999px;padding:5px 11px;background:var(--btn-bg);color:var(--muted);cursor:pointer;font-size:11px;font-weight:700}
.chip:hover{background:var(--btn-hover-bg)}
.chip.is-on{background:var(--brand-fill);border-color:var(--brand-fill);color:var(--on-fill)}

.badge.sahli{background:var(--blue-soft);color:var(--blue)}

.data-table td.qty-cell{font-variant-numeric:tabular-nums;font-weight:650}
.data-table td.qty-cell.locked{cursor:help}
.data-table td.qty-cell .lock-mark{margin-inline-end:6px;opacity:.5;font-size:10px}
.data-table td.qty-cell.is-zero .qty-val{color:var(--red)}
/* An out-of-stock row is worth noticing, not shouting about: 17 of 74 rows
   as solid red competed with the dashboard's own manual red cells. */
.data-table tr[data-zero="1"] td.qty-cell{background:var(--zero-bg);box-shadow:inset 3px 0 0 var(--red)}
[dir="rtl"] .data-table tr[data-zero="1"] td.qty-cell{box-shadow:inset -3px 0 0 var(--red)}
.data-table tr[data-zero="1"]:hover td.qty-cell{background:var(--zero-bg-hover)}

.btn.is-busy{opacity:.7}
.btn .sync-icon{display:inline-block}
.btn.is-busy .sync-icon{animation:wf-spin .9s linear infinite}
@keyframes wf-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.btn.is-busy .sync-icon{animation:none}}

/* In column mode flex-basis is a HEIGHT, so the 280px basis on the
   title block left a ~214px void above the metrics. */
@media (max-width:900px){.page-head>div:first-child{flex:0 0 auto;width:100%}}

/* ── Empire MENA brand lockup ─────────────────────────────────── */
.brand-logo{height:26px;width:auto;display:block;flex:none}
.brand-sep{width:1px;height:26px;background:var(--header-sep);flex:none}
.brand:hover .brand-logo{opacity:.9}

/* ── UX pack ──────────────────────────────────────────────────────────── */

/* 1. Pin the first column. Both grids scroll sideways (9 and 17 columns), and
      without this you lose track of which row you are reading. */
.data-table th:first-child,.data-table td:first-child{position:sticky;inset-inline-start:0;z-index:3}
.data-table thead th:first-child{z-index:6}
.data-table th:first-child,.data-table td:first-child{box-shadow:2px 0 5px var(--sticky-shadow)}
[dir="rtl"] .data-table th:first-child,[dir="rtl"] .data-table td:first-child{box-shadow:-2px 0 5px var(--sticky-shadow)}

/* 4. Keyboard users should not tab the whole nav on every page. */
.skip-link{position:absolute;inset-inline-start:-9999px;top:0;z-index:200;padding:10px 16px;background:var(--brand-fill);color:var(--on-fill);font-weight:700;border-end-end-radius:8px}
.skip-link:focus{inset-inline-start:0}
#main-content:focus{outline:none}

/* 5. One visible focus style everywhere, in the brand colour. */
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,
.chip:focus-visible,.check input:focus-visible,summary:focus-visible{outline:2px solid var(--green-2);outline-offset:2px}
.app-header a:focus-visible,.app-header button:focus-visible{outline-color:var(--header-focus)}

/* 8. The header runs out of room well before the existing 900px breakpoint. */
@media(max-width:1100px){
  .app-header{grid-template-columns:auto 1fr auto;gap:10px;padding:0 14px}
  .brand-sep,.brand small,.account small{display:none}
}

/* 6. Printing: drop the chrome, let the grid breathe, ink on paper. The only
      hardcoded colours left in this file, and deliberately so - paper does
      not have a theme. */
@media print{
  .app-header,.flash-stack,.toast-region,.toolbar,.page-actions,.chips,
  .resize-handle,.skip-link,.lang-toggle,#addForm,#no-results,#grid-no-results,
  /* The names the modules and templates ACTUALLY emit. This list previously
     used invented names, so every wave-4 control - the compare overlay
     included - printed on the page. */
  .wfi-toolbar,.wfi-popover,.wfi-compare-panel,.wfi-pin-btn,.wfi-caret,
  tr.wfi-pin-header,#grid-insight-mount,#wf-trend,#wf-views-btn,#wf-views-menu,
  #wf-export-csv,#wf-export-xlsx,#wf-export-menu,#f-lowstock,#wf-lowstock-input,
  #lowstock-threshold,.saved-views-menu,.export-menu-panel,.compare-panel,
  .trend-panel,.pinned-sep,.group-header .group-toggle,.lowstock-chip,
  .confirmbtn,.wf-delete-row,.history-handle{display:none!important}
  /* Force a light palette when printing whatever data-theme says: a dark
     theme on paper is unreadable and empties the toner. */
  :root,:root[data-theme]{--bg:#fff;--bg-2:#fff;--surface:#fff;--card-solid:#fff;--surface-raise:#fff;
    --ink:#000;--ink-2:#333;--ink-3:#555;--line:#999;--line-strong:#666}
  body{padding-top:0;background:#fff;color:#000}
  .app-main{padding:0}
  .panel{box-shadow:none;border:1px solid #999;margin:0 0 10px}
  .data-wrap{overflow:visible!important;max-height:none!important}
  .data-table{width:100%!important;table-layout:auto!important;white-space:normal!important;font-size:10px}
  /* Insight-pack additions print only the rows the screen was already
     showing (a filtered-out or collapsed-group row is [hidden] in the DOM,
     which stays display:none here same as on screen), the header repeats on
     every page instead of scrolling once, and no row is split mid-page. */
  .data-table thead{display:table-header-group}
  .data-table tbody tr{page-break-inside:avoid;break-inside:avoid}
  .data-table th,.data-table td{width:auto!important;min-width:0!important;max-width:none!important;
    position:static!important;background:#fff!important;color:#000!important;box-shadow:none!important}
  /* the cell rule does not reach a button nested inside it, so per-row
     controls kept printing a dark fill */
  .data-table button,.data-table .btn{background:#fff!important;color:#000!important;
    border-color:#999!important;box-shadow:none!important}
  .badge,.code-pill,.vchip,.lowstock-badge,.deleted-badge,.audit-badge{
    border:1px solid #999;color:#000!important;background:#fff!important}
  .vchip::before{color:#000!important}
  @page{size:A4;margin:12mm}
}

/* ── Warehouse deduction ledger ───────────────────────────────────────── */
/* "Projected" = Al-Sahli's figure minus what the warehouse has issued but
   Al-Sahli has not been told about yet. It is a PREDICTION, so it reads
   quieter than the authoritative quantity beside it. */
.data-table td.projected-cell{font-variant-numeric:tabular-nums;color:var(--muted)}
.data-table td.projected-cell.has-pending{color:var(--ink);font-weight:650;background:var(--amber-soft)}
.data-table td.projected-cell .pending-chip{
  display:inline-block;margin-inline-start:6px;padding:1px 7px;
  border-radius:var(--r-pill,999px);background:var(--warn-fill);color:var(--on-fill);
  font-size:10px;font-weight:700;font-variant-numeric:tabular-nums}
.data-table tr:hover td.projected-cell.has-pending{background:var(--pending-hover)}
.metric.is-pending{border-color:var(--amber);background:var(--amber-soft)}
.metric.is-pending strong{color:var(--amber)}

/* ── Filter bar ───────────────────────────────────────────────────────── */
.filter-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:9px 14px;border-bottom:1px solid var(--line);background:var(--surface-2)}
.filter-bar .spacer{flex:1}
.filter-bar .input,.filter-bar select{min-height:32px;font-size:12px;padding:4px 8px}
.f-date{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted);font-weight:650}
.f-date input{min-height:32px;font-size:12px}
#f-count,#s-count{font-variant-numeric:tabular-nums;white-space:nowrap}
@media(max-width:760px){.filter-bar{gap:6px;padding:8px 10px}.f-date span{display:none}}

/* The number is the figure; the unit is context. Keep it quiet but always
   present, so "8.85" can never be mistaken for 8.85 square metres. */
.data-table td .qty-unit{
  margin-inline-start:5px;font-size:10px;font-weight:600;
  color:var(--muted);cursor:help;white-space:nowrap}
.data-table td.qty-cell.is-zero .qty-unit{color:var(--red)}

/* ── Login form ───────────────────────────────────────────────────────── */
/* Login form: one field per row. The shared .form-grid auto-fits two columns
   once there is ~330px, which put username and password side by side - they
   are a sequence to work through, not parallel fields. */
.login-card .form-grid{grid-template-columns:1fr}
.login-submit{margin-top:4px}
.login-submit[aria-busy="true"]{opacity:.75;pointer-events:none}
.login-card .field label{font-size:12px}
.login-card input{min-height:42px;font-size:14px}

/* Audit page: the candidate codes offered when history cannot decide for us.
   Each is a button, because the person reading this page is the one who knows
   which board was actually cut. */
.cand{display:inline-flex;align-items:center;gap:4px;margin-inline-end:8px;white-space:nowrap}
.btn.btn-sm{padding:2px 8px;font-size:11px;line-height:1.6;font-variant-numeric:tabular-nums}
#no-material .hint{font-size:11px}
/* The item names run to 60+ characters. Let the cell keep a sane width and
   show the full name on hover, rather than widening the table until the
   action scrolls out of sight. */
#no-material .sugg-cell{min-width:230px;max-width:420px;
  /* the shared .data-table cell is nowrap+hidden for the resizable grid; this
     table has no resize handles, and a third candidate was being clipped to
     an ellipsis rather than wrapping onto the next line */
  white-space:normal;overflow:visible}
#no-material td,#no-material th{overflow:visible}
#no-material .sugg-name{display:block;margin-block-start:2px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;max-width:370px}
#no-material td{vertical-align:middle}

/* The selected cell, the way a spreadsheet marks one. This is the whole point
   of separating selection from editing: you can see where you are without an
   input box being open, so arrow keys move and nothing is saved. A ring, not a
   fill - a background here would fight the value chips.
   Selection now has to survive on a LOCKED cell too, because arrow keys walk
   through cells the user may not edit. On those the ring alone is doing all
   the work against a grey fill, so it gets a faint wash and the cell's muted
   text is lifted back to full --ink while it is the one selected. */
.data-table td.cell-selected{position:relative;z-index:3;outline:2px solid var(--sel-ring);
  outline-offset:-2px;box-shadow:inset 0 0 0 3px var(--sel-glow)}
.data-table td.cell-selected:focus{outline-color:var(--sel-ring)}
.data-table td.cell-selected::after{content:"";position:absolute;inset-inline-end:-3px;bottom:-3px;
  width:6px;height:6px;border-radius:1px;background:var(--sel-ring);
  box-shadow:0 0 0 1px var(--sel-handle)}
@media (prefers-reduced-motion:no-preference){
  .data-table td.cell-selected{transition:outline-color var(--fast) var(--ease)}
}
.data-table td.locked.cell-selected{background:var(--sel-locked-bg);color:var(--ink)}
.data-table tr:hover td.locked.cell-selected{background:var(--sel-locked-bg)}
.data-table td.locked.cell-selected .qty-unit,
.data-table td.locked.cell-selected .lock-mark{color:var(--ink);opacity:.75}

/* ── Row numbers ──────────────────────────────────────────────────────── */
/* A leading "#" gutter on both grids. The count is of VISIBLE rows, so the
   JS refills it after every filter - which is also why nothing here may
   depend on :nth-child, and why the column must never look pressable. */
.data-table th.rownum-h,.data-table td.rownum{
  width:var(--rownum-w);min-width:var(--rownum-w);max-width:var(--rownum-w);
  text-align:end;font-variant-numeric:tabular-nums;
  user-select:none;-webkit-user-select:none;cursor:default}
.data-table td.rownum{background:var(--rownum-bg);color:var(--rownum-ink);
  font-size:11px;font-weight:650;padding-inline:6px}
.data-table tr:hover td.rownum{background:var(--rownum-bg-hover)}
.data-table th.rownum-h{padding-inline:6px;font-weight:750}
/* The gutter is never editable and never a colour target, whatever a stray
   data-attribute says. */
body.color-mode td.rownum{cursor:default!important}
.data-table td.rownum.cell-selected{outline:none;box-shadow:none}
.data-table td.rownum.cell-selected::after{display:none}

/* The gutter takes over :first-child, so the column that USED to be pinned
   (the code, the thing you actually read the row by) would have stopped
   sticking. Push stickiness one column along wherever a gutter is present,
   and move the edge shadow with it. :has() is the only way to detect that
   from CSS alone; where it is unsupported the grid simply keeps one pinned
   column instead of two, which is the pre-existing behaviour. */
@supports selector(:has(*)){
  .data-table tr:has(>.rownum-h,>.rownum)>:nth-child(2){
    position:sticky;inset-inline-start:var(--rownum-w);z-index:3;
    box-shadow:2px 0 5px var(--sticky-shadow)}
  .data-table thead tr:has(>.rownum-h)>:nth-child(2){z-index:6}
  .data-table tr:has(>.rownum-h,>.rownum)>:first-child{box-shadow:none}
  [dir="rtl"] .data-table tr:has(>.rownum-h,>.rownum)>:nth-child(2){
    box-shadow:-2px 0 5px var(--sticky-shadow)}
}

/* ═══ UX pack 2: sticky/density/zebra/crosshair/save/legend/badge ══════ */

/* Third pinned column: PO. The master grid's field order is date, PO - the
   gutter and the date column already stick via the :has() block above, this
   adds the one after it. Attribute-scoped (not :nth-child(3)) so the stock
   grid, whose third column is never a PO field, is untouched. --date-col-w
   is the DEFAULT rendered width of the date column (see its definition on
   :root) - see that comment for the resize caveat. */
.data-table th[data-field="PO"],.data-table td[data-field="PO"]{
  position:sticky;inset-inline-start:calc(var(--rownum-w) + var(--date-col-w));
  z-index:3;box-shadow:2px 0 5px var(--sticky-shadow)}
.data-table thead th[data-field="PO"]{z-index:6}
[dir="rtl"] .data-table th[data-field="PO"],[dir="rtl"] .data-table td[data-field="PO"]{
  box-shadow:-2px 0 5px var(--sticky-shadow)}

/* Opaque hover for every pinned column. The generic ".data-table tr:hover
   td" rule near the top of the file paints --brand-tint, which is rgba -
   exactly the translucent wash a sticky column must never carry (see the
   THEME TOKENS banner). Same cells, same :hover, solid colour instead. */
.data-table tr:hover td.rownum{background:var(--rownum-bg-hover)}
.data-table tr:hover td[data-field="PO"]{background:var(--sticky-wash)}
@supports selector(:has(*)){
  .data-table tbody tr:has(>.rownum-h,>.rownum):hover>:nth-child(2){background:var(--sticky-wash)}
}

/* Row density. body[data-density] drives height/padding/font-size on every
   grid cell; no attribute at all (density script hasn't run yet, or storage
   was empty) keeps the 36px baseline already defined earlier in this file. */
body[data-density="compact"] .data-table th,body[data-density="compact"] .data-table td{height:30px;padding:5px 8px;font-size:11.5px}
body[data-density="compact"] .data-table th.rownum-h,body[data-density="compact"] .data-table td.rownum{padding-inline:5px}
body[data-density="dense"] .data-table th,body[data-density="dense"] .data-table td{height:24px;padding:3px 7px;font-size:11px}
body[data-density="dense"] .data-table th.rownum-h,body[data-density="dense"] .data-table td.rownum{padding-inline:4px}
body[data-density="comfortable"] .data-table th,body[data-density="comfortable"] .data-table td{height:44px;padding:9px 12px;font-size:12.5px}
body[data-density="comfortable"] .data-table th.rownum-h,body[data-density="comfortable"] .data-table td.rownum{padding-inline:8px}
/* ...and only at comfortable density, the long Arabic material names wrap
   onto a second line instead of being ellipsised away - there is finally
   room for it. The row (not the cell) grows to fit; every other cell in
   that row keeps its own fixed height, table layout does the rest. */
body[data-density="comfortable"] .data-table td[data-field="material_name"]{
  white-space:normal;overflow:visible;text-overflow:clip;
  height:auto;min-height:44px;vertical-align:middle;line-height:1.35}

/* Zebra striping. --zebra-bg is --check-locked-bg, a token already carried
   through every theme and already measured against --ink for the checkbox
   rows it was built for - not a new colour the audit hasn't seen. Wrapped
   in :where() so it carries ZERO specificity of its own: a cell that means
   something on its own (editable/locked/wh/done/zero/new-row/negative/
   qty-cell/projected-cell, all one extra class = higher specificity) always
   keeps its own colour instead of having that meaning striped away. Zebra
   only ever shows on a cell with no state class of its own. */
.data-table tbody :where(tr:nth-child(even)) td{background:var(--zebra-bg)}

/* The crosshair (row + column of the selected cell) and a dragged multi-
   cell range. Solid overrides, appended last on purpose - exactly like
   .cell-selected already overrides .locked above, a temporary interaction
   highlight is allowed to take precedence over a cell's resting colour.
   Because both are opaque, "which zebra stripe is underneath" never
   changes the result - that is the point, not an oversight. */
.data-table tbody td.crosshair-row,.data-table tbody td.crosshair-col{background:var(--crosshair-bg)}
.data-table tbody td.cell-range{background:var(--range-bg)}
.data-table tbody tr:hover td.crosshair-row,.data-table tbody tr:hover td.crosshair-col{background:var(--crosshair-bg)}
.data-table tbody tr:hover td.cell-range{background:var(--range-bg)}

/* Small per-cell save indicator. Markup: a <span class="save-dot"
   data-state="saving|saved|failed"> anywhere inside the cell being edited.
   Reuses --pos/--green-soft, --neg/--red-soft, --warn/--amber-soft - the
   same ink-on-soft pairs the .badge rule above already relies on - plus a
   distinct glyph per state, so state never rides on colour alone. */
.save-dot{display:inline-flex;align-items:center;justify-content:center;
  margin-inline-start:6px;width:15px;height:15px;border-radius:50%;
  font-size:9px;font-weight:900;line-height:1;vertical-align:middle}
.save-dot[data-state="saving"]{background:var(--amber-soft);color:var(--amber)}
.save-dot[data-state="saving"]::before{content:"\2026"}
.save-dot[data-state="saved"]{background:var(--green-soft);color:var(--green)}
.save-dot[data-state="saved"]::before{content:"\2713"}
.save-dot[data-state="failed"]{background:var(--red-soft);color:var(--red)}
.save-dot[data-state="failed"]::before{content:"\2715"}
@media (prefers-reduced-motion:no-preference){
  .save-dot[data-state="saving"]{animation:wf-save-pulse 1s ease-in-out infinite}
}
@keyframes wf-save-pulse{0%,100%{opacity:1}50%{opacity:.45}}

/* The chip legend strip. Markup: <div class="chip-legend"><span
   class="chip-legend-item"><span class="vchip vchip-navy vchip-sm"></span>
   <bdi>label</bdi></span>...</div> - labels are i18n text from the
   template layer, this only supplies the chrome around them. */
.chip-legend{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;
  padding:9px 14px;border-bottom:1px solid var(--line);background:var(--surface-2);
  font-size:11px;color:var(--muted)}
.chip-legend-item{display:inline-flex;align-items:center;gap:6px}
.vchip.vchip-sm{padding:1px 8px;font-size:10px;min-width:18px;justify-content:center}

/* (colour-blind mode was removed at the operator's request)
   Historic note: chips carried a letter token inside every
   chip, so hue is never the only signal. One rule per fixed chip class
   (there are exactly 16 - see the --vc-* tokens on :root) rather than a
   data-attribute another lane would need to add. */

/* The audit badge. State reads from shape + icon + border, never colour
   alone - "readable without colour" per the wave brief. Markup: <span
   class="audit-badge is-ok|is-flagged|is-pending"><span
   class="audit-badge-icon"></span>label</span>, label text from the
   template/i18n layer. Same green/red/amber ink-on-soft pairs as .badge
   above; the icon glyph inherits that same colour (currentColor), so it
   never needs a contrast pair of its own to check. */
.audit-badge{display:inline-flex;align-items:center;gap:5px;
  padding:2px 10px;border-radius:999px;font-size:10.5px;font-weight:750;
  border:1px solid currentColor}
.audit-badge-icon{font-weight:900}
.audit-badge.is-ok{background:var(--green-soft);color:var(--green)}
.audit-badge.is-ok .audit-badge-icon::before{content:"\2713"}
.audit-badge.is-flagged{background:var(--red-soft);color:var(--red)}
.audit-badge.is-flagged .audit-badge-icon::before{content:"!"}
.audit-badge.is-pending{background:var(--amber-soft);color:var(--amber)}
.audit-badge.is-pending .audit-badge-icon::before{content:"\2026"}

/* ═══ Row audit pack: soft delete, conflict, health, import preview ═══ */

/* Soft-deleted rows, shown only while "show deleted" is on. Inert on sight:
   dimmed fill, struck-through text and an italic slant stack three signals
   that never depend on colour, then the row-level state overrides every
   per-cell state class after it (editable/locked/wh/qty/negative/zero) the
   same way .done-status already does with !important - a deleted row must
   never look like an ordinary editable one underneath. */
.data-table tr.is-deleted td{background:var(--deleted-bg)!important;color:var(--deleted-ink)!important;
  text-decoration:line-through;text-decoration-thickness:1px;text-decoration-color:var(--deleted-line);
  font-style:italic;cursor:not-allowed;box-shadow:none!important}
.data-table tr.is-deleted:hover td{background:var(--deleted-bg)!important}
.data-table tr.is-deleted td.cell-selected{outline:1px solid var(--deleted-line);box-shadow:none}
.data-table tr.is-deleted td.cell-selected::after{display:none}
.data-table tr.is-deleted td .qty-unit,.data-table tr.is-deleted td .lock-mark{color:var(--deleted-ink);opacity:.7}
/* Small inline marker (row.deleted_badge) placed in the row's own cell -
   icon + label, not fill alone, so it does not rely on fill alone. */
.deleted-badge{display:inline-flex;align-items:center;gap:5px;padding:1px 8px;
  border-radius:999px;background:var(--deleted-bg);color:var(--deleted-ink);
  border:1px solid var(--deleted-line);font-size:10px;font-weight:750;
  text-decoration:none;font-style:normal}
.deleted-badge::before{content:"\2715";font-weight:900}

/* The 409 conflict prompt. A modal over a neutral scrim - --scrim is a fixed
   dimming layer, not a themed ink/fill, so it does not need a per-theme
   value the way text and borders do. */
.conflict-backdrop{position:fixed;inset:0;background:var(--scrim);z-index:150;
  display:grid;place-items:center;padding:20px}
.conflict-dialog{width:min(480px,100%);background:var(--surface);
  border:1px solid var(--line-strong);border-radius:var(--radius);
  box-shadow:var(--shadow-lift);padding:20px;display:grid;gap:12px}
.conflict-dialog h3{margin:0;font-size:15px;display:flex;align-items:center;gap:8px}
.conflict-dialog h3::before{content:"\26A0";color:var(--amber)}
.conflict-dialog p{margin:0;color:var(--muted);font-size:12.5px;line-height:1.55}
.conflict-current{padding:9px 11px;border-radius:8px;background:var(--warn-soft);
  color:var(--amber);border:1px solid var(--amber);font-size:12px;font-weight:650}
.conflict-meta{font-size:11px;color:var(--muted)}
.conflict-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}

/* Admin health panel. .health-card is the outer surface, .health-item is one
   metric row; .is-warn/.is-bad add a distinct border STYLE as well as a
   colour, so the three states stay distinguishable without it. */
.health-card{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow);padding:14px;
  display:grid;gap:9px}
.health-card h2,.health-card h3{margin:0 0 2px;font-size:14px}
.health-item{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:9px 11px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface-2);font-size:12px}
.health-item .health-label{display:flex;align-items:center;gap:7px;color:var(--muted)}
.health-item .health-label::before{content:"";flex:none;width:7px;height:7px;
  border-radius:50%;background:var(--ink-3)}
.health-item strong{font-variant-numeric:tabular-nums;font-size:13px}
.health-item.is-warn{background:var(--warn-soft);border-color:var(--amber);border-style:dashed}
.health-item.is-warn .health-label{color:var(--amber)}
.health-item.is-warn .health-label::before{background:var(--amber)}
.health-item.is-warn strong{color:var(--amber)}
.health-item.is-bad{background:var(--bad-soft);border-color:var(--red);border-width:2px}
.health-item.is-bad .health-label{color:var(--red)}
.health-item.is-bad .health-label::before{background:var(--red)}
.health-item.is-bad strong{color:var(--red);font-weight:800}

/* The destructive-import diff table. "Will be replaced" reads as a warning
   through colour, a left bar AND bold weight together, never colour alone. */
.import-preview{width:100%;border-collapse:collapse;font-size:12px}
.import-preview th,.import-preview td{padding:8px 10px;border-bottom:1px solid var(--line);text-align:start}
.import-preview th{background:var(--surface-2);color:var(--muted);font-size:10px;text-transform:uppercase}
.import-preview td.will-replace{background:var(--bad-soft);color:var(--red);font-weight:800;
  border-inline-start:3px solid var(--red)}
.import-preview td.incoming{color:var(--green);font-weight:650}
.import-preview .discard-note{margin-top:8px;padding:9px 11px;border-radius:8px;
  background:var(--warn-soft);color:var(--amber);border:1px solid var(--amber);font-size:11.5px}

/* New-row fields: a locked/disabled one, and one that is required before the
   row can be added. Both signal through more than fill colour. */
.data-table .new-row input.newrow-locked{background:var(--check-locked-bg);
  color:var(--muted);cursor:not-allowed;border-color:var(--line)}
.data-table .new-row input.is-required{border-inline-start:3px solid var(--warn-fill)}
.data-table .new-row input.is-required::placeholder{color:var(--amber)}

/* Activity-log revert action. Text stays plain --ink on --btn-bg (the pair
   every other .btn already relies on, 12.4:1+ in all seven themes) - the
   accent lives in the border and the icon glyph instead of the label ink,
   which is what kept a brand-tinted label out of the dark/midnight themes
   (as low as 3.04:1 there). */
.revert-btn{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;
  border:1px solid var(--brand-2);border-radius:999px;background:var(--btn-bg);
  color:var(--ink);font-size:10.5px;font-weight:750;cursor:pointer}
.revert-btn::before{content:"\21BA";font-weight:900}
.revert-btn:hover{background:var(--btn-hover-bg);border-color:var(--btn-hover-line)}
.revert-btn:disabled{opacity:.45;cursor:not-allowed}

/* ═══ Grid-insight pack: grouping, pinning, compare, saved views, ═══════ */
/* ═══ low-stock, trend chart, export menu ═══════════════════════════════ */
/* No new colour tokens: every rule below reuses a pair the file has already
   measured (see the contrast table in the accompanying review note) - the
   16 --vc-* chip pairs, --green/--green-soft, --red/--red-soft, --amber/
   --amber-soft, --ink/--surface, --muted/--surface-2, --on-fill/--brand-fill
   and --on-fill/--warn-fill. Reusing them means nothing here can regress an
   already-audited pair, and nothing new needs measuring per theme. */

/* ── Grouped grid: group.by ─────────────────────────────────────────────
   Markup: <tr class="group-header" data-group-key="..."><td colspan="N">
   <button class="group-toggle" aria-expanded="true">…</button>
   <bdi class="group-label"></bdi><span class="group-count"></span></td></tr>
   followed by the group's ordinary <tr data-rid> rows, each additionally
   carrying class="group-row". Collapsing a group sets the DOM `hidden`
   property on its rows exactly like the existing filter does (tr.hidden
   above) and toggles is-collapsed on the header - no extra CSS is needed to
   hide them, [hidden]{display:none!important} already does that, so a
   collapsed group's rows drop out of the row-number recount and totals the
   same way a filtered-out row already does. */
.data-table tr.group-header td{
  position:static;background:var(--surface-2);color:var(--ink);font-weight:750;
  border-bottom:1px solid var(--line-strong);cursor:pointer;user-select:none}
.data-table tr.group-header:hover td{background:var(--btn-hover-bg)}
.group-header .group-toggle{
  display:inline-flex;align-items:center;justify-content:center;
  width:16px;height:16px;margin-inline-end:8px;border:0;background:transparent;
  color:var(--ink);cursor:pointer;font-size:11px;line-height:1;
  transition:transform var(--fast) var(--ease)}
.group-header .group-toggle::before{content:"\25BE"}
.group-header.is-collapsed .group-toggle{transform:rotate(-90deg)}
[dir="rtl"] .group-header.is-collapsed .group-toggle{transform:rotate(90deg)}
.group-header .group-count{
  margin-inline-start:8px;color:var(--muted);font-weight:650;font-size:11px;
  font-variant-numeric:tabular-nums}
/* A grouped row keeps every state class it already had (editable/locked/
   done/zero/negative/new-row/is-deleted all clear a plain state class, so
   this rides in at the SAME zero specificity as the zebra rule above and
   never outranks them - only an otherwise-plain cell picks it up. */
.data-table tbody :where(tr.group-row) td{background:var(--brand-tint)}

/* ── Pinning: pin.pin / pin.unpin ─────────────────────────────────────── */
.data-table tbody tr.is-pinned td{background:var(--brand-tint)}
.data-table tbody tr.is-pinned:hover td{background:var(--sticky-wash)}
/* Opaque restatement for the sticky/pinned columns, same idiom as the
   "opaque hover for every pinned column" block above - a translucent tint
   on a sticky cell would let the columns scrolling underneath show through. */
.data-table tbody tr.is-pinned td.rownum,
.data-table tbody tr.is-pinned td[data-field="PO"],
.data-table tbody tr.is-pinned td:first-child{background:var(--sticky-wash)}
.data-table tbody tr.is-pinned:hover td.rownum,
.data-table tbody tr.is-pinned:hover td[data-field="PO"],
.data-table tbody tr.is-pinned:hover td:first-child{background:var(--sticky-wash)}
/* The separator row between the pinned section and the rest of the grid:
   <tr class="pinned-sep"><td colspan="N"></td></tr>. */
.data-table tr.pinned-sep td{
  height:3px!important;min-height:0!important;padding:0!important;
  border:0!important;background:var(--brand-2)!important;box-shadow:none!important}

/* ── Compare: compare.title / compare.same / compare.differs ───────────
   Markup: <div class="compare-panel"><div class="compare-row"><span
   class="compare-field"></span><span></span><span></span></div>…</div>,
   each row additionally carrying compare-same or compare-differs. Reuses
   the exact green/red ink-on-soft pairs the .badge and .audit-badge rules
   above already rely on, so "differs" never depends on colour alone here
   either - the row is bold as well as red. */
.compare-panel{background:var(--surface);border:1px solid var(--line-strong);
  border-radius:var(--radius);box-shadow:var(--shadow);padding:14px;
  display:grid;gap:8px}
.compare-panel h3{margin:0;font-size:14px}
.compare-row{display:grid;grid-template-columns:minmax(100px,160px) 1fr 1fr;
  gap:10px;align-items:center;padding:7px 9px;border-radius:7px;font-size:12px}
.compare-field{color:var(--muted);font-weight:650}
.compare-row.compare-same{background:var(--green-soft);color:var(--green)}
.compare-row.compare-differs{background:var(--red-soft);color:var(--red);font-weight:750}
.compare-empty{color:var(--muted);text-align:center;padding:20px;font-size:12px}
.compare-clear{justify-self:end}
@media(max-width:760px){.compare-row{grid-template-columns:1fr;gap:2px}}

/* ── Saved views: views.save / views.saved / views.apply / views.delete ─
   Markup: <div class="saved-views"><button class="btn">…</button><div
   class="saved-views-menu"><div class="sv-item"><span></span><span
   class="sv-del">✕</span></div>…<div class="sv-empty"></div></div></div>. */
.saved-views{position:relative;display:inline-block}
.saved-views-menu{position:absolute;top:calc(100% + 6px);inset-inline-start:0;
  min-width:220px;max-height:320px;overflow:auto;background:var(--surface);
  border:1px solid var(--line-strong);border-radius:var(--r-sm);
  box-shadow:var(--shadow-lift);z-index:40;padding:6px;display:grid;gap:2px}
.saved-views-menu .sv-item{display:flex;align-items:center;justify-content:space-between;
  gap:8px;padding:7px 9px;border-radius:6px;font-size:12px;color:var(--ink);cursor:pointer}
.saved-views-menu .sv-item:hover{background:var(--btn-hover-bg)}
.saved-views-menu .sv-del{opacity:.6;color:var(--red);font-weight:800;
  cursor:pointer;padding:2px 5px;border-radius:5px}
.saved-views-menu .sv-del:hover{opacity:1;background:var(--red-soft)}
.saved-views-menu .sv-empty{padding:14px;color:var(--muted);font-size:11.5px;text-align:center}

/* ── Low stock: lowstock.chip / lowstock.badge ───────────────────────────
   .lowstock-chip composes with the existing .chip; .lowstock-badge composes
   with the existing .badge.warning (see templates/stock.html), so both
   already carry an audited pair - the rules below only add the marker glyph
   and the "on" state, which reuses --warn-fill/--on-fill exactly like every
   other solid-fill toggle (.chip.is-on, .btn.primary) in this file. */
.chip.lowstock-chip{border-color:var(--amber)}
.chip.lowstock-chip::before{content:"\25BC";margin-inline-end:5px;font-size:8px;opacity:.85}
.chip.lowstock-chip.is-on{background:var(--warn-fill);border-color:var(--warn-fill);color:var(--on-fill)}
.badge.lowstock-badge::before{content:"\25BC";margin-inline-end:4px;font-size:8px}

/* ── Trend: trend.title / trend.weeks / trend.consumed ─────────────────
   A plain inline SVG or a <table> fallback, either wrapped in .trend-panel.
   Axis/gridlines are non-text UI (WCAG 1.4.11, 3:1) against --surface -
   --line-strong on --surface clears 3:1 in every theme (it is the same
   token used for the panel's own border above); the trend line itself uses
   --brand-2, whose measured contrast against --surface is 4.24-8.63:1
   across all seven themes, so the line is never the only way to read it -
   the table fallback carries the same numbers as text. */
.trend-panel{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow);padding:14px;
  display:grid;gap:10px}
.trend-panel h3{margin:0;font-size:14px;color:var(--ink)}
.trend-panel .trend-empty{color:var(--muted);text-align:center;padding:24px;font-size:12px}
.trend-panel svg{width:100%;height:auto;display:block;overflow:visible}
.trend-panel .trend-axis{stroke:var(--line-strong);stroke-width:1}
.trend-panel .trend-gridline{stroke:var(--line);stroke-width:1}
.trend-panel .trend-line{fill:none;stroke:var(--brand-2);stroke-width:2}
.trend-panel .trend-point{fill:var(--brand-fill);stroke:var(--surface);stroke-width:1.5}
.trend-panel .trend-axis-label{fill:var(--muted);font-size:10px}
.trend-panel table{width:100%;border-collapse:collapse;font-size:11px}
.trend-panel table th,.trend-panel table td{padding:5px 7px;border-bottom:1px solid var(--line);
  text-align:end;font-variant-numeric:tabular-nums}
.trend-panel table td:first-child,.trend-panel table th:first-child{text-align:start}
.trend-panel table th{background:var(--surface-2);color:var(--muted);
  text-transform:uppercase;font-size:10px;font-weight:750}

/* ── Export: export.button / export.xlsx / export.csv / export.rows ───── */
.export-menu{position:relative;display:inline-block}
.export-menu-panel{position:absolute;top:calc(100% + 6px);inset-inline-end:0;
  min-width:200px;background:var(--surface);border:1px solid var(--line-strong);
  border-radius:var(--r-sm);box-shadow:var(--shadow-lift);z-index:40;
  padding:6px;display:grid;gap:2px}
.export-menu-panel button{display:flex;align-items:center;gap:8px;width:100%;
  padding:8px 10px;border:0;background:transparent;color:var(--ink);
  font-size:12px;font-weight:650;border-radius:6px;cursor:pointer;text-align:start}
.export-menu-panel button:hover{background:var(--btn-hover-bg)}
.export-menu-count{margin-inline-start:auto;color:var(--muted);font-size:10.5px;
  font-variant-numeric:tabular-nums}

/* ═══════════════════════════════════════════════════════════════════════
   WAVE 5: tablet/phone cards, touch targets, kiosk, reduced transparency,
   and the styling contract for grid-assist.js's new UI (presence, resume
   bar, empty state, coach marks, scan input, bulk paste, handover notes).
   No new colour is invented anywhere below - every rule reuses a token
   this file has already measured (see the per-block notes), so nothing
   here can regress an audited pair and nothing new needs measuring per
   theme. The contrast table for the states that ARE new (soft-token
   pairs applied to a new shape) is pasted in the wave notes.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Touch targets: body.touch-targets, and unconditionally under a coarse
   pointer (a touch screen never fires hover, so the preference should not
   be required to get a usable hit area there). 44px is the WCAG 2.5.5 /
   iOS HIG floor. Spacing grows with it so adjacent 44px targets do not
   collide. */
@media(pointer:coarse){
  .btn,.chip,.input,input[type=text],input[type=password],input[type=number],
  input[type=date],select,.app-nav a,.check,.wf-scan-input{min-height:44px}
  .btn,.chip{padding-block:11px}
  .app-nav a{padding-block:12px}
  .data-table td.rownum .history-handle,.revert-btn,.saved-views-menu .sv-del,
  .export-menu-panel button{min-height:38px}
  .toolbar,.filter-bar,.page-actions,.chips{gap:10px}
  .data-table td.editable,.data-table td.qty-cell.editable{min-height:44px}
}
body.touch-targets .btn,body.touch-targets .chip,body.touch-targets .input,
body.touch-targets input[type=text],body.touch-targets input[type=password],
body.touch-targets input[type=number],body.touch-targets input[type=date],
body.touch-targets select,body.touch-targets .app-nav a,body.touch-targets .check,
body.touch-targets .wf-scan-input{min-height:44px}
body.touch-targets .btn,body.touch-targets .chip{padding-block:11px}
body.touch-targets .app-nav a{padding-block:12px}
body.touch-targets .toolbar,body.touch-targets .filter-bar,
body.touch-targets .page-actions,body.touch-targets .chips{gap:10px}
body.touch-targets .data-table td.editable,body.touch-targets .data-table td.qty-cell.editable{min-height:44px}

/* ── Kiosk: body.kiosk. A wall screen shows the grid and nothing else - no
   header to tap, no toolbar to misclick from across the room. The header
   is fixed and body padding-top compensates for its height; both collapse
   together so nothing leaves a blank strip behind. */
body.kiosk{padding-top:0}
body.kiosk .app-header,body.kiosk .app-nav,body.kiosk .toolbar,
body.kiosk .filter-bar,body.kiosk .page-head,body.kiosk .chip-legend,
body.kiosk .skip-link,body.kiosk .flash-stack,body.kiosk .toast-region{display:none!important}
body.kiosk .app-main{padding:0}
body.kiosk .panel{border:0;border-radius:0;box-shadow:none;margin:0}
body.kiosk .data-wrap{max-height:100vh}
/* The one way back out has to survive kiosk mode itself - a button the
   assist module places, styled small and out of the way of the data. */
body.kiosk .wf-kiosk-exit{position:fixed;inset-block-end:10px;inset-inline-end:10px;z-index:70;
  opacity:.55}

/* ── Reduced transparency: body.reduce-transparency. Swap every rgba()/
   backdrop-filter surface in the file for its solid counterpart - the
   @supports block above is the only place blur is added, and --card is
   the only rgba() surface fill, so both are named exhaustively here. */
body.reduce-transparency .panel,body.reduce-transparency .kpi{
  background:var(--card-solid)!important;-webkit-backdrop-filter:none!important;
  backdrop-filter:none!important}
body.reduce-transparency .conflict-backdrop{background:var(--ink)!important;opacity:.85}
body.reduce-transparency .login-brand:after{display:none}

/* ── Presence: presence.here / presence.editing / presence.nobody /
   presence.hint. Markup: <span class="wf-presence-dot" data-state="here|
   editing|nobody" title="…"></span>, one per row or field, plus an
   optional <span class="wf-presence-list"> of avatars/initials beside the
   toolbar. Same shape as the existing .save-dot / .role-dot so it reads
   as the same design language, not a bolted-on one. */
/* A textless graphic needs 3:1 (WCAG 1.4.11). --role-dot measured 2.34:1 on the
   light default, so the one signal telling you a colleague has this row open was
   effectively invisible. The outer ring in --ink-2 is a text-grade token, so the
   dot now reads on any surface whatever its fill happens to be. */
.wf-presence-dot{display:inline-flex;align-items:center;justify-content:center;
  width:9px;height:9px;border-radius:50%;background:var(--ink-2);
  box-shadow:0 0 0 2px var(--surface),0 0 0 3px var(--ink-2);flex:none;vertical-align:middle}
.wf-presence-dot[data-state="here"]{background:var(--pos)}
.wf-presence-dot[data-state="editing"]{background:var(--warn);
  animation:wf-save-pulse 1.1s ease-in-out infinite}
@media(prefers-reduced-motion:reduce){.wf-presence-dot[data-state="editing"]{animation:none}}
.wf-presence-dot[data-state="nobody"]{background:transparent;box-shadow:inset 0 0 0 1px var(--line-strong)}
.wf-presence-status{display:inline-flex;align-items:center;gap:5px;padding:2px 9px;
  border-radius:999px;background:var(--surface-2);border:1px solid var(--line);
  color:var(--ink);font-size:10.5px;font-weight:700;white-space:nowrap}

/* ── Resume bar: resume.title / resume.restore / resume.dismiss. Markup:
   <div class="wf-resume-bar"><div class="wf-resume-text"><strong></strong>
   <span></span></div><div class="wf-resume-actions"><button class="btn
   primary">…</button><button class="btn">…</button></div></div>, inserted
   above the grid. Same left-accent-bar idiom as .conflict-current /
   .import-preview .discard-note - amber ink-on-soft, already audited. */
.wf-resume-bar{display:flex;align-items:center;justify-content:space-between;
  gap:14px;flex-wrap:wrap;padding:10px 14px;margin-bottom:10px;
  background:var(--warn-soft);border:1px solid var(--amber);
  border-inline-start:4px solid var(--amber);border-radius:var(--r-sm);
  color:var(--amber)}
.wf-resume-msg{display:flex;flex-direction:column;gap:2px;min-width:0}
.wf-resume-msg strong{font-size:12.5px;color:var(--amber)}
.wf-resume-msg span{font-size:11px;color:var(--ink-2)}

/* ── Empty state: empty.none_match / empty.because / empty.clear_this /
   empty.clear_all. Extends the existing bare .empty-state (a plain
   "nothing here" message) with the WHY and a way out - filters that hide
   every row are a dead end otherwise. Markup: <div class="empty-state
   wf-empty-state"><strong></strong><p class="wf-empty-because"></p>
   <div class="wf-empty-actions"><button class="chip">clear this
   filter</button>…<button class="btn">clear all</button></div></div>. */
.wf-empty-detail{padding:34px 20px}
.wf-empty-because{margin:2px 0 12px;color:var(--muted);font-size:12px}
.wf-empty-because b{color:var(--ink);font-weight:750}
.wf-empty-chips{display:flex;flex-wrap:wrap;gap:7px;justify-content:center}

/* ── Coach marks: coach.title / coach.next / coach.done / coach.skip /
   coach.step.*. A short guided tour over the real UI: a dimmed scrim (the
   same --scrim token as the 409 conflict dialog) with one element cut out
   via box-shadow spread instead of a hole, and a small callout card
   pointing at it. Markup: <div class="wf-coach-scrim"></div><div
   class="wf-coach-spot" style="top:…;left:…;width:…;height:…"></div>
   <div class="wf-coach-card"><span class="wf-coach-step"></span><h3
   class="wf-coach-title"></h3><p></p><div class="wf-coach-actions">
   <button class="btn" data-coach-skip>…</button><button class="btn
   primary" data-coach-next>…</button></div></div>. */
.wf-coach-overlay{position:fixed;inset:0;background:var(--scrim);z-index:160}
.wf-coach-target{position:fixed;z-index:161;border-radius:8px;pointer-events:none;
  box-shadow:0 0 0 4px var(--brand-fill),0 0 0 9999px var(--scrim)}
.wf-coach-box{position:fixed;z-index:162;width:min(300px,90vw);
  background:var(--surface);border:1px solid var(--line-strong);
  border-radius:var(--radius);box-shadow:var(--shadow-lift);padding:16px;
  display:grid;gap:8px}
.wf-coach-body{font-size:10px;font-weight:800;text-transform:uppercase;
  letter-spacing:.06em;color:var(--muted)}
.wf-coach-title{margin:0;font-size:14.5px;color:var(--ink)}
.wf-coach-box p{margin:0;color:var(--ink-2);font-size:12.5px;line-height:1.5}
.wf-coach-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
.wf-coach-actions [data-coach-skip]{margin-inline-end:auto}

/* ── Scan input: scan.title / scan.hint / scan.not_found / scan.found. A
   barcode/PO scanner drops its input into a text field fast - the field
   just needs to be easy to hit and to say clearly when it did or did not
   match. Reuses .input's own border/background; only the accent border
   and the found/not-found states are new, both existing badge pairs. */
.wf-scan-input{position:relative;display:flex;align-items:center}
.wf-scan-input input{border-inline-start:3px solid var(--brand-2);padding-inline-start:32px}
.wf-scan-input::before{content:"";position:absolute;inset-inline-start:9px;
  width:14px;height:14px;border-radius:3px;border:2px solid var(--brand-2);pointer-events:none}
.wf-scan-input[data-state="found"] input{border-color:var(--green);box-shadow:0 0 0 3px var(--green-soft)}
.wf-scan-input[data-state="not_found"] input{border-color:var(--red);box-shadow:0 0 0 3px var(--red-soft)}
/* A soft-chip background, not bare coloured text: the caller may drop this
   directly on the page canvas (--bg), and --red as plain text measured
   4.43:1 there in the light theme - just under 4.5. Filled like .badge
   (an already-audited pair against ITS OWN background) removes the
   dependency on whatever surface happens to be behind it. */
.wf-scan-result{display:inline-block;margin-top:5px;padding:2px 9px;
  border-radius:999px;font-size:11px;font-weight:700}
.wf-scan-result[data-state="found"]{background:var(--green-soft);color:var(--green)}
.wf-scan-result[data-state="not_found"]{background:var(--red-soft);color:var(--red)}

/* ── Bulk paste: bulk.title / bulk.hint / bulk.parse / bulk.preview /
   bulk.apply / bulk.cancel / bulk.rows / bulk.bad_line / bulk.done. Paste
   many rows of tab/comma-separated text, see a parsed preview, then apply.
   The preview table reuses .import-preview's own look; a bad line gets the
   same "will not apply" treatment .import-preview td.will-replace uses. */
.wf-bulk-body{display:grid;gap:10px}
.wf-bulk-text{width:100%;min-height:120px;padding:9px 10px;border:1px solid var(--line-strong);
  border-radius:7px;background:var(--input-bg);color:var(--ink);font:inherit;
  font-family:"SFMono-Regular",Consolas,monospace;font-size:12px;resize:vertical}
.wf-bulk-text:focus{outline:none;border-color:var(--focus-line);box-shadow:0 0 0 3px var(--focus-ring)}
.wf-bulk-count{font-size:11px;color:var(--muted)}
.wf-bulk-count b{color:var(--ink)}
.wf-bulk-preview{width:100%;border-collapse:collapse;font-size:11.5px}
.wf-bulk-preview th,.wf-bulk-preview td{padding:6px 8px;border-bottom:1px solid var(--line);text-align:start}
.wf-bulk-preview th{background:var(--surface-2);color:var(--muted);font-size:10px;text-transform:uppercase}
.wf-bulk-preview tr.wf-bulk-preview td{background:var(--bad-soft);color:var(--red)}
.wf-bulk-preview tr.wf-bulk-preview td:first-child{border-inline-start:3px solid var(--red)}
.wf-bulk-actions{display:flex;justify-content:flex-end;gap:8px}

/* ── Handover notes: handover.title / handover.write / handover.save /
   handover.empty / handover.delete / handover.by / handover.hint. A shift
   log per department on the dashboard - short notes, newest first, each
   attributable and removable by its author or an admin. Markup: <div
   class="handover-note"><div class="handover-note-head"><bdi
   class="handover-note-author"></bdi><span
   class="handover-note-ts"></span><button class="handover-note-del"
   title="…">✕</button></div><p class="handover-note-body"></p></div>. */
.handover-note{padding:9px 11px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface-2)}
.handover-note+.handover-note{margin-top:7px}
.handover-note-head{display:flex;align-items:center;gap:8px;margin-bottom:4px}
.handover-note-author{font-size:11.5px;font-weight:750;color:var(--ink)}
.handover-note-ts{font-size:10px;color:var(--muted);margin-inline-end:auto}
.handover-note-del{border:0;background:transparent;color:var(--red);opacity:.6;
  cursor:pointer;font-weight:800;padding:2px 5px;border-radius:5px;flex:none}
.handover-note-del:hover{opacity:1;background:var(--red-soft)}
.handover-note-body{margin:0;font-size:12px;color:var(--ink-2);line-height:1.5;white-space:pre-wrap}

/* ── Numerals: assist.numerals / assist.numerals_hint. body[data-numerals=
   "arabic"] switches the RENDERED digits to Arabic-Indic elsewhere (JS/
   template concern - CSS cannot substitute glyphs); this file's part is
   making sure the digits still line up and still use the Arabic type
   family they were designed against, everywhere the app already sets
   tabular-nums for numbers. */
body[data-numerals="arabic"] .data-table td,body[data-numerals="arabic"] .kpi strong,
body[data-numerals="arabic"] .metric strong{font-family:var(--fa),var(--fs)}

/* ═══ Tablet/phone card layout ═══════════════════════════════════════════
   Below 900px the 15-column master grid and the 11-column stock grid are
   unusable as a table - the operator would be scrolling sideways on a
   tablet held in one hand. Below that width every row becomes a card: the
   header row is visually hidden (not removed - a screen reader still gets
   it) and each cell prints its own label via generated content keyed off
   data-field, so this works without any other lane adding a data-label
   attribute anywhere. Labels are bilingual by :lang(), matching the
   Arabic-default/English-LTR split the app already has, and fall back to
   the raw field key via attr() for any field this file has not named yet
   (a future column shows *something* readable instead of a blank line). */
@media(max-width:900px){
  .data-wrap{max-height:none;overflow:visible;background:transparent}
  .data-table{width:100%!important;min-width:0!important;table-layout:auto!important;white-space:normal}
  /* The server renders per-column pixel widths INLINE on every th and td (they
     carry the operator's saved column widths). An inline style outranks any
     stylesheet rule, so without !important the card layout kept a 600px column
     and clipped ~250px off every row - invisibly, because .panel hides the
     overflow and the page itself never gains a scrollbar. stock.html already
     had a scoped version of this; the production grid had none. */
  .data-table th[style],.data-table td[style]{
    width:auto!important;min-width:0!important;max-width:none!important}
  .data-table thead{position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
  .data-table tbody{display:grid;gap:10px}
  .data-table tbody tr{display:grid;grid-template-columns:1fr 1fr;
    gap:2px 12px;padding:10px 12px;border:1px solid var(--line);
    border-radius:var(--r-sm);background:var(--surface);box-shadow:var(--shadow)}
  .data-table tbody :where(tr:nth-child(even)) td,
  .data-table tbody tr:hover td,.data-table tbody tr:hover td[data-field="PO"],
  .data-table tbody tr:hover td.rownum{background:transparent}
  .data-table tbody tr.is-pinned,.data-table tbody tr.group-row{background:var(--brand-tint)}
  .data-table tr.group-header td{grid-column:1/-1}
  .data-table tr.pinned-sep{display:none}
  .data-table td{position:static!important;display:flex;flex-direction:column;
    gap:1px;height:auto!important;min-height:32px;padding:4px 2px!important;
    border:0!important;box-shadow:none!important;background:transparent!important;
    overflow:visible;text-overflow:clip;font-size:12.5px}
  /* The column name used to print above EVERY cell of every card - fifteen
     repetitions per row, 360 rows. Removed at the operator's request. Only
     the bare quantities keep a label below, because two unexplained numbers
     side by side are genuinely ambiguous without the header row. */
  .data-table td[data-field]::before{content:none}
  /* The row number moves into a corner badge instead of its own card row -
     it is how you find the row in the audit log, not a field of the row. */
  .data-table td.rownum{grid-column:1/-1;flex-direction:row;align-items:center;
    gap:8px;min-height:0;padding-bottom:6px!important;
    border-bottom:1px solid var(--line)!important;order:-1}
  /* Card-width fields: long text (material name, chip values) and the
     locked reference columns get the full two-column width so they are
     not squeezed into half a card. */
  .data-table td[data-field="material_name"],.data-table td[data-field="name"],
  .data-table td.wf-action-cell,.data-table td.projected-cell,
  #grid tbody tr td:nth-last-child(3),#grid tbody tr td:nth-last-child(2),
  #stock-grid tbody tr td.projected-cell+td{grid-column:1/-1}
  .data-table td.wf-action-cell{flex-direction:row;flex-wrap:wrap;gap:8px;
    padding-top:8px!important;margin-top:4px;border-top:1px solid var(--line)!important}
  .data-table td.wf-action-cell::before{display:none}
  #new-row td{grid-column:1/-1}
  #new-row td::before{display:none}
  /* Column resize is a mouse/desktop affordance with nothing to grip on a
     card row. */
  .resize-handle{display:none}
}

/* Field labels, English default. */
#grid tbody tr td:nth-last-child(3)::before{content:"Stock Name"}
#grid tbody tr td:nth-last-child(2)::before{content:"Stock Qty"}
.data-table td.projected-cell::before{content:"Projected"}
#stock-grid tbody tr td.projected-cell+td::before{content:"Qty Source"}

/* Field labels, Arabic - overrides the English set above under :lang(ar),
   the same mechanism base.html already drives dir/theme from. */

/* ── Tighten further below 600px: phone, one column, larger tap surface
   on every per-row control (delete, confirm) since there is no mouse. */
@media(max-width:600px){
  .data-table tbody tr{grid-template-columns:1fr;padding:9px 10px}
  .data-table td[data-field]{grid-column:1/-1}
  .app-main{padding:10px}
  .kpi-grid{grid-template-columns:1fr 1fr}
  .wf-coach-box{width:calc(100vw - 24px);inset-inline:12px!important}
  .wf-resume-bar{flex-direction:column;align-items:stretch}
}

/* ═══ Production-order pack ═══════════════════════════════════════════════
   order.new / order.header / order.lines / order.add_line / order.remove_line
   / order.save / order.saved / order.bad_line / order.repeat /
   order.repeat_hint / order.repeated / order.card_title / order.progress /
   order.open / order.done. No new colour tokens: every pair below is one
   already measured elsewhere in this file (--ink on --surface-2 is the
   .panel-head pair; --green/--green-soft and --amber/--amber-soft are the
   .badge/.health-item pairs) - see the contrast table in the accompanying
   review note for the re-verified numbers.
   Markup: <form class="order-form"><div class="order-lines"><div
   class="order-line">…<button class="btn danger order-line-remove"></div>
   </div><button class="btn order-line-add"></button></form>, and
   <div class="po-card"><div class="po-card-head">…<span class="po-status">
   </span></div><div class="po-progress"><div class="po-progress-track">
   <div class="po-progress-fill"></div></div><span class="po-progress-label">
   </span></div></div>. */
.order-form{display:flex;flex-direction:column;gap:12px}
.order-header-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
.order-lines{display:flex;flex-direction:column;gap:8px}
.order-line{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr)) auto;
  gap:8px;align-items:end;padding:10px;border:1px solid var(--line);
  border-radius:var(--r-sm);background:var(--surface-2)}
/* A line missing a required value gets the same start-border-plus-tint signal
   the new-row "required" fields already use, so it never depends on colour
   alone to say which line is bad. */
.order-line.is-invalid{border-inline-start:3px solid var(--warn-fill);background:var(--warn-soft)}
.order-line-remove{flex:none;align-self:center}
.order-line-add{align-self:flex-start}
.order-lines-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}

.po-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:16px;display:flex;flex-direction:column;gap:12px}
.po-card-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
.po-card-head h2,.po-card-head h3{margin:0;font-size:15px}
/* "open"/"done" reuse the audited badge pairs (amber/amber-soft, green/
   green-soft) so a third glance-able state never needed a new pair. */
.po-status{display:inline-flex;align-items:center;gap:5px;border-radius:var(--r-pill);
  padding:2px 10px;font-size:10.5px;font-weight:750;white-space:nowrap}
.po-status.is-open{background:var(--amber-soft);color:var(--amber)}
.po-status.is-done{background:var(--green-soft);color:var(--green)}
.po-status.is-done::before{content:"\2713";font-weight:900}
.po-progress{display:flex;align-items:center;gap:10px}
.po-progress-track{flex:1;height:8px;border-radius:999px;background:var(--surface-2);
  overflow:hidden;box-shadow:inset 0 0 0 1px var(--line)}
.po-progress-fill{height:100%;background:var(--brand-fill);border-radius:999px;
  transition:width var(--med) var(--ease)}
.po-card[data-status="done"] .po-progress-fill{background:var(--green)}
.po-progress-label{flex:none;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}

/* ═══ Board (kanban) pack ═══════════════════════════════════════════════
   board.title / board.active / board.done / board.hint / board.move.
   Markup: <div class="board"><div class="board-col" data-col="active">
   <h3></h3><div class="board-card" draggable="true">…</div></div>
   <div class="board-col" data-col="done">…</div></div>. A card's column is
   already named by its position and the column header text, so "done" adds
   only a start-border accent, never a fill swap that would fight the card's
   own state colours (zero/negative/short) underneath it. */
.board{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px}
.board-col{background:var(--surface-2);border:1px solid var(--line);
  border-radius:var(--radius);padding:10px;display:flex;flex-direction:column;gap:8px;
  min-height:120px}
.board-col h3{margin:0 0 2px;font-size:11px;font-weight:750;color:var(--muted);
  text-transform:uppercase;letter-spacing:.04em;display:flex;align-items:center;
  justify-content:space-between;gap:8px}
.board-col h3 .board-count{font-variant-numeric:tabular-nums;color:var(--ink-3)}
.board-card{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-sm);padding:10px;box-shadow:var(--shadow);cursor:grab;
  display:flex;flex-direction:column;gap:5px;font-size:12px}
.board-card:active{cursor:grabbing}
.board-card.is-done{border-inline-start:3px solid var(--green)}
.board-card-title{font-weight:700}
.board-card-meta{color:var(--muted);font-size:10.5px}
.board-col.is-drop-target{box-shadow:inset 0 0 0 2px var(--brand-2)}

/* ═══ Demand / short pack ═════════════════════════════════════════════════
   demand.title / demand.empty / demand.sheets / demand.hint, remaining.col /
   remaining.hint, short.chip / short.hint / short.none, cancut.ok /
   cancut.short / cancut.unknown / cancut.hint. "Short" (actual cut below
   required, or stock below remaining demand) never relies on the red fill
   alone: the chip carries a down-arrow glyph and the table row also gets a
   start-border, mirroring how .data-table tr[data-zero] already signals out
   of stock (box-shadow inset + colour, never colour on its own). */
.demand-panel{display:flex;flex-direction:column;gap:10px}
.demand-panel table{width:100%;border-collapse:collapse;font-size:12px}
.demand-panel th,.demand-panel td{padding:8px 10px;border-bottom:1px solid var(--line);
  text-align:center}
.demand-panel th{background:var(--surface-2);color:var(--muted);font-size:10px;
  text-transform:uppercase;text-align:center}
.demand-panel th[data-field="material_type"],.demand-panel td[data-field="material_type"]{text-align:start}
.demand-panel tr.is-short td{background:var(--bad-soft);box-shadow:inset 3px 0 0 var(--red)}
[dir="rtl"] .demand-panel tr.is-short td{box-shadow:inset -3px 0 0 var(--red)}
.short-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 9px;
  border-radius:999px;background:var(--red-soft);color:var(--red);font-size:10.5px;
  font-weight:800;white-space:nowrap}
.short-chip::before{content:"\25BC";font-size:7px}
.short-none{color:var(--muted);font-size:11px}
/* Can-cut readout: ok/short/unknown, the same three-state shape as the health
   panel (green / red / neutral), never a fourth colour invented for it. */
.cancut{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700}
.cancut.is-ok{color:var(--green)}.cancut.is-ok::before{content:"\2713"}
.cancut.is-short{color:var(--red)}.cancut.is-short::before{content:"\2715"}
.cancut.is-unknown{color:var(--muted)}.cancut.is-unknown::before{content:"?"}

/* ═══ Deductions pack ═════════════════════════════════════════════════════
   deduct.title / deduct.pending / deduct.why / deduct.what_next / deduct.none
   / deduct.hint / deduct.posted / deduct.awaiting. Markup: <div
   class="deduct-panel"><div class="deduct-row"><div class="deduct-main">
   <strong></strong><span class="why"></span></div><span class="badge
   warning">deduct.awaiting</span></div></div> - the pending/posted states
   reuse the already-audited .badge.warning (amber/amber-soft) and
   .badge.warehouse (green/green-soft) pairs rather than inventing new ones. */
.deduct-panel{display:flex;flex-direction:column}
.deduct-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:10px 12px;border-bottom:1px solid var(--line);font-size:12px}
.deduct-row:last-child{border-bottom:0}
.deduct-main{display:flex;flex-direction:column;gap:2px;min-width:0}
.deduct-main strong{font-size:12.5px}
.deduct-row .why{color:var(--muted);font-size:11px}
.deduct-empty{padding:20px;text-align:center;color:var(--muted);font-size:12px}

/* ═══ Burn-down pack ══════════════════════════════════════════════════════
   burn.title / burn.weeks_left / burn.never / burn.hint. weeks_left <= 2
   reuses the red/red-soft pair (critical), <= 6 reuses amber/amber-soft
   (watch), anything else and "never" (null - nothing consumed yet) stay
   plain ink/muted - the same three-tier shape as .cancut above. */
.burn-panel table{width:100%;border-collapse:collapse;font-size:12px}
.burn-panel th,.burn-panel td{padding:8px 10px;border-bottom:1px solid var(--line);text-align:center}
.burn-panel th{background:var(--surface-2);color:var(--muted);font-size:10px;text-transform:uppercase}
.burn-panel th[data-field="name"],.burn-panel td[data-field="name"]{text-align:start}
.burn-panel td.weeks-left{font-weight:750;font-variant-numeric:tabular-nums}
.burn-panel tr.is-critical td.weeks-left{color:var(--red)}
.burn-panel tr.is-watch td.weeks-left{color:var(--amber)}
.burn-panel td.weeks-never{color:var(--muted);font-style:italic}

/* ═══ Inline warnings pack ════════════════════════════════════════════════
   warn.duplicate / warn.outlier / warn.title / warn.dismiss. Advisory only -
   it never blocks a save, so it reads as a caution, not an error: the same
   amber/amber-soft pair as .conflict-current and .health-item.is-warn,
   never the red used for an actual failure. */
.warn-inline{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;
  border-radius:999px;background:var(--amber-soft);color:var(--amber);
  font-size:11px;font-weight:700}
.warn-inline::before{content:"\26A0";font-weight:900}
.warn-list{display:flex;flex-direction:column;gap:6px}
.warn-list .warn-inline{width:fit-content;max-width:100%}
.warn-dismiss{border:0;background:transparent;color:inherit;opacity:.65;cursor:pointer;
  font-weight:900;padding:0 0 0 4px;line-height:1}
.warn-dismiss:hover{opacity:1}

/* ═══ Recent-changes pack ═════════════════════════════════════════════════
   recent.title / recent.empty / recent.revert / recent.reverted /
   recent.hint. Revert reuses the existing .revert-btn as-is (already
   audited, see the row-audit pack above) rather than a second control. */
.recent-panel{display:flex;flex-direction:column}
.recent-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:9px 12px;border-bottom:1px solid var(--line);font-size:12px}
.recent-row:last-child{border-bottom:0}
.recent-row-main{display:flex;flex-direction:column;gap:2px;min-width:0}
.recent-row-main .field-label{color:var(--muted);font-size:10.5px}
.recent-row-change{font-variant-numeric:tabular-nums}
.recent-row-change .old-value{color:var(--muted);text-decoration:line-through}
.recent-row-change .arrow{color:var(--ink-3);margin:0 4px}
/* Already reverted: same dimmed/struck treatment as a soft-deleted row,
   never colour alone (opacity + strike + the revert button disabled). */
.recent-row.is-reverted{opacity:.65}
.recent-row.is-reverted .recent-row-change{text-decoration:line-through}
.recent-empty{padding:20px;text-align:center;color:var(--muted);font-size:12px}

/* ═══ Focus mode ══════════════════════════════════════════════════════════
   focus.on / focus.off / focus.hint. grid-production.js toggles this class
   on individual th/td (from opts.focus_fields, PREF_KEYS "focus_fields") to
   narrow the grid down to the columns a operator is actively filling in.
   Scoped to .data-table cells only, like every other single-purpose state
   class in this file (.editable/.locked/.wh/…), so the name can never
   collide with an unrelated element elsewhere. */
.data-table th.focus-hidden,.data-table td.focus-hidden{display:none}

/* ═══ Weekly/operator report pack ═════════════════════════════════════════
   report.weekly / report.operators / report.title / report.hint /
   report.none / report.created / report.updated / report.confirmed. Same
   shape as .simple-table (already audited: --muted on --surface-2 header),
   with a totals row that reads through weight and a top border, not a
   colour swap. */
.report-table{width:100%;border-collapse:collapse;font-size:12px}
.report-table th,.report-table td{padding:9px 10px;border-bottom:1px solid var(--line);
  text-align:center}
.report-table th{background:var(--surface-2);color:var(--muted);font-size:10px;
  text-transform:uppercase}
.report-table th[data-field="username"],.report-table td[data-field="username"]{text-align:start}
.report-table tfoot td{font-weight:800;border-top:2px solid var(--line-strong);
  border-bottom:0;background:var(--surface-2)}
.report-hint{color:var(--muted);font-size:11px;margin:0}

/* ═══ Calendar preference (base.html header control) ═════════════════════
   calendar.label / calendar.gregorian / calendar.both / calendar.hint. Same
   two-pill segmented shape as .lang-toggle/.lang-opt, reusing that pair's
   already-audited on-fill/brand-fill selected state so a second brand pair
   never needed measuring. Full styling (including the on-dark header
   variant) lives in base.html's own <style> block next to .theme-select and
   .view-menu, which this file's own header comment already keeps separate
   from app.css for anything that belongs to base.html alone. */
.cal-toggle{display:flex;align-items:center;gap:2px;padding:2px;border-radius:999px}
.cal-opt{padding:3px 9px;border-radius:999px;font-size:10px;font-weight:800;
  letter-spacing:.02em;border:0;background:transparent;cursor:pointer;line-height:1.4}
.cal-opt.is-on{background:var(--brand-fill);color:var(--on-fill)}

/* write-once roles: a filled cell they may not change any more */
.data-table td.final{cursor:not-allowed}
/* ::before, not ::after - grid-flow paints the stock ceiling in ::after of the cut cell */
.data-table td.final::before{content:"\1F512";font-size:.7em;opacity:.55;margin-inline-end:.3em}

/* ═══════════════════════════════════════════════════════════════════════
   WAVE 8: phones - Android Chrome, iOS Safari. Every rule lives inside a
   media query, so the desktop rendering is untouched. Two concerns:
   (1) coarse pointers: no 300 ms tap delay, no tap flash, no callout on a
       long press (wf-mobile.js turns a long press into the row menu);
   (2) narrow viewports: 16px inputs (iOS zooms on anything smaller), a nav
       that scrolls instead of wrapping into three rows, full-width filter
       controls, tables that scroll inside their wrapper, bottom sheets and
       fixed badges that respect the home-indicator safe area and never sit
       on each other (sum bar above unsaved badge above undo toast), and
       popovers that can never be wider than the screen.
   ═══════════════════════════════════════════════════════════════════════ */
@media (pointer: coarse){
  html{-webkit-tap-highlight-color:transparent}
  a,button,.btn,.chip,.input,input,select,textarea,summary,.data-table td{touch-action:manipulation}
  .data-table tr{-webkit-touch-callout:none}
}
@media (max-width: 700px){
  input,select,textarea,.input{font-size:16px!important}
  .app-nav{display:flex;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .app-nav::-webkit-scrollbar{display:none}
  .app-nav a{white-space:nowrap;flex:0 0 auto}
  .toolbar,.filter-bar,.chips{flex-wrap:wrap}
  .toolbar>.input,.filter-bar>.input,.toolbar>select,.filter-bar>select,.filter-bar label.f-date{flex:1 1 100%;min-width:0}
  .data-wrap,.panel-body{overflow-x:auto;-webkit-overflow-scrolling:touch}
  .panel table:not(.data-table){display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .wf-menu[open]>.wf-menu-panel{max-height:80dvh;overflow-y:auto;
    padding-bottom:calc(12px + env(safe-area-inset-bottom))}
  .wf-sumbar{inset-block-end:calc(64px + env(safe-area-inset-bottom))!important;max-width:calc(100vw - 16px)}
  .wfs-badge{inset-block-end:calc(120px + env(safe-area-inset-bottom))!important}
  .wfs-toast{inset-block-end:calc(10px + env(safe-area-inset-bottom))!important;max-width:calc(100vw - 16px)}
  .wfr-popover,.wfr-menu,.wfi-popover,.wf-views-menu,.wfp-pop,.wfh-pop,.wfe-pop,.wf-entry-warn{max-width:calc(100vw - 16px)!important}
  .wfi-compare-panel{max-width:calc(100vw - 12px)!important;max-height:85dvh!important}
  body.kiosk .data-wrap{max-height:100dvh}
  .page-head .metrics{overflow-x:auto;-webkit-overflow-scrolling:touch}
}

/* ── WAVE 8, part 2: the grid stays a grid on phones ──────────────────────
   Measured on a 375px viewport: the wave-5 card layout stacked every row into
   an 840px card - 367 rows made a 314,000px page - and .panel's backdrop
   filter made the panel the containing block for position:fixed, so the
   bottom-sheet menus were laid out 314,000px down: "the button opens but
   nothing shows". The warehouse's card view now lives on /confirm; here the
   table scrolls sideways inside its wrapper with the header sticky and the
   order number pinned, the way a spreadsheet behaves on a phone. Every rule
   below reverses one card rule from the 900px block above it. */
@media (max-width: 900px){
  .panel{-webkit-backdrop-filter:none!important;backdrop-filter:none!important;background:var(--card-solid)!important}
  .data-wrap{max-height:calc(100dvh - 230px)!important;overflow:auto!important;-webkit-overflow-scrolling:touch;background:var(--grid-bg)}
  .data-table{width:max-content!important;min-width:100%!important;table-layout:auto!important;white-space:nowrap!important;font-size:12px}
  .data-table thead{position:static!important;width:auto!important;height:auto!important;overflow:visible!important;
    clip:auto!important;clip-path:none!important}
  .data-table thead th{position:sticky!important;top:0;z-index:4}
  .data-table tbody{display:table-row-group!important;gap:0}
  .data-table tbody tr{display:table-row!important;padding:0!important;border:0!important;border-radius:0!important;
    background:transparent!important;box-shadow:none!important}
  .data-table tr.pinned-sep{display:table-row}
  .data-table th,.data-table td{display:table-cell!important;height:34px!important;min-height:0!important;
    padding:5px 8px!important;border-inline-end:1px solid var(--line)!important;border-bottom:1px solid var(--line)!important;
    overflow:hidden!important;text-overflow:ellipsis!important;background:var(--grid-bg)!important;font-size:12px;
    vertical-align:middle!important;position:static!important;width:auto!important;min-width:0!important;max-width:220px!important}
  .data-table td.editable{background:var(--cell-editable)!important}
  .data-table td.locked{background:var(--cell-locked)!important}
  .data-table tr:hover td{background:var(--brand-tint)!important}
  .data-table td.rownum{order:0;padding-bottom:5px!important;flex-direction:row}
  .data-table td.wf-action-cell{padding-top:5px!important;margin-top:0!important;border-top:0!important}
  .data-table td[data-field],#new-row td,.data-table td.projected-cell,.data-table td[data-field="material_name"]{grid-column:auto}
  #new-row td{display:table-cell!important}
  #new-row input{min-width:96px}
  /* the order number stays put while the row scrolls sideways */
  .data-table td[data-field="PO"],.data-table th[data-field="PO"]{position:sticky!important;inset-inline-start:0;z-index:3;
    box-shadow:1px 0 0 var(--line-strong)}
  .data-table thead th[data-field="PO"]{z-index:5}
  .resize-handle{display:none}
}

/* no column name inside ANY cell - the operator's standing rule. The four
   labels below were written for cells that carry no data-field (stock name,
   stock quantity, projected, quantity source), so the earlier content:none on
   td[data-field] never reached them and they kept printing inside the cell. */
#grid tbody tr td:nth-last-child(3)::before,#grid tbody tr td:nth-last-child(2)::before,
.data-table td.projected-cell::before,#stock-grid tbody tr td.projected-cell+td::before{content:none!important}
