/* XWARES — brand tokens, applied to base templates.
   Source: design/01-architecture.md §2 and the user-supplied brand-guide.html
   Two-mode ready: dark default, light variant via [data-theme="light"],
   switched by static/js/theme-toggle.js.
*/
/* --- Plus Jakarta Sans (v2.9.11) -------------------------------------
   The UI face, replacing Inter (which was bundled but never declared, so
   the app had been rendering in system-ui). Variable 400-700, split the
   way Google Fonts ships it so a Latin page loads one 27 KB file. Declared
   here rather than in vendor/css/fonts.css because installer/build.ps1
   rewrites that file. OFL: vendor/fonts/PlusJakartaSans-OFL.txt. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../vendor/fonts/PlusJakartaSans-var-latin.7660bd9909fb.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../vendor/fonts/PlusJakartaSans-var-latin-ext.341687eeeb6a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* The two other theme faces were bundled the same way and never declared
   either; Source Sans 3 (sand-terracotta) and IBM Plex Sans
   (graphite-brass) now render as their themes say. */
@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../vendor/fonts/SourceSans3-var.30164609c163.woff2") format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../vendor/fonts/IBMPlexSans-var.b2c9031d9fd6.woff2") format('woff2');
}

:root {
  /* v2.0 — retheme to the approved Claude Design prototype / brand-guide.html
     palette (dark forest / emerald / mint, replacing v1's onyx / brass gold).
     Token NAMES are unchanged on purpose — hundreds of templates and this
     file's own component rules read colors through these variables, and
     BrandSettings overrides --brass/--surface/--bg per tenant at runtime
     (see base.html). Only the values move. */
  --onyx:       #101413;
  --graphite:   #171B1D;
  --graphite-2: #1D2224;
  --porcelain:  #F4F1EC;
  --bone:       #FBF9F5;
  --ash:        #8C8F94;
  --brass:      #199C5B;
  --brass-soft: #0B6B41;
  --good:       #7FC49E;
  --bad:        #C2554A;
  /* RGB triplets of the four colors above, for rgba() overlays (hover tints,
     translucent chips, focus glows). v1 hardcoded these as literal
     rgba(var(--brass-rgb),...) etc. all over this file, so changing --brass's
     hex above did nothing for them; every such literal has been converted
     to rgba(var(--*-rgb), alpha) so overlays retint automatically. */
  --brass-rgb:  25, 156, 91;
  --good-rgb:   127, 196, 158;
  --bad-rgb:    194, 85, 74;
  /* Warning is now its own hue. v1 reused --brass (a neutral gold) for both
     "primary action" and "warning", which worked only because gold read as
     neither green nor red. Now that --brass IS green, badge-good /
     badge-warn / badge-bad would collapse to "green vs green vs red" on the
     3-tier performance indicators used throughout the dashboards — a real
     loss of the traffic-light signal. Amber restores the 3-way contrast. */
  --amber:      #C9A227;
  --amber-rgb:  201, 162, 39;
  /* Data-viz highlight accent (gauges, sparkline peaks) — brighter than
     --good so a single emphasized value can stand out from a field of
     --good/--brass bars without introducing a fifth unrelated hue. */
  --glow:       #A8E0C0;
  --glow-rgb:   168, 224, 192;
  /* Fifth/sixth semantic hues, added for the light-mode pass below: the
     Raw Materials "Moderate" tier (yellow, distinct from --amber's "Low
     Stock") and an info/neutral-blue used by a couple of message/badge
     variants. Both previously lived only as literal hex scattered in this
     file, invisible to any future retheme — now real tokens like the rest. */
  --yellow:     #D9C233;
  --yellow-rgb: 217, 194, 51;
  --info:       #A6C4E6;
  --info-rgb:   125, 163, 200;

  /* dark mode (default) */
  --bg:           #0B0D0E;
  --surface:      var(--graphite);
  --surface-2:    var(--graphite-2);
  /* Sunken/recessed content surfaces — input fields, bar-chart tracks,
     table headers, line-item rows. In dark mode this has always just been
     --onyx; split into its own token because --onyx is ALSO used as the
     ink color sitting on top of solid --brass buttons/pills (see --on-brass
     below), and that specific pairing must stay dark in both themes for
     contrast, while sunken surfaces must flip light in light mode. Keeping
     them separate means the dark theme below is byte-for-byte unchanged. */
  --well:         var(--onyx);
  /* Text/foreground color for a solid --brass background (buttons, active
     pills, the avatar chip). Kept separate from --text so it can stay dark
     even where --on-brass would otherwise need to invert for contrast
     against a deliberately-darkened light-mode --brass-ink (see below).
     --brass itself is never darkened for light mode (see --brass-ink's
     own note), so --on-brass never needs to invert either — it stays
     var(--onyx) in BOTH themes, unlike --on-tint below. */
  --on-brass:     var(--onyx);
  /* Text/foreground color for a solid fill that DOES darken for light
     mode — --good/--bad/--amber used as an active pill's own background
     (e.g. a metric-toggle button), as opposed to --brass's buttons above.
     Dark --onyx ink reads fine on dark-mode's light --good etc., but once
     --good darkens for light-mode contrast (see :root[data-theme="light"]
     below), the same dark ink no longer has enough contrast against it —
     this flips to white there instead. */
  --on-tint:      var(--onyx);
  /* Brass, used as TEXT directly on the page (links, the .brass utility
     class, active-tab underlines) rather than as a button's own
     background, needs a darker shade once the page behind it turns light
     — the current --brass measures well under AA on --porcelain. Solid
     --brass BACKGROUNDS (buttons, pills) don't have this problem, since
     their contrast is against --on-brass, not the page, so they keep
     using --brass/--brass-rgb unchanged in both themes. */
  --brass-ink:    var(--brass);
  --line:         rgba(233,238,235,0.07);
  --line-2:       rgba(233,238,235,0.14);
  /* Table interior detail. Separate tokens because these are tinted from
     the TEXT colour, not the surface, so they must flip with the theme:
     a white-tinted zebra stripe is invisible on a white page, and a
     white-tinted column rule is invisible against bone. */
  --zebra:        rgba(233,238,235,0.012);
  --rule:         rgba(233,238,235,0.04);
  --text:         #E9EEEB;
  /* v1.13.1 contrast pass. These were #8C8F94 / #5a5d62, which measured
     4.73:1 and 2.32:1 against the card surface (--surface-2, #23252A).
     WCAG AA needs 4.5:1 for body text and --text-3 was less than half of
     that, yet it colours .tbl th, .field-label and .dim - the table
     headers and form labels staff read all shift. Both values are now
     solved against --surface (#2E3033), the lightest live surface and so
     the hardest for light text, which means they clear 4.5:1 on the page,
     on cards, on panels and on table headers alike. The luminance gap between
     the two tiers is preserved so the hierarchy still reads. */
  --text-2:       #b3b6bb;   /* 9.43 page / 7.54 card / 6.51 panel / 8.47 th */
  --text-3:       #94979c;   /* 6.54 page / 5.23 card / 4.52 panel / 5.88 th */

  --serif: 'Cormorant Garamond', Georgia, serif;
  --sans:  'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --sans-fa: 'Vazirmatn', 'Plus Jakarta Sans', system-ui, sans-serif;
  --mono:  'JetBrains Mono', ui-monospace, monospace;

  --radius:    8px;
  --radius-lg: 12px;

  /* ---- tokens introduced by the theme specification ----------------
     Declared here (and in the light block below) so the two bundled
     themes gain them too; every value is the one already in effect,
     so nothing changes appearance until a preset overrides it. */

  /* A real control boundary, as opposed to --line's decorative card
     edge. The spec separates these because only this one has to clear
     3:1 to be a perceivable input affordance. */
  --input-line: rgba(233,238,235,0.28);
  /* Background of a selected nav item / filter chip. */
  --selected:   rgba(var(--brass-rgb), 0.16);
  /* Row hover. Previously a literal rgba scattered through the table
     rules, which meant a retheme could not reach it. */
  --row-hover:  rgba(233,238,235,0.055);
  /* Keyboard focus ring. */
  --focus:      var(--brass);

  /* Status tints - a status chip's own background, so status is carried
     by a labelled chip rather than by coloured text alone. */
  --good-bg:    rgba(var(--good-rgb), 0.14);
  --amber-bg:   rgba(var(--amber-rgb), 0.14);
  --bad-bg:     rgba(var(--bad-rgb), 0.14);
  --info-bg:    rgba(var(--info-rgb), 0.14);

  /* Chart series in a fixed order: actual, target, secondary, tertiary.
     Fixed so a series keeps its colour when a filter changes how many
     series are on screen. */
  --chart-1:    var(--brass);
  --chart-2:    var(--amber);
  --chart-3:    var(--info);
  --chart-4:    var(--glow);

  /* Page-palette aliases. The chrome pin further down overrides --text
     inside .topbar/.sidebar; these are the same values under names it
     never touches, so panels anchored to the topbar can restore the
     page palette in any theme. */
  --page-text:   var(--text);
  --page-text-2: var(--text-2);
  --page-text-3: var(--text-3);
  --page-accent: var(--brass-ink);

  /* Navigation chrome. Defaults reproduce the values the .topbar /
     .sidebar rules hardcoded before this change. */
  --chrome-bg:     var(--onyx);
  --chrome-text:   #E9EEEB;
  --chrome-text-2: #b3b6bb;
  --chrome-text-3: #94979c;
  --chrome-accent: #199C5B;
  --chrome-line:   var(--line);
  /* Controls that sit ON the chrome: the bell, the theme button, the FY
     pill, the language switch, and the nav hover. These used
     var(--graphite-2) directly - a raw palette colour, identical in
     every theme - so on a preset with a white sidebar they rendered as
     near-black boxes with invisible text. Defaults are the literal
     values they had, so the bundled themes are unchanged. */
  --chrome-raise:  var(--graphite-2);
  --chrome-hover:  var(--graphite-2);
  /* The ACTIVE navigation item. Kept apart from --chrome-accent because
     it also needs a fill and a border, and because the bundled themes
     have always used --glow here - a colour tuned for charts on a card,
     which is why it failed so badly once a sidebar could be a light
     surface or the primary hue itself. */
  --chrome-active:      var(--glow);
  --chrome-active-bg:   rgba(var(--brass-rgb), .13);
  --chrome-active-line: rgba(var(--brass-rgb), .28);
}

[lang="fa"] body { font-family: var(--sans-fa); }

/* Light mode. Every status/accent hue that's used as TEXT directly on a
   page/card surface (badges, links, bar labels) is darkened here relative
   to its dark-mode value, because a hue tuned to read on near-black
   (--good/--bad/--amber/--yellow/--info/--brass-ink) does the opposite of
   what you want on a white one — --good at #7FC49E, for instance, is
   barely visible on --bone. Hues used only as a solid button/pill
   BACKGROUND (--brass, --on-brass) are self-contained pairs and don't need
   to change: a medium-green button with dark text reads fine regardless
   of the page around it. Values chosen to clear WCAG AA (4.5:1) against
   both --surface (#FFF) and --bg (#F4F1EC).

   Selector is :root[data-theme="light"], not bare [data-theme="light"]:
   a plain attribute selector has the SAME specificity as :root (0,1,0),
   so it would only win by appearing later in the cascade — exactly the
   trap base.html's tenant-color <style> block fell into. The extra
   :root here (0,2,0) wins on specificity instead, so this can't be
   silently reordered-out by some later :root rule again. */
:root[data-theme="light"] {
  --bg:        var(--porcelain);
  --surface:   var(--bone);
  --surface-2: #fff;
  --well:      #ECE7DC;
  --on-tint:   #fff;
  --line:      rgba(16,20,19,0.10);
  --line-2:    rgba(16,20,19,0.20);
  --zebra:     rgba(16,20,19,0.022);
  --rule:      rgba(16,20,19,0.06);
  --text:      var(--onyx);
  --text-2:    #44464a;
  --text-3:    #63676a;

  --brass-ink:  #0E7A45;
  --good:       #1B7A4A;
  --good-rgb:   27, 122, 74;
  --bad:        #B3372B;
  --bad-rgb:    179, 55, 43;
  --amber:      #8A6D1E;
  --amber-rgb:  138, 109, 30;
  --yellow:     #7A6A12;
  --yellow-rgb: 122, 106, 18;
  --info:       #2A5C8A;
  --info-rgb:   42, 92, 138;
  --glow:       #14804C;
  --glow-rgb:   20, 128, 76;

  --input-line: rgba(16,20,19,0.32);
  --row-hover:  rgba(16,20,19,0.05);
  --page-text:   var(--text);
  --page-text-2: var(--text-2);
  --page-text-3: var(--text-3);
  --page-accent: var(--brass-ink);
}

/* Chrome (topbar + sidebar) stays visually dark in both themes — see the
   note above explaining why --surface/--bg don't flip for tenant colors,
   same reasoning applies here by design (persistent nav chrome, not
   page content). That means its own TEXT must also stay pinned to the
   dark-mode-safe (light) values regardless of theme: --text/--text-2/
   --text-3/--brass-ink/--on-brass otherwise flip to light-mode's DARK
   values in light mode, which would then sit on chrome's still-dark
   background and vanish. Scoped here instead of renaming every
   individual chrome rule (nav-item, brand-name, the theme toggle button
   itself, etc.) to a new token — they keep reading var(--text) as
   before, they just get chrome's pinned value instead of the page's. */
.topbar, .sidebar {
  --text: var(--chrome-text);
  --text-2: var(--chrome-text-2);
  --text-3: var(--chrome-text-3);
  --brass-ink: var(--chrome-accent);
}
/* ...but the FY / notification / user-menu panels anchored to the topbar
   are themselves theme-aware surfaces (background: var(--surface), which
   DOES flip) — undo the chrome pin above inside them so their text flips
   along with their own background instead of inheriting chrome's fixed
   light-on-dark palette, which would otherwise put light text on their
   now-light surface. (--on-brass/--on-tint need no such pin/un-pin: they
   never inherit the chrome-vs-content distinction in the first place.) */
/* No longer scoped to the light theme. Ivory & Forest pairs a DARK
   sidebar with a LIGHT page, so these panels need the page palette
   restored there too - and Porcelain & Indigo the reverse. Restoring
   from the --page-* aliases makes this correct in every theme instead
   of in one. */
.fy-drop,
.bell-panel,
.user-drop {
  --text: var(--page-text);
  --text-2: var(--page-text-2);
  --text-3: var(--page-text-3);
  --brass-ink: var(--page-accent);
  /* Redeclaring the custom properties above is enough for any descendant
     that reads var(--text) etc. itself (.fy-opt, .bell-drop-row...), but
     .fy-drop/.bell-panel/.user-drop don't set their OWN color — so
     anything inside them that ALSO has no explicit color (plain text
     nodes, .fy-check's non-active state) would otherwise inherit the
     already-computed light value from .topbar rather than re-resolving
     var(--text) locally. Setting color here directly is what breaks
     that inheritance chain. */
  color: var(--text);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--serif); font-weight: 500; margin: 0 0 12px; }
h1 { font-size: 30px; letter-spacing: 0.5px; }
h2 { font-size: 22px; }

a { color: var(--brass-ink); text-decoration: none; }
a:hover { text-decoration: underline; }

.muted { color: var(--text-2); }
.dim   { color: var(--text-3); }
.mono  { font-variant-numeric: tabular-nums; font-weight: 600; letter-spacing: 0; }
/* Referenced throughout the dashboards (highlight numbers, accent labels)
   but never actually defined — every <span class="brass"> was silently
   rendering in plain body text color. */
.brass { color: var(--brass-ink); }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 28px;
  background: var(--chrome-bg);
  border-bottom: 0.5px solid var(--chrome-line);
  /* Without an explicit color here, elements like .brand-name that set
     no color of their own (or use color:inherit, e.g. .brand-link) climb
     straight past this element to body's `color: var(--text)` — which
     resolves --text at body's OWN (root) scope, not the pin above, since
     inheritance passes down an already-COMPUTED value, not a live
     variable. Setting color here makes --topbar's own computed color use
     the pinned value, and THAT is what descendants without their own
     color then inherit. */
  color: var(--text);
}
.brand-block { display: flex; align-items: center; gap: 14px; min-width: 0; }
.brand-link { display: flex; align-items: center; gap: 14px; text-decoration: none; color: inherit; }
.brand-link:hover { text-decoration: none; }
.brand-text { display: flex; flex-direction: column; min-width: 0; }
/* Logo — locked to a square box, image letterboxed via object-fit: contain so
   any source aspect ratio displays without distortion. No circular crop —
   the brand mark itself defines its silhouette. */
.logo {
  width: 38px; height: 38px;
  flex-shrink: 0;
  object-fit: contain;
  background: transparent;
}
.brand-name { font-family: var(--serif); letter-spacing: 3px; font-size: 17px; white-space: nowrap; }
.brand-tagline { font-size: 11px; color: var(--text-3); letter-spacing: 0.6px; white-space: nowrap; }
[lang="fa"] .brand-name { letter-spacing: 0; font-family: var(--sans-fa); }
[lang="fa"] .brand-tagline { letter-spacing: 0; font-family: var(--sans-fa); }
.topbar-right { display: flex; align-items: center; gap: 12px; font-size: 13px; flex-wrap: wrap; justify-content: flex-end; }

.container { max-width: 980px; margin: 48px auto; padding: 0 28px; }

.welcome { text-align: start; max-width: 640px; }
.welcome h1 { margin-bottom: 10px; }
.welcome p { margin: 12px 0; }

.btn-brass {
  display: inline-block;
  background: var(--brass);
  color: var(--on-brass);
  padding: 10px 20px;
  border-radius: var(--radius);
  border: 0.5px solid var(--brass);
  font-weight: 500;
}
.btn-brass:hover { background: var(--brass-soft); text-decoration: none; }

/* ============================================================
   Phase 1 UI — layout, sidebar, topbar, forms, tables
   ============================================================ */
[x-cloak] { display: none !important; }

body.guest { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; }

.topbar-fy { font-size: 12px; padding: 5px 10px; border: 0.5px solid var(--line-2); border-radius: 16px; }
.lang-switch select {
  padding: 6px 10px; background: var(--chrome-raise); color: var(--text);
  border: 0.5px solid var(--line-2); border-radius: 16px; font-size: 12px;
  font-family: var(--sans); cursor: pointer;
}

/* Pill toggle for language switcher — visible at-a-glance */
.lang-toggle {
  display: inline-flex; align-items: stretch;
  border: 0.5px solid var(--line-2); border-radius: 16px;
  overflow: hidden; background: var(--chrome-raise);
}
.lang-toggle-form { margin: 0; padding: 0; display: inline-flex; }
.lang-btn {
  appearance: none; -webkit-appearance: none;
  background: transparent; color: var(--text-2);
  border: 0; padding: 6px 12px;
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.04em; cursor: pointer; min-width: 38px;
  border-inline-end: 0.5px solid var(--line-2);
  transition: background 120ms ease, color 120ms ease;
}
.lang-toggle-form:last-child .lang-btn { border-inline-end: 0; }
.lang-btn:hover { background: var(--chrome-hover); color: var(--text); }
.lang-btn.is-active {
  background: var(--chrome-accent); color: var(--chrome-bg); font-weight: 600;
}

/* Language toggle on the guest (login) screen */
.guest-lang {
  position: fixed; top: 18px; inset-inline-end: 18px; z-index: 5;
}

/* Light/dark theme toggle — sized like .bell-btn so the topbar's row of
   icon buttons (theme, notifications) reads as one family. Both icons
   are always in the DOM; only one is ever visible, swapped purely by CSS
   off [data-theme] so there's no layout jump and no JS needed to redraw
   on toggle beyond flipping the attribute (see static/js/theme-toggle.js). */
.theme-toggle-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 36px; padding: 6px 10px;
  background: var(--chrome-raise); color: var(--text);
  border: 0.5px solid var(--line); border-radius: var(--radius);
  cursor: pointer;
}
.theme-toggle-btn:hover { border-color: var(--line-2); }
.theme-icon { width: 17px; height: 17px; }
/* The sun/moon pair and the rules that swapped them went with the
   two-state toggle: with seven themes there is no single opposite to
   flip to, so the button now carries one neutral half-filled circle and
   the dropdown names the choice instead. */

/* Numeric column alignment in tables and forms */
.num { text-align: end; font-variant-numeric: tabular-nums; }
.tbl th.num, .tbl td.num { text-align: end; }
[dir="rtl"] .num { text-align: end; }

/* Signed numbers (+1,185 / −300).
   A leading + or − is bidi-neutral, so inside Persian RTL text the sign
   gets reordered to the far side of the digits and "−300" renders as
   "300−". Isolating the value as its own LTR run keeps the sign glued to
   the front of the number, which is how signed numbers read in Persian
   too. Deliberately NOT applied to .num wholesale — plenty of numeric
   cells also carry translated words, and forcing those LTR would reorder
   the Persian instead. */
.signed { direction: ltr; unicode-bidi: isolate; }

/* Row action links (edit / delete) at the end of a table row */
.row-actions { white-space: nowrap; text-align: end; }
.row-actions a { font-size: 12px; }
.row-actions a + a { margin-inline-start: 12px; }
.row-actions .bad { color: var(--bad); }
.row-actions .bad:hover { opacity: 0.7; text-decoration: none; }

/* ============================================================
   Page tabs (Raw materials / Outbound — list vs. dashboard)
   ============================================================ */
.page-tabs {
  display: flex; gap: 4px; margin: 0 0 18px;
  border-bottom: 0.5px solid var(--line);
  padding-bottom: 0;
}
.page-tab {
  display: inline-block; padding: 10px 18px;
  font-size: 13px; color: var(--text-2); text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms ease, border-color 120ms ease;
}
.page-tab:hover { color: var(--text); text-decoration: none; }
.page-tab.is-active {
  color: var(--brass-ink);
  border-bottom-color: var(--brass-ink);
  font-weight: 500;
}

/* ============================================================
   Analytics dashboards — KPI hero grid, bar charts, trend
   ============================================================ */
.dash-hero { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 6px; }
.dash-hero-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1100px) {
  .dash-hero, .dash-hero-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
  .dash-hero, .dash-hero-4 { grid-template-columns: 1fr; }
}
.kpi-label { font-size: 11px; letter-spacing: 1.6px; text-transform: uppercase; }
.kpi-value { font-family: var(--sans); font-size: 34px; font-weight: 700; line-height: 1.15; font-variant-numeric: tabular-nums; margin-top: 4px; }
/* The unit or scale beside a headline figure - "kg", "IRR",
   "billion". Sans and lighter on purpose: it annotates the number
   rather than being part of it, and inheriting the tile's mono face
   made a scale word read as more digits. */
.kpi-unit {
  font-size: 14px;
  font-family: var(--sans);
  font-weight: 500;
  letter-spacing: normal;
}
.kpi-sub { font-size: 11px; margin-top: 4px; line-height: 1.5; }

.dash-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 1100px) { .dash-row { grid-template-columns: 1fr; } }

/* Inline color swatch for color/material rows */

/* Horizontal bar rows (consumption-by-color, sales-by-print, etc.) */
.bar-row {
  display: grid; grid-template-columns: minmax(120px, 1fr) 2fr minmax(110px, max-content);
  align-items: center; gap: 12px;
  padding: 6px 0; font-size: 12px;
}
.bar-label { font-size: 13px; }
.bar-track {
  height: 8px; background: var(--well); border-radius: 4px; overflow: hidden;
}
.bar-fill {
  height: 100%; background: var(--brass);
  transition: width 360ms ease;
}
.bar-value { text-align: end; font-size: 12px; }

/* Per-row utilization bar inside the materials table */
.util-bar {
  display: inline-block; width: 80px; height: 6px;
  background: var(--well); border-radius: 3px; overflow: hidden;
  vertical-align: middle; margin-inline-end: 6px;
}
.util-bar-fill { height: 100%; background: var(--brass); }

/* Sparkline-style weekly trend chart */
.trend-chart {
  display: flex; align-items: flex-end; gap: 4px;
  height: 140px; padding: 0 0 22px;
  border-bottom: 0.5px solid var(--line);
}
.trend-bar {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end;
  position: relative; min-width: 0; cursor: default;
}
.trend-bar-fill {
  width: 70%; min-height: 2px;
  background: var(--brass); opacity: 0.8;
  border-radius: 3px 3px 0 0;
  transition: opacity 120ms ease;
}
.trend-bar:hover .trend-bar-fill { opacity: 1; }
.trend-bar-label {
  position: absolute; bottom: -20px;
  font-family: var(--mono); font-size: 11px; color: var(--text-3);
  white-space: nowrap;
}
/* The bar's own figure, sat just above its fill (bottom: <pct>%, set
   inline). The chart reserves 14px over the tallest bar for it, and the
   bar takes the chart's full height so that % resolves on iOS 12. */
.trend-chart.has-values { padding-top: 14px; }
.trend-chart.has-values .trend-bar { height: 100%; cursor: pointer; }
.trend-bar-v {
  position: absolute; left: 50%; transform: translateX(-50%);
  margin-bottom: 2px; line-height: 1; pointer-events: none;
  font-family: var(--mono); font-size: 9.5px; color: var(--text-2);
  white-space: nowrap; unicode-bidi: isolate;
}
.trend-bar.is-sel .trend-bar-fill { opacity: 1; background: var(--good); }
.trend-bar.is-sel .trend-bar-v { color: var(--text); font-weight: 600; }

/* Server-drawn SVG charts (apps/core/charts.py): a transparent column per
   bucket that reveals a thinned-out figure on tap, and a halo so that
   revealed figure stays legible over a neighbour's line. */
.chart-hit { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.chart-v { paint-order: stroke; stroke: var(--surface-2); stroke-width: 3px; stroke-linejoin: round; }

/* Status badge colors used across analytics */
.badge-good { color: var(--good); font-weight: 500; }
.badge-warn { color: var(--amber); font-weight: 500; }
.badge-bad  { color: var(--bad); font-weight: 500; }

/* The same three meanings as plain ink, for a word or a figure sitting
   inside a sentence rather than in a pill. Seventeen templates were
   already writing class="bad" on the strength of the badge rules above
   and getting ordinary text: "3 negative balances" on the inventory
   figure, the late count on the delivery panel, and so on. Weight is
   deliberately not set - a pill carries its own, a word in a sentence
   should not shout twice. */
.bad   { color: var(--bad); }
.warn  { color: var(--amber); }
.amber { color: var(--amber); }
.good  { color: var(--good); }

/* Wholesale / Retail price-tier toggle on the outbound form */
.tier-toggle {
  display: inline-flex; gap: 0;
  border: 0.5px solid var(--line-2); border-radius: 999px;
  padding: 3px; background: var(--well);
}
.tier-opt {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 8px 18px; font-size: 13px; font-weight: 500;
  color: var(--text-2); cursor: pointer;
  border-radius: 999px;
  transition: background 120ms ease, color 120ms ease;
  user-select: none;
  min-width: 110px;
}
.tier-opt input[type="radio"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; padding: 0; opacity: 0; cursor: inherit;
}
.tier-opt:hover { color: var(--text); background: rgba(var(--brass-rgb),0.08); }
.tier-opt.tier-wholesale:has(input:checked) { background: rgba(var(--brass-rgb),0.22); color: var(--brass-ink); }
.tier-opt.tier-retail:has(input:checked)    { background: var(--brass); color: var(--on-brass); }
/* Generic active state for any other two/three-way .tier-toggle (e.g. the
   production form's "one press / two presses" picker) that doesn't need
   its own per-value color like wholesale/retail does. Lower specificity
   than the two rules above, so it never overrides them. */
.tier-opt:has(input:checked) { background: var(--brass); color: var(--on-brass); }

/* ============================================================
   Filter / search bar — sits above list tables and dashboards
   ============================================================ */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 10px;
  align-items: end;
  padding: 12px 14px; margin-bottom: 14px;
  background: var(--surface); border: 0.5px solid var(--line);
  border-radius: var(--radius);
}
.filter-bar .filter-field { display: flex; flex-direction: column; gap: 4px; min-width: 140px; }
.filter-bar .filter-field label {
  font-size: 14px; font-weight: 600; letter-spacing: normal;
  text-transform: none;
  color: var(--text-2);
}
.filter-bar input, .filter-bar select {
  padding: 9px 10px; font-size: 15px;
  background: var(--well); color: var(--text);
  border: 0.5px solid var(--line-2); border-radius: 5px;
}
.filter-bar .filter-search { flex: 1; min-width: 180px; }
.filter-bar .filter-actions {
  display: flex; gap: 6px; align-items: end;
  margin-inline-start: auto;
}
/* Match the 44px control height used by every other field in the row —
   .btn already guarantees that globally, but .btn-brass (a bigger CTA
   style elsewhere in the app, e.g. header "+ New" buttons) does not, and
   the padding/font-size this rule used to set shrank it well below that
   without a floor to stop it, leaving Apply visibly shorter than Reset. */
.filter-bar .filter-actions .btn,
.filter-bar .filter-actions .btn-brass {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 16px;
  font-size: 13px;
}
.filter-bar.is-empty { display: none; }

/* Banner that surfaces active filters above results */
.active-filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 8px 14px; margin-bottom: 14px;
  background: rgba(var(--brass-rgb),0.10);
  border: 0.5px solid rgba(var(--brass-rgb),0.28);
  border-radius: var(--radius);
  font-size: 12px; color: var(--text);
}
.active-filters .af-label {
  text-transform: uppercase; letter-spacing: 1.2px;
  font-size: 11px; color: var(--brass-ink); font-weight: 500;
}
.active-filters .af-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  background: var(--surface-2); color: var(--text);
  border: 0.5px solid var(--line-2); font-size: 11px;
}
.active-filters .af-clear {
  margin-inline-start: auto; color: var(--bad);
  font-size: 12px; text-decoration: none;
}
.active-filters .af-clear:hover { text-decoration: underline; }

/* Compact tier label inside tables */
.tier-badge {
  display: inline-block; padding: 2px 8px;
  border-radius: 999px; font-size: 11px; font-weight: 500;
}
.tier-badge-wholesale { background: rgba(var(--brass-rgb),0.18); color: var(--brass-ink); }
.tier-badge-retail    { background: var(--brass); color: var(--on-brass); }
.tier-badge-custom    { background: rgba(140,143,148,0.20); color: var(--text); }

/* ============================================================
   Multi-line shipment form
   ============================================================ */
.line-items { display: flex; flex-direction: column; gap: 10px; }
.line-item {
  background: var(--well);
  border: 0.5px solid var(--line);
  border-radius: var(--radius);
  padding: 12px;
}
/* Two-row layout per line item:
   row 1 — Mould | Color | Print | Qty | Tier  (5 columns)
   row 2 — Unit price | Discount % | Discount IRR | Line total | Remove
   This keeps the form readable at any reasonable viewport without
   horizontal overflow. */
.line-grid {
  display: grid;
  /* 6 columns × 2 rows lets Mould/Color/Print/Qty/Tier/Label fit row 1
     and Unit price/Disc%/Disc IRR/Line total/Remove fit row 2 cleanly. */
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 10px 12px;
  align-items: end;
}
.line-grid .field { margin-bottom: 0; min-width: 0; }
.line-grid select, .line-grid input { width: 100%; }
.line-total {
  padding: 10px 12px;
  background: rgba(var(--brass-rgb),0.10);
  border: 0.5px solid rgba(var(--brass-rgb),0.20);
  border-radius: 6px;
  color: var(--brass-ink);
  text-align: end;
  min-height: 38px;
  display: flex; align-items: center; justify-content: flex-end;
  font-variant-numeric: tabular-nums;
}
.line-remove-btn {
  padding: 8px 12px; font-size: 12px; color: var(--bad);
  border-color: rgba(var(--bad-rgb),0.3);
  width: 100%;
}
.line-remove-btn:hover:not(:disabled) { background: rgba(var(--bad-rgb),0.10); }
.line-remove-btn:disabled { opacity: 0.4; cursor: not-allowed; }

@media (max-width: 980px) {
  .line-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .line-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Jalali datepicker input — text input that pops the embeddable calendar */
input.jdp {
  font-family: var(--mono); letter-spacing: 0.04em; cursor: pointer;
}
/* Override the picker's default light theme to match brand.
   <jdp-container> is a custom element the vendored library injects into
   <body> — a TAG, not a class — so these selectors carry no leading dot
   on the container itself. (A `.jdp-container` class selector never
   matches a real <jdp-container> element, which is why the previous
   version of this block silently did nothing.) Class names below were
   confirmed against the actual rendered popup, not the library's docs. */
jdp-container {
  background: var(--surface) !important;
  border: 0.5px solid var(--line-2) !important;
  color: var(--text) !important;
  border-radius: var(--radius) !important;
  font-family: var(--sans) !important;
  box-shadow: 0 8px 28px rgba(0,0,0,0.45) !important;
}
jdp-container .jdp-months,
jdp-container .jdp-years {
  color: var(--text) !important;
}
jdp-container .jdp-month,
jdp-container .jdp-month input,
jdp-container .jdp-month select,
jdp-container .jdp-year,
jdp-container .jdp-year input,
jdp-container .jdp-year select {
  background: var(--surface-2) !important;
  color: var(--text) !important;
}
jdp-container .jdp-icon-plus,
jdp-container .jdp-icon-minus {
  border-color: var(--line-2) !important;
}
jdp-container .jdp-icon-plus svg,
jdp-container .jdp-icon-minus svg {
  fill: var(--text) !important;
}
jdp-container .jdp-day-name {
  background: var(--surface-2) !important;
  color: var(--text-3) !important;
}
jdp-container .jdp-day {
  color: var(--text) !important;
}
jdp-container .jdp-day-name.holly-day,
jdp-container .jdp-day-name.last-week,
jdp-container .jdp-day.holly-day,
jdp-container .jdp-day.last-week {
  color: var(--bad) !important;
}
jdp-container .jdp-day.not-in-month {
  color: var(--text-3) !important;
}
jdp-container .jdp-day:not(.disabled-day):hover {
  background: rgba(var(--brass-rgb),0.18) !important;
  color: var(--text) !important;
}
jdp-container .jdp-day.today {
  border-color: var(--brass-ink) !important;
}
jdp-container .jdp-day.selected {
  background: var(--brass) !important;
  color: var(--on-brass) !important;
}
jdp-container .jdp-footer {
  border-top: 0.5px solid var(--line-2) !important;
}
jdp-container .jdp-btn-today,
jdp-container .jdp-btn-empty,
jdp-container .jdp-btn-close {
  background: var(--surface-2) !important;
  color: var(--text) !important;
  border: 0.5px solid var(--line-2) !important;
}

.user-menu { position: relative; }
.user-pill {
  display: flex; align-items: center; gap: 8px; padding: 5px 10px 5px 5px;
  background: transparent; border: 0.5px solid var(--line-2); border-radius: 20px;
  color: var(--text); cursor: pointer; font-family: var(--sans); font-size: 12px;
}
.user-pill:hover { background: var(--chrome-hover); }
.avatar {
  width: 22px; height: 22px; border-radius: 50%; background: var(--brass);
  color: var(--on-brass); font-weight: 500; font-size: 11px;
  display: flex; align-items: center; justify-content: center;
}
.user-drop {
  position: absolute; top: calc(100% + 6px); right: 0; min-width: 180px;
  background: var(--surface); border: 0.5px solid var(--line-2);
  border-radius: 8px; padding: 6px; z-index: 100;
}
[dir="rtl"] .user-drop { right: auto; left: 0; }
.user-drop a, .user-drop .dropdown-link {
  display: block; padding: 8px 12px; color: var(--text); font-size: 13px;
  border-radius: 4px; background: transparent; border: 0; width: 100%;
  text-align: left; font-family: var(--sans); cursor: pointer;
}
.user-drop a:hover, .user-drop .dropdown-link:hover {
  background: var(--surface-2); text-decoration: none;
}

.layout {
  display: grid;
  /* CSS Grid auto-flips this under dir=rtl: column 1 (220px) becomes the
     right edge in RTL, which is exactly where the sidebar belongs. */
  /* minmax(0, 1fr), not 1fr. A `1fr` track carries an automatic
     min-content floor, so ANY unusually wide child - the machine x
     mould matrix is 1073px - pushes the track wider than the screen
     even when that child sits in its own horizontal scroller. The page
     then exceeds the viewport, a phone shrinks the whole thing to fit,
     and the app renders as a narrow column against a field of white
     with everything too small to read. minmax(0, ...) lets the track
     shrink and hands the overflow back to the scroller that asked
     for it. */
  grid-template-columns: 220px minmax(0, 1fr);
  min-height: calc(100vh - 67px);
  position: relative;
}

.sidebar {
  background: var(--chrome-bg);
  border-inline-end: 0.5px solid var(--chrome-line);
  padding: 16px 0;
  min-height: 100%;
  color: var(--text); /* see the comment on .topbar's same declaration */
}

/* Mobile drawer mode — sidebar slides in from the start edge */
.nav-burger {
  display: none;
  background: transparent; border: 0; padding: 8px; margin-inline-end: 4px;
  cursor: pointer; flex-direction: column; gap: 4px; align-items: center;
  /* iOS Safari: kill the 300ms tap delay on the burger so the menu opens
     instantly when the user taps. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(var(--brass-rgb),0.18);
}
.nav-burger span {
  display: block; width: 22px; height: 2px; background: var(--text);
  border-radius: 2px;
}
.nav-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 40;
  display: none;
}

.nav-section {
  padding: 18px 18px 6px; font-size: 11px; color: var(--text-3);
  letter-spacing: 1.6px; text-transform: uppercase;
}
[lang="fa"] .nav-section { letter-spacing: 0.4px; }
.nav-item {
  display: block; margin: 0 8px; padding: 8px 10px; font-size: 13px; color: var(--text-2);
  text-decoration: none; border-radius: 6px;
  border: 0.5px solid transparent;
}
.nav-item:hover { color: var(--text); background: var(--chrome-hover); text-decoration: none; }
/* v2.0: filled rounded pill instead of a left accent bar — matches the
   approved design prototype's sidebar treatment. */
.nav-item.active {
  color: var(--chrome-active);
  background: var(--chrome-active-bg);
  border-color: var(--chrome-active-line);
  font-weight: 500;
}

.content { padding: 28px 36px; max-width: 100%; min-width: 0; }
.content-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 22px; }
.content-header h1 { font-family: var(--serif); font-size: 28px; }
.content-header .sub { font-size: 12px; color: var(--text-3); margin-top: 4px; }

.messages { margin-bottom: 14px; }
.msg { padding: 10px 14px; border-radius: 6px; font-size: 13px; margin-bottom: 6px; }
.msg-success { background: rgba(var(--good-rgb),0.08); border: 0.5px solid rgba(var(--good-rgb),0.3); color: var(--good); }
.msg-error   { background: rgba(var(--bad-rgb),0.08); border: 0.5px solid rgba(var(--bad-rgb),0.3); color: var(--bad); }
.msg-info    { background: rgba(var(--brass-rgb),0.08); border: 0.5px solid rgba(var(--brass-rgb),0.3); color: var(--brass-ink); }

/* Cards & sections */
.card { background: var(--surface-2); border: 0.5px solid var(--line); border-radius: var(--radius-lg); padding: 18px 20px; }
.card + .card { margin-top: 12px; }

/* Tables */
.tbl { width: 100%; border-collapse: collapse; font-size: 15px; }
.tbl th { text-align: start; font-weight: 600; color: var(--text-2); font-size: 14px; letter-spacing: normal; padding: 12px; text-transform: none; }
.tbl td { padding: 14px 12px; border-top: 0.5px solid var(--rule); }
.tbl tbody tr:hover td { background: var(--row-hover); }
.tbl tbody tr.hoverless:hover td { background: transparent; }
.tbl-card { background: var(--surface-2); border: 0.5px solid var(--line); border-radius: var(--radius-lg); padding: 0; overflow: hidden; }
.tbl-card .tbl th { background: var(--well); padding-left: 16px; }
.tbl-card .tbl td:first-child { padding-left: 16px; }
.tbl-card .tbl td:last-child { padding-right: 16px; }

/* Forms */
.form-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.form-row.full > * { grid-column: 1 / -1; }
.field { margin-bottom: 14px; }
.field-label { display: block; font-size: 14px; font-weight: 600; color: var(--text-2); letter-spacing: normal; margin-bottom: 6px; text-transform: none; }
.input, input[type="text"], input[type="password"], input[type="email"],
input[type="number"], input[type="date"], input[type="search"], select, textarea {
  width: 100%; padding: 11px 12px; background: var(--well);
  border: 0.5px solid var(--line-2); border-radius: 6px; color: var(--text);
  /* v1.13.1: 44px min target and 16px text on touch devices. 13px input
     text makes iOS/Android zoom the viewport on focus, which throws the
     operator out of position mid-entry; 16px is the threshold that stops
     it. Kept at 13px on pointer devices so dense forms do not grow. */
  font-size: 16px; font-family: var(--sans);
  min-height: 44px;
}
/* Safari/Chrome give type="search" its own native rounded chrome that
   fights the border-radius/padding above unless explicitly reset. */
input[type="search"] { -webkit-appearance: none; appearance: none; }
textarea { min-height: 88px; }
@media (pointer: coarse) {
  .input, input[type="text"], input[type="password"], input[type="email"],
  input[type="number"], input[type="date"], input[type="search"], select, textarea { font-size: 16px; }
}
.input:focus, input:focus, select:focus, textarea:focus {
  border-color: var(--brass-ink); outline: none;
}
/* Keyboard users need a ring the mouse users do not get. Removing the
   outline above without this leaves tab-navigation invisible. */
.input:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, .btn:focus-visible, .btn-brass:focus-visible,
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--brass-ink);
  outline-offset: 2px;
}
.field-error { color: var(--bad); font-size: 12px; margin-top: 4px; }

/* Buttons (extra variants) */
.btn { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 9px 16px; border: 0.5px solid var(--line-2); border-radius: 6px; background: transparent; color: var(--text); font-size: 13px; cursor: pointer; font-family: var(--sans); text-decoration: none; }
.btn:hover { background: var(--surface-2); text-decoration: none; }
.btn-row { display: flex; gap: 8px; }

/* Permission matrix (legacy 6-checkbox grid; kept for fallback) */
.perm-matrix { width: 100%; border-collapse: collapse; }
.perm-matrix th { font-size: 11px; letter-spacing: 1.4px; color: var(--text-3); padding: 10px 8px; text-transform: uppercase; font-weight: 400; }
.perm-matrix th:first-child { text-align: start; padding-inline-start: 16px; }
.perm-matrix th:not(:first-child) { text-align: center; }
.perm-matrix td { padding: 10px 8px; border-top: 0.5px solid var(--line); font-size: 13px; }
.perm-matrix td:first-child { padding-left: 16px; }
.perm-matrix td:not(:first-child) { text-align: center; }
.perm-matrix input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--brass-ink); cursor: pointer; }

/* Friendly per-section access level picker
   ================================================================
   - 4 segmented buttons: None / View / Edit / Manage
   - Each level is its own color so the active state reads at a glance
   - Radio inputs are visually hidden; the <label> wraps everything
*/
.perm-tbl td { vertical-align: middle; }
.perm-tbl tr:hover td { background: transparent; }

.perm-segmented {
  display: inline-flex; flex-wrap: wrap; gap: 0;
  border: 0.5px solid var(--line-2); border-radius: 999px;
  padding: 3px; background: var(--well);
  position: relative;
}
.perm-seg {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative;
  padding: 6px 14px; font-size: 12px; font-weight: 500;
  color: var(--text-2); cursor: pointer;
  border-radius: 999px;
  transition: background 120ms ease, color 120ms ease;
  user-select: none;
  white-space: nowrap;
}
/* Visually hide the radio but keep it focusable + clickable
   (label clicks still toggle it via implicit association). */
.perm-seg input[type="radio"] {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  margin: 0; padding: 0;
  opacity: 0;
  cursor: inherit;
}
.perm-seg:hover { color: var(--text); background: rgba(var(--brass-rgb),0.08); }
.perm-seg.is-disabled,
.perm-seg:has(input:disabled) { cursor: not-allowed; opacity: 0.55; }
.perm-seg.is-disabled:hover,
.perm-seg:has(input:disabled):hover { background: transparent; color: var(--text-2); }

/* Reactive active state — updates instantly when the radio is clicked,
   no JS, no page reload. The :has() selector targets the label whose
   nested radio is :checked. The .is-active class stays as a fallback
   for browsers that don't support :has() (pre-2022 Safari, etc.). */
.perm-seg.perm-seg-none.is-active,
.perm-seg.perm-seg-none:has(input:checked)   { background: rgba(140,143,148,0.18); color: var(--text); }
.perm-seg.perm-seg-view.is-active,
.perm-seg.perm-seg-view:has(input:checked)   { background: rgba(var(--good-rgb),0.20); color: var(--good); }
.perm-seg.perm-seg-edit.is-active,
.perm-seg.perm-seg-edit:has(input:checked)   { background: rgba(var(--brass-rgb),0.22); color: var(--brass-ink); }
.perm-seg.perm-seg-manage.is-active,
.perm-seg.perm-seg-manage:has(input:checked) { background: var(--brass); color: var(--on-brass); }

/* Keyboard focus ring for accessibility */
.perm-seg:has(input:focus-visible) {
  outline: 2px solid var(--brass-ink);
  outline-offset: 2px;
}

/* Legend pills — match the segmented states above */
.perm-legend { display: grid; gap: 8px; margin-top: 10px; }
.perm-legend > div { font-size: 12px; }
.perm-pill {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.4px;
  margin-inline-end: 6px;
}
.perm-pill.is-none   { background: rgba(140,143,148,0.18); color: var(--text); }
.perm-pill.is-view   { background: rgba(var(--good-rgb),0.20); color: var(--good); }
.perm-pill.is-edit   { background: rgba(var(--brass-rgb),0.22); color: var(--brass-ink); }
.perm-pill.is-manage { background: var(--brass); color: var(--on-brass); }

@media (max-width: 720px) {
  .perm-tbl td:first-child { padding-bottom: 4px; }
  .perm-segmented { width: 100%; justify-content: space-between; }
  .perm-seg { flex: 1; padding: 8px 6px; min-width: 0; }
}

/* Login screen */
.login-card {
  width: 100%; max-width: 420px;
  background: var(--surface);
  border: 0.5px solid var(--line);
  border-radius: 18px;
  padding: 40px 34px;
}
.login-card h1 {
  font-family: var(--serif);
  font-size: 34px; font-weight: 500;
  text-align: center;
  margin: 0 0 6px;
  color: var(--text);
}
.login-lede {
  text-align: center;
  color: var(--text-3);
  font-size: 14px;
  margin: 0 0 18px;
}
/* Hairline with a brand dot centred on it. */
.login-sep {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-bottom: 10px;
}
.login-sep::before, .login-sep::after {
  content: ""; height: 1px; width: 54px;
  background: linear-gradient(90deg, transparent, rgba(var(--brass-rgb), 0.55));
}
.login-sep::after { transform: rotate(180deg); }
.login-sep span {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brass);
}
.login-meta {
  text-align: center;
  font-size: 13px;
  letter-spacing: 1px;
  color: var(--text-3);
  margin: 0 0 26px;
}
.login-meta-brand { color: var(--brass-ink); }

/* Input with a leading icon badge (and, for the password, a trailing eye) */
.input-lockup {
  display: flex; align-items: stretch;
  border: 0.5px solid var(--line-2);
  border-radius: 10px;
  background: var(--well);
  overflow: hidden;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.input-lockup:focus-within {
  border-color: var(--brass);
  box-shadow: 0 0 0 3px rgba(var(--brass-rgb), 0.16);
}
.input-badge {
  flex: none;
  width: 46px;
  display: grid; place-items: center;
  background: rgba(var(--brass-rgb), 0.16);
  color: var(--brass-ink);
  border-inline-end: 0.5px solid var(--line);
}
.input-badge svg { width: 19px; height: 19px; }
/* Reset the shared input chrome — the lockup owns the border now. */
.input-lockup input {
  flex: 1 1 auto; min-width: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 13px 14px;
}
.input-lockup input:focus { outline: none; box-shadow: none; }
.input-eye {
  flex: none;
  width: 44px;
  display: grid; place-items: center;
  background: transparent; border: 0;
  color: var(--text-3);
  cursor: pointer;
}
.input-eye:hover { color: var(--text-2); }
.input-eye svg { width: 19px; height: 19px; }
/* Only one of the two eye icons is shown at a time. */
.input-eye svg.is-off { display: none; }

.login-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  margin: 4px 0 20px;
}
.login-remember {
  display: flex; align-items: center; gap: 9px;
  font-size: 13px; color: var(--text-2);
  cursor: pointer; margin: 0;
}
.login-remember input { accent-color: var(--brass); width: 16px; height: 16px; }
/* <details> so "forgot password" can explain itself without a dead link. */
.login-forgot { position: relative; }
.login-forgot > summary {
  list-style: none; cursor: pointer;
  font-size: 13px; color: var(--brass-ink);
}
.login-forgot > summary::-webkit-details-marker { display: none; }
.login-forgot > summary:hover { text-decoration: underline; }
.login-forgot > p {
  position: absolute; z-index: 5;
  inset-inline-end: 0; inset-block-start: calc(100% + 8px);
  width: 250px; margin: 0; padding: 10px 12px;
  background: var(--surface-2, var(--graphite-2));
  border: 0.5px solid var(--line-2);
  border-radius: 8px;
  font-size: 12px; line-height: 1.55; color: var(--text-2);
  box-shadow: 0 10px 26px rgba(0,0,0,0.35);
}

.login-submit {
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 14px 18px;
  font-family: inherit; font-size: 15px; font-weight: 600;
  /* White, not --on-brass. Every other primary button in the app sits on
     flat --brass, where the dark ink reads well; this one is a gradient
     that runs down into --brass-soft, and dark ink on that end measures
     about 2.9:1 — unreadable. --brass is theme-invariant, so white holds
     in both light and dark.
     The --brass stop sits at 200% so the gradient never actually reaches
     full --brass inside the button: white on flat --brass is only 3.5:1,
     under AA for 15px text, whereas stopping half way keeps the lightest
     visible point at ~4.7:1 while still reading as a gradient. */
  color: #fff;
  background: linear-gradient(100deg, var(--brass-soft) 0%, var(--brass) 200%);
  border: 0; border-radius: 10px;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.05s ease;
}
.login-submit:hover { filter: brightness(1.07); }
.login-submit:active { transform: translateY(1px); }
.login-submit svg { width: 19px; height: 19px; }
/* The arrow points along the reading direction, so flip it in RTL. */
[dir="rtl"] .login-submit svg { transform: scaleX(-1); }

.login-or {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 18px;
  color: var(--text-3); font-size: 11px;
  letter-spacing: 1.4px; text-transform: uppercase;
}
.login-or::before, .login-or::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
}

.login-sso {
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 11px;
  padding: 13px 18px;
  font-family: inherit; font-size: 14px; font-weight: 500;
  color: var(--text);
  background: var(--well);
  border: 0.5px solid var(--line-2);
  border-radius: 10px;
  cursor: pointer;
}
.login-sso svg { width: 19px; height: 19px; }
.login-sso:hover:not(:disabled) { border-color: var(--line-2); filter: brightness(1.08); }
.login-sso:disabled { opacity: 0.45; cursor: not-allowed; }

.login-note {
  display: flex; align-items: flex-start; justify-content: center; gap: 7px;
  text-align: center;
  margin: 16px 0 0;
  font-size: 12px; line-height: 1.55;
  color: var(--text-3);
}
.login-note svg { width: 14px; height: 14px; flex: none; margin-top: 2px; }

/* ============================================================
   FY dropdown menu (topbar)
   ============================================================ */
.fy-menu { position: relative; }
.fy-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--chrome-raise); cursor: pointer;
  font-family: var(--sans); color: var(--text);
}
.fy-btn .caret { font-size: 11px; }
.fy-drop {
  position: absolute; top: calc(100% + 6px); inset-inline-end: 0;
  min-width: 160px; background: var(--surface);
  border: 0.5px solid var(--line-2); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0,0,0,0.32);
  z-index: 60; padding: 6px;
}
.fy-drop form { margin: 0; }
.fy-opt {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding: 8px 10px; gap: 8px;
  background: transparent; border: 0; color: var(--text);
  font-family: var(--sans); font-size: 13px; cursor: pointer;
  border-radius: 4px; text-align: start;
}
.fy-opt:hover { background: var(--surface-2); }
.fy-opt.is-active { background: rgba(var(--brass-rgb),0.16); color: var(--brass-ink); }

.user-pill .user-name { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============================================================
   Responsive — collapse the sidebar to a drawer below 960px
   and tighten the topbar layout below 720px.
   ============================================================ */
@media (max-width: 960px) {
  .topbar { padding: 12px 16px; }
  .nav-burger { display: inline-flex; }
  .layout { grid-template-columns: minmax(0, 1fr) !important; }
  .sidebar {
    position: fixed; top: 0; bottom: 0;
    /* Physical first: iOS 12 Safari ignores inset-inline-*, and without a
       left/right the drawer falls back to its static position - in RTL
       that left it hanging past the screen edge. */
    left: 0;
    inset-inline-start: 0;
    width: 260px; max-width: 80vw;
    transform: translateX(-100%);
    transition: transform 220ms ease;
    z-index: 50;
    border-inline-end: 0.5px solid var(--line);
    box-shadow: 0 0 32px rgba(0,0,0,0.5);
    padding-top: 60px;
  }
  /* In RTL the base rule's `inset-inline-start: 0` already pins the sidebar
     to the right edge (start = right in RTL). We only need to flip the
     transform direction so it slides OFF that edge instead of off the
     left edge. Touching `inset-inline-end` here would place the sidebar
     at the LEFT edge (end = left in RTL) — that was the previous bug. */
  [dir="rtl"] .sidebar {
    left: auto; right: 0;
    transform: translateX(100%);
  }
  .sidebar.is-open { transform: translateX(0); }
  .nav-backdrop { display: block; }
  .content { padding: 18px 16px; }
}

@media (max-width: 1100px) {
  /* Dashboard KPI hero — 2 columns instead of 4 once we lose width. */
  .content [style*="grid-template-columns: repeat(4, 1fr)"] {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}

@media (max-width: 720px) {
  .brand-tagline { display: none; }
  .brand-name { font-size: 15px; letter-spacing: 2px; }
  .topbar-fy { padding: 4px 8px; font-size: 11px; }
  .lang-btn { padding: 5px 9px; min-width: 32px; }
  .user-pill .user-name { max-width: 80px; }
  .topbar-right { gap: 8px; }
  .content-header { flex-direction: column; align-items: flex-start; gap: 12px; }
  .content-header .btn-row { align-self: flex-start; }
  .form-row { grid-template-columns: 1fr; }
  .tbl-card { overflow-x: auto; }
  /* Dashboard hero: 1 column on phones */
  .content [style*="grid-template-columns: repeat(4, 1fr)"],
  .content [style*="grid-template-columns: 1.4fr 1fr"],
  .content [style*="grid-template-columns: 1fr 1fr"] {
    grid-template-columns: 1fr !important;
  }
}

@media (max-width: 480px) {
  .user-pill .user-name { display: none; }
  .login-card { padding: 24px 18px; }
  .topbar { padding: 10px 12px; }
}

/* === Top bar: one height for every control (v2.9.4) ================= */
/* The year pill, the bell, the theme toggle, the EN/FA switch and the
   user pill were each sized by their own padding and font-size, so on a
   phone they came out as five different heights sitting on a shared
   centre line - visibly ragged, and the taller ones made the bar grow.
   Nothing declared a common height because nothing had to: at desktop
   width the differences are a pixel or two and invisible.

   A single custom property is the height, every control claims it, and
   the padding becomes horizontal only. Changing the bar's density is
   then one number rather than five. */
@media (max-width: 960px) {
  .topbar { --topbar-ctl: 36px; }

  .topbar .topbar-fy,
  .topbar .bell-btn,
  .topbar .theme-toggle-btn,
  .topbar .lang-toggle,
  .topbar .user-pill,
  .topbar .nav-burger {
    block-size: var(--topbar-ctl);
    min-block-size: var(--topbar-ctl);
    /* Vertical padding would fight a fixed height; the flex centring
       below is what positions the contents. */
    padding-block: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
  }

  /* The burger is a square tap target, not a pill. */
  .topbar .nav-burger { inline-size: var(--topbar-ctl); padding-inline: 0; }

  /* The avatar must not exceed the control it sits in, or it pushes the
     pill taller again and undoes the alignment. */
  .topbar .user-pill .avatar {
    width: 24px; height: 24px; font-size: 11px; flex: 0 0 auto;
  }

  /* Icons inside those controls get an explicit box for the same reason
     the search icon did: a glyph's size is a property of the font. */
  .topbar .bell-icon,
  .topbar .theme-icon,
  .topbar .theme-toggle-btn svg,
  .topbar .bell-btn svg { width: 18px; height: 18px; line-height: 1; }

  /* The EN/FA pair is a segmented control: the wrapper owns the height
     and the two buttons fill it, so neither can be taller than the row
     it sits in.

     transform:none undoes a `scale(0.9)` applied further down. That was
     a way to squeeze this control onto a narrow bar back when nothing
     declared a height - and it is what made this one pill 32px while
     every other control was 36. A scale also lies to layout: the box
     still measures 36 and only paints at 32, so the gap it leaves is
     wrong too. With a real height there is nothing left to squeeze. */
  .topbar .lang-toggle {
    padding-inline: 0; overflow: hidden; transform: none;
  }
  .topbar .lang-toggle-form { display: flex; block-size: 100%; }
  .topbar .lang-btn {
    block-size: 100%; padding-block: 0;
    display: inline-flex; align-items: center; justify-content: center;
  }
}

/* ============================================================
   Responsive polish — phase 2.  Covers tablet + phone gaps
   that were missed by the original media queries: long tables,
   multi-line forms, filter bars, touch target sizing, iOS
   form-zoom prevention, scrollable tabs, narrow-screen card
   padding.  Stacks cleanly on portrait phones (320px+).
   ============================================================ */

/* Always allow tables to scroll horizontally — most of our tables have
   8+ columns and just don't fit below 1024px even with shrunk fonts.
   Without this users can't see the right-hand columns at all. */
.tbl-card { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tbl      { min-width: 100%; }

/* Tablet & below: tighten table padding so more columns fit before
   horizontal scroll kicks in. */
@media (max-width: 1024px) {
  .tbl th, .tbl td { padding: 10px 12px; font-size: 13px; }
  .tbl .row-actions { white-space: nowrap; }
}

/* iOS Safari zooms in when an input has font-size < 16px and gets focus.
   Bump form-control fonts to 16px on touch widths to suppress it. */
@media (max-width: 720px) {
  input, select, textarea, button { font-size: 16px; }
  .field-label { font-size: 12px; }

  /* Filter bars: stack each field full-width on phones so labels and
     controls don't crash into each other. */
  .filter-bar {
    /* was grid-template-columns, which is inert on a flex container */
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .filter-bar .filter-field,
  .filter-bar .filter-search { width: 100%; }
  .filter-bar .filter-actions {
    width: 100%; display: flex; gap: 8px;
  }
  .filter-bar .filter-actions > * { flex: 1; }

  /* Touch targets: every button at least 44px tall on phones. */
  .btn, .btn-brass, .lang-btn, .fy-btn, button[type="submit"] {
    min-height: 44px;
  }

  /* Tabs (e.g. on Outbound): horizontal scroll instead of wrap so the
     row stays one line and stays usable on narrow screens. */
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .tabs > * { flex: 0 0 auto; }

  /* Cards: pull the inner padding in a bit so content gets more room. */
  .card { padding: 14px 14px; }

  /* Multi-line shipment form: collapse to single column. */
  .line-grid { grid-template-columns: 1fr !important; }
  .line-item { padding: 14px 12px; }

  /* Page-header buttons: stack below the title on phones. */
  .content-header .btn-row {
    width: 100%; flex-wrap: wrap;
  }
  .content-header .btn-row > * { flex: 1; min-width: 130px; }

  /* Active filters strip should wrap. */
  .active-filters { flex-wrap: wrap; }
}

/* Phone portrait — ~360–414px */
@media (max-width: 480px) {
  /* Even tighter card padding on phones. */
  .card { padding: 12px 10px; }
  .content { padding: 14px 12px; }

  /* Drop the brand-name beside the logo when there's barely any topbar
     room, leaving just the coin icon. */
  .brand-name { font-size: 14px; letter-spacing: 1px; }

  /* Permission segmented picker: stack into 2x2 instead of 1x4 */
  .perm-segmented { flex-wrap: wrap; }
  .perm-seg { flex: 0 0 calc(50% - 4px); }

  /* Dashboard hero numbers: shrink the big 26px figures so they don't
     overflow their cards on small screens. */
  .hero-stat { font-size: 22px; }
}

/* Print mode — legacy KPI summary rules; the full print stylesheet
   (P12) sits at the bottom of this file and supersedes most of this. */

/* ============================================================
   Notifications — bell button + dropdown panel + inbox styling
   ============================================================ */
.bell-menu  { position: relative; }
.bell-btn   {
  background: var(--chrome-raise);
  border: 0.5px solid var(--line);
  border-radius: var(--radius);
  padding: 6px 10px;
  cursor: pointer;
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 36px;
  color: var(--text);
}
.bell-icon  { font-size: 16px; line-height: 1; }
.bell-dot {
  position: absolute; top: -4px; inset-inline-end: -4px;
  background: var(--bad, #c2554a); color: #fff;
  font-size: 11px; line-height: 1;
  min-width: 16px; height: 16px;
  border-radius: 8px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600;
}
.bell-panel {
  position: absolute; top: calc(100% + 6px); inset-inline-end: 0;
  width: 340px; max-width: 90vw;
  background: var(--surface);
  border: 0.5px solid var(--line-2);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0,0,0,0.42);
  z-index: 60;
  max-height: 70vh; overflow-y: auto;
}
.bell-drop-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 14px;
  border-bottom: 0.5px solid var(--line);
  font-size: 12px;
}
.bell-drop-row {
  display: block; padding: 10px 14px;
  border-bottom: 0.5px solid var(--line);
  text-decoration: none; color: var(--text);
}
.bell-drop-row:hover { background: var(--surface-2); }
.bell-drop-row.is-unread { background: rgba(var(--brass-rgb),0.06); }
.bell-drop-meta {
  display: flex; gap: 8px; align-items: center;
  margin-bottom: 4px;
}
.bell-drop-title { font-size: 13px; font-weight: 500; line-height: 1.3; }
.bell-drop-body  { font-size: 12px; color: var(--text-3); margin-top: 2px; }

/* Inbox table — emphasize unread rows */
.tbl tr.is-unread td        { background: rgba(var(--brass-rgb),0.05); }
.tbl tr.is-unread td:first-child::before {
  content: "•"; color: var(--brass-ink);
  font-size: 18px; line-height: 1; margin-inline-end: 4px;
}

.badge-info { background: rgba(var(--info-rgb),0.18); color: var(--info); padding: 2px 8px; border-radius: 999px; font-size: 11px; }

@media (max-width: 720px) {
  .bell-panel { width: 96vw; inset-inline-end: -8px; }
}

/* ============================================================
   Form polish — sectioned cards, helper text, error styling.
   These classes are intended to be reused across every form in
   the app (mould orders, production, shipments, catalog CRUD,
   admin settings) so they look consistent.
   ============================================================ */

/* A form-section is a card with a numbered title at the top. */
.form-section {
  margin-bottom: 14px;
  padding: 18px 20px;
}
.form-section + .form-section { margin-top: 14px; }

.form-section-title {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 500;
  color: var(--text);
  margin: 0 0 14px 0;
  padding-bottom: 12px;
  border-bottom: 0.5px solid var(--line);
}
.form-section-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  background: var(--brass);
  color: var(--on-brass);
  border-radius: 50%;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 600;
}

/* Help text below an input. Subtle, italic-feeling. */
.field-help {
  font-size: 11px;
  color: var(--text-3);
  margin-top: 4px;
  line-height: 1.5;
}

/* Inline error chip below an invalid field. */
.field-error {
  font-size: 12px;
  color: var(--bad, #c2554a);
  background: rgba(193, 74, 74, 0.08);
  border-inline-start: 3px solid var(--bad, #c2554a);
  padding: 6px 10px;
  margin-top: 6px;
  border-radius: 4px;
  line-height: 1.4;
}

/* Required asterisk emphasis on labels (use ` *` in template). */
.field-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-2, var(--text-3));
  margin-bottom: 6px;
  display: block;
}

/* Tighten the gap between fields in a row, but not so much that they
   collide on narrow screens. */
.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
  margin-bottom: 12px;
}
.form-row .field { margin-bottom: 0; }

/* Narrow field for short numeric inputs (qty, price) */
.field-narrow input { max-width: 180px; }

/* Make every text-ish input look the same: same border, same focus ring,
   same height. We override the ad-hoc styles that exist on individual
   forms. */
.field input[type="text"],
.field input[type="number"],
.field input[type="date"],
.field input[type="email"],
.field input[type="password"],
.field input[type="search"],
.field select,
.field textarea {
  width: 100%;
  background: var(--well);
  color: var(--text);
  border: 0.5px solid var(--line);
  border-radius: var(--radius);
  padding: 9px 11px;
  font-family: var(--sans);
  font-size: 14px;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--brass-ink);
  box-shadow: 0 0 0 3px rgba(var(--brass-rgb),0.18);
}
.field textarea { resize: vertical; min-height: 64px; }

/* Disable browser autofill yellowing — it clashes with the dark theme. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--well) inset;
  caret-color: var(--text);
}

/* Inline message banners (success/error/warn) above forms */
.msg {
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: 13px;
  margin-bottom: 14px;
  border: 0.5px solid var(--line);
}
.msg-error { background: rgba(var(--bad-rgb),0.10); border-color: rgba(var(--bad-rgb),0.30); color: var(--bad); }
.msg-info  { background: rgba(var(--info-rgb),0.10); border-color: rgba(var(--info-rgb),0.25); color: var(--info); }
.msg-warn  { background: rgba(var(--amber-rgb),0.10); border-color: rgba(var(--amber-rgb),0.30); color: var(--amber); }
.msg-ok    { background: rgba(var(--good-rgb),0.10); border-color: rgba(var(--good-rgb),0.30); color: var(--good); }

@media (max-width: 720px) {
  .form-section { padding: 14px; }
  .form-section-title { font-size: 14px; }
  .form-section-num { width: 22px; height: 22px; font-size: 11px; }
}

/* ============================================================
   Login shell — two-column lockup (P1 + P2)
   Welcome panel on the left (>=720px), sign-in card on the right.
   Stacks vertically below the breakpoint. Re-uses brand tokens.
   ============================================================ */
body.guest {
  background:
    linear-gradient(180deg, var(--surface) 0%, var(--bg) 38%, var(--bg) 100%);
  flex-direction: column;
}
.login-shell {
  display: grid;
  grid-template-columns: 1fr 440px;
  gap: 36px;
  align-items: stretch;
  width: 100%;
  max-width: 1000px;
  margin: auto;
}
.login-welcome {
  position: relative;
  overflow: hidden;
  display: flex; flex-direction: column; gap: 22px;
  padding: 40px 36px;
  /* Brand-tinted wash rather than a flat panel, so the accent colour a
     tenant picks carries through to the sign-in screen too. */
  background:
    radial-gradient(120% 90% at 0% 0%, rgba(var(--brass-rgb), 0.16) 0%, transparent 62%),
    linear-gradient(160deg, var(--surface) 0%, var(--well) 100%);
  border: 0.5px solid var(--line);
  border-radius: 18px;
  color: var(--text);
}
.login-brand { display: flex; align-items: center; gap: 16px; }
.login-logo {
  width: 60px; height: 60px;
  flex: none;
  object-fit: contain;
  padding: 8px;
  background: linear-gradient(150deg, rgba(var(--brass-rgb), 0.30), rgba(var(--brass-rgb), 0.12));
  border: 0.5px solid rgba(var(--brass-rgb), 0.35);
  border-radius: 14px;
}
.login-name {
  font-family: var(--serif);
  font-size: 30px;
  letter-spacing: 3px;
  line-height: 1.1;
  color: var(--text);
}
.login-sub {
  font-size: 11px;
  letter-spacing: 2.4px;
  text-transform: uppercase;
  color: var(--text-3);
  margin-top: 4px;
}
.login-rule {
  display: block;
  width: 46px; height: 2px;
  border-radius: 2px;
  background: var(--brass);
}
.login-tagline {
  margin: 0;
  font-size: 16px;
  line-height: 1.65;
  color: var(--text-2);
}
.login-features {
  list-style: none;
  padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 16px;
}
.login-features li {
  display: flex; align-items: center; gap: 14px;
  font-size: 14px;
  color: var(--text-2);
  line-height: 1.45;
}
.feat-icon {
  flex: none;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 0.5px solid rgba(var(--brass-rgb), 0.40);
  border-radius: 11px;
  background: rgba(var(--brass-rgb), 0.10);
  color: var(--brass-ink);
}
.feat-icon svg { width: 19px; height: 19px; }
/* Dot field in the lower corner — decoration only, hidden from a11y. */
.login-dots {
  position: absolute;
  inset-block-end: 18px; inset-inline-start: 18px;
  width: 108px; height: 58px;
  background-image: radial-gradient(rgba(var(--brass-rgb), 0.55) 1px, transparent 1px);
  background-size: 11px 11px;
  opacity: 0.5;
  pointer-events: none;
}
.login-footer {
  width: 100%;
  text-align: center;
  font-size: 11px;
  letter-spacing: 0.8px;
  color: var(--text-3);
  margin-top: 28px;
  padding: 14px 16px 8px;
}
.extras-link {
  color: var(--text-3);
  text-decoration: none;
  font-size: 12px;
}
.extras-link:hover { color: var(--text-2); text-decoration: none; }

@media (max-width: 720px) {
  .login-shell {
    grid-template-columns: 1fr;
    gap: 24px;
    max-width: 420px;
  }
  .login-welcome {
    /* On phones the welcome panel is shorter and less prominent. */
    padding: 24px 20px;
    gap: 16px;
  }
  .login-name { font-size: 24px; }
  .login-tagline { font-size: 14px; }
  .login-features li { font-size: 13px; }
  .login-card { padding: 28px 22px; }
  .login-card h1 { font-size: 27px; }
  /* The dot field crowds a narrow panel — drop it rather than shrink it. */
  .login-dots { display: none; }
}

/* ============================================================
   Toast feedback + HTMX indicator + button-busy state (P10)
   Recommended (NOT applied globally yet):
     - on htmx-driven forms, add
         hx-on::before-request="this.setAttribute('aria-busy', 'true')"
         hx-on::after-request="this.removeAttribute('aria-busy')"
       to surface the busy state on the submit button.
   ============================================================ */
.toast-stack {
  position: fixed;
  inset-block-end: 16px;
  inset-inline-end: 16px;
  z-index: 200;
  display: flex; flex-direction: column; gap: 8px;
  max-width: 360px;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  min-width: 240px;
  background: var(--bone);
  color: var(--onyx);
  border: 0.5px solid var(--line-2);
  border-inline-start: 3px solid var(--brass-ink);
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: 13px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.32);
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 300ms ease, transform 300ms ease;
  animation: toast-in 300ms ease forwards;
}
@keyframes toast-in {
  to { opacity: 1; transform: translateY(0); }
}
.toast.is-leaving { opacity: 0; transform: translateY(-6px); }
.toast-info     { border-inline-start-color: var(--brass-ink); }
.toast-success  { border-inline-start-color: var(--good); }
.toast-error    { border-inline-start-color: var(--bad); }
.toast-warn     { border-inline-start-color: var(--amber); }

/* HTMX request feedback */
.htmx-indicator { opacity: 0; transition: opacity 160ms ease; }
.htmx-request .htmx-indicator { opacity: 1; }
.htmx-request.htmx-indicator { opacity: 1; }

[aria-busy="true"] {
  opacity: 0.5;
  cursor: progress;
  pointer-events: none;
}

/* ============================================================
   Empty state partial (P3)
   ============================================================ */
.empty-state {
  background: var(--surface);
  border: 0.5px solid var(--line);
  border-top: 3px solid var(--brass-ink);
  border-radius: var(--radius-lg);
  padding: 56px 32px;
  text-align: center;
  max-width: 520px;
  margin: 24px auto;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.empty-state .es-icon {
  font-size: 36px;
  line-height: 1;
  color: var(--brass-ink);
  margin-bottom: 6px;
}
.empty-state .es-title {
  font-family: var(--serif);
  font-size: 22px;
  margin: 0;
  color: var(--text);
}
.empty-state .es-body {
  font-size: 13px;
  color: var(--text-2);
  margin: 0 0 10px;
  max-width: 380px;
  line-height: 1.55;
}
.empty-state .btn-brass { margin-top: 6px; }

/* ============================================================
   Print stylesheet — shipping slips & daily production sheet (P12)
   ============================================================ */
@media print {
  .topbar, .sidebar, .lang-toggle, .nav-burger, .nav-backdrop,
  .toast-stack, .messages, .bell-menu, .user-menu, .fy-menu,
  .filter-bar, .filter-actions, .row-actions, .btn-row,
  .active-filters {
    display: none !important;
  }
  body { background: white !important; color: black !important; font-size: 11pt; }
  .layout { grid-template-columns: 1fr !important; }
  .content { padding: 0 !important; }
  .tbl, .tbl th, .tbl td { border-color: #999 !important; color: #000 !important; }
  .card { border-color: #999 !important; }

  .print-only { display: block !important; }
  .no-print   { display: none !important; }

  .print-letterhead {
    display: flex; align-items: center; gap: 14px;
    padding: 0 0 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid #333;
  }
  .print-letterhead img { height: 40px; width: auto; }
  .print-letterhead .print-company {
    font-family: var(--serif);
    font-size: 18pt;
    letter-spacing: 2px;
    color: #000;
  }
  .print-letterhead .print-title {
    margin-inline-start: auto;
    font-size: 11pt;
    color: #333;
  }
}
.print-only { display: none; }


/* ===========================================================================
   Table sorting + pagination controls  (v1.9.0)
   Used by partials/_sort_th.html and partials/_table_nav.html via the
   {% sort_th %} / {% table_nav %} template tags.
   =========================================================================== */
.tbl th .sort-link {
  display: inline-flex; align-items: center; gap: 5px;
  color: inherit; text-decoration: none;
  text-transform: inherit; letter-spacing: inherit; font-size: inherit;
  white-space: nowrap;
}
.tbl th .sort-link:hover { color: var(--brass-ink); }
.tbl th .sort-link.is-active { color: var(--brass-ink); }
.tbl th.num .sort-link { justify-content: flex-end; width: 100%; }
.sort-caret { font-size: 11px; opacity: .45; line-height: 1; }
.sort-link.is-active .sort-caret { opacity: 1; }
.sort-link:hover .sort-caret { opacity: .9; }

.table-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  padding: 10px 2px 0;
  font-size: 12px;
}
.table-nav-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.table-nav-label { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }
.page-sizes { display: inline-flex; gap: 2px; }
.page-size {
  display: inline-block; min-width: 28px; text-align: center;
  padding: 3px 8px; border: 0.5px solid var(--line); border-radius: 4px;
  color: var(--text-3); text-decoration: none;
  font-family: var(--mono); font-size: 11px;
}
.page-size:hover { border-color: var(--brass-ink); color: var(--brass-ink); }
.page-size.is-active { background: var(--brass); border-color: var(--brass-ink); color: var(--on-brass); }
.page-steps { display: inline-flex; align-items: center; gap: 8px; }
.page-step {
  display: inline-block; padding: 3px 10px;
  border: 0.5px solid var(--line); border-radius: 4px;
  color: var(--text-2); text-decoration: none;
}
.page-step:hover { border-color: var(--brass-ink); color: var(--brass-ink); }
.page-step.is-off { opacity: .3; }
.page-of { font-size: 11px; }

@media print { .table-nav { display: none !important; } }

/* --- Per-table search box ------------------------------------------------
   Sits above any table rendered through build_table. Logical properties
   throughout (inline-start/end, not left/right) so the icon stays on the
   leading edge and the buttons on the trailing edge in Farsi RTL as well
   as English LTR - a hardcoded `left` would put the magnifier under the
   text in one of the two languages. */
.tbl-search { margin: 0 0 10px; }
.tbl-search-box {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  position: relative;
}
.tbl-search-field {
  /* The icon positions against THIS, not against .tbl-search-box. That
     row wraps once the Search button or a Clear link no longer fits, and
     an icon centred on a wrapped two-line row floats between the lines
     instead of sitting in the field. */
  position: relative;
  display: flex;
  flex: 1 1 260px;
  min-width: 180px;
  max-width: 420px;
}
.tbl-search-field .tbl-search-input { flex: 1 1 auto; min-width: 0; }

.tbl-search-icon {
  position: absolute; inset-inline-start: 11px;
  /* Centred on the input. It previously had only an inline offset, so
     `position: absolute` left it at its static block position - the
     top of the box, not the middle of the field. */
  top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center;
  color: var(--text-3); pointer-events: none;
}
.tbl-search-input {
  flex: 1 1 260px; min-width: 180px; max-width: 420px;
  /* The indent that keeps the text clear of the magnifier.

     This is written on a two-class selector on purpose. The generic
     control rule further up matches `input[type="search"]`, which is
     specificity (0,1,1) and therefore OUTRANKS a single class - so its
     `padding: 11px 12px` shorthand was quietly winning and the indent
     here never applied at all. That, not the width of the old text
     glyph, is why the icon sat on top of the first letter.

     16px of icon starting at 11px needs 27px cleared; 35px leaves a
     comfortable gap. */
  padding: 7px 10px;
  background: var(--surface-2); color: var(--text);
  border: 0.5px solid var(--line); border-radius: 5px;
  font-family: inherit; font-size: 13px;
}
.tbl-search-icon svg { width: 16px; height: 16px; display: block; }
.tbl-search-input:focus {
  outline: none; border-color: var(--brass-ink);
}
.tbl-search-box .tbl-search-input { padding-inline-start: 35px; }
.tbl-search-input::placeholder { color: var(--text-3); }
/* The browser's own clear affordance duplicates our Clear link and sits
   awkwardly in RTL, so it is suppressed. */
.tbl-search-input::-webkit-search-cancel-button { display: none; }
.tbl-search-go {
  padding: 7px 14px; border-radius: 5px; cursor: pointer;
  background: var(--brass); color: var(--on-brass);
  border: 0.5px solid var(--brass-ink);
  font-family: inherit; font-size: 12px;
}
.tbl-search-go:hover { filter: brightness(1.08); }
.tbl-search-clear {
  padding: 7px 10px; font-size: 12px;
  color: var(--text-3); text-decoration: none;
}
.tbl-search-clear:hover { color: var(--brass-ink); }
.tbl-search-count { font-size: 11px; margin-top: 6px; }
@media print { .tbl-search { display: none !important; } }

/* --- Fiscal-year multi-select picker (v1.10.0) --------------------------- */
.fy-drop-multi { min-width: 230px; padding: 10px; }
.fy-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
  color: var(--text-3); padding: 2px 4px 8px;
  border-bottom: 0.5px solid var(--line); margin-bottom: 6px;
}
.fy-list { max-height: 260px; overflow-y: auto; }
.fy-check {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 4px; cursor: pointer; font-size: 13px;
}
.fy-check:hover { background: var(--surface-2); }
.fy-check.is-active { color: var(--brass-ink); }
.fy-check input { accent-color: var(--brass-ink); }
.fy-span { font-size: 11px; }
.fy-apply { width: 100%; margin-top: 8px; padding: 7px; font-size: 12px; }
.fy-plus {
  font-family: var(--mono); font-size: 11px;
  background: var(--brass); color: var(--on-brass);
  border-radius: 3px; padding: 1px 4px; margin-inline-start: 2px;
}

/* Company switcher (multi-company P2): the FY dropdown's look, with each
   company a one-button form. Buttons drop their own chrome so a row reads
   like the year rows; spacing by margin, not flex gap (iOS 12). */
.co-switch { margin: 0; }
.co-switch .fy-check {
  width: 100%; justify-content: space-between;
  background: none; border: 0; color: inherit; font: inherit;
  font-size: 13px; text-align: start;
}
.co-switch .fy-check > span + span { margin-inline-start: 12px; }
.co-switch .fy-check > span { white-space: nowrap; }
.co-switch .fy-check.is-active { color: var(--brass-ink); cursor: default; }
.co-page-link {
  display: block; margin-top: 6px; padding: 8px 8px 2px;
  border-top: 0.5px solid var(--line); font-size: 12px;
}

/* ============================================================
   v1.14 — Jalali-first dates
   Every date control is Shamsi. The Gregorian equivalent renders
   underneath as a secondary hint (see static/js/xw-dates.js), so the
   primary calendar is unambiguous but foreign paperwork can still be
   cross-checked without leaving the form.
   ============================================================ */
.xw-date-greg {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
  margin-top: 3px;
  min-height: 14px;      /* reserve the line so the layout does not jump
                            the moment a date is picked */
  line-height: 14px;
  letter-spacing: 0;      /* never letter-space digits */
  direction: ltr;         /* an ISO date reads left-to-right even in RTL */
  text-align: start;
  unicode-bidi: isolate;  /* keep it from reordering inside an RTL parent */
}
/* Inside a filter bar, .filter-bar aligns every field's BOTTOM edge
   (align-items: end) so controls line up with the Apply/Reset buttons
   beside them. The Gregorian hint's reserved 14px line (above) used to
   count toward a date field's height even while empty — the common case,
   before anything is typed — so a From/To field always sat taller than
   its neighbours and the whole row read as misaligned. Taking the hint
   out of flow here lets it render in the same visual spot without
   affecting that alignment math. */
.filter-bar .filter-field { position: relative; }
.filter-bar .filter-field .xw-date-greg {
  position: absolute;
  top: 100%;
  inset-inline-start: 0;
  margin-top: 2px;
  min-height: 0;
}

/* v1.15.1 — number spinners.
   The up/down arrows are a small target sitting right next to the digits,
   and a mis-click silently changes a quantity. Typing is the only intended
   way to set these values; the wheel is already neutralised in
   static/js/xw-inputs.js. */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}

/* ============================================================
   v2.0 — Operations console dashboard components
   New primitives for the redesigned landing page: a 5-column KPI
   strip, a card header with an optional trailing action, a trend
   chip, and wrappers for the three server-rendered SVG charts
   (gauge / ring / donut — geometry computed in apps/core/charts.py,
   no client-side charting library). Everything below reuses the
   existing --brass/--good/--bad/--amber/--glow tokens so it retints
   automatically with the palette above.
   ============================================================ */
.dash-hero-5 { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 1100px) { .dash-hero-5 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 720px)  { .dash-hero-5 { grid-template-columns: 1fr; } }

.kpi-icon-chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex-shrink: 0;
  background: var(--surface-2); border: 0.5px solid var(--line);
  border-radius: 6px; font-size: 11px; color: var(--brass-ink);
}
.kpi-card-head { display: flex; align-items: center; gap: 8px; }

.trend-chip {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 6px; border-radius: 4px; white-space: nowrap;
}
.trend-chip.is-up   { color: var(--good); background: rgba(var(--good-rgb),.14); border: 0.5px solid rgba(var(--good-rgb),.3); }
.trend-chip.is-down { color: var(--bad);  background: rgba(var(--bad-rgb),.14);  border: 0.5px solid rgba(var(--bad-rgb),.3); }

.card-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 14px; gap: 10px;
}
.card-title { font-size: 13px; font-weight: 600; color: var(--text); }
.card-sub   { font-family: var(--mono); font-size: 9px; color: var(--text-3); letter-spacing: .06em; text-transform: uppercase; margin-top: 3px; }
.card-action { font-size: 11px; color: var(--brass-ink); white-space: nowrap; }

/* Vertical bar chart — material balance, one bar per raw material. Bar
   color is the material's own Color.hex_code when set (an inline style),
   falling back to the .series-* cycle below when the color has no swatch
   filled in (e.g. glazing powders, which aren't color-bound at all). */
.vbar-chart { display: flex; align-items: flex-end; gap: 18px; height: 190px; padding: 8px 4px 0; overflow-x: auto; }
.vbar-col { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; min-width: 56px; flex: 1 0 auto; }
.vbar-value { font-family: var(--mono); font-size: 11px; color: var(--text-2); margin-bottom: 6px; white-space: nowrap; }
.vbar-track { flex: 1; width: 32px; display: flex; align-items: flex-end; background: var(--well); border-radius: 4px; overflow: hidden; }
.vbar { width: 100%; min-height: 2px; border-radius: 4px 4px 0 0; transition: height 240ms ease; }
.vbar.is-negative { opacity: .55; }
.vbar-label { font-family: var(--sans); font-size: 10.5px; color: var(--text-2); text-align: center; margin-top: 8px; white-space: nowrap; }

/* A tiny fixed palette for chart series, cycled by index (data-viz only —
   deliberately distinct from the semantic --good/--bad/--amber trio so a
   4-slice donut doesn't accidentally read as a status indicator). */
.series-a { color: var(--chart-1); fill: var(--chart-1); background: var(--chart-1); }
.series-b { color: var(--chart-3); fill: var(--chart-3); background: var(--chart-3); }
.series-c { color: var(--glow); fill: var(--glow); background: var(--glow); }
.series-d { color: var(--amber); fill: var(--amber); background: var(--amber); }

/* ============================================================
   Stock-urgency palette (v2.0) — four tiers, not the usual three.
   Introduced for the Raw Materials Dashboard's 6-status model, and now
   shared with the Main Dashboard's "Materials Requiring Attention" panel
   so a "Critical" badge means the same color everywhere it appears.
   Data Issue / Out of Stock / Critical all read red — the badge TEXT
   tells those apart; color only needs to say "this needs attention".
   ============================================================ */
.mtl-s-red    { color: var(--bad); }
.mtl-s-amber  { color: var(--amber); }
.mtl-s-yellow { color: var(--yellow); }
.mtl-s-green  { color: var(--good); }
.mtl-badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 500; white-space: nowrap;
}


/* ============================================================
   Mobile (v2.3) — phone layout for the whole console
   ============================================================
   The console was built desktop-first and read badly on a phone: the
   topbar wrapped onto two cramped rows, every list screen opened with a
   full screen of filter controls before a single row of data, and tables
   were clipped rather than scrollable. Nothing here changes the desktop
   layout; everything is inside a max-width query or fixes a bug that was
   wrong in both.

   Logical properties throughout (inline-start/end, not left/right) so
   Farsi RTL gets the mirror image rather than a broken copy.
   ------------------------------------------------------------ */

/* --- tables: scroll, do not clip ---------------------------------- */
/* `.tbl-card` was `overflow: hidden`, which CLIPS a wide table instead of
   letting it scroll - on a phone the right-hand columns simply could not
   be reached. `auto` still clips the rounded corners, which is all the
   original rule was for. */
.tbl-card {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}
/* These three were physical properties, so in Farsi the padding landed on
   the wrong edge and the first column sat flush against the border. */
.tbl-card .tbl th { padding-inline-start: 16px; }
.tbl-card .tbl td:first-child { padding-inline-start: 16px; }
.tbl-card .tbl td:last-child { padding-inline-end: 16px; }

/* --- the filter drawer -------------------------------------------- */
/* Desktop: the toggle does not exist and the panel is simply there. */
.filter-drawer-toggle { display: none; }
.filter-drawer-body { display: block; }

.filter-drawer-toggle {
  align-items: center; gap: 8px;
  width: 100%; margin-bottom: 12px;
  padding: 11px 14px;
  background: var(--surface); color: var(--text);
  border: 0.5px solid var(--line-2); border-radius: var(--radius);
  font: inherit; font-size: 13px; text-align: start;
  cursor: pointer;
}
.filter-drawer-toggle:hover { border-color: var(--brass-ink); }
.filter-drawer-label { flex: 1; text-align: start; }
.filter-drawer-count {
  min-width: 20px; padding: 1px 7px; border-radius: 10px;
  background: var(--brass); color: var(--on-brass);
  font-family: var(--mono); font-size: 11px; text-align: center;
}
.filter-drawer-caret {
  /* inline-block, not the span default: a transform has no effect on a
     non-replaced INLINE box, so the open/closed caret never turned. */
  display: inline-block;
  color: var(--text-3); font-size: 11px;
  transition: transform 140ms ease;
}
.filter-drawer-caret.is-open { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .filter-drawer-caret { transition: none; }
}

@media (max-width: 720px) {
  .filter-drawer-toggle { display: flex; }
  .filter-drawer-body { display: none; }
  .filter-drawer-body.is-open { display: block; }
}

/* --- phone layout -------------------------------------------------- */
@media (max-width: 720px) {

  /* Topbar on one row.
     It wrapped because the brand text, the fiscal-year label, the user's
     full name and four controls cannot share 375px. The identity is the
     logo; the name is not needed to know which app you are in. */
  .topbar {
    padding: 10px 12px;
    gap: 8px;
    flex-wrap: nowrap;
  }
  .topbar .brand-text { display: none; }
  .topbar .logo { width: 30px; height: 30px; }
  .topbar-right { gap: 6px; flex-wrap: nowrap; }
  .topbar .user-name { display: none; }
  .topbar .user-pill { padding: 4px; gap: 0; }
  .topbar .user-pill .caret { display: none; }
  .topbar-fy { padding: 5px 8px; font-size: 11px; }
  .topbar-fy .muted { display: none; }        /* the "FY" word; the year is enough */
  .theme-toggle-btn { width: 34px; height: 34px; }
  .lang-toggle { transform: scale(0.9); transform-origin: center; }

  /* The company switcher (multi-company) gets a row of its own. Account,
     language, theme, bell, year and menu already fill a 390px bar; with the
     chip as well the row overflowed and the chip slid over the logo. Only
     people in several companies see it, so only their bar grows a line. */
  .topbar .brand-block { flex: 0 0 auto; }
  .topbar-right { min-width: 0; flex-wrap: wrap; row-gap: 8px; }
  .topbar-right > .co-menu {
    order: 99;
    flex: 1 0 100%;
    display: flex;
    justify-content: flex-end;
  }
  .topbar-right > .co-menu .topbar-fy .muted { display: inline; }
  .topbar-right > .co-menu .brass {
    max-width: 60vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* Page headers stack instead of fighting for one line. */
  .content-header { flex-direction: column; align-items: stretch; gap: 10px; }
  .content-header .btn-row { width: 100%; }
  .content-header .btn-row > * { flex: 1; text-align: center; }
  h1 { font-size: 26px; line-height: 1.25; }

  /* Filter fields go full width once the drawer is open - a two-column
     grid at this size produces controls too narrow to read a date in. */
  .filter-bar { padding: 12px; gap: 12px; }
  .filter-bar .filter-field { min-width: 0; width: 100%; }
  .filter-bar .filter-search { width: 100%; }
  .filter-bar .filter-actions { width: 100%; margin-inline-start: 0; }
  .filter-bar .filter-actions > * { flex: 1; }

  /* Inputs at a size a thumb can hit and iOS will not zoom into. Safari
     zooms the whole page when a focused input is under 16px, which is why
     the header jumped on every tap. */
  .filter-bar input, .filter-bar select,
  .tbl-search-input, .field input, .field select, .field textarea {
    font-size: 16px;
    min-height: 44px;
  }

  /* Date fields read as empty black boxes when they hold no value: no
     placeholder, no visible affordance. Give them the calendar hint and
     the monospace shape the filled state has. */
  input.jdp, input[type="date"] {
    background-image: none;
    font-family: var(--mono);
  }
  input.jdp::placeholder { color: var(--text-3); letter-spacing: 0.02em; }

  /* The standalone table search, where a screen still has one. */
  .tbl-search-box { flex-wrap: wrap; }
  .tbl-search-field { flex: 1 1 100%; max-width: none; }
  .tbl-search-go, .tbl-search-clear { flex: 1; text-align: center; }

  /* Tables: keep them tables. A production log is a grid and turning each
     row into a card destroys the column-scanning that makes it useful, so
     it scrolls sideways instead - with the first column pinned so you can
     still tell which row you are reading. */
  .tbl { font-size: 12px; }
  .tbl th, .tbl td { padding: 9px 10px; white-space: nowrap; }
  .tbl-card { border-radius: var(--radius); }

  /* Pager and row-size controls stack rather than squeezing. */
  .table-nav { gap: 8px; }
  .table-nav-right { width: 100%; justify-content: space-between; }

  /* KPI tiles and dashboard rows are already single-column at this width
     via their own queries; only the type needs to come down. */
  .kpi-value { font-size: 24px; }

  /* Forms: one field per row. Two columns at 375px gives inputs about
     150px wide, which cannot hold a date or a mould name. */
  .form-row { grid-template-columns: 1fr; gap: 12px; }

  /* Buttons reachable by thumb. */
  .btn, .btn-brass { min-height: 44px; padding: 10px 16px; }
}

/* Very narrow phones (iPhone SE and similar). */
@media (max-width: 380px) {
  .topbar { padding: 8px 10px; }
  .topbar-right { gap: 4px; }
  h1 { font-size: 22px; }
  .kpi-value { font-size: 21px; }
}


/* ============================================================
   Site footer (v2.2.1) - vendor credit strip
   ============================================================
   Rendered once in base.html, outside the authenticated/guest branch,
   so it lands on every page including login and the error pages.

   Flat by design: a hairline rule and small type. No card, no panel, no
   background block - the same treatment the rest of the app gives a
   section boundary.
   ------------------------------------------------------------ */
.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 18px 20px;
  border-block-start: 0.5px solid var(--line);
  font-size: 12px;
  color: var(--text-3);
  /* body.guest is a centring column flexbox, so without this the footer
     shrinks to its text width and the hairline above it stops short. */
  align-self: stretch;
}

.site-footer-sep { color: var(--line-2); }

.site-footer-link {
  color: var(--text-2);
  text-decoration: none;
  /* `x-wares.com` is a Latin run sitting in an otherwise RTL line. Isolate
     it so the bidi algorithm cannot drag the neighbouring separators
     inside it. `isolate` is direction-NEUTRAL - it does not force LTR the
     way dir="ltr" would, it only stops the run from leaking. */
  unicode-bidi: isolate;
}
.site-footer-link:hover,
.site-footer-link:focus-visible {
  color: var(--brass);
  /* Underline as well as colour. Colour on its own is not a signal. */
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* Focus ring comes from the global `a:focus-visible` rule further up -
   deliberately not re-declared here, so the footer matches every other
   link in the app. */

/* Keep the footer at the bottom of the VIEWPORT on a short page, not
   merely at the end of the document. `.layout` carries
   `min-height: calc(100vh - 67px)`, which on a sparse page would push
   the footer just past the fold - present, invisible, and reachable only
   by scrolling a page that has nothing else to scroll.

   Making the app shell a column flexbox lets `.layout` absorb the slack
   instead. The sidebar still reaches the bottom, because a stretched
   flex item is exactly as tall as the min-height made it before.
   body.guest already handles this on its own: the login shell's
   `margin: auto` eats the free space and drops the footer to the floor. */
body.app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
body.app .layout {
  flex: 1 1 auto;
  min-height: 0;
}

@media (max-width: 720px) {
  .site-footer { padding: 14px 12px; gap: 6px; }
}


/* ============================================================
   Screen-reader-only text (v2.3)
   ============================================================
   The app had no visually-hidden utility, so anything that conveyed
   meaning through colour or an icon alone had no text alternative
   available to it. Added while building the chat roster, where a green
   dot means "online" and a bare dot means nothing at all to a screen
   reader.

   `clip-path` plus a 1px box rather than `display:none` or
   `visibility:hidden`, both of which remove the element from the
   accessibility tree along with the screen - which is the opposite of
   what is wanted here.
   ------------------------------------------------------------ */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ============================================================
   Chat widget (v2.3)
   ============================================================
   A bubble in the bottom corner and a panel above it. One elevated
   surface, because the panel genuinely floats over the page and has
   earned it; everything inside is flat with hairline dividers, the same
   as the rest of the app.

   Brass appears exactly twice - the unread badge and Send - so unread is
   the only thing in here that pulls the eye.

   Logical properties throughout. `inset-inline-end` puts the bubble
   bottom-right in English and bottom-left in Persian with no second rule,
   and the roster/thread panes swap sides for free because they are flex
   children in DOM order.
   ------------------------------------------------------------ */

.chat-widget { position: fixed; inset-block-end: 20px; inset-inline-end: 20px;
               z-index: 150; display: flex; flex-direction: column;
               align-items: flex-end; gap: 12px; }

/* --- the bubble ---------------------------------------------------- */
.chat-bubble {
  position: relative;
  width: 52px; height: 52px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface);
  color: var(--text-2);
  border: 0.5px solid var(--line-2);
  border-radius: 50%;
  cursor: pointer;
  /* The one shadow in the component. It is what says "floating above the
     page" rather than "part of it", which is the entire job here. */
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.28);
  transition: color 140ms ease, border-color 140ms ease;
}
.chat-bubble:hover { color: var(--text); border-color: var(--brass-soft); }
.chat-bubble-icon { width: 22px; height: 22px; }

.chat-bubble-badge {
  position: absolute; inset-block-start: -2px; inset-inline-end: -2px;
  min-width: 19px; height: 19px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brass); color: var(--on-brass);
  border-radius: 10px;
  font-family: var(--mono); font-size: 11px; line-height: 1;
}

/* --- the panel ----------------------------------------------------- */
.chat-panel {
  width: 620px; height: 460px;
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 0.5px solid var(--line-2);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.34);
  overflow: hidden;
}

.chat-panel-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px;
  border-block-end: 0.5px solid var(--line);
}
.chat-panel-title {
  flex: 1; margin: 0;
  font-family: var(--serif); font-size: 17px; font-weight: 500;
  text-align: start;
}
.chat-close, .chat-back {
  background: none; border: 0; cursor: pointer;
  color: var(--text-3); font: inherit; font-size: 15px;
  padding: 4px 8px; border-radius: var(--radius);
}
.chat-close:hover, .chat-back:hover { color: var(--text); }
/* The chevron is a direction, so it has to turn round with the text. */
[dir="rtl"] .chat-back span[aria-hidden] { display: inline-block;
                                           transform: scaleX(-1); }
.chat-back { display: none; }          /* phone only - see the query below */

.chat-panes { flex: 1; display: flex; min-height: 0; }

.chat-roster-pane {
  width: 210px; flex: none;
  border-inline-end: 0.5px solid var(--line);
  /* The search box stays put while the names scroll under it, so the
     scroll belongs to the list rather than to the whole pane. */
  display: flex; flex-direction: column;
  overflow: hidden;
}
#chat-roster { flex: 1; min-height: 0; overflow-y: auto; }
.chat-thread-pane {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
}

/* --- roster -------------------------------------------------------- */
.chat-roster { list-style: none; margin: 0; padding: 6px 0; }
.chat-roster-btn {
  width: 100%;
  display: flex; align-items: center; gap: 9px;
  padding: 8px 14px;
  background: none; border: 0; cursor: pointer;
  color: var(--text-2); font: inherit; font-size: 13px;
  text-align: start;
}
.chat-roster-btn:hover { background: var(--surface-2); color: var(--text); }
.chat-roster-item.is-selected .chat-roster-btn {
  background: var(--surface-2); color: var(--text);
  /* A border on the reading edge, so it marks the same side in both
     directions. */
  box-shadow: inset 2px 0 0 0 var(--brass);
}
[dir="rtl"] .chat-roster-item.is-selected .chat-roster-btn {
  box-shadow: inset -2px 0 0 0 var(--brass);
}
.chat-roster-item.has-unread .chat-roster-btn { color: var(--text); }
.chat-roster-name { flex: 1; overflow: hidden; text-overflow: ellipsis;
                    white-space: nowrap; }
.chat-roster-unread {
  min-width: 18px; padding: 1px 6px; border-radius: 9px;
  background: var(--brass); color: var(--on-brass);
  font-family: var(--mono); font-size: 10px; text-align: center;
}
.chat-roster-empty { padding: 14px; font-size: 12px; }

.chat-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  /* Offline is the default, so a row whose status is missing or unknown
     reads as "not reachable" rather than falsely as available. */
  background: var(--line-2);
}
.chat-dot[data-status="online"]  { background: var(--good); }
.chat-dot[data-status="away"]    { background: var(--amber); }
.chat-dot[data-status="busy"]    { background: var(--bad); }
.chat-dot[data-status="offline"] { background: var(--line-2); }

/* --- thread -------------------------------------------------------- */
.chat-thread-head {
  padding: 9px 14px;
  border-block-end: 0.5px solid var(--line);
  font-size: 13px; color: var(--text);
}
.chat-messages { flex: 1; overflow-y: auto; padding: 12px 14px;
                 display: flex; flex-direction: column; gap: 10px; }
.chat-placeholder { padding: 22px 16px; font-size: 13px; text-align: center; }

/* The bubble itself is `.chat-msg` - see the UI-kit block further down,
   which owns the incoming/outgoing tint, the border and the read tick.
   `.chat-msg-body` used to be the bubble here, with its own background,
   padding and radius; both rule sets applied at once and every message
   drew a second darker box inside the tinted one. The body is text now. */
.chat-msg { display: flex; flex-direction: column; gap: 3px; }
/* align-self, not float or margin-auto: it mirrors on its own under RTL. */
.chat-msg.is-mine   { align-self: flex-end;   text-align: end; }
.chat-msg.is-theirs { align-self: flex-start; text-align: start; }
.chat-msg-body {
  overflow-wrap: anywhere;      /* a pasted URL must not widen the panel */
}
.chat-msg-time { font-size: 10px; color: var(--text-3);
                 font-family: var(--mono); }

.chat-more { align-self: center; background: none; border: 0;
             color: var(--text-3); font: inherit; font-size: 11px;
             cursor: pointer; padding: 4px 8px; }
.chat-more:hover { color: var(--brass); }
.chat-empty { padding: 18px 4px; font-size: 12px; text-align: center; }

/* --- compose ------------------------------------------------------- */
.chat-compose {
  display: flex; align-items: flex-end; gap: 8px;
  padding: 10px 12px;
  border-block-start: 0.5px solid var(--line);
}
.chat-compose textarea {
  flex: 1; resize: none;
  padding: 7px 10px;
  background: var(--bg); color: var(--text);
  border: 0.5px solid var(--line-2); border-radius: var(--radius);
  font: inherit; font-size: 13px; line-height: 1.45;
}

/* --- phone --------------------------------------------------------- */
/* Two panes will not fit in 375px, so the panel goes full-screen and
   shows one at a time. Which one is driven by a data attribute Alpine
   already maintains, so no JS media query is involved and the two never
   disagree. */
@media (max-width: 720px) {
  .chat-widget { inset-block-end: 14px; inset-inline-end: 14px; }
  .chat-panel {
    position: fixed; inset: 0;
    width: auto; height: auto;
    border: 0; border-radius: 0;
  }
  .chat-back { display: flex; align-items: center; gap: 4px; }
  .chat-roster-pane { width: 100%; border-inline-end: 0; }
  .chat-panes[data-has-thread="true"]  .chat-roster-pane { display: none; }
  .chat-panes[data-has-thread="false"] .chat-thread-pane { display: none; }
  .chat-msg { max-width: 88%; }
  /* 16px or iOS zooms the whole page when the field takes focus. */
  .chat-compose textarea { font-size: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .chat-bubble { transition: none; }
}

/* A checkbox and its words as one click target. The app had several of
   these built from inline styles on a <label>; this is the same thing,
   named, so the next one does not have to be re-invented. Logical gap so
   the box sits on the reading edge in both directions. */
.check-inline {
  display: inline-flex; align-items: center; gap: 9px;
  cursor: pointer; font-size: 13px; color: var(--text-2);
}
.check-inline input[type="checkbox"] {
  width: 16px; height: 16px; accent-color: var(--brass); cursor: pointer;
}
.check-inline:hover { color: var(--text); }

/* === Chat emoji picker (v2.3) ======================================= */
/* A small curated grid above the composer. `position: relative` on the
   wrapper and `inset-block-end: 100%` on the grid puts it above the
   button in both writing directions without a single physical property -
   in RTL the panel simply anchors to the other edge and nothing else
   changes. */
.chat-emoji { position: relative; flex: 0 0 auto; }

.chat-emoji-toggle {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  background: none; border: 0; cursor: pointer;
  color: var(--text-3); border-radius: var(--radius);
}
.chat-emoji-toggle svg { width: 19px; height: 19px; }
.chat-emoji-toggle:hover { color: var(--brass); }
.chat-emoji-toggle:focus-visible,
.chat-emoji-btn:focus-visible {
  outline: 2px solid var(--brass); outline-offset: 1px;
}

.chat-emoji-grid {
  /* Closed is the default, and it is a plain CSS fact rather than
     something JavaScript has to assert. Alpine adds .is-open to reveal
     it. See the comment in _thread.html: x-show did not apply its
     initial hide on htmx-swapped markup, which left the picker open
     after every send. */
  display: none;
  position: absolute;
  inset-block-end: calc(100% + 6px);
  inset-inline-end: 0;
  z-index: 5;
  /* Fixed tracks, and the box sizes itself to them. With `1fr` columns
     and a declared width the maths runs the other way: the buttons did
     not shrink to the track, the content overflowed by a couple of
     pixels, a scrollbar appeared, the scrollbar narrowed the content box,
     and it overflowed further. Absolutely positioned boxes shrink to fit,
     so letting the content decide the width ends that feedback. */
  grid-template-columns: repeat(8, 30px);
  gap: 2px;
  max-height: 190px; overflow-y: auto; overflow-x: hidden;
  padding: 8px;
  background: var(--surface-2);
  border: 0.5px solid var(--line-2);
  border-radius: var(--radius-lg);
}

.chat-emoji-grid.is-open { display: grid; }

.chat-emoji-btn {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 0; cursor: pointer;
  border-radius: var(--radius);
  /* The system emoji font, not the UI stack. Vazirmatn and Plus Jakarta Sans have no
     emoji coverage, so without this the glyphs fall back inconsistently
     and land at the wrong size next to each other. */
  font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji",
               sans-serif;
  font-size: 17px; line-height: 1;
}
.chat-emoji-btn:hover { background: var(--surface); }

/* Message direction follows the message, not the page.
   Emoji are Bidi_Class=ON, so a trailing run of them after Latin text in
   an RTL paragraph resolves to the paragraph direction and jumps to the
   visual start - "press 2 back up (thumbs up)" rendered with the emoji
   leading. `isolate` separates the body from its surroundings but leaves
   ordering inside it alone; `plaintext` takes the direction from the
   first strong character of each line, which is the right rule for
   user-written text: a Persian message reads RTL, an English one reads
   LTR, and emoji stay where they were typed. */
.chat-msg-body { unicode-bidi: plaintext; text-align: start; }

@media (max-width: 720px) {
  /* Narrower than 375px minus the composer padding, so it never pushes
     the panel sideways. */
  .chat-emoji-grid { grid-template-columns: repeat(7, 30px); }
}


/* === Chat: scrollbars, search, status (v2.3.1) ====================== */

/* The app has no scrollbar styling at all, so the chat panel - a small
   floating dark surface - got the browser's default light scrollbar
   straight down the middle of it. Scoped to the panel rather than applied
   globally: this is the surface where it reads as wrong, and restyling
   every scrollbar in the app is a bigger visual change than was asked
   for. */
.chat-panel,
.chat-panel * {
  scrollbar-width: thin;                          /* Firefox */
  scrollbar-color: var(--line-2) transparent;
}
.chat-panel *::-webkit-scrollbar { width: 8px; height: 8px; }
.chat-panel *::-webkit-scrollbar-track { background: transparent; }
.chat-panel *::-webkit-scrollbar-thumb {
  background: var(--line-2);
  border-radius: 999px;
  /* A transparent border plus content-box clipping insets the thumb, so
     it reads as a slim pill with air around it rather than a bar wedged
     against the edge. */
  border: 2px solid transparent;
  background-clip: content-box;
}
.chat-panel *::-webkit-scrollbar-thumb:hover {
  background: var(--text-3);
  background-clip: content-box;
}
.chat-panel *::-webkit-scrollbar-corner { background: transparent; }

/* --- search over people --------------------------------------------- */
.chat-search {
  position: relative; flex: none;
  padding: 8px 8px 6px;
  border-block-end: 0.5px solid var(--line);
}
.chat-search-icon {
  position: absolute;
  inset-inline-start: 16px;
  /* Centred on the input rather than pushed down by a hand-counted
     offset, which assumed an input height and drifted the moment the
     padding changed. */
  inset-block-start: 50%; transform: translateY(-50%);
  width: 13px; height: 13px;
  color: var(--text-3); pointer-events: none;
}
/* The clear button is centred the same way, for the same reason. */
.chat-search-clear {
  inset-block-start: 50%; transform: translateY(-50%);
}
/* Descendant + class (0,2,0) to outrank the app's global
   `input[type="search"]` rule, which is element+attribute (0,1,1) and was
   winning - its 11px/12px padding left the magnifier sitting on top of
   the placeholder. */
.chat-search .chat-search-input {
  width: 100%;
  padding: 5px 8px;
  /* Room for the icon on the leading side and the clear button on the
     trailing one - logical properties, so both swap under RTL without a
     second rule. */
  padding-inline-start: 26px;
  padding-inline-end: 22px;
  background: var(--bg); color: var(--text);
  border: 0.5px solid var(--line-2); border-radius: var(--radius);
  font: inherit; font-size: 12px;
}
.chat-search .chat-search-input::placeholder { color: var(--text-3); }
.chat-search .chat-search-input:focus-visible {
  outline: 2px solid var(--brass); outline-offset: 1px;
}
/* Otherwise the browser's own clear affordance sits next to ours. */
.chat-search .chat-search-input::-webkit-search-cancel-button { display: none; }
.chat-search-clear {
  position: absolute;
  inset-inline-end: 14px; inset-block-start: 12px;
  width: 18px; height: 18px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer;
  color: var(--text-3); font-size: 15px; line-height: 1;
  border-radius: var(--radius);
}
.chat-search-clear:hover { color: var(--text); }
.chat-search-clear:focus-visible {
  outline: 2px solid var(--brass); outline-offset: 1px;
}
.chat-roster-nomatch { padding: 14px 12px; font-size: 12px; }

/* --- your own status ------------------------------------------------ */
.chat-status {
  flex: none;
  display: flex; align-items: center; gap: 7px;
  padding: 7px 14px;
  border-block-end: 0.5px solid var(--line);
}
.chat-status-select {
  background: none; border: 0;
  color: var(--text-2); font: inherit; font-size: 12px;
  padding: 2px 4px; border-radius: var(--radius);
  cursor: pointer;
}
.chat-status-select:focus-visible {
  outline: 2px solid var(--brass); outline-offset: 1px;
}
/* The dropdown itself is drawn by the OS, so the options need a readable
   background of their own against this dark surface. */
.chat-status-select option { background: var(--surface); color: var(--text); }


/* === Chat: UI-kit visual system (v2.3.1) ============================ */
/* Geometry, states and type scale from the supplied Team Chat kit. The
   kit's literal hex is deliberately NOT copied: --brass / --surface /
   --bg are overridden per tenant at runtime, so the accent maps onto the
   app's own tokens and the few surfaces the app lacked are derived from
   them with color-mix. A white-labelled install stays white-labelled. */
.chat-panel {
  /* Kit: 760x800, minimum practical 600x640. Clamped to the viewport so
     it cannot grow taller than the window on a laptop. */
  width: min(760px, calc(100vw - 40px));
  height: min(800px, calc(100vh - 132px));

  --chat-hover:  color-mix(in srgb, var(--text) 5%, var(--surface));
  --chat-active: color-mix(in srgb, var(--text) 9%, var(--surface));
  --chat-in:     color-mix(in srgb, var(--text) 7%, var(--surface));
  --chat-out:    color-mix(in srgb, var(--brass) 18%, var(--surface));
  --chat-rail:   var(--brass);
}

/* --- sidebar -------------------------------------------------------- */
.chat-roster-pane { width: 232px; }          /* kit: 232 */

.chat-roster-item { position: relative; }
.chat-roster-btn {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 58px;             /* kit: 58px rows */
  padding: 8px 12px;
  background: none; border: 0; cursor: pointer;
  color: inherit; font: inherit; text-align: start;
}
.chat-roster-btn:hover { background: var(--chat-hover); }
.chat-roster-item.is-selected .chat-roster-btn { background: var(--chat-active); }
/* Kit: active rows carry a 2px accent rail on the leading edge. Logical
   inset, so it moves to the right-hand side under RTL by itself. */
.chat-roster-item.is-selected::before {
  content: ""; position: absolute;
  inset-block: 0; inset-inline-start: 0;
  width: 2px; background: var(--chat-rail);
}
.chat-roster-btn:focus-visible {
  outline: 2px solid var(--brass); outline-offset: -2px;
}

.chat-avatar {
  position: relative; flex: none;
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--text-2);
  border: 1px solid var(--line-2);
  font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
}
/* The dot rides the avatar so the name and its state line stay aligned
   however long the name is. */
.chat-avatar-dot {
  position: absolute;
  inset-block-end: -1px; inset-inline-end: -1px;
  width: 9px; height: 9px;
  border: 2px solid var(--surface);
}

.chat-roster-text {
  display: flex; flex-direction: column; gap: 1px;
  min-width: 0; flex: 1;
}
.chat-roster-name {
  font-size: 13px; font-weight: 600;         /* kit: 13/16, 600 */
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-roster-state {
  font-size: 11px; color: var(--text-3);     /* kit: metadata 11/14 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-roster-state[data-status="online"] { color: var(--good); }
.chat-roster-state[data-status="away"]   { color: var(--amber); }
.chat-roster-state[data-status="busy"]   { color: var(--bad); }

.chat-roster-unread {
  flex: none;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brass); color: var(--on-brass);
  border-radius: 999px;
  font-size: 10px; font-weight: 600;
}

/* --- conversation header -------------------------------------------- */
.chat-thread-who { display: flex; flex-direction: column; gap: 1px; }
.chat-thread-with { font-size: 13px; font-weight: 600; }
.chat-thread-state {
  display: flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--text-3);
}
.chat-thread-state[data-status="online"] { color: var(--good); }
.chat-thread-state[data-status="away"]   { color: var(--amber); }
.chat-thread-state[data-status="busy"]   { color: var(--bad); }

/* --- day divider ---------------------------------------------------- */
/* Kit: a centred rule with the date sitting in a gap in it. */
.chat-day {
  display: flex; align-items: center; gap: 10px;
  margin: 14px 0 10px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--text-3);
}
.chat-day::before, .chat-day::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

/* --- messages -------------------------------------------------------- */
.chat-msg {
  position: relative;
  max-width: 72%;                            /* kit: 72% of the column */
  padding: 7px 11px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--chat-in);
}
.chat-msg.is-mine {
  margin-inline-start: auto;
  background: var(--chat-out);
  border-color: color-mix(in srgb, var(--brass) 34%, transparent);
}
.chat-msg-meta {
  display: flex; align-items: baseline; gap: 7px;
  margin-block-end: 2px;
  font-size: 11px; color: var(--text-3);
}
.chat-msg-who { font-weight: 600; }
.chat-msg-time { font-size: 11px; }
.chat-msg-body { font-size: 14px; line-height: 1.5; }   /* kit: 14/21 */
.chat-msg-read {
  position: absolute;
  inset-block-end: 4px; inset-inline-end: 7px;
  font-size: 11px; color: var(--brass);
}
.chat-msg.is-mine .chat-msg-body { padding-inline-end: 14px; }

/* --- empty state ----------------------------------------------------- */
.chat-empty {
  display: flex; flex-direction: column; align-items: center;
  gap: 4px; padding: 40px 20px; text-align: center;
}
.chat-empty-icon { width: 46px; height: 46px; color: var(--text-3); }
.chat-empty-title {
  margin: 8px 0 0;
  font-family: var(--serif); font-size: 17px; color: var(--text);
}
.chat-empty-sub { margin: 0; font-size: 12px; color: var(--text-3); }

/* --- composer -------------------------------------------------------- */
.chat-send { display: inline-flex; align-items: center; gap: 6px; }
.chat-send-icon { width: 15px; height: 15px; }
/* The arrow points along the reading direction, so it mirrors in RTL. */
[dir="rtl"] .chat-send-icon { transform: scaleX(-1); }
.chat-compose-hint {
  margin: 0; padding: 0 14px 9px;
  font-size: 11px; color: var(--text-3);
}

/* --- launcher -------------------------------------------------------- */
/* Kit: 56px, 1px border, badge overlapping the edge, hidden at zero. */
.chat-bubble {
  width: 56px; height: 56px;
  border: 1px solid var(--line-2);
}
.chat-bubble-badge { inset-block-start: -3px; inset-inline-end: -3px; }

@media (max-width: 720px) {
  /* Kit: below 720 the sidebar collapses and the panel fills the
     viewport - which the phone rules above already do. Only the widths
     that were pinned for the desktop layout need releasing. */
  .chat-roster-pane { width: 100%; }
  .chat-panel { width: auto; height: auto; }
}

/* --- your own status, in the header ---------------------------------- */
/* Compact by design. It was a full-width strip under the header, which
   read as the panel's main content when it is a personal setting touched
   about once a day. */
.chat-status {
  flex: none;
  display: flex; align-items: center; gap: 5px;
  padding: 0; border: 0;
  margin-inline-end: 4px;
}
.chat-status-select {
  max-width: 92px;
  background: none; border: 0;
  color: var(--text-3); font: inherit; font-size: 11px;
  padding: 2px 2px; border-radius: var(--radius);
  cursor: pointer;
}


/* === Wide tables inside a plain card (v2.3.1) ======================= */
/* Nearly every table in the console sits in `.tbl-card`, which is already
   a horizontal scroll box (see the v2.3 block above). Five dashboard
   tables sit directly inside a plain `.card` instead and so had no scroll
   box at all: a table wider than the viewport pushed the whole document
   sideways rather than scrolling in place. On the operational dashboard at
   375px the nine-column "Recent production" table ended at 677px, and the
   entire page - topbar, cards, KPI hero - panned with it.

   `.tbl-scroll` is `.tbl-card`'s scrolling behaviour without its surface,
   border and radius, which would double up inside the card that already
   draws them. It also stops the intrinsic width from propagating outward:
   a scroll container's min-content contribution in its scrolling axis is
   zero, so a wide table can no longer stretch the card, the grid track,
   or the page.

   No physical properties anywhere, so the scroll edge follows the writing
   direction - English scrolls from the inline start on the left, Farsi RTL
   from the inline start on the right, with no second rule needed. */
.tbl-scroll {
  max-width: 100%;
  overflow-x: auto;
  /* Declared as a pair on purpose: with only `overflow-x: auto` set, the
     used value of `overflow-y: visible` becomes `auto` too, which leaves
     room for a vertical scrollbar that is never needed here. */
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}

/* === Tab strips: scroll, do not pan the page (v2.3.1) =============== */
/* Same bug as `.tbl-scroll` above, different element. A tab strip wider
   than the phone viewport pushed the whole document sideways instead of
   scrolling in place, so the topbar and every card panned with it.
   Measured at 375px (document.scrollWidth - clientWidth), EN / FA:
   /accounts/users/ 325/308, /import/ 325/307, /chat/settings/ 323/307,
   /workforce/performance/ 26/0.

   The reason it was never caught: the "Responsive polish - phase 2" block
   above has a rule for `.tabs`, and NO template has ever used that class.
   All six strips - the Settings, Outbound, Raw materials, Workers and
   Inbox partials, plus the granularity strip inside the Output trend card
   on Worker performance - are `.page-tabs`. That rule has been dead since
   it was written; `.page-tabs` still computed to `overflow-x: visible`.
   Left in place rather than deleted, but it is not what does the work.

   Deliberately not inside a max-width query. `overflow-x: auto` shows a
   scrollbar only when the content actually overflows, so a strip that
   fits is untouched at any width - and the Settings strip is permission
   gated, so a superuser gets eight tabs and can overrun a window far
   wider than a phone. Guarding it at 720px would just reintroduce the bug
   one breakpoint up.

   Logical properties throughout, so the strip scrolls from the reading
   edge in both directions: first tab on the left in English, on the right
   in Farsi, with no second rule. */
.page-tabs {
  flex-wrap: nowrap;
  overflow-x: auto;
  /* Paired with `overflow-x` on purpose: left at `visible` it would be
     promoted to `auto` and reserve room for a scrollbar never needed. */
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  /* `.page-tab` carries `margin-bottom: -1px` so the active tab's 2px
     brass indicator sits over the strip's hairline rail. That pixel falls
     outside the padding box, and overflow clips to the padding box - so
     `overflow-y: hidden` alone clipped the indicator to 1px and left the
     strip permanently 1px overflowing in the block axis. One pixel of end
     padding brings the indicator back inside; it is taken off the end
     margin (18px, above) so the strip occupies exactly the same space as
     before. */
  padding-block-end: 1px;
  margin-block-end: 17px;
}
/* Let the tabs keep their natural width. As flex items they defaulted to
   `flex-shrink: 1`, so a crowded strip squeezed the labels - visibly
   tighter padding, then wrapped text - before the strip ever admitted it
   had run out of room. Scrolling is the honest failure mode. */
.page-tabs > * { flex: 0 0 auto; }

/* === Activity log: the orphan class vocabulary (v2.3.1) ============= */
/* `core/activity_log.html` was written against a set of class names that
   were never added to this file. Six of them, none matching anything:
   `data`, `nowrap`, `pill`, `pill-<action>`, `chip`, `chip-row`. The table
   is fixed in the template - it now uses the house `.tbl-card` + `.tbl`
   pair like the other 33 lists, which is what it should have been - but
   the rest are real markup that renders as unstyled inline text, so the
   action column read as bare words where every other list in the console
   shows a badge. The classes are kept and given the styling they were
   written for, rather than rewritten in the template, because these are
   the right names for what they mark up.

   Every colour is an existing semantic token via its `-rgb` triplet, so
   all of it retints with the light-mode block above and with any future
   retheme - no new hues, no literal hex. Logical properties throughout;
   the chip row and the pills have no physical edges to get wrong. */

/* Action badges. Four values, set in views_activity.ACTION_LABELS:
   create / update / delete / access. `update` is blue rather than a second
   green: `--brass` and `--good` are both green (see the token note at the
   top of this file), so create-vs-update would have collapsed into one
   signal. `access` stays neutral on purpose - "Viewed" is the least
   consequential row in the log and should not compete with a deletion. */
.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px; font-weight: 500;
  white-space: nowrap;
  border: 0.5px solid transparent;
}
.pill-create {
  color: var(--good);
  background: rgba(var(--good-rgb), 0.12);
  border-color: rgba(var(--good-rgb), 0.35);
}
.pill-update {
  color: var(--info);
  background: rgba(var(--info-rgb), 0.14);
  border-color: rgba(var(--info-rgb), 0.38);
}
.pill-delete {
  color: var(--bad);
  background: rgba(var(--bad-rgb), 0.12);
  border-color: rgba(var(--bad-rgb), 0.35);
}
.pill-access {
  color: var(--text-3);
  background: var(--well);
  border-color: var(--line-2);
}

/* Active-filter chips, the row above the table that echoes back what the
   filter panel is currently narrowing to. Deliberately quieter than the
   action pills - it is a restatement of the reader's own input, not a
   status - so it is a neutral well with the value picked out in ink. */
.chip-row {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-block-end: 12px;
}
.chip {
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--well);
  border: 0.5px solid var(--line-2);
  color: var(--text-3);
  font-size: 11px;
}
.chip b { color: var(--text-2); font-weight: 500; }

/* Used on the timestamp cell, where the date and the time sit on two lines
   and neither should break mid-value. */
.nowrap { white-space: nowrap; }


/* === Phone: stacked tables and touch targets (v2.3.1) =============== */

/* A wide table read on a phone. `.tbl-scroll` (above) already stops one
   panning the page; this makes it legible without sideways scrolling at
   all. Opt in per table - add `tbl-stack` to the <table> and `data-label`
   to each <td> - because a narrow table is already fine and stacking it
   would only make it taller.

   Not applied wholesale to `.tbl`: 30-odd tables in the console differ in
   how many columns actually matter on a phone, and turning them all into
   tall card stacks unasked would be a bigger change than the one that was
   reported. */
@media (max-width: 560px) {
  .tbl-stack thead {
    /* Hidden from sight but not from assistive tech, which still reads
       the header association even while the visual layout is stacked. */
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .tbl-stack, .tbl-stack tbody, .tbl-stack tr, .tbl-stack td { display: block; }
  .tbl-stack tr {
    padding: 8px 2px;
    border-block-end: 0.5px solid var(--line);
  }
  .tbl-stack tr:last-child { border-block-end: 0; }
  .tbl-stack td {
    display: flex; align-items: baseline; gap: 10px;
    padding: 3px 0;
    border: 0;
    /* The row-level rule above owns the alignment, so a cell that was
       right-aligned for a numeric column does not fight it here. */
    text-align: start !important;
  }
  .tbl-stack td::before {
    content: attr(data-label);
    flex: 0 0 40%;
    color: var(--text-3);
    font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  }
  /* A cell with no label is a spacer or an actions cell; it spans instead
     of showing an empty gutter. */
  .tbl-stack td:not([data-label])::before { content: none; }
  /* Nothing to scroll once the rows are stacked. */
  .tbl-stack { min-width: 0; }
}

/* Touch targets. This is capability detection, not device guessing: it
   asks whether the pointer is coarse - a finger - rather than trying to
   identify the hardware from a user-agent string, which lies, changes,
   and says nothing about a tablet in desktop mode.

   The console is deliberately dense on a mouse, so this only raises the
   controls that are genuinely hard to hit with a thumb rather than
   loosening every row. */
@media (pointer: coarse) {
  .btn, .btn-brass, .btn-sm,
  .row-actions a, .card-action, .table-nav a {
    min-height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .nav-item { min-height: 42px; display: flex; align-items: center; }
}

/* Shown beside the status control when a change could not be saved. A
   silent revert reads as "the setting is broken"; this says which it is. */
.chat-status-error {
  font-size: 11px; color: var(--bad);
  /* Not clipped. It was max-width:130px with an ellipsis, which cut the
     message to "Not ..." - the one thing on screen whose whole job was
     to explain the failure did not fit. */
  white-space: nowrap;
}

/* =====================================================================
   Inventory dashboard (inventory/dashboard.html)
   ---------------------------------------------------------------------
   Ranked bar panels. Magnitude is carried by bar LENGTH alone and every
   bar wears the same hue, so the panels stay readable for a colour-blind
   reader and in print. The only colour that varies is the small product
   swatch beside a label, which names the colour rather than measuring
   anything.

   All horizontal spacing uses logical properties so the whole thing
   mirrors correctly under RTL without a second stylesheet.
   ===================================================================== */

.inv-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

.inv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

.inv-grid .panel-head { margin-bottom: 10px; }
.inv-grid .panel-head h2 { font-size: 15px; margin: 0 0 2px; }
.inv-grid .panel-head .dim { font-size: 11px; }

.inv-row {
  display: grid;
  grid-template-columns: minmax(124px, 1.45fr) minmax(56px, 1.35fr) auto;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
}

.inv-row-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  min-width: 0;
}
.inv-row-label > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The product's real colour, as identification only - never as the
   measure. A ring keeps white and near-white swatches visible on the
   card surface in both themes. */

.inv-track {
  position: relative;
  height: 9px;
  border-radius: 999px;
  background: var(--well);
  overflow: hidden;
}

.inv-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--brass);
  min-width: 2px;
}

/* Arrived / left are two different questions, not two series on one
   chart, so they sit in separate panels and simply take the good and
   warning hues the rest of the app already uses for that meaning. */
.inv-fill-in  { background: var(--good, var(--brass)); }
.inv-fill-out { background: var(--amber, var(--brass)); }

.inv-row-value {
  font-size: 12px;
  text-align: end;
  white-space: nowrap;
}

@media (max-width: 640px) {
  /* Stack label above the bar rather than letting three columns fight
     over ~300px; the value stays on the bar's row. */
  .inv-row { grid-template-columns: 1fr auto; row-gap: 3px; }
  .inv-row-label { grid-column: 1 / -1; }
}

/* =====================================================================
   PRESET THEMES  (design handoff, 16 September 2026)
   ---------------------------------------------------------------------
   Five palettes from the theme specification, expressed in THIS file's
   existing token names rather than the spec's. That is deliberate: every
   component rule below, and hundreds of templates, already read colour
   through --bg/--surface/--brass/--text/--good/... so mapping onto those
   names makes every existing screen retint with no template churn. The
   spec's own names (canvas/primary/success/...) appear in the comments
   beside each mapping so the two can be reconciled later.

   The bundled XWARES Dark (:root) and XWARES Light
   (:root[data-theme="light"]) above are untouched and remain available;
   these are additions, not a replacement.

   Selector is :root[data-theme=...] (0,2,0) for the same specificity
   reason documented on the light block above.

   Tokens the spec introduced that this file had no equivalent for
   (--input-line, --selected, --focus, --chart-1..4, the *-bg status
   tints, and the --chrome-* set) are declared with defaults in :root and
   in the light block, so the two bundled themes gain them too.
   ===================================================================== */

/* --- Ivory & Forest (light) --------------------------------- */
:root[data-theme="ivory-forest"] {
  color-scheme: light;
  --sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --sans-fa: 'Vazirmatn', 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;

  /* canvas / surface / surface-alt */
  --bg:         #F5F6F2;
  --surface:    #FFFFFF;
  --surface-2:  #FFFFFF;
  --well:       #EEF2ED;
  --selected:   #E7F1EA;

  /* text / muted */
  --text:       #17251E;
  --text-2:     #526158;
  --text-3:     #64716A;
  /* Same three values under names the chrome pin never overrides, so the
     panels anchored to the topbar can restore the PAGE palette in every
     theme. Ivory & Forest is why this is not light-mode-only: it pairs a
     dark sidebar with a light page, so a dropdown on a white surface
     would otherwise inherit chrome's near-white ink and vanish. */
  --page-text:   #17251E;
  --page-text-2: #526158;
  --page-text-3: #76827A;
  --page-accent: #176343;

  /* border vs input-border: the spec separates a decorative card edge
     from a real control boundary, and only the latter needs 3:1. */
  --line:       #D5DED7;
  --line-2:     #B3BDB6;
  --input-line: #7A8B80;

  /* Table interior. Tinted from the TEXT colour so a stripe stays
     visible whichever way round the theme is. Deliberately stronger
     than the bundled themes' values - tracing a row across a wide
     table was the specific complaint these presets answer. */
  --zebra:      rgba(23, 37, 30, 0.035);
  --rule:       rgba(23, 37, 30, 0.08);
  --row-hover:  rgba(23, 37, 30, 0.055);

  /* primary */
  --brass:      #176343;
  --brass-rgb:  23, 99, 67;
  --brass-soft: #124F36;
  --brass-ink:  #176343;
  --on-brass:   #FFFFFF;
  --on-tint:    #FFFFFF;

  /* status */
  --good:       #176343;
  --good-rgb:   23, 99, 67;
  --good-bg:    #E7F3EB;
  --amber:      #8A4B08;
  --amber-rgb:  138, 75, 8;
  --amber-bg:   #FFF4D6;
  --bad:        #B42332;
  --bad-rgb:    180, 35, 50;
  --bad-bg:     #FDECEE;
  --info:       #1D5E91;
  --info-rgb:   29, 94, 145;
  --info-bg:    #EAF3FB;
  --yellow:     #6E6410;
  --yellow-rgb: 110, 100, 16;
  --glow:       #1D5E91;
  --glow-rgb:   29, 94, 145;

  --focus:      #1D5E91;

  /* chart series, in the spec's order: actual, target, secondary, tertiary */
  --chart-1:    #176343;
  --chart-2:    #8A4B08;
  --chart-3:    #1D5E91;
  --chart-4:    #76539B;

  /* Navigation chrome. The bundled themes pin the topbar and sidebar
     dark regardless of mode; three of these presets have a light
     sidebar, so chrome reads its colours from tokens instead. */
  --chrome-bg:     #183D2D;
  --chrome-text:   #F1F7F3;
  --chrome-text-2: #C1D5C8;
  --chrome-text-3: #97AFA1;
  /* Readable on the RAISED chrome surface too - the FY pill and
     the language toggle sit on --chrome-raise, not on the bare
     sidebar. This was 3.41 there. */
  --chrome-accent: #A3C2B5;
  --chrome-line:   rgba(241, 247, 243, 0.14);
  --chrome-raise:  rgba(241, 247, 243, 0.10);
  --chrome-hover:  rgba(241, 247, 243, 0.16);
  /* Readable on the ACTIVE ROW, not just on the bare sidebar.
     This was 3.05 on its own --chrome-active-bg: the ink was tuned
     against the sidebar, and the row it labels is lighter than
     that, so the one navigation item you most need to find was
     the hardest to read. */
  --chrome-active:      #AEC9BD;
  --chrome-active-bg:   rgba(241, 247, 243, 0.14);
  --chrome-active-line: rgba(241, 247, 243, 0.26);
}

/* --- Slate & Cobalt (dark) ---------------------------------- */
:root[data-theme="slate-cobalt"] {
  color-scheme: dark;
  --sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --sans-fa: 'Vazirmatn', 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;

  /* canvas / surface / surface-alt */
  --bg:         #0F172A;
  --surface:    #182338;
  --surface-2:  #212C40;
  --well:       #22314A;
  --selected:   #243C5A;

  /* text / muted */
  --text:       #F1F5F9;
  --text-2:     #B6C2D4;
  --text-3:     #919CAF;
  /* Same three values under names the chrome pin never overrides, so the
     panels anchored to the topbar can restore the PAGE palette in every
     theme. Ivory & Forest is why this is not light-mode-only: it pairs a
     dark sidebar with a light page, so a dropdown on a white surface
     would otherwise inherit chrome's near-white ink and vanish. */
  --page-text:   #F1F5F9;
  --page-text-2: #B6C2D4;
  --page-text-3: #919CAF;
  --page-accent: #93C5FD;

  /* border vs input-border: the spec separates a decorative card edge
     from a real control boundary, and only the latter needs 3:1. */
  --line:       #334155;
  --line-2:     #556173;
  --input-line: #71839B;

  /* Table interior. Tinted from the TEXT colour so a stripe stays
     visible whichever way round the theme is. Deliberately stronger
     than the bundled themes' values - tracing a row across a wide
     table was the specific complaint these presets answer. */
  --zebra:      rgba(241, 245, 249, 0.045);
  --rule:       rgba(241, 245, 249, 0.10);
  --row-hover:  rgba(241, 245, 249, 0.075);

  /* primary */
  --brass:      #93C5FD;
  --brass-rgb:  147, 197, 253;
  --brass-soft: #BFDBFE;
  --brass-ink:  #93C5FD;
  --on-brass:   #0F172A;
  --on-tint:    #0F172A;

  /* status */
  --good:       #6EE7B7;
  --good-rgb:   110, 231, 183;
  --good-bg:    #163C32;
  --amber:      #FCD34D;
  --amber-rgb:  252, 211, 77;
  --amber-bg:   #42351A;
  --bad:        #FDA4AF;
  --bad-rgb:    253, 164, 175;
  --bad-bg:     #472736;
  --info:       #93C5FD;
  --info-rgb:   147, 197, 253;
  --info-bg:    #243C5A;
  --yellow:     #E4D08A;
  --yellow-rgb: 228, 208, 138;
  --glow:       #6EE7B7;
  --glow-rgb:   110, 231, 183;

  --focus:      #FBBF24;

  /* chart series, in the spec's order: actual, target, secondary, tertiary */
  --chart-1:    #93C5FD;
  --chart-2:    #C4B5FD;
  --chart-3:    #6EE7B7;
  --chart-4:    #FDBA74;

  /* Navigation chrome. The bundled themes pin the topbar and sidebar
     dark regardless of mode; three of these presets have a light
     sidebar, so chrome reads its colours from tokens instead. */
  --chrome-bg:     #111C30;
  --chrome-text:   #F1F5F9;
  --chrome-text-2: #B6C2D4;
  --chrome-text-3: #8D98AB;
  --chrome-accent: #93C5FD;
  --chrome-line:   rgba(241, 245, 249, 0.14);
  --chrome-raise:  rgba(241, 245, 249, 0.10);
  --chrome-hover:  rgba(241, 245, 249, 0.16);
  --chrome-active:      #93C5FD;
  --chrome-active-bg:   rgba(241, 245, 249, 0.14);
  --chrome-active-line: rgba(241, 245, 249, 0.26);
}

/* --- Porcelain & Indigo (light) ----------------------------- */
:root[data-theme="porcelain-indigo"] {
  color-scheme: light;
  --sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --sans-fa: 'Vazirmatn', 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;

  /* canvas / surface / surface-alt */
  --bg:         #F4F6FB;
  --surface:    #FFFFFF;
  --surface-2:  #FFFFFF;
  --well:       #ECEFF7;
  --selected:   #EEECFF;

  /* text / muted */
  --text:       #182230;
  --text-2:     #526176;
  --text-3:     #626D7D;
  /* Same three values under names the chrome pin never overrides, so the
     panels anchored to the topbar can restore the PAGE palette in every
     theme. Ivory & Forest is why this is not light-mode-only: it pairs a
     dark sidebar with a light page, so a dropdown on a white surface
     would otherwise inherit chrome's near-white ink and vanish. */
  --page-text:   #182230;
  --page-text-2: #526176;
  --page-text-3: #768293;
  --page-accent: #4338CA;

  /* border vs input-border: the spec separates a decorative card edge
     from a real control boundary, and only the latter needs 3:1. */
  --line:       #D9DEEA;
  --line-2:     #B6BCC9;
  --input-line: #7A869B;

  /* Table interior. Tinted from the TEXT colour so a stripe stays
     visible whichever way round the theme is. Deliberately stronger
     than the bundled themes' values - tracing a row across a wide
     table was the specific complaint these presets answer. */
  --zebra:      rgba(24, 34, 48, 0.035);
  --rule:       rgba(24, 34, 48, 0.08);
  --row-hover:  rgba(24, 34, 48, 0.055);

  /* primary */
  --brass:      #4338CA;
  --brass-rgb:  67, 56, 202;
  --brass-soft: #3730A3;
  --brass-ink:  #4338CA;
  --on-brass:   #FFFFFF;
  --on-tint:    #FFFFFF;

  /* status */
  --good:       #176343;
  --good-rgb:   23, 99, 67;
  --good-bg:    #E7F3EB;
  --amber:      #8A4B08;
  --amber-rgb:  138, 75, 8;
  --amber-bg:   #FFF4D6;
  --bad:        #B42332;
  --bad-rgb:    180, 35, 50;
  --bad-bg:     #FDECEE;
  --info:       #1D5E91;
  --info-rgb:   29, 94, 145;
  --info-bg:    #EAF3FB;
  --yellow:     #6E6410;
  --yellow-rgb: 110, 100, 16;
  --glow:       #16716B;
  --glow-rgb:   22, 113, 107;

  --focus:      #4338CA;

  /* chart series, in the spec's order: actual, target, secondary, tertiary */
  --chart-1:    #4338CA;
  --chart-2:    #8A4B08;
  --chart-3:    #16716B;
  --chart-4:    #76539B;

  /* Navigation chrome. The bundled themes pin the topbar and sidebar
     dark regardless of mode; three of these presets have a light
     sidebar, so chrome reads its colours from tokens instead. */
  --chrome-bg:     #FFFFFF;
  --chrome-text:   #182230;
  --chrome-text-2: #526176;
  --chrome-text-3: #6C7786;
  --chrome-accent: #4338CA;
  --chrome-line:   rgba(24, 34, 48, 0.22);
  --chrome-raise:  rgba(24, 34, 48, 0.10);
  --chrome-hover:  rgba(24, 34, 48, 0.16);
  --chrome-active:      #4338CA;
  --chrome-active-bg:   rgba(24, 34, 48, 0.14);
  --chrome-active-line: rgba(24, 34, 48, 0.26);
}

/* --- Sand & Terracotta (light) ------------------------------ */
:root[data-theme="sand-terracotta"] {
  color-scheme: light;
  --sans: 'Source Sans 3', system-ui, -apple-system, sans-serif;
  --sans-fa: 'Vazirmatn', 'Source Sans 3', system-ui, -apple-system, sans-serif;

  /* canvas / surface / surface-alt */
  --bg:         #F5F0E8;
  --surface:    #FFFDFA;
  --surface-2:  #FFFDFA;
  --well:       #F0E9DF;
  --selected:   #F3E2D8;

  /* text / muted */
  --text:       #2F2823;
  --text-2:     #6A5D52;
  --text-3:     #72685E;
  /* Same three values under names the chrome pin never overrides, so the
     panels anchored to the topbar can restore the PAGE palette in every
     theme. Ivory & Forest is why this is not light-mode-only: it pairs a
     dark sidebar with a light page, so a dropdown on a white surface
     would otherwise inherit chrome's near-white ink and vanish. */
  --page-text:   #2F2823;
  --page-text-2: #6A5D52;
  --page-text-3: #897D73;
  --page-accent: #A4472D;

  /* border vs input-border: the spec separates a decorative card edge
     from a real control boundary, and only the latter needs 3:1. */
  --line:       #DDD2C5;
  --line-2:     #BEB3A8;
  --input-line: #918172;

  /* Table interior. Tinted from the TEXT colour so a stripe stays
     visible whichever way round the theme is. Deliberately stronger
     than the bundled themes' values - tracing a row across a wide
     table was the specific complaint these presets answer. */
  --zebra:      rgba(47, 40, 35, 0.035);
  --rule:       rgba(47, 40, 35, 0.08);
  --row-hover:  rgba(47, 40, 35, 0.055);

  /* primary */
  --brass:      #A4472D;
  --brass-rgb:  164, 71, 45;
  --brass-soft: #883820;
  --brass-ink:  #A4472D;
  --on-brass:   #FFFFFF;
  --on-tint:    #FFFFFF;

  /* status */
  --good:       #236549;
  --good-rgb:   35, 101, 73;
  --good-bg:    #E7F1E9;
  --amber:      #855008;
  --amber-rgb:  133, 80, 8;
  --amber-bg:   #FFF1D6;
  --bad:        #B42332;
  --bad-rgb:    180, 35, 50;
  --bad-bg:     #FDECEE;
  --info:       #235F87;
  --info-rgb:   35, 95, 135;
  --info-bg:    #E8F2F9;
  --yellow:     #6B5A0E;
  --yellow-rgb: 107, 90, 14;
  --glow:       #235F87;
  --glow-rgb:   35, 95, 135;

  --focus:      #235F87;

  /* chart series, in the spec's order: actual, target, secondary, tertiary */
  --chart-1:    #A4472D;
  --chart-2:    #16716B;
  --chart-3:    #235F87;
  --chart-4:    #76539B;

  /* Navigation chrome. The bundled themes pin the topbar and sidebar
     dark regardless of mode; three of these presets have a light
     sidebar, so chrome reads its colours from tokens instead. */
  --chrome-bg:     #FFFDFA;
  --chrome-text:   #2F2823;
  --chrome-text-2: #6A5D52;
  --chrome-text-3: #7D736B;
  --chrome-accent: #A4472D;
  --chrome-line:   rgba(47, 40, 35, 0.22);
  --chrome-raise:  rgba(47, 40, 35, 0.10);
  --chrome-hover:  rgba(47, 40, 35, 0.16);
  /* Readable on the ACTIVE ROW, not just on the bare sidebar.
     This was 4.50 on its own --chrome-active-bg: the ink was tuned
     against the sidebar, and the row it labels is lighter than
     that, so the one navigation item you most need to find was
     the hardest to read. */
  --chrome-active:      #9D442B;
  --chrome-active-bg:   rgba(47, 40, 35, 0.14);
  --chrome-active-line: rgba(47, 40, 35, 0.26);
}

/* --- Graphite & Brass (dark) -------------------------------- */
:root[data-theme="graphite-brass"] {
  color-scheme: dark;
  --sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
  --sans-fa: 'Vazirmatn', 'IBM Plex Sans', system-ui, -apple-system, sans-serif;

  /* canvas / surface / surface-alt */
  --bg:         #151719;
  --surface:    #22262A;
  --surface-2:  #2B2F33;
  --well:       #2D3338;
  --selected:   #3C3422;

  /* text / muted */
  --text:       #F2EFE7;
  --text-2:     #B9B7B0;
  --text-3:     #9C9A95;
  /* Same three values under names the chrome pin never overrides, so the
     panels anchored to the topbar can restore the PAGE palette in every
     theme. Ivory & Forest is why this is not light-mode-only: it pairs a
     dark sidebar with a light page, so a dropdown on a white surface
     would otherwise inherit chrome's near-white ink and vanish. */
  --page-text:   #F2EFE7;
  --page-text-2: #B9B7B0;
  --page-text-3: #95948F;
  --page-accent: #D8B75C;

  /* border vs input-border: the spec separates a decorative card edge
     from a real control boundary, and only the latter needs 3:1. */
  --line:       #41474D;
  --line-2:     #616569;
  --input-line: #818990;

  /* Table interior. Tinted from the TEXT colour so a stripe stays
     visible whichever way round the theme is. Deliberately stronger
     than the bundled themes' values - tracing a row across a wide
     table was the specific complaint these presets answer. */
  --zebra:      rgba(242, 239, 231, 0.045);
  --rule:       rgba(242, 239, 231, 0.10);
  --row-hover:  rgba(242, 239, 231, 0.075);

  /* primary */
  --brass:      #D8B75C;
  --brass-rgb:  216, 183, 92;
  --brass-soft: #E8CA78;
  --brass-ink:  #D8B75C;
  --on-brass:   #151719;
  --on-tint:    #151719;

  /* status */
  --good:       #8DD8AA;
  --good-rgb:   141, 216, 170;
  --good-bg:    #1E392B;
  --amber:      #F3CF7A;
  --amber-rgb:  243, 207, 122;
  --amber-bg:   #44361B;
  --bad:        #F4A5A5;
  --bad-rgb:    244, 165, 165;
  --bad-bg:     #45282C;
  --info:       #9FD4EE;
  --info-rgb:   159, 212, 238;
  --info-bg:    #233B49;
  --yellow:     #D9C98A;
  --yellow-rgb: 217, 201, 138;
  --glow:       #8DD8AA;
  --glow-rgb:   141, 216, 170;

  --focus:      #9FD4EE;

  /* chart series, in the spec's order: actual, target, secondary, tertiary */
  --chart-1:    #D8B75C;
  --chart-2:    #9FD4EE;
  --chart-3:    #8DD8AA;
  --chart-4:    #C5B0E8;

  /* Navigation chrome. The bundled themes pin the topbar and sidebar
     dark regardless of mode; three of these presets have a light
     sidebar, so chrome reads its colours from tokens instead. */
  --chrome-bg:     #191C1F;
  --chrome-text:   #F2EFE7;
  --chrome-text-2: #B9B7B0;
  --chrome-text-3: #91908C;
  --chrome-accent: #D8B75C;
  --chrome-line:   rgba(242, 239, 231, 0.14);
  --chrome-raise:  rgba(242, 239, 231, 0.10);
  --chrome-hover:  rgba(242, 239, 231, 0.16);
  --chrome-active:      #D8B75C;
  --chrome-active-bg:   rgba(242, 239, 231, 0.14);
  --chrome-active-line: rgba(242, 239, 231, 0.26);
}

/* =====================================================================
   READABILITY PASS
   ---------------------------------------------------------------------
   The specification's stated complaints, answered:

     "table labels too small, secondary text faint"  -> the type scale
       edits above (cells 13->15px, headers 11->14px at weight 600 in
       sentence case, labels 11->14px, inputs 13->16px).
     "rows hard to trace"                            -> zebra + hover
       below. --zebra had been DECLARED since the light-mode pass but
       was never once used; no table in the app was ever striped.
     "numbers need to be bold"                       -> the numeric
       rules below, using real tabular figures rather than a
       monospaced face.

   Row height lands at 52px desktop from 14px cell padding on a 15px
   line, matching the spec's figure without hardcoding a height that
   would clip two-line cells.
   ===================================================================== */

/* Striping. Applied to the ODD row so a table that starts on a stripe
   reads as banded rather than as a header that bled into row one. */
.tbl tbody tr:nth-child(odd) td { background: var(--zebra); }
/* Hover has to beat the stripe, hence the higher specificity here. */
.tbl tbody tr:hover td,
.tbl tbody tr:nth-child(odd):hover td { background: var(--row-hover); }
.tbl tbody tr.hoverless:hover td,
.tbl tbody tr.hoverless:nth-child(odd):hover td { background: var(--zebra); }
.tbl tbody tr.hoverless:nth-child(even):hover td { background: transparent; }

/* Numbers carry the meaning in this app, so they get the weight. Both
   the explicit .num column class and any cell marked .mono. */
.tbl td.num,
.tbl td .mono,
.tbl td.mono {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text);
}
/* The row's primary field - first column - is bold too, which is what
   makes a row findable when scanning down. The spec asks for exactly
   this and warns against bolding the whole table. */
.tbl tbody td:first-child { font-weight: 600; }

/* Totals row, where present, is the one place a whole row is bold. */
.tbl tfoot td { font-weight: 700; border-top: 1px solid var(--line-2); }

/* Sentence-case headers mean the sort link no longer needs tracking. */
.tbl th .sort-link { letter-spacing: normal; font-weight: 600; }

/* Focus. The spec asks for 3px at 2px offset and explicitly forbids
   hiding the ring; this app had no global focus-visible rule at all. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* Control boundaries read from --input-line, which is the token allowed
   to be strong enough to perceive; --line stays decorative. */
.input, input[type="text"], input[type="password"], input[type="email"],
input[type="number"], input[type="date"], input[type="search"],
select, textarea {
  border-color: var(--input-line);
}

/* Buttons: 15px/600 and a 44px touch target, per the spec's control
   sizing. */
.btn, .btn-brass, .btn-bad, button.btn, button.btn-brass {
  font-size: 15px;
  font-weight: 600;
  min-height: 44px;
}

/* Persian sets looser than Latin at the same size; 1.7 on body content
   is the specification's figure. */
[lang="fa"] body,
body:lang(fa) { line-height: 1.7; letter-spacing: normal; }

@media (max-width: 767px) {
  /* Record cells grow to 16px on touch, matching input sizing so a
     table and a form do not disagree about what "readable" means. */
  .tbl { font-size: 16px; }
  .kpi-value { font-size: 28px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ---------------------------------------------------------------------
   Theme picker (topbar) and the theme grid on the Branding screen.
   The swatch is three stacked bands - canvas, surface, primary - which
   is the least that makes a palette recognisable at a glance. Themes are
   named as well as shown: the chips alone would be colour-only
   information, which is exactly what the status rules forbid elsewhere.
   --------------------------------------------------------------------- */

.theme-picker { position: relative; display: inline-flex; }

.theme-drop {
  position: absolute;
  inset-block-start: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 60;
  min-inline-size: 248px;
  padding: 6px;
  background: var(--surface);
  color: var(--page-text);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
  /* Anchored to the topbar, which pins chrome colours; restore the page
     palette so this panel reads correctly in every theme. */
  --text: var(--page-text);
  --text-2: var(--page-text-2);
  --text-3: var(--page-text-3);
  --brass-ink: var(--page-accent);
}

.theme-drop-head {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-3);
  padding: 8px 10px 6px;
}

.theme-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  inline-size: 100%;
  min-block-size: 44px;
  padding: 8px 10px;
  background: none;
  border: 0;
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: start;
  cursor: pointer;
}
.theme-opt:hover { background: var(--row-hover); }
.theme-opt.is-active { background: var(--selected); font-weight: 600; }

.theme-opt-label { flex: 1 1 auto; }
.theme-opt-tag { font-size: 11px; text-transform: none; }

/* The "use the house theme for me too" button sits inside a warning
   message rather than in the dropdown, so it needs to be inline. */
.theme-opt-inline {
  display: inline-flex;
  inline-size: auto;
  min-block-size: 36px;
  margin-inline-start: 8px;
  padding: 6px 10px;
  border: 1px solid var(--input-line);
  font-size: 13px;
  font-weight: 600;
}

.theme-opt-reset {
  margin-block-start: 4px;
  border-block-start: 1px solid var(--line);
  border-radius: 0 0 var(--radius) var(--radius);
  color: var(--text-2);
  font-size: 13px;
}

.theme-swatch {
  display: inline-flex;
  flex: 0 0 auto;
  inline-size: 34px;
  block-size: 20px;
  border-radius: 5px;
  overflow: hidden;
  box-shadow: 0 0 0 1px var(--line-2);
}
.theme-swatch > span { flex: 1 1 0; display: block; }

/* --- Branding screen ------------------------------------------------- */

.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
  margin-block-start: 8px;
  align-items: stretch;
}
@media (max-width: 520px) {
  /* One per row rather than two cramped ones; the swatch and the
     description both need the width to stay legible. */
  .theme-grid { grid-template-columns: 1fr; }
}

/* The label is a bare wrapper; the visible card is .theme-card-body, a
   SIBLING of the radio. That is what lets `input:checked ~ body` paint
   the selected state with no script - and without :has(), which Safari
   12 does not support and several floor iPads are stuck on. Before this
   the selected state came from a server-rendered class, so clicking a
   card appeared to do nothing until the page was saved and reloaded. */
.theme-card { display: block; cursor: pointer; height: 100%; }

.theme-card input {
  position: absolute;
  opacity: 0;
  /* NOT pointer-events:none - the input must stay focusable and
     reachable by keyboard; the label handles pointer activation. */
  width: 1px; height: 1px;
  margin: 0;
}

.theme-card-body {
  /* Column flex + full height keeps every card in a row the same
     height, so the swatches line up instead of stepping. */
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}
.theme-card:hover .theme-card-body { border-color: var(--input-line); }

.theme-card input:checked ~ .theme-card-body {
  border-color: var(--brass);
  box-shadow: 0 0 0 1px var(--brass);
  background: var(--selected);
}

/* Keyboard users get the same ring the rest of the app uses. */
.theme-card input:focus-visible ~ .theme-card-body {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.theme-card-swatch {
  display: flex;
  block-size: 46px;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--line-2);
  margin-block-end: 10px;
}
.theme-card-swatch > span { flex: 1 1 0; }

/* Each on its own line: as inline spans the name ran straight into the
   description, which read as one run-on sentence. */
.theme-card-name { display: block; font-weight: 600; font-size: 15px; }
.theme-card-desc { display: block; font-size: 13px; color: var(--text-2);
  margin-block-start: 4px; line-height: 1.45; }
.theme-card-meta { display: block; font-size: 12px; color: var(--text-3);
  /* auto top margin pins the meta line to the bottom of every card, so
     they align across the row whatever the description length. */
  margin-block-start: auto; padding-block-start: 6px; }

/* Safari 12 fallback for the rules above. iOS 12 - still in service on
   the floor iPads - has no :has(), so a tapped radio toggled its input
   with NOTHING visibly changing. The operator reasonably concludes the
   control is broken; this is what 'I click it and nothing happens' on
   the one/two-press selector actually was. xw-inputs.js keeps the
   .is-checked class in step, so modern browsers use :has() and older
   ones get the identical result from the class. */
.tier-opt.tier-wholesale.is-checked { background: rgba(var(--brass-rgb),0.22); color: var(--brass-ink); }
.tier-opt.tier-retail.is-checked { background: var(--brass); color: var(--on-brass); }
.tier-opt.is-checked { background: var(--brass); color: var(--on-brass); }
.perm-seg.perm-seg-none.is-checked { background: rgba(140,143,148,0.18); color: var(--text); }
.perm-seg.perm-seg-view.is-checked { background: rgba(var(--good-rgb),0.20); color: var(--good); }
.perm-seg.perm-seg-edit.is-checked { background: rgba(var(--brass-rgb),0.22); color: var(--brass-ink); }
.perm-seg.perm-seg-manage.is-checked { background: var(--brass); color: var(--on-brass); }

/* ---------------------------------------------------------------------
   .kpi-row / .kpi — a compact figure strip above a table.

   Both were used by markup and defined by nothing: activity_log.html has
   carried .kpi-row since well before the theme work, and the two return
   lists added it too, so all three rendered as unstyled stacked divs.
   .kpi-label/.kpi-value/.kpi-sub/.kpi-unit were already defined; only
   the container and the tile were missing.

   Sized smaller than the dashboard's .inv-kpis cards on purpose: this is
   a supporting strip above a table, not the headline of a screen.
   --------------------------------------------------------------------- */
.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px;
}

.kpi {
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.kpi .kpi-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  letter-spacing: normal;
  text-transform: none;
}
.kpi .kpi-value {
  font-size: 22px;
  line-height: 1.2;
  margin-top: 2px;
}

@media (max-width: 520px) {
  .kpi-row { grid-template-columns: 1fr; }
}

/* =====================================================================
   Customisable dashboard: the layout grid, the view picker, and the
   customise screen's pick list.
   ===================================================================== */

/* The body is a two-column grid; a "full" widget spans both. Replaces
   the per-section inline grids (1.7fr 1fr, 1.4fr 1fr, 1fr 1fr) the fixed
   dashboard used, which could not survive one of a pair being hidden. */
.dash-body {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-top: 14px;
  /* stretch, not start. A grid row is already as tall as its tallest
     card; with `start` the shorter one stopped early and left a visible
     hole under it, which is what made the dashboard look ragged rather
     than laid out. Filling the row reads as deliberate. */
  align-items: stretch;
}

.dash-slot { min-width: 0; display: flex; }
.dash-slot-full { grid-column: 1 / -1; }

/* The card fills its slot, and its own content lays out top-to-bottom so
   a table or a bar list sits under the heading rather than being
   vertically centred in the leftover space. */
.dash-slot > .card {
  inline-size: 100%;
  display: flex;
  flex-direction: column;
}

/* An empty panel is the worst offender: one line of text in a card that
   is otherwise 300px tall. Give the message room and centre it, so it
   reads as "nothing here yet" rather than as a broken card. */
.dash-slot > .card > p.dim:last-child {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 96px;
  margin: 0;
  text-align: center;
}

/* Bar lists and tables take the slack instead of the card's bottom
   edge floating away from its content. */
.dash-slot > .card > .tbl-scroll { flex: 1 1 auto; }

.dash-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.dash-toolbar-view { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.dash-toolbar-pick { margin-inline-start: auto; display: flex; gap: 8px; }
.dash-toolbar-pick select { min-width: 210px; }

.badge-soft {
  font-size: 11px; font-weight: 600;
  padding: 2px 7px; border-radius: 999px;
  background: var(--selected); color: var(--text-2);
}

/* --- customise screen ------------------------------------------------ */

.preset-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px;
  align-items: stretch;
}
.preset-card-form { display: block; height: 100%; }
.preset-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  inline-size: 100%;
  gap: 4px;
  padding: 12px;
  text-align: start;
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.preset-card:hover { border-color: var(--input-line); }
.preset-card.is-active {
  border-color: var(--brass);
  box-shadow: 0 0 0 1px var(--brass);
  background: var(--selected);
}
.preset-card-name { font-weight: 600; font-size: 15px; }
.preset-card-desc { font-size: 13px; color: var(--text-2); line-height: 1.45; }
.preset-card-meta { margin-block-start: auto; padding-block-start: 6px;
  font-size: 12px; color: var(--text-3); }

.saved-view-list { display: flex; flex-wrap: wrap; gap: 8px; }
.saved-view {
  display: flex; align-items: stretch;
  border: 1px solid var(--line-2); border-radius: var(--radius);
  overflow: hidden;
}
.saved-view.is-active { border-color: var(--brass); box-shadow: 0 0 0 1px var(--brass); }
.saved-view-btn {
  display: flex; align-items: center; gap: 6px;
  min-block-size: 44px; padding: 8px 12px;
  background: none; border: 0; color: var(--text);
  font: inherit; font-size: 14px; cursor: pointer;
}
.saved-view-btn:hover { background: var(--row-hover); }
.saved-view-del {
  min-inline-size: 40px; min-block-size: 44px;
  background: none; border: 0; border-inline-start: 1px solid var(--line);
  color: var(--bad); font-size: 18px; cursor: pointer;
}
.saved-view-del:hover { background: var(--bad-bg); }

.dash-pick-head { font-size: 15px; font-weight: 600; margin: 16px 0 6px; }
.dash-pick { list-style: none; margin: 0; padding: 0; }

.dash-pick-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: var(--radius);
  border: 1px solid transparent;
}
.dash-pick-row:nth-child(odd) { background: var(--zebra); }
.dash-pick-row:hover { background: var(--row-hover); }

.dash-pick-label {
  display: flex; align-items: flex-start; gap: 10px;
  flex: 1 1 auto; min-width: 0;
  min-block-size: 44px;
  padding-block: 4px;
  cursor: pointer;
}
.dash-pick-label input { margin-block-start: 4px; flex: 0 0 auto; }
.dash-pick-text { display: flex; flex-direction: column; min-width: 0; }
.dash-pick-name { font-weight: 600; font-size: 14px; }
.dash-pick-desc { font-size: 13px; color: var(--text-2); line-height: 1.4; }

.dash-pick-move { display: flex; gap: 4px; flex: 0 0 auto; }
.btn-move {
  min-inline-size: 40px; min-block-size: 40px;
  background: var(--well); color: var(--text);
  border: 1px solid var(--input-line); border-radius: var(--radius);
  font-size: 15px; line-height: 1; cursor: pointer;
}
.btn-move:hover { background: var(--selected); }

.dash-save {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--line);
}
.dash-share { display: flex; align-items: center; gap: 8px; min-block-size: 44px; font-size: 14px; }
.dash-save .btn-row { margin-inline-start: auto; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 900px) {
  /* One column: a half-width panel at 450px is narrower than its own
     chart labels. */
  .dash-body { grid-template-columns: 1fr; }
  .dash-slot-full { grid-column: auto; }
}

@media (max-width: 720px) {
  .dash-toolbar-pick { margin-inline-start: 0; width: 100%; }
  .dash-toolbar-pick select { flex: 1; min-width: 0; }
  .preset-grid { grid-template-columns: 1fr; }
  .dash-save .btn-row { margin-inline-start: 0; width: 100%; }
  .dash-save .btn-row > * { flex: 1; }
  .dash-pick-desc { display: none; }   /* the name carries it on a phone */
}


/* === Arranging the dashboard in place (v2.6.3) ======================= */
/* The dashboard is its own editor: a pane of available widgets beside the
   live page, with each tile carrying a grip, reorder buttons and a
   remove. All of that furniture is in the markup on every load and shown
   only while `body.is-arranging` - which keeps the editing path free of a
   round trip, and keeps the reading path free of editing chrome.

   Every colour here is an existing token. Nothing in this block
   introduces a hue, so it retints with the five presets like the rest of
   the console. */

/* --- the KPI strip's slots ------------------------------------------- */
/* KPI cards used to be direct grid items. They are wrapped now so a
   figure and a panel are the same kind of thing to the arranger, and the
   wrapper reuses .dash-slot's own rules - the cards look exactly as they
   did, and now share a row height. */
.dash-slot-kpi { display: flex; min-inline-size: 0; }
.dash-slot-kpi > .card { inline-size: 100%; }

/* --- drop zones ------------------------------------------------------ */
.dash-dropzone-hint { display: none; }
.dash-zone-empty { display: none; }

body.is-arranging .dash-zone-empty { display: grid; }
body.is-arranging .dash-dropzone-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  grid-column: 1 / -1;
  min-block-size: 76px;
  border: 1px dashed var(--input-line);
  border-radius: 10px;
  color: var(--text-3);
  font-size: 13px;
}
body.is-arranging .dash-hero:not(.dash-zone-empty) .dash-dropzone-hint,
body.is-arranging .dash-body:not(.dash-zone-empty) .dash-dropzone-hint {
  display: none;
}

/* --- per-tile chrome -------------------------------------------------- */
.dash-slot { position: relative; }
.dash-chrome { display: none; }

body.is-arranging .dash-chrome {
  display: flex;
  gap: 2px;
  position: absolute;
  inset-block-start: 6px;
  inset-inline-end: 6px;
  z-index: 3;
  padding: 3px;
  border-radius: 8px;
  background: var(--surface);
  border: 0.5px solid var(--line);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.dash-chrome-grip,
.dash-chrome-btn {
  inline-size: 26px; block-size: 26px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 6px;
  background: transparent;
  color: var(--text-2);
  font-size: 14px; line-height: 1;
  cursor: pointer;
}
.dash-chrome-grip {
  cursor: grab;
  color: var(--text);
  /* Without this a drag on a touch screen scrolls the page instead. */
  touch-action: none;
}
.dash-chrome-grip:active { cursor: grabbing; }
.dash-chrome-btn:hover,
.dash-chrome-grip:hover { background: var(--row-hover); color: var(--text); }
.dash-chrome-del:hover { background: var(--bad-bg); color: var(--bad); }

/* While arranging, a tile reads as a movable object rather than a
   report: dimmed content, a visible edge, and no pointer events on the
   card itself so a click cannot follow one of its links. */
body.is-arranging .dash-slot > .card {
  outline: 1px dashed var(--input-line);
  outline-offset: -1px;
  /* Room for the chrome, which floats in the card's top corner. Without
     this it sat on top of the KPI label it is meant to act on. */
  padding-block-start: 42px;
}

/* The pane is a rail, not a modal: there is deliberately no scrim, since
   the page underneath is the thing being edited and has to stay
   clickable. It does have to stay VISIBLE, so the content column gives
   up the pane's width while arranging. */
/* Only where the pane is a side rail. Below 720px it is a bottom sheet
   and this padding would squeeze the dashboard - the thing being
   arranged - into a sliver beside nothing. */
@media (min-width: 721px) {
  body.is-arranging .content {
    /* Adds to the 36px the column already has; this block is at the end
       of the file, so it beats the `padding` shorthand above. */
    padding-inline-end: min(404px, 94vw);
  }
}
body.is-arranging .dash-slot > .card a { pointer-events: none; }

.dash-slot.is-dragged {
  opacity: 0.92;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.32);
  transform: rotate(-0.4deg);
}
body.is-dragging { user-select: none; -webkit-user-select: none; }

.dash-drop-marker {
  min-block-size: 76px;
  border: 2px dashed var(--brass);
  border-radius: 10px;
  background: var(--selected);
}

/* A widget added but not yet applied has no data - the server has not
   computed it. Say that on the tile rather than showing a card of
   zeroes, which would look like an answer. */
.dash-ghost {
  display: flex; flex-direction: column; gap: 4px;
  justify-content: center;
  min-block-size: 96px;
  border-style: dashed;
  border-color: var(--brass);
  background: var(--selected);
}
.dash-ghost-name { font-weight: 600; font-size: 15px; }
.dash-ghost-note { font-size: 12px; }

/* --- the pane --------------------------------------------------------- */
#dash-arrange { display: none; }
body.is-arranging #dash-arrange { display: block; }

/* The chat bubble is fixed to the same bottom corner and sat on top of
   the Save button. It is not part of arranging, so it steps out of the
   way rather than being worked around. */
body.is-arranging .chat-widget { display: none; }

.arrange-pane {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: min(380px, 92vw);
  z-index: 50;
  display: flex; flex-direction: column;
  background: var(--surface);
  border-inline-start: 0.5px solid var(--line);
  box-shadow: -8px 0 28px rgba(0, 0, 0, 0.28);
}

.arrange-head {
  padding: 14px 16px 12px;
  border-block-end: 0.5px solid var(--line);
}
.arrange-title { font-size: 17px; font-weight: 600; }
.arrange-sub { font-size: 12.5px; line-height: 1.5; margin-block-start: 2px; }
.arrange-count-line { white-space: nowrap; }
.arrange-search {
  inline-size: 100%;
  margin-block-start: 10px;
}

.arrange-list { flex: 1 1 auto; overflow-y: auto; padding: 6px 10px 10px; }
.arrange-group-head {
  margin: 12px 6px 4px;
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-3);
}

.arrange-item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 8px; border-radius: 8px;
  border: 0.5px solid transparent;
}
.arrange-item:hover { background: var(--row-hover); border-color: var(--line); }
.arrange-item.is-placed { opacity: 0.55; }
.arrange-grip {
  flex: 0 0 auto;
  inline-size: 20px; block-size: 20px;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-3); cursor: grab;
  touch-action: none;
}
.arrange-item.is-placed .arrange-grip { cursor: default; }
.arrange-text { display: flex; flex-direction: column; min-inline-size: 0; flex: 1 1 auto; }
.arrange-name { font-size: 13.5px; font-weight: 600; }
.arrange-desc { font-size: 12px; color: var(--text-2); line-height: 1.4; }
.arrange-add {
  flex: 0 0 auto;
  inline-size: 28px; block-size: 28px;
  border: 0.5px solid var(--input-line); border-radius: 7px;
  background: transparent; color: var(--text);
  font-size: 15px; line-height: 1; cursor: pointer;
}
.arrange-add:hover:not(:disabled) { background: var(--brass); color: var(--on-brass); border-color: var(--brass); }
.arrange-add:disabled { cursor: default; color: var(--good); border-color: transparent; }

.arrange-foot {
  padding: 12px 16px;
  border-block-start: 0.5px solid var(--line);
  display: flex; flex-direction: column; gap: 8px;
}
.arrange-name-field { margin: 0; }
.arrange-share { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.arrange-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.arrange-actions > * { flex: 1 1 auto; }
.arrange-more { font-size: 12px; text-align: center; }

/* On a phone the pane is a bottom sheet: a 380px rail beside a 375px
   screen would leave nothing to arrange onto. Half the height, so the
   dashboard stays visible above it while widgets are dragged out. */
@media (max-width: 720px) {
  .arrange-pane {
    inset-block-start: auto;
    inset-inline: 0;
    /* Overrides the rail's min(380px, 92vw), which otherwise left a
       30px gutter down one side of a full-width sheet. */
    inline-size: auto;
    block-size: 62vh;
    border-inline-start: 0;
    border-block-start: 0.5px solid var(--line);
    border-start-start-radius: 14px;
    border-start-end-radius: 14px;
    box-shadow: 0 -8px 28px rgba(0, 0, 0, 0.28);
  }
  body.is-arranging { padding-block-end: 62vh; }
}


/* === Branding: the palette editor (v2.6.4) =========================== */
/* Six colour rows on one grid instead of three hand-written blocks. The
   inline styles the old three carried are gone: a swatch that has to be
   48x38 in four places is a class. */

.palette-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px 18px;
}
.palette-field { margin: 0; min-inline-size: 0; }

.palette-row { display: flex; gap: 8px; align-items: center; }
.palette-row input[type="text"] { flex: 1 1 auto; min-inline-size: 0; }

.palette-well {
  flex: 0 0 auto;
  inline-size: 44px; block-size: 38px;
  padding: 2px;
  background: transparent;
  border: 0.5px solid var(--line-2);
  border-radius: 6px;
  cursor: pointer;
}

/* Empties the field, which is how you hand a surface back to the theme.
   Worth a control of its own: "delete the text" is not discoverable as
   the way to say "I do not want to set this". */
.palette-clear {
  flex: 0 0 auto;
  inline-size: 30px; block-size: 30px;
  display: flex; align-items: center; justify-content: center;
  border: 0.5px solid var(--input-line);
  border-radius: 6px;
  background: transparent;
  color: var(--text-3);
  font-size: 12px; line-height: 1;
  cursor: pointer;
}
.palette-clear:hover { color: var(--text); background: var(--row-hover); }

.palette-help { font-size: 11px; margin-block-start: 4px; }

/* The live contrast reading, under the field that caused it. Amber, not
   red: an unreadable pairing halfway through building a palette is a
   warning, not an error, and the form still saves. */
.palette-note {
  margin-block-start: 4px;
  font-size: 11px; font-weight: 600;
  color: var(--amber);
}

.palette-live {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-block-start: 14px;
  padding-block-start: 12px;
  border-block-start: 0.5px solid var(--line);
  font-size: 12.5px;
}
.palette-revert { margin-inline-start: auto; }

@media (max-width: 720px) {
  .palette-revert { margin-inline-start: 0; }
}


/* === Delivery performance panel (v2.6.8) ============================= */
/* The scoreboard half. The work-list half below it reuses .inv-row, the
   same label / value pair every other panel uses, so a late order reads
   like every other row on the dashboard. */

.otd-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.otd-figure { font-size: 30px; font-weight: 700; font-variant-numeric: tabular-nums; }
.otd-note { font-size: 12.5px; }

/* The track is the late share, not empty space. Painting the remainder
   in --well made 40% on time read as "40% measured, the rest unknown",
   which is a different and much kinder claim than the one the figure is
   making. The tint is low enough that a healthy bar still reads as one
   colour. */
.otd-track {
  block-size: 6px;
  border-radius: 999px;
  background: rgba(var(--bad-rgb), 0.30);
  overflow: hidden;
  margin-block: 10px 4px;
}
.otd-fill { block-size: 100%; background: var(--good); border-radius: 999px; }

.otd-coverage { font-size: 11.5px; margin-block-start: 6px; }

.otd-late-head {
  margin-block-start: 14px;
  padding-block-start: 10px;
  border-block-start: 0.5px solid var(--line);
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-2);
}
/* Two classes, because .good and this rule are both one class deep and
   this one is written later in the file. */
.otd-late-head.good { color: var(--good); }


/* === Material flow panels (v2.6.9) ================================== */
/* The footer line under How material arrived / left. The rows above are
   .inv-row, borrowed wholesale from the finished-goods pair so the two
   halves of the page read as one thing. */
.mtl-flow-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-block-start: 10px;
  padding-block-start: 9px;
  border-block-start: 0.5px solid var(--line);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
}


/* =====================================================================
   Themes 06 and 07, from design/XWARES-Themes-06-07-and-Icons.
   =====================================================================

   Arctic & Teal is an operational-floor palette; Pearl & Plum is the
   first preset in the app with a WHITE sidebar, which is why its
   chrome tokens look nothing like the others'. Every preset before it
   pairs light navigation ink with a dark sidebar, so the overlays are
   all rgba(255,255,255,...). Copying that here would have drawn white
   hairlines on white and a navigation border nobody could see.

   The kit supplies about twenty tokens; a theme block here carries
   about sixty. The rest were not eyeballed: --text-3, --line-2 and
   every chrome ink were computed against the surface they actually sit
   on with apps/core/contrast.py, and all 34 pairs in
   test_themes.ContrastTests clear AA. The generator is recorded in the
   commit for this change rather than kept as a script nothing runs.

   FONTS. The kit specifies Manrope and Plus Jakarta Sans. Plus Jakarta
   Sans is vendored and declared since v2.9.11 (top of this file) and is
   now every theme's fallback; Manrope is not shipped yet, so arctic-teal
   renders in Plus Jakarta Sans until it is.

   --chart-4 is named per theme instead of derived: deriving it from the
   primary produced a fourth series identical to the first on Pearl &
   Plum, which is two indistinguishable lines on one chart.
   ===================================================================== */
:root[data-theme="arctic-teal"] {
  color-scheme: light;
  --sans:                'Manrope', 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --sans-fa:             'Vazirmatn', 'Manrope', 'Plus Jakarta Sans', system-ui, sans-serif;
  --bg:                  #EFF6F8;
  --surface:             #FFFFFF;
  --surface-2:           #FFFFFF;
  --well:                #E5EFF2;
  --selected:            #DDF2EF;
  --text:                #16303B;
  --text-2:              #4E6670;
  --text-3:              #5C6E76;
  --page-text:           #16303B;
  --page-text-2:         #4E6670;
  --page-text-3:         #5C6E76;
  --page-accent:         #0F6B70;
  --line:                #CEDDE2;
  --line-2:              #ADB7BA;
  --input-line:          #738992;
  --zebra:               rgba(22, 48, 59, 0.035);
  --rule:                rgba(22, 48, 59, 0.08);
  --row-hover:           rgba(22, 48, 59, 0.055);
  --brass:               #0F6B70;
  --brass-rgb:           15, 107, 112;
  --brass-soft:          #0B565B;
  --brass-ink:           #0F6B70;
  --on-brass:            #FFFFFF;
  --on-tint:             #FFFFFF;
  --good:                #176343;
  --good-rgb:            23, 99, 67;
  --good-bg:             #E7F3EB;
  --amber:               #8A4B08;
  --amber-rgb:           138, 75, 8;
  --amber-bg:            #FFF4D6;
  --bad:                 #B42332;
  --bad-rgb:             180, 35, 50;
  --bad-bg:              #FDECEE;
  --info:                #1D5E91;
  --info-rgb:            29, 94, 145;
  --info-bg:             #EAF3FB;
  --yellow:              #6E6410;
  --yellow-rgb:          110, 100, 16;
  --glow:                #1D5E91;
  --glow-rgb:            29, 94, 145;
  --focus:               #1D5E91;
  --chart-1:             #0F6B70;
  --chart-2:             #8A4B08;
  --chart-3:             #4338CA;
  --chart-4:             #76539B;
  --chrome-bg:           #12323C;
  --chrome-text:         #F0F8FA;
  --chrome-text-2:       #BFDAE2;
  /* Tuned against the hover/active overlay, not the bare
     sidebar: a group label sitting under one lost ~0.3 of its
     ratio and dropped below AA. 10.39 bare / 4.58 over the active row. */
  --chrome-text-3:       #D9E3E6;
  /* Readable on the RAISED chrome surface too - the FY pill and
     the language toggle sit on --chrome-raise, not on the bare
     sidebar. This was 3.50 there. */
  --chrome-accent:       #8EB9BC;
  --chrome-line:         rgba(255, 255, 255, 0.14);
  --chrome-raise:        rgba(255, 255, 255, 0.10);
  --chrome-hover:        rgba(255, 255, 255, 0.16);
  /* Readable on the ACTIVE ROW, not just on the bare sidebar.
     This was 3.30 on its own --chrome-active-bg: the ink was tuned
     against the sidebar, and the row it labels is lighter than
     that, so the one navigation item you most need to find was
     the hardest to read. */
  --chrome-active:       #95BDC0;
  --chrome-active-bg:    #1D4C56;
  --chrome-active-line:  rgba(255, 255, 255, 0.26);
}

:root[data-theme="pearl-plum"] {
  color-scheme: light;
  --sans:                'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --sans-fa:             'Vazirmatn', 'Plus Jakarta Sans', system-ui, sans-serif;
  --bg:                  #F7F4F8;
  --surface:             #FFFFFF;
  --surface-2:           #FFFFFF;
  --well:                #F0EAF3;
  --selected:            #EFE4F4;
  --text:                #302638;
  --text-2:              #695B72;
  --text-3:              #6E6774;
  --page-text:           #302638;
  --page-text-2:         #695B72;
  --page-text-3:         #6E6774;
  --page-accent:         #74408A;
  --line:                #DFD6E4;
  --line-2:              #B7B3B9;
  --input-line:          #8A7A93;
  --zebra:               rgba(48, 38, 56, 0.035);
  --rule:                rgba(48, 38, 56, 0.08);
  --row-hover:           rgba(48, 38, 56, 0.055);
  --brass:               #74408A;
  --brass-rgb:           116, 64, 138;
  --brass-soft:          #5F3073;
  --brass-ink:           #74408A;
  --on-brass:            #FFFFFF;
  --on-tint:             #FFFFFF;
  --good:                #176343;
  --good-rgb:            23, 99, 67;
  --good-bg:             #E7F3EB;
  --amber:               #8A4B08;
  --amber-rgb:           138, 75, 8;
  --amber-bg:            #FFF4D6;
  --bad:                 #B42332;
  --bad-rgb:             180, 35, 50;
  --bad-bg:              #FDECEE;
  --info:                #1D5E91;
  --info-rgb:            29, 94, 145;
  --info-bg:             #EAF3FB;
  --yellow:              #6E6410;
  --yellow-rgb:          110, 100, 16;
  --glow:                #235F87;
  --glow-rgb:            35, 95, 135;
  --focus:               #235F87;
  --chart-1:             #74408A;
  --chart-2:             #16716B;
  --chart-3:             #235F87;
  --chart-4:             #8A4B08;
  --chrome-bg:           #FFFFFF;
  --chrome-text:         #302638;
  --chrome-text-2:       #695B72;
  /* Tuned against the hover/active overlay, not the bare
     sidebar: a group label sitting under one lost ~0.3 of its
     ratio and dropped below AA. 6.83 bare / 4.64 over the active row. */
  --chrome-text-3:       #5F5866;
  --chrome-accent:       #74408A;
  --chrome-line:         rgba(48, 38, 56, 0.12);
  --chrome-raise:        rgba(48, 38, 56, 0.05);
  --chrome-hover:        rgba(48, 38, 56, 0.07);
  --chrome-active:       #74408A;
  --chrome-active-bg:    #EFE4F4;
  --chrome-active-line:  rgba(48, 38, 56, 0.20);
}


/* === Dashboard widget grammar (v2.9.10) ===============================
   One set of rules for every tile and panel the operational dashboard
   can hold, native or borrowed. Before this each widget carried its own
   inline styles and three different bar idioms; a dashboard of thirty
   of them read as thirty small screens.

   The grammar, top to bottom of a KPI tile:
     label (what)  ->  figure in mono numerals (how much)  ->  a meter or
     status strip where a target exists (against what)  ->  one context
     line  ->  a hairline, then the change against the comparison window
     and a sparkline of the period behind the figure (moving which way).
   And of a panel:
     h2 title + one line saying what period it covers  ->  rows or a chart
     ->  a legend only when there is more than one series to tell apart.

   No new flex container here uses `gap` (iOS 12 has none for flex);
   spacing is margins, or grid where a gap is wanted.
   ===================================================================== */

/* --- the figure strip ------------------------------------------------ */
/* Any number of figures, not a fixed 3/4/5: the strip is a layout the
   user chooses, so it fills to a minimum tile width instead. */
#dash-kpis.dash-hero {
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

.kpi-tile {
  display: flex;
  flex-direction: column;
  padding: 16px 18px 14px;
  min-inline-size: 0;
}
.kpi-tile .kpi-card-head { min-block-size: 22px; }
.kpi-tile .kpi-label {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.09em;
  color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kpi-tile .kpi-value {
  font-family: var(--mono);
  font-size: 29px; font-weight: 600; line-height: 1.1;
  letter-spacing: -0.02em;
  margin-top: 12px;
  color: var(--text);
  /* The figure never breaks; the "/ target unit" after it may drop to a
     second line, which a long Persian unit word needs. */
  overflow-wrap: normal;
}
.kpi-tile .kpi-value.bad { color: var(--bad); }
.kpi-tile .kpi-value.brass { color: var(--brass-ink); }
.kpi-tile .kpi-value.dim { color: var(--text-3); }
.kpi-tile .kpi-unit {
  font-size: 13px; font-weight: 500; letter-spacing: 0;
  color: var(--text-3);
}
[lang="fa"] .kpi-tile .kpi-unit { font-family: var(--sans-fa); }
.kpi-tile .kpi-sub { margin-top: 7px; font-size: 12px; line-height: 1.45; }
.kpi-sub-aside { margin-inline-start: 8px; color: var(--text-3); white-space: nowrap; }

/* A target as a meter: how far along it the figure is. Capped at the
   track; the badge beside it carries anything past 100%. */
.kpi-meter {
  position: relative; block-size: 4px; margin-top: 10px;
  border-radius: 999px; background: var(--well); overflow: hidden;
}
.kpi-meter-fill { display: block; block-size: 100%; border-radius: inherit; background: var(--good); }
.kpi-meter.is-warn .kpi-meter-fill { background: var(--amber); }
.kpi-meter.is-bad .kpi-meter-fill { background: var(--bad); }

/* One segment per press, in its status colour. */
.kpi-strip { display: flex; block-size: 6px; margin-top: 10px; }
.kpi-strip-seg {
  flex: 1 1 0; min-inline-size: 3px; border-radius: 2px;
  margin-inline-end: 2px; background: var(--line-2);
}
.kpi-strip-seg:last-child { margin-inline-end: 0; }
.kpi-strip-seg.is-ahead { background: var(--good); }
.kpi-strip-seg.is-running { background: var(--amber); }
.kpi-strip-seg.is-behind { background: var(--bad); }

/* The comparison row: pushed to the bottom so a strip of tiles with
   different amounts of context still lines their deltas up. */
.kpi-tile .kpi-foot {
  margin-top: auto;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: end; column-gap: 10px;
  padding-top: 10px;
  border-top: 0.5px solid var(--line);
}
.kpi-tile > .kpi-sub { margin-bottom: 12px; }
.kpi-foot-text { min-inline-size: 0; }
.kpi-delta-line { font-size: 11.5px; line-height: 1.35; }
.kpi-delta { display: inline-block; font-weight: 600; white-space: nowrap; margin-inline-end: 6px; }
.kpi-delta.is-good { color: var(--good); }
.kpi-delta.is-bad  { color: var(--bad); }
.kpi-delta.is-flat { color: var(--text-2); }
.kpi-delta-arrow { font-size: 8px; vertical-align: 1px; margin-inline-end: 3px; }
.kpi-vs { display: block; margin-top: 1px; color: var(--text-3); font-size: 11px; }

.kpi-spark { display: block; inline-size: 96px; block-size: 26px; overflow: visible; }
.kpi-spark-line { fill: none; stroke: var(--brass-ink); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.kpi-spark-area { fill: rgba(var(--brass-rgb), 0.12); stroke: none; }
.kpi-spark-dot { fill: var(--brass-ink); }

/* --- panel header ----------------------------------------------------- */
/* An h2 for the outline (h1 is the page), set in the UI face: the
   serif is for page titles, not for thirty card headings. */
.card-head-text { min-inline-size: 0; }
h2.card-title {
  margin: 0;
  font-family: inherit; font-size: 14px; font-weight: 600;
  line-height: 1.3; letter-spacing: -0.005em;
}
/* Was 9px mono uppercase: unreadable at arm's length on a press-side
   iPad, and a date range set in capitals reads as a code. */
.card-sub {
  font-family: inherit; font-size: 11.5px; letter-spacing: 0;
  text-transform: none; color: var(--text-3); margin-top: 2px;
}
.card-action { font-weight: 500; text-decoration: none; padding: 4px 0; }
.card-action:hover { text-decoration: underline; }
.card-action-arrow { display: inline-block; margin-inline-start: 4px; }
[dir="rtl"] .card-action-arrow { transform: scaleX(-1); }

/* --- ranked rows ------------------------------------------------------- */
.inv-row.w-rank {
  grid-template-columns: minmax(0, 1.3fr) minmax(56px, 1fr) auto;
  padding: 6px 0;
}
.w-rank .inv-track { block-size: 6px; }
.w-rank .inv-row-label { font-size: 12.5px; }
.w-rank .inv-row-value { font-size: 12px; }
.inv-fill.is-ahead   { background: var(--good); }
.inv-fill.is-running { background: var(--amber); }
.inv-fill.is-behind  { background: var(--bad); }
.inv-fill.is-idle    { background: var(--line-2); }
.w-pct { display: inline-block; min-inline-size: 3.2em; text-align: end; margin-inline-start: 8px; }
.w-aside { margin-inline-start: 8px; }
.w-note { font-size: 12px; margin: 10px 0 0; }

/* --- list rows (name | figure | status) ------------------------------ */
.w-list-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center; column-gap: 10px;
  padding: 9px 0; font-size: 12.5px;
  border-top: 0.5px solid var(--line);
}
.card-head + .w-list-row { border-top: 0; }
.w-list-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.w-list-figure { white-space: nowrap; }

/* --- alerts ------------------------------------------------------------ */
.w-chips { display: flex; flex-wrap: wrap; margin-bottom: 6px; }
.w-chips .w-chip { margin-inline-end: 6px; margin-bottom: 6px; }

/* Tables inside a panel: the app-wide table is set for a full page of
   records; in a half-width card it needs tighter rows and lighter
   numerals or four rows fill the card. */
.dash-slot .tbl { font-size: 13px; }
.dash-slot .tbl th { font-size: 11.5px; padding: 8px 10px; color: var(--text-3); }
.dash-slot .tbl td { padding: 8px 10px; }
.dash-slot .tbl .mono { font-weight: 500; }
.w-chip {
  display: inline-block; padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 500; text-decoration: none;
  border: 0.5px solid transparent;
}
.w-chip.is-bad  { color: var(--bad);   background: rgba(var(--bad-rgb), 0.10);   border-color: rgba(var(--bad-rgb), 0.35); }
.w-chip.is-warn { color: var(--amber); background: rgba(var(--amber-rgb), 0.10); border-color: rgba(var(--amber-rgb), 0.35); }
.w-alert {
  padding: 7px 10px; margin-bottom: 6px; border-radius: 6px; font-size: 12px;
  border: 0.5px solid transparent; border-inline-start-width: 3px;
}
.w-alert.is-bad  { background: rgba(var(--bad-rgb), 0.07);   border-color: rgba(var(--bad-rgb), 0.30); }
.w-alert.is-warn { background: rgba(var(--amber-rgb), 0.08); border-color: rgba(var(--amber-rgb), 0.30); }
.w-alert-title { font-weight: 600; }
.w-alert.is-bad .w-alert-title  { color: var(--bad); }
.w-alert.is-warn .w-alert-title { color: var(--amber); }
.w-alert-meta { margin-top: 2px; }

/* --- server-drawn charts ------------------------------------------------ */
.xw-chart { display: block; inline-size: 100%; block-size: auto; overflow: visible; }
/* A chart drawn for a half-width card, shown in a full-width one (the
   grid goes single-column on an iPad), would otherwise scale its labels
   to 17px. Capping the height keeps the drawing - and its type - close to
   the size it was laid out at; the SVG centres itself in the spare width. */
.xw-chart-scroll > .xw-chart { max-block-size: 250px; margin-inline: auto; }
.xw-baseline { stroke: var(--line-2); stroke-width: 1; }
.xw-bar { fill: var(--good); }
/* A period that missed its target: same hue, less ink. The dashed line
   already says where the target was; the fill says which fell short. */
.xw-bar.is-missed { fill: rgba(var(--good-rgb), 0.55); }
.xw-bar.is-brand { fill: var(--brass); }
.xw-target { fill: none; stroke: var(--amber); stroke-width: 1.75; stroke-dasharray: 4 3; }
.xw-target.is-flat { stroke-width: 1.25; }
.xw-line { fill: none; stroke: var(--good); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.xw-dot { fill: var(--good); stroke: var(--surface-2); stroke-width: 1.5; }
.xw-v { fill: var(--text); font-family: var(--mono); font-weight: 600; }
.xw-target-v { fill: var(--amber); font-family: var(--mono); }
.xw-axis { fill: var(--text-3); font-family: var(--mono); font-size: 10px; }

.chart-legend {
  display: flex; flex-wrap: wrap; align-items: center;
  margin-top: 8px; font-size: 11px; color: var(--text-3); line-height: 1.7;
}
.chart-legend-item { margin-inline-end: 14px; white-space: nowrap; }
.chart-legend-note { margin-inline-start: auto; }
.lg-swatch { display: inline-block; vertical-align: middle; margin-inline-end: 6px; }
.lg-swatch.is-bar { inline-size: 9px; block-size: 9px; border-radius: 2px; background: var(--good); }
.lg-swatch.is-bar.is-missed { background: rgba(var(--good-rgb), 0.55); }
.lg-swatch.is-dash { inline-size: 14px; block-size: 0; border-top: 2px dashed var(--amber); }
.w-legend-row { margin: 0 0 8px; }

/* --- empty, loading, failed -------------------------------------------- */
.w-empty {
  flex: 1 1 auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-block-size: 110px; padding: 14px 10px; text-align: center;
  border: 1px dashed var(--line-2); border-radius: var(--radius);
}
.w-empty-msg { margin: 0; font-size: 13px; color: var(--text-2); max-inline-size: 44ch; line-height: 1.45; }
.w-empty-hint { margin: 6px 0 0; font-size: 11.5px; color: var(--text-3); }

.w-loading { min-block-size: 190px; }
.w-skel span {
  display: block; block-size: 8px; margin: 16px 0;
  border-radius: 999px; background: var(--well);
}
.w-skel span:nth-child(1) { inline-size: 92%; }
.w-skel span:nth-child(2) { inline-size: 74%; }
.w-skel span:nth-child(3) { inline-size: 56%; }
@media (prefers-reduced-motion: no-preference) {
  .w-skel span { animation: w-skel-pulse 1.4s ease-in-out infinite; }
  .w-skel span:nth-child(2) { animation-delay: 0.15s; }
  .w-skel span:nth-child(3) { animation-delay: 0.3s; }
}
@keyframes w-skel-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
.w-loading.is-failed .w-skel { display: none; }
.w-loading.is-failed .card-sub { color: var(--bad); }

/* The view picker's hidden native select is 1px wide (xw-select.css);
   the toolbar's min-width above beat that and pushed the page 14px
   past an iPad's width. */
.dash-toolbar-pick .xw-sel-ready .xw-sel-native,
.xw-sel-ready .dash-toolbar-pick .xw-sel-native { min-width: 0; }
.dash-toolbar-pick select.xw-sel-native { min-width: 0; }

/* Charts keep a readable size on a phone: below that they scroll inside
   their card rather than shrinking their labels to 4px. */
.xw-chart-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (max-width: 640px) {
  .xw-chart-scroll > .xw-chart { min-inline-size: 520px; }
  .dash-slot-half .xw-chart-scroll > .xw-chart { min-inline-size: 400px; }
}

/* The same panel on its source dashboard. */
.outbound-trend { margin-top: 14px; }

/* A widget just added in arrange mode, shown for real but not saved. */
.card.is-preview { outline: 1px dashed rgba(var(--brass-rgb), 0.55); outline-offset: 2px; }

/* --- the catalogue: an icon per widget, and what kind of thing it is -- */
.arrange-icon,
.dash-pick-icon {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 30px; block-size: 30px; border-radius: 8px;
  background: var(--surface); border: 0.5px solid var(--line);
  color: var(--brass-ink);
}
.arrange-icon.is-chart { background: rgba(var(--brass-rgb), 0.08); }
.arrange-icon .xw-widget-icon,
.dash-pick-icon .xw-widget-icon { inline-size: 15px; block-size: 15px; }
.dash-pick-icon { margin-inline-end: 2px; }
.arrange-meta,
.dash-pick-meta {
  display: block; margin-top: 3px;
  font-size: 10.5px; color: var(--text-3); letter-spacing: 0.02em;
}
/* A finger, not a cursor: the add button grows to a real touch target. */
@media (pointer: coarse) {
  .arrange-add { inline-size: 40px; block-size: 40px; }
}

/* --- phone and iPad ----------------------------------------------------- */
@media (max-width: 640px) {
  /* Two figures abreast, smaller, rather than one 300px-wide tile per
     screen: the strip should still be a strip. The sparkline goes - the
     delta beside it says the same in less room. */
  #dash-kpis.dash-hero { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .kpi-tile { padding: 12px 12px 10px; }
  .kpi-tile .kpi-value { font-size: 21px; margin-top: 8px; }
  .kpi-tile .kpi-unit { font-size: 11px; }
  .kpi-tile .kpi-sub { font-size: 11px; }
  .kpi-tile .kpi-foot { grid-template-columns: minmax(0, 1fr); }
  .kpi-spark { display: none; }
  .inv-row.w-rank { grid-template-columns: 1fr auto; }
  .chart-legend-note { margin-inline-start: 0; }
}

/* === Soft surfaces (v2.9.12) =========================================
   Replaces the v2.9.11 "double bezel" pass, which the owner reviewed and
   did not like (a visible tray and ring round every card read as heavy).
   The language now, adapted from the reference the owner chose:

   * A calm canvas and clean cards: a large radius, comfortable padding,
     and almost no shadow. On the light themes separation comes from the
     canvas/surface contrast plus one whisper-soft, wide, low-opacity
     shadow; on the dark themes there is no shadow at all, only the step
     from --bg to --surface-2 and a hairline.
   * App-wide, not only on dashboards: every .card, table card and button
     takes the same radius and pill shape, so a list or a form looks like
     the same product as the dashboard above it. Nothing is restructured.
   * On dashboards: one HERO figure per page in the theme's accent, the
     others plain; a circular arrow on each figure to its detail screen;
     capsule bars in accent shades with hatched empties; smooth area
     lines; a half-circle gauge for shares; small tinted status chips.

   Every colour is a token resolved from the theme's own --brass/--line/
   --on-brass, so all nine themes and a tenant's palette carry it.
   ===================================================================== */
:root {
  --ease-spring: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --card-radius: 20px;
  /* dark themes: surface steps, no shadow */
  --card-shadow: none;
  --card-border: var(--line);
  --hero-bg: var(--brass);
  --hero-ink: var(--on-brass);
  --cap-strong: var(--brass);
  --cap-mid: rgba(var(--brass-rgb), 0.72);
  --cap-soft: rgba(var(--brass-rgb), 0.46);
  --cap-hatch: repeating-linear-gradient(135deg,
      rgba(var(--brass-rgb), 0.34) 0, rgba(var(--brass-rgb), 0.34) 2px,
      rgba(var(--brass-rgb), 0.07) 2px, rgba(var(--brass-rgb), 0.07) 7px);
  --chip-bg: rgba(var(--brass-rgb), 0.12);
  --chart-grid: rgba(233, 238, 235, 0.07);
  --chart-band: rgba(233, 238, 235, 0.05);
  --seg-bg: rgba(255, 255, 255, 0.05);
}
:root[data-theme="light"],
:root[data-theme="ivory-forest"],
:root[data-theme="porcelain-indigo"],
:root[data-theme="sand-terracotta"],
:root[data-theme="arctic-teal"],
:root[data-theme="pearl-plum"] {
  --card-shadow: 0 1px 2px rgba(20, 24, 40, 0.025), 0 12px 32px -20px rgba(20, 24, 40, 0.12);
  --card-border: transparent;
  --chart-grid: rgba(20, 24, 32, 0.07);
  --chart-band: rgba(20, 24, 32, 0.035);
  --seg-bg: rgba(20, 24, 32, 0.045);
}

/* --- cards and buttons, app-wide ------------------------------------------ */
.card,
.tbl-card,
.kpi-card {
  border-radius: var(--card-radius);
  border-color: var(--card-border);
  box-shadow: var(--card-shadow);
}
.btn, .btn-brass, .btn-bad, .btn-bad-outline,
button.btn, button.btn-brass {
  border-radius: 999px;
  transition: transform 220ms var(--ease-spring), background-color 220ms var(--ease-spring),
              border-color 220ms var(--ease-spring), color 220ms var(--ease-spring);
}
.btn:active, .btn-brass:active, .btn-bad:active, .btn-bad-outline:active { transform: scale(0.98); }
/* Primary: a filled accent pill with a little depth; secondary stays the
   outlined pill .btn already is. */
.btn-brass { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14); }

/* --- dashboards: spacing and type ---------------------------------------- */
.is-dashboard .card { padding: 20px 22px; }
.is-dashboard .dash-body,
.is-dashboard #dash-kpis.dash-hero,
.is-dashboard .inv-grid,
.is-dashboard .inv-kpis,
.is-dashboard .kpi-grid,
.is-dashboard .dash-row,
.is-dashboard .two-up,
.is-dashboard .dash-hero { gap: 16px; }
/* The app-wide `.card + .card { margin-top }` is for cards stacked in a
   column; inside a grid row it pushed every card after the first down. */
.is-dashboard .dash-hero > .card + .card,
.is-dashboard .dash-row > .card + .card,
.is-dashboard .inv-grid > .card + .card,
.is-dashboard .inv-kpis > .card + .card,
.is-dashboard .kpi-grid > .card + .card,
.is-dashboard .two-up > .card + .card,
.is-dashboard [style*="grid-template-columns"] > .card + .card { margin-top: 0; }
.is-dashboard .card-title,
.is-dashboard h2.card-title,
.is-dashboard .panel-head h2 {
  font-family: inherit; font-size: 15px; font-weight: 650; letter-spacing: 0;
  border: 0; padding: 0;
}
.is-dashboard .kpi-value { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }

/* Eyebrow: kept from v2.9.11, quieter. No capitals or tracking in FA. */
.eyebrow {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 10px; font-weight: 600; line-height: 1.4;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-2); background: var(--seg-bg);
  margin-bottom: 8px;
}
[lang="fa"] .eyebrow { letter-spacing: 0; text-transform: none; font-size: 11px; }

/* --- KPI figures ----------------------------------------------------------- */
/* Label top-left in sentence case, the way out top-right as a circular
   arrow, the figure big and bold, the change on a chip. */
.kpi-tile .kpi-card-head,
.is-dashboard .kpi-card-head { justify-content: space-between; }
.kpi-tile .kpi-label,
.is-dashboard .kpi-card .kpi-label,
.is-dashboard .card > .kpi-card-head .kpi-label {
  font-size: 13px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--text);
}
.kpi-tile .kpi-value { font-size: 32px; font-weight: 700; margin-top: 14px; }
.tile-link {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 30px; block-size: 30px; border-radius: 999px;
  border: 1px solid var(--line-2); color: var(--text);
  font-size: 14px; line-height: 1; text-decoration: none;
  margin-inline-start: 8px;
  transition: transform 260ms var(--ease-spring), background-color 260ms var(--ease-spring);
}
.tile-link:hover { text-decoration: none; background: var(--chip-bg); transform: translate(1px, -1px); }
[dir="rtl"] .tile-link span { display: inline-block; transform: scaleX(-1); }
@media (pointer: coarse) { .tile-link { inline-size: 36px; block-size: 36px; } }
.kpi-tile .kpi-foot { border-top: 0; padding-top: 12px; }
.kpi-delta-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 16px; block-size: 16px; border-radius: 5px;
  margin-inline-end: 5px; font-size: 7px; vertical-align: 1px;
  background: var(--chip-bg); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.25);
}
.kpi-delta.is-bad .kpi-delta-arrow { background: rgba(var(--bad-rgb), 0.12); box-shadow: inset 0 0 0 1px rgba(var(--bad-rgb), 0.3); }

/* The hero: the first figure on each dashboard, in the accent. The ink is
   the theme's own --on-brass, which is solved for contrast against
   --brass in every theme (white on the deep accents, near-black on the
   bright ones of the dark themes) - so no theme gets white on yellow. */
#dash-kpis > .dash-slot:first-child .kpi-tile,
.is-dashboard .kpi-grid:not(.kpi-grid-secondary) > .kpi-card:first-child,
.is-dashboard .inv-kpis > .card:first-child,
.is-dashboard .dash-hero > .card:first-child,
.is-dashboard .outcome-kpis.is-hero {
  background-color: var(--hero-bg);
  background-image:
    radial-gradient(circle at 108% -12%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 52px,
                    rgba(255, 255, 255, 0.10) 53px, rgba(255, 255, 255, 0.10) 54px,
                    rgba(255, 255, 255, 0) 55px, rgba(255, 255, 255, 0) 78px,
                    rgba(255, 255, 255, 0.08) 79px, rgba(255, 255, 255, 0.08) 80px,
                    rgba(255, 255, 255, 0) 81px),
    linear-gradient(140deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 58%);
  border-color: transparent;
  color: var(--hero-ink);
}
#dash-kpis > .dash-slot:first-child .kpi-tile *,
.is-dashboard .kpi-grid:not(.kpi-grid-secondary) > .kpi-card:first-child *,
.is-dashboard .inv-kpis > .card:first-child *,
.is-dashboard .dash-hero > .card:first-child * { color: inherit; }
#dash-kpis > .dash-slot:first-child .kpi-tile .dim,
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-unit,
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-vs,
.is-dashboard .kpi-grid:not(.kpi-grid-secondary) > .kpi-card:first-child .dim,
.is-dashboard .inv-kpis > .card:first-child .dim,
.is-dashboard .dash-hero > .card:first-child .dim { opacity: 0.82; }
#dash-kpis > .dash-slot:first-child .kpi-tile .tile-link { border-color: currentColor; }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-meter { background: rgba(255, 255, 255, 0.22); }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-meter-fill { background: var(--hero-ink); }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-delta-arrow { background: rgba(255, 255, 255, 0.18); box-shadow: none; }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-spark-line { stroke: var(--hero-ink); }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-spark-area { fill: rgba(255, 255, 255, 0.16); }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-spark-dot { fill: var(--hero-ink); }
#dash-kpis > .dash-slot:first-child .kpi-tile .kpi-icon-chip,
.is-dashboard .dash-hero > .card:first-child .kpi-icon-chip,
.is-dashboard .inv-kpis > .card:first-child .kpi-icon-chip { background: rgba(255, 255, 255, 0.16); border-color: transparent; }

/* --- the way out of a panel: a small outlined pill ------------------------- */
.card-action {
  display: inline-flex; align-items: center;
  padding: 4px 12px; border-radius: 999px;
  border: 1px solid var(--line-2);
  font-size: 11.5px; line-height: 1.5; text-decoration: none; white-space: nowrap;
  transition: background-color 220ms var(--ease-spring), transform 220ms var(--ease-spring);
}
.card-action:hover { text-decoration: none; background: var(--chip-bg); }
.card-action:active { transform: scale(0.98); }
.card-action-arrow { display: inline-block; margin-inline-start: 6px; }
[dir="rtl"] .card-action-arrow { transform: scaleX(-1); }

/* --- segmented control and stat chips --------------------------------------- */
.seg,
.metric-toggle {
  display: inline-flex; flex-wrap: wrap; padding: 3px;
  border-radius: 999px; background: var(--seg-bg);
}
.seg > button, .seg > a, .metric-toggle > button {
  padding: 5px 13px; min-block-size: 30px; border: 0; border-radius: 999px;
  background: transparent; color: var(--text-2);
  font: inherit; font-size: 12px; font-weight: 500; line-height: 20px;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background-color 220ms var(--ease-spring), color 220ms var(--ease-spring);
}
.seg > button.is-active, .seg > a.is-active, .metric-toggle > button.is-active {
  background: var(--surface-2); color: var(--text); font-weight: 650;
  box-shadow: 0 1px 2px rgba(20, 24, 40, 0.10);
}
.seg > a:hover { text-decoration: none; color: var(--text); }
@media (pointer: coarse) { .seg > button, .seg > a { min-block-size: 36px; } }
/* The customer page's own .metric-toggle rules came out in v2.9.12; it is
   a .seg now. */
.metric-toggle { margin-bottom: 12px; }
.chart-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.chart-toolbar > .seg { margin: 0 8px 6px 0; }
[dir="rtl"] .chart-toolbar > .seg { margin: 0 0 6px 8px; }
.stat-chips { display: flex; flex-wrap: wrap; align-items: center; }
.stat-chip {
  display: inline-flex; align-items: baseline;
  padding: 4px 10px; margin: 0 6px 6px 0; border-radius: 999px;
  background: var(--seg-bg); font-size: 11.5px; color: var(--text-2); white-space: nowrap;
}
[dir="rtl"] .stat-chip { margin: 0 0 6px 6px; }
.stat-chip b { color: var(--text); font-weight: 650; margin: 0 4px; }
.trend-stats { margin-bottom: 8px; }

/* --- charts fill their card ------------------------------------------------- */
/* A chart card is a column; its plot takes whatever height is left, so a
   chart beside a long list grows instead of floating in the top third. */
.chart-card { display: flex; flex-direction: column; }
.cap-swap { flex: 1 1 auto; display: flex; flex-direction: column; }
.cap-frame,
.area-frame { position: relative; flex: 1 1 auto; min-block-size: 200px; }

/* Capsules (charts.capsule_chart). */
.cap-plot { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; }
.cap-col {
  flex: 1 1 0; min-inline-size: 0; display: flex; flex-direction: column; align-items: center;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.cap-track { position: relative; flex: 1 1 auto; inline-size: 100%; margin-top: 28px; }
.cap-bar {
  position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  inline-size: 64%; max-inline-size: 44px; min-block-size: 12px;
  border-radius: 999px; background: var(--cap-mid);
  transition: background-color 260ms var(--ease-spring), opacity 260ms var(--ease-spring);
}
.cap-bar.is-strong { background: var(--cap-strong); }
.cap-bar.is-mid { background: var(--cap-mid); }
.cap-bar.is-soft { background: var(--cap-soft); }
.cap-bar.is-empty { background: var(--cap-hatch); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.22); }
/* The period still under way: outlined at its partial height. */
.cap-bar.is-open { background: rgba(var(--brass-rgb), 0.12); box-shadow: inset 0 0 0 2px rgba(var(--brass-rgb), 0.75); }
.cap-col.is-sel .cap-bar:not(.is-empty) { background: var(--cap-strong); }
.cap-col.is-dim .cap-bar { opacity: 0.55; }
.cap-target {
  position: absolute; left: 14%; right: 14%; block-size: 0;
  border-top: 2px dashed var(--amber); pointer-events: none; z-index: 1;
}
.cap-v {
  position: absolute; left: 50%; transform: translateX(-50%);
  margin-bottom: 6px; padding: 2px 5px; border-radius: 999px;
  font-family: var(--mono); font-size: 10px; line-height: 1;
  color: var(--text-2); background: var(--surface-2);
  white-space: nowrap; unicode-bidi: isolate; pointer-events: none; z-index: 2;
}
.cap-v.is-pill,
.cap-col.is-sel .cap-v {
  display: inline-block !important;
  padding: 4px 8px; margin-bottom: 8px; border-radius: 999px;
  background: var(--cap-strong); color: var(--on-brass); font-weight: 650;
  box-shadow: 0 6px 14px -8px rgba(var(--brass-rgb), 0.9);
}
.cap-plot .cap-col.is-dim .cap-v.is-pill { background: var(--cap-mid); }
.cap-x { margin-top: 8px; min-block-size: 14px; text-align: center; font-size: 10.5px; color: var(--text-3); white-space: nowrap; }
.cap-x.is-quiet { visibility: hidden; }
.cap-col.is-sel .cap-x { visibility: visible; color: var(--text); font-weight: 600; }

/* Area lines (charts.area_series). */
.area-plot { position: absolute; top: 0; right: 0; bottom: 22px; left: 0; }
.area-svg { position: absolute; top: 0; left: 0; inline-size: 100%; block-size: 100%; overflow: visible; }
.area-grid { stroke: var(--chart-grid); stroke-width: 1; stroke-dasharray: 2 4; }
.area-base { stroke: var(--line-2); stroke-width: 1; }
.area-target { stroke: var(--amber); stroke-width: 1.5; stroke-dasharray: 4 3; }
.area-line { fill: none; stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }
.area-dot {
  position: absolute; inline-size: 8px; block-size: 8px; margin: -4px 0 0 -4px;
  border-radius: 999px; box-shadow: 0 0 0 2px var(--surface-2); pointer-events: none;
}
.area-dot.is-sel, .area-dot.is-peak { inline-size: 10px; block-size: 10px; margin: -5px 0 0 -5px; }
.area-v {
  position: absolute; transform: translate(-50%, -50%);
  padding: 2px 5px; border-radius: 999px;
  font-family: var(--mono); font-size: 10px; font-weight: 600; line-height: 1.1; color: var(--text-2);
  background: var(--surface-2); box-shadow: 0 0 0 1px var(--surface-2);
  white-space: nowrap; pointer-events: none; z-index: 3;
}
.area-v.is-pill { padding: 4px 8px; background: var(--cap-strong); color: var(--on-brass); font-weight: 650; }
/* The period still under way: a dashed segment, a hollow point, a note. */
.area-line.is-open { stroke-dasharray: 5 5; opacity: 0.85; }
.area-dot { z-index: 2; }
.area-dot.is-open { background: var(--surface-2); border: 2px solid; inline-size: 10px; block-size: 10px; margin: -5px 0 0 -5px; box-sizing: border-box; }
.area-x-l i, .cap-x i, .activity-open-note { display: block; font-style: normal; font-size: 9.5px; color: var(--text-3); line-height: 1.2; }
.area-x-l.is-open, .cap-x.is-open { color: var(--text-2); }
.area-hit.is-open { background-image: repeating-linear-gradient(135deg, var(--chart-band) 0, var(--chart-band) 2px, transparent 2px, transparent 8px); }
.area-target-v { position: absolute; right: 0; transform: translateY(-120%); font-family: var(--mono); font-size: 10px; color: var(--amber); }
[dir="rtl"] .area-target-v { right: auto; left: 0; }
.area-hit { position: absolute; top: 0; bottom: 0; cursor: pointer; border-radius: 10px; -webkit-tap-highlight-color: transparent; }
.area-hit.is-sel { background: var(--chart-band); }
.area-x { position: absolute; left: 0; right: 0; bottom: 0; block-size: 16px; }
.area-x-l { position: absolute; transform: translateX(-50%); font-size: 10.5px; color: var(--text-3); white-space: nowrap; }
.area-x-l.is-quiet { visibility: hidden; }
/* Time runs the reading way in Persian: the percent box is mirrored. */
[dir="rtl"] .area-plot, [dir="rtl"] .area-x { transform: scaleX(-1); }
[dir="rtl"] .area-x-l, [dir="rtl"] .area-target-v { transform: scaleX(-1) translate(50%, 0); }
[dir="rtl"] .area-v { transform: scaleX(-1) translate(50%, -50%); }
[dir="rtl"] .area-target-v { transform: scaleX(-1) translateY(-120%); }

/* Legend swatches for the capsule language. */
.lg-cap { display: inline-block; vertical-align: middle; inline-size: 8px; block-size: 14px; border-radius: 999px; margin-inline-end: 6px; }
.lg-cap.is-strong { background: var(--cap-strong); }
.lg-cap.is-soft { background: var(--cap-soft); }
.lg-cap.is-empty { background: var(--cap-hatch); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.22); }
.lg-cap.is-open { background: rgba(var(--brass-rgb), 0.12); box-shadow: inset 0 0 0 2px rgba(var(--brass-rgb), 0.75); }
.heat-cell.is-today { box-shadow: inset 0 0 0 2px var(--text-2); }

/* Half-circle gauge (reports/parts/_gauge.html). */
.gauge { position: relative; max-inline-size: 260px; margin: 4px auto 0; text-align: center; }
.gauge-svg { display: block; inline-size: 100%; block-size: auto; overflow: visible; }
.gauge-fill { fill: none; stroke: var(--cap-strong); stroke-linecap: round; }
.gauge-rest { fill: none; stroke-linecap: round; }
.gauge-hatch-bg { fill: rgba(var(--brass-rgb), 0.08); }
.gauge-hatch-line { stroke: rgba(var(--brass-rgb), 0.38); stroke-width: 3; }
.gauge-figure { position: absolute; left: 0; right: 0; bottom: 2px; }
.gauge-figure b { display: block; font-size: 30px; font-weight: 700; line-height: 1.05; color: var(--text); }
.gauge-figure span { display: block; font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.gauge-legend { display: flex; justify-content: center; flex-wrap: wrap; margin-top: 10px; font-size: 11.5px; color: var(--text-2); }
.gauge-legend > span { margin: 0 8px; white-space: nowrap; }
.gauge-key { display: inline-block; inline-size: 10px; block-size: 10px; border-radius: 999px; margin-inline-end: 6px; vertical-align: -1px; }
.gauge-key.is-fill { background: var(--cap-strong); }
.gauge-key.is-rest { background: var(--cap-hatch); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.25); }
.gauge.is-mini { max-inline-size: 150px; margin-top: 8px; }
.gauge.is-mini .gauge-figure b { font-size: 22px; }
.otd-gauge { margin-bottom: 10px; }
.otd-gauge .otd-note { text-align: center; margin-top: 8px; }

/* Calendar heatmap (worker dashboard). */
.heat { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: stretch; margin-top: 4px; }
.heat-days { display: grid; grid-template-rows: 14px repeat(7, minmax(0, 1fr)); row-gap: 3px; margin-inline-end: 8px; font-size: 10px; color: var(--text-3); }
.heat-days > span { line-height: 1; display: flex; align-items: center; min-block-size: 8px; }
.heat-body { min-inline-size: 0; }
.heat-months { display: grid; block-size: 14px; margin-bottom: 3px; font-size: 10px; line-height: 14px; color: var(--text-3); white-space: nowrap; }
.heat-grid { display: grid; grid-template-rows: repeat(7, auto); grid-auto-flow: column; gap: 3px; }
.heat-cell {
  display: block; position: relative; padding: 0; padding-top: 100%; border: 0;
  border-radius: 4px; background: var(--seg-bg); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.heat-cell.is-out { background: transparent; cursor: default; }
.heat-cell.is-l1 { background: rgba(var(--brass-rgb), 0.24); }
.heat-cell.is-l2 { background: rgba(var(--brass-rgb), 0.46); }
.heat-cell.is-l3 { background: rgba(var(--brass-rgb), 0.72); }
.heat-cell.is-l4 { background: var(--brass); }
.heat-cell.is-sel { box-shadow: 0 0 0 2px var(--text); }
.heat-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin-top: 10px; font-size: 11.5px; }
.heat-legend { display: inline-flex; align-items: center; }
.heat-legend .heat-cell { display: inline-block; inline-size: 11px; padding-top: 0; block-size: 11px; margin-inline-start: 3px; cursor: default; }
/* The days column lines up with the grid rows by sharing their count;
   the month row above the grid is matched by a spacer. */
.heat-days::before { content: ""; display: block; }

/* --- lists: small tinted status chips ------------------------------------- */
.chip {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; line-height: 1.5; white-space: nowrap;
  background: var(--chip-bg); color: var(--brass-ink);
}
.chip.is-bad  { background: rgba(var(--bad-rgb), 0.12); color: var(--bad); }
.chip.is-warn { background: rgba(var(--amber-rgb), 0.14); color: var(--amber); }
.chip.is-good { background: rgba(var(--good-rgb), 0.14); color: var(--good); }
.is-dashboard .mtl-badge { border-radius: 999px; }
.w-alert { border-width: 0; border-radius: 12px; }
.w-chip { border-width: 0; }

/* --- the arranger and the customise page ----------------------------------- */
.arrange-pane { box-shadow: 0 24px 60px -24px rgba(20, 24, 40, 0.28); }
.arrange-item { border-radius: 12px; transition: background-color 220ms var(--ease-spring); }
.arrange-add { border-radius: 999px; transition: transform 220ms var(--ease-spring), background-color 220ms var(--ease-spring); }
.arrange-add:active:not(:disabled) { transform: scale(0.94); }
.dash-chrome-btn:active { transform: scale(0.94); }
.preset-card { border-radius: 16px; transition: transform 260ms var(--ease-spring); }
.preset-card:active { transform: scale(0.98); }
.dash-pick-row { border-radius: 12px; }
.is-dashboard .dash-toolbar select { border-radius: 999px; }

/* The multi-line chart's legend and readout (reports/parts/_area.html,
   and the older _lines_chart.html). */
.mtl-legend { list-style: none; display: flex; flex-wrap: wrap; margin: 0; padding: 0; font-size: 12px; }
.mtl-legend li { display: inline-flex; align-items: center; margin: 0 16px 6px 0; }
[dir="rtl"] .mtl-legend li { margin: 0 0 6px 16px; }
.mtl-legend li .mtl-leg-line { margin-inline-end: 6px; }
.mtl-legend li .mono { margin: 0 0 0 6px; }
[dir="rtl"] .mtl-legend li .mono { margin: 0 6px 0 0; }
.mtl-leg-line { display: inline-block; width: 16px; height: 3px; border-radius: 999px; }
.mtl-line-chart { display: block; width: 100%; height: auto; margin-top: 8px; }
.mtl-band { fill: var(--chart-band); }
.mtl-grid { stroke: var(--chart-grid); stroke-width: 1; stroke-dasharray: 2 4; }
.mtl-axis { font: 10.5px var(--mono); fill: var(--text-3); }
.mtl-readout { font-size: 11px; margin-top: 6px; min-height: 16px; }
.mtl-readout b { color: var(--text); margin-inline-end: 10px; }
.mtl-readout-v { margin-inline-end: 12px; white-space: nowrap; }
.mtl-readout .mtl-leg-line { margin-inline-end: 4px; vertical-align: middle; }

/* --- stock status: one colour per tier, everywhere ------------------------- */
/* Six tiers, six hues, used identically by the status chips, the row
   badges, the remaining-stock bars and the Stock Health bars - before,
   "Moderate" and "Low Stock" were the same yellow in one place and not in
   another, and three tiers shared one red. Out of stock is the danger
   red, critical a deep orange, low the amber, moderate a calm blue-grey
   (it needs watching, not alarm), healthy the good green, a data issue a
   neutral grey (the ledger is wrong, not the stock). Solved per scheme
   for at least 3:1 against the card surface. */
:root {
  --st-data: #A2AAB3;        --st-data-rgb: 162, 170, 179;
  --st-out: var(--bad);      --st-out-rgb: var(--bad-rgb);
  --st-critical: #F08A4B;    --st-critical-rgb: 240, 138, 75;
  --st-low: var(--amber);    --st-low-rgb: var(--amber-rgb);
  --st-moderate: #8FAACB;    --st-moderate-rgb: 143, 170, 203;
  --st-healthy: var(--good); --st-healthy-rgb: var(--good-rgb);
}
:root[data-theme="light"],
:root[data-theme="ivory-forest"],
:root[data-theme="porcelain-indigo"],
:root[data-theme="sand-terracotta"],
:root[data-theme="arctic-teal"],
:root[data-theme="pearl-plum"] {
  --st-data: #66707A;        --st-data-rgb: 102, 112, 122;
  --st-critical: #B4541C;    --st-critical-rgb: 180, 84, 28;
  --st-moderate: #48678C;    --st-moderate-rgb: 72, 103, 140;
}
.st-data_issue   { --st: var(--st-data);     --st-rgb: var(--st-data-rgb); }
.st-out_of_stock { --st: var(--st-out);      --st-rgb: var(--st-out-rgb); }
.st-critical     { --st: var(--st-critical); --st-rgb: var(--st-critical-rgb); }
.st-low_stock    { --st: var(--st-low);      --st-rgb: var(--st-low-rgb); }
.st-moderate     { --st: var(--st-moderate); --st-rgb: var(--st-moderate-rgb); }
.st-healthy      { --st: var(--st-healthy);  --st-rgb: var(--st-healthy-rgb); }
.mtl-badge.st-tone { color: var(--st); }
.mtl-summary-chip.st-tone {
  font-size: 11.5px; font-weight: 600; line-height: 1.5;
  padding: 2px 9px; border: 0; border-radius: 999px;
  color: var(--st); background: rgba(var(--st-rgb), 0.14);
}
.mtl-alert-inline { display: flex; flex-wrap: wrap; align-items: center; }
.mtl-alert-inline > .mtl-summary-chip { margin: 0 0 4px 6px; }
[dir="rtl"] .mtl-alert-inline > .mtl-summary-chip { margin: 0 6px 4px 0; }
.mtl-health-fill.st-tone,
.mtl-remain-fill.st-tone,
.inv-fill.st-tone { background: var(--st); }
.st-dot { display: inline-block; inline-size: 8px; block-size: 8px; border-radius: 999px; background: var(--st); margin-inline-end: 7px; flex: 0 0 auto; }
.mtl-health-label { display: inline-flex; align-items: center; color: var(--text-2); }

/* --- the balance as a sum: one line under the stock tiles ------------------ */
/* opening + movements = on hand, every term a small chip so the eye can
   add it up; the total is the accent chip at the end. Margins, not flex
   gap (iOS 12). */
.eq-strip {
  display: flex; flex-wrap: wrap; align-items: center;
  margin: 14px 0 4px; font-size: 12px; line-height: 1.4;
}
.eq-term {
  display: inline-flex; align-items: baseline; white-space: nowrap;
  padding: 4px 10px; margin: 0 6px 6px 0; border-radius: 999px;
  background: var(--seg-bg);
}
[dir="rtl"] .eq-term { margin: 0 0 6px 6px; }
.eq-op { margin-inline-end: 6px; color: var(--text-3); font-weight: 700; }
.eq-v { color: var(--text); font-weight: 650; margin-inline-end: 5px; }
.eq-l { color: var(--text-2); }
.eq-term.is-total { background: rgba(var(--brass-rgb), 0.14); }
.eq-term.is-total .eq-v { color: var(--brass-ink); }
.eq-term.is-total .eq-l { color: var(--text); font-weight: 600; }
.eq-term .kpi-unit { margin: 0 0 0 5px; font-size: inherit; }
[dir="rtl"] .eq-term .kpi-unit { margin: 0 5px 0 0; }
@media (max-width: 640px) { .eq-strip { font-size: 11.5px; } .eq-term { padding: 3px 8px; } }

/* Raw materials: a fifth tile for the returns (and any correction), so
   every term of the strip has a tile. Five in a row only where five fit;
   below that the hero takes two columns and the rest pair up - never an
   orphan tile. */
.dash-hero.mtl-hero-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1399px) {
  .dash-hero.mtl-hero-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dash-hero.mtl-hero-5 > .card:first-child { grid-column: span 2; }
}
@media (max-width: 1100px) {
  .dash-hero.mtl-hero-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .dash-hero.mtl-hero-5 { grid-template-columns: 1fr; }
  .dash-hero.mtl-hero-5 > .card:first-child { grid-column: auto; }
}
.mtl-other { list-style: none; margin: 8px 0 0; padding: 0; }
.mtl-other li { display: flex; align-items: baseline; flex-wrap: wrap; margin-top: 4px; }
.mtl-other-v { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.2; color: var(--text); }
.mtl-other .kpi-unit { margin: 0 8px 0 5px; }
[dir="rtl"] .mtl-other .kpi-unit { margin: 0 5px 0 8px; }
.mtl-other-l { font-size: 12px; }
.mtl-other .mtl-leg-line { align-self: center; margin: 0 8px 0 0; }
[dir="rtl"] .mtl-other .mtl-leg-line { margin: 0 0 0 8px; }

/* Five legend entries: the head wraps, so the legend drops under the
   title instead of squeezing it to a word per line. */
.card-head.mtl-flow-head { flex-wrap: wrap; }
.mtl-flow-head > div:first-child { flex: 1 1 280px; min-width: 0; }
.mtl-flow-head > .mtl-legend { flex: 0 1 auto; }
@media (max-width: 720px) { .mtl-flow-head > .mtl-legend { flex-basis: 100%; } }

/* A minor flow's line and its legend swatch: broken, in the series colour
   (the swatch takes it as `color`). */
.area-line.is-dash { stroke-dasharray: 7 5; }
.area-line.is-dot { stroke-dasharray: 0.5 5; stroke-width: 2.8; }
.mtl-leg-line.is-dash { background: repeating-linear-gradient(90deg, currentColor 0, currentColor 6px, transparent 6px, transparent 9px); }
.mtl-leg-line.is-dot { background: repeating-linear-gradient(90deg, currentColor 0, currentColor 3px, transparent 3px, transparent 6px); }
/* Material transformations (2.9.20): dash-dot, so they never read as a return. */
.area-line.is-dashdot { stroke-dasharray: 9 4 1.5 4; }
.mtl-leg-line.is-dashdot { background: repeating-linear-gradient(90deg, currentColor 0, currentColor 7px, transparent 7px, transparent 9px, currentColor 9px, currentColor 11px, transparent 11px, transparent 13px); }

/* --- first paint: a subtle fade-up ----------------------------------------- */
/* Only once xw-reveal.js has marked a card - with no script, no
   IntersectionObserver (iOS 12.0/12.1) or reduced motion, nothing is ever
   hidden. Transform and opacity only. */
.xw-pre { opacity: 0; transform: translateY(6px); }
.xw-pre.is-in {
  opacity: 1; transform: none;
  transition: opacity 520ms var(--ease-out-soft), transform 520ms var(--ease-out-soft);
}
@media (prefers-reduced-motion: reduce) {
  .xw-pre { opacity: 1; transform: none; }
  .btn, .btn-brass, .card-action, .tile-link, .seg > button, .seg > a,
  .cap-bar, .preset-card { transition: none; }
}

@media (max-width: 640px) {
  :root { --card-radius: 16px; }
  .is-dashboard .card { padding: 14px 14px; }
  .is-dashboard .dash-body,
  .is-dashboard #dash-kpis.dash-hero,
  .is-dashboard .inv-grid,
  .is-dashboard .kpi-grid { gap: 12px; }
  .kpi-tile .kpi-value { font-size: 22px; }
  .tile-link { inline-size: 28px; block-size: 28px; font-size: 12px; }
  .cap-frame, .area-frame { min-block-size: 170px; }
  .cap-bar { inline-size: 72%; }
}

/* === Icon set (v2.8.0) ============================================== */
/* Outline SVGs from design/XWARES-Themes-06-07-and-Icons. Geometry is
   fixed at 24x24 with a 1.75 stroke; only the rendered size and the
   colour change, so an icon is recognisably the same mark at 16px in a
   table and at 24px on a dashboard card.

   Every symbol strokes with currentColor, which is what makes the set
   follow all nine themes without a second copy: the icon takes the ink
   of the text beside it. Nothing here sets a colour of its own except
   the widget icon, which is deliberately the accent. */

.xw-icon,
.xw-nav-icon,
.xw-widget-icon {
  display: inline-block;
  flex: none;
  vertical-align: -0.15em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.xw-icon       { inline-size: 20px; block-size: 20px; }
.xw-nav-icon   { inline-size: 20px; block-size: 20px; opacity: 0.9; }
.xw-widget-icon{ inline-size: 24px; block-size: 24px; color: var(--brass-ink); }

/* A 20px mark still needs a 44px target. The kit is explicit about
   this and the floor is a touch environment: gloved hands on a tablet
   propped next to a press. */
.xw-icon-button {
  display: inline-grid; place-items: center;
  min-inline-size: 44px; min-block-size: 44px;
  background: none; border: 0; cursor: pointer;
  color: var(--text-2); border-radius: 8px;
}
.xw-icon-button:hover { color: var(--text); background: var(--row-hover); }
.xw-icon-button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* The KPI chip holds an SVG now rather than a text glyph, so it no
   longer needs a font-size and does need the mark centred. */
.kpi-icon-chip .xw-widget-icon { inline-size: 15px; block-size: 15px; }

/* Sidebar rows become icon + label. gap rather than a margin so RTL
   mirrors without a second rule. */
.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
}
.nav-item .xw-nav-icon { opacity: 0.75; }
.nav-item.active .xw-nav-icon,
.nav-item:hover .xw-nav-icon { opacity: 1; }

/* A chevron points along the reading direction, so it flips in RTL.
   Charts, currency and product marks must NOT flip - the kit is
   explicit, and a mirrored truck or banknote reads as a mistake. */
[dir="rtl"] .xw-icon-flip { transform: scaleX(-1); }


/* === Idle warning + restored drafts (v2.9.0) ======================== */
/* The dialog is rendered on every signed-in page and hidden with the
   `hidden` attribute, so it costs nothing until the countdown starts.
   Styled from tokens only, so it follows all nine themes. */

.xw-idle[hidden] { display: none; }
.xw-idle {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, 0.55);
  padding: 16px;
}
.xw-idle-box {
  inline-size: min(440px, 100%);
  background: var(--surface);
  color: var(--text);
  border: 0.5px solid var(--line);
  border-radius: 12px;
  padding: 24px;
  text-align: center;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.35);
}
.xw-idle-title { font-size: 19px; margin: 0 0 8px; }
.xw-idle-text  { margin: 0 0 12px; color: var(--text-2); font-size: 14px; }
.xw-idle-count {
  margin: 0 0 12px;
  font-size: 34px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--bad);
}
.xw-idle-note { font-size: 12px; margin: 0 0 18px; }
.xw-idle-actions {
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
}
/* 44px targets: this is answered on a tablet, often in a hurry. */
.xw-idle-actions .btn,
.xw-idle-actions .btn-brass { min-block-size: 44px; padding-inline: 18px; }

/* The banner a restored draft puts at the top of its form. Amber, not
   green: it is telling the operator that what they are looking at is
   NOT what the server has, which is a thing to check rather than
   celebrate. */
.xw-draft-note {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-block-end: 14px;
  padding: 10px 14px;
  border-radius: 8px;
  border: 0.5px solid var(--amber);
  background: var(--amber-bg);
  color: var(--text);
  font-size: 13px;
}
.xw-draft-clear {
  margin-inline-start: auto;
  background: none; border: 0.5px solid var(--line-2);
  color: var(--text); border-radius: 6px;
  padding: 6px 12px; min-block-size: 32px; cursor: pointer;
  font-size: 12px;
}
.xw-draft-clear:hover { background: var(--row-hover); }


/* === Mobile drawer scrolling (v2.9.1) =============================== */
/* Reported from the PWA on a phone: the burger opens the menu, but a
   swipe scrolls the dashboard behind it and the lower sections -
   Settings among them - cannot be reached at all.

   Two separate causes, both needed:

   1. The drawer is `position: fixed` with top/bottom 0, so it is exactly
      viewport height, and it had no overflow rule. Content taller than
      the screen was simply unreachable - there was nothing to scroll.
   2. Once it does scroll, a touch continuing past either end chains to
      the page behind. `overscroll-behavior` stops that on modern
      engines; the shop floor's iOS 12 iPads have never heard of it, so
      the page is also frozen outright while the drawer is open. */
@media (max-width: 960px) {
  .sidebar {
    overflow-y: auto;
    /* iOS 12 needs this for momentum scrolling inside a fixed element;
       without it the menu moves one finger-length and stops dead. */
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    /* Say out loud that a vertical drag here is a scroll. Without it a
       touch engine is free to hand the gesture to the nearest ancestor
       that will take it - which is the page behind the drawer. */
    touch-action: pan-y;
    /* Room past the last row for a thumb, and for the phone's own home
       indicator, which otherwise sits on top of the final entry. */
    padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  }

  /* Belt and braces for engines without overscroll-behavior. position:
     fixed rather than overflow:hidden because iOS ignores the latter on
     body; `top: 0` is safe here since the drawer covers the page and it
     is restored the moment the menu closes. */
  body.nav-locked {
    overflow: hidden;
    position: fixed;
    inset-inline: 0;
    width: 100%;
    /* `top` is set by xw-navlock.js to minus the scroll position, so
       the frozen page shows the same pixels it did a moment ago
       instead of snapping to the top of the document. */
  }

  /* The scrim must stay UNDER the drawer. Both are position:fixed
     siblings-by-stacking, and if the scrim ever paints on top the bug
     is not that it looks wrong - it is that every touch meant for the
     menu lands on the scrim instead, so the menu appears open and
     completely dead, and the swipe falls through to the page behind.
     That is indistinguishable from "the drawer does not scroll", which
     is how it was reported. Stated here rather than relied on from the
     base rules, where the two values sit hundreds of lines apart. */
  .nav-backdrop { z-index: 40; }
  .sidebar { z-index: 60; }
}

/* === Folder browser (v2.9.4) ======================================== */
/* Shared by the "browse this server" and "browse cloud storage" panels.
   Capped in height because a Windows drive root can hold hundreds of
   directories, and a list that pushes the Save button off the screen is
   how somebody loses the rest of the form. */
.folder-browser {
  border: 0.5px solid var(--line);
  border-radius: var(--radius);
  background: var(--well);
  padding: 10px 12px;
  max-block-size: 320px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.folder-list { list-style: none; margin: 0; padding: 0; }
.folder-list li {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 4px 0;
  border-block-end: 0.5px solid var(--line);
}
.folder-list li:last-child { border-block-end: 0; }
.folder-open {
  text-align: start; flex: 1 1 auto;
  /* A long path must wrap rather than stretch the panel sideways. */
  overflow-wrap: anywhere;
}
.folder-list .btn { flex: 0 0 auto; padding: 4px 10px; font-size: 12px; }
.btn-link {
  background: none; border: 0; padding: 0;
  color: var(--brass-ink); cursor: pointer;
  font-family: inherit; font-size: inherit;
  /* 44px is the touch target the kit insists on; these sit in a list
     on a tablet propped beside a press. */
  min-block-size: 44px;
}
.btn-link:hover { text-decoration: underline; }

/* === Checkbox rows and paired fields (v2.9.4) ======================= */
/* A checkbox is 13px by default, and the kit's own rule for anything
   tapped on the floor is 44px. Making the label part of the target is
   what recovers the rest - it costs nothing and roughly triples the
   area, which matters on a tablet operated with gloves. */
.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-block-size: 44px;
  cursor: pointer;
}
.check-row input[type="checkbox"] {
  inline-size: 20px;
  block-size: 20px;
  flex: 0 0 auto;
  accent-color: var(--brass-ink);
  cursor: pointer;
}
.check-row span { font-size: 14px; }
/* The stacked label above the box is redundant once the row carries it. */
.field-check > .field-label { display: none; }

/* Two small fields that belong together, side by side at every width.
   Without a min-width they collapse to nothing on a narrow screen; with
   one they wrap as a pair rather than splitting up. */
.field-pair {
  display: flex;
  gap: 10px;
  flex: 1 1 220px;
  min-inline-size: 0;
}
.field-pair > .field { flex: 1 1 0; min-inline-size: 0; }

/* A quiet aside inside a form - guidance, not a warning. Warnings here
   are amber and errors red, so this stays neutral on purpose. */
.note-soft {
  background: var(--well);
  border: 0.5px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 13px;
  color: var(--text-2);
  line-height: 1.6;
}

/* === .data-table ==================================================== */
/* This class was used by three templates and defined by none of them,
   so it rendered as a bare browser table: 1990s borders, no padding,
   and no idea what to do at 375px. Same visual language as .tbl. */
.data-table {
  width: 100%;
  max-width: 100%;
  border-collapse: collapse;
  font-size: 15px;
}
.data-table th {
  text-align: start;
  font-weight: 600;
  color: var(--text-2);
  font-size: 14px;
  padding: 12px;
}
.data-table td {
  padding: 14px 12px;
  border-top: 0.5px solid var(--rule);
  vertical-align: top;
}
/* A filename is one long unbreakable token. Allowed to break anywhere
   rather than forcing the column - and the whole card - wider than the
   screen, which is what pushed the Download button off the right edge. */
.data-table .cell-file code {
  overflow-wrap: break-word;
  word-break: break-all;
}

@media (max-width: 720px) {
  /* Each row becomes a card. Four columns cannot hold a filename, a
     badge, two lines of explanation, a size, a timestamp and a button
     at phone width; stacked, every one of them gets the full width. */
  .data-table.is-stacked,
  .data-table.is-stacked tbody,
  .data-table.is-stacked tr,
  .data-table.is-stacked td {
    display: block;
    width: 100%;
  }
  /* The headings live on the cells now (data-label), so the real
     header row is redundant. Moved out of view rather than set to
     display:none, which would take it from screen readers too. */
  .data-table.is-stacked thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .data-table.is-stacked tr {
    border: 0.5px solid var(--line);
    border-radius: var(--radius);
    padding: 12px;
    margin-block-end: 10px;
    background: var(--surface-2);
  }
  .data-table.is-stacked td {
    border-top: 0;
    padding: 3px 0;
  }
  /* A dump filename is 37 characters. At the body monospace size that
     is one character too wide for a 375px screen, so every row wrapped
     a single letter onto a second line. Slightly smaller and it fits. */
  .data-table.is-stacked .cell-file code { font-size: 0.92em; }
  /* Label on the left, value on the right - the shape of a spec sheet,
     which is what a stacked row is. */
  .data-table.is-stacked td[data-label] {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
  }
  .data-table.is-stacked td[data-label]::before {
    content: attr(data-label);
    color: var(--text-3);
    font-size: 12px;
    flex: 0 0 auto;
  }
  /* Full-width tap target. 44px because this is the one control in the
     row and it is being pressed on a phone held in one hand. */
  .data-table.is-stacked .cell-action { padding-block-start: 10px; }
  .data-table.is-stacked .cell-action .btn {
    display: block;
    width: 100%;
    min-block-size: 44px;
    line-height: 44px;
    padding-block: 0;
    text-align: center;
  }
}

/* === Destructive buttons ============================================ */
/* `.btn-bad` was already listed in the 44px touch-target rule but never
   given a colour anywhere, so every delete button in the app carried its
   red as an inline style instead - the same three declarations copied
   per template, drifting one at a time. */
.btn-bad {
  background: var(--bad);
  border: 0.5px solid var(--bad);
  color: #fff;
}
.btn-bad:hover { filter: brightness(1.08); text-decoration: none; }
/* The quieter form, for a page where deleting is available but is not
   what the operator came to do. */
.btn-bad-outline {
  background: transparent;
  border: 0.5px solid var(--bad);
  color: var(--bad);
}
.btn-bad-outline:hover { background: var(--bad); color: #fff; text-decoration: none; }

/* === Colour swatch - the one component ================================
   Drawn by {% color_swatch %}, by every colour dropdown, and by
   xw-select.js. Replaces .color-dot, .swatch, .swatch-lg, .inv-swatch
   and .sched-color, which were five sizes and two shapes for one idea.

   The outline is a real border on every swatch, transparent unless the
   colour needs one, so all swatches are the same size whatever their
   tone. */
.xw-color {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  vertical-align: middle;
}
.xw-color-name { min-width: 0; }
.xw-swatch {
  display: inline-block;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid transparent;
  vertical-align: middle;
  /* A printed schedule is still read by colour. */
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
/* White, cream, the palest greys: a thin grey edge, or they are simply
   not there on a white page. */
.xw-swatch.is-light { border-color: #A3A8AE; }
/* The mirror problem: black on a dark theme's surface. --line-2 is a
   theme token, so the ring is quiet on light themes and visible on dark
   ones, which is exactly where it is needed. */
.xw-swatch.is-dark { border-color: var(--line-2); }
/* No hex stored. A hollow dashed ring says "not set" rather than
   inventing a colour from the name. */
.xw-swatch.is-none {
  background: transparent;
  border: 1px dashed var(--text-3);
}

/* Inside the searchable dropdown the name has to truncate like the
   plain text it replaces, which an inline-flex child will not do. */
.xw-sel-btn .xw-color { display: flex; min-width: 0; }
/* Inline in a row, not block: the selected row draws its check mark as
   an inline ::before, and a block-level box would push it onto a line
   of its own. */
.xw-sel-opt .xw-color {
  display: inline-flex;
  max-width: 100%;
  min-width: 0;
  vertical-align: middle;
}
.xw-sel-btn .xw-color-name,
.xw-sel-opt .xw-color-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* === Hex field with a colour picker ==================================
   Picker, typed hex, live swatch - one row. The picker itself is hidden
   until xw-hex.js confirms the browser implements type="color"; see the
   script for why that is not optimism but the iOS 12 floor. */
.xw-hex {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.xw-hex-text {
  flex: 1 1 140px;
  min-width: 0;
  font-family: var(--mono);
  text-transform: uppercase;
}
/* 44px, because this is tapped on a tablet on the shop floor. The
   browser draws the swatch inside; the padding is what stops it filling
   the control edge to edge. */
.xw-hex-pick {
  flex: 0 0 auto;
  inline-size: 52px;
  block-size: 44px;
  padding: 4px;
  border: 0.5px solid var(--line-2);
  border-radius: 6px;
  background: var(--surface-2);
  cursor: pointer;
}
.xw-hex-pick::-webkit-color-swatch-wrapper { padding: 0; }
.xw-hex-pick::-webkit-color-swatch { border: none; border-radius: 3px; }
.xw-hex-pick::-moz-color-swatch { border: none; border-radius: 3px; }
.xw-hex-pick:focus-visible { outline: none; border-color: var(--brass-ink); }
/* The live preview is the same swatch the rest of the app draws, at the
   size it is drawn everywhere else - so what you pick here is literally
   what the tables will show. */
.xw-hex-preview { inline-size: 16px; block-size: 16px; }

@media (max-width: 520px) {
  /* The typed box keeps the row on a narrow screen; the picker and the
     preview are small enough to ride along beside it. */
  .xw-hex-text { flex: 1 1 120px; }
}

/* === Shipment status ==================================================
   Ready = amber (waiting), shipped = green (done). Always with the word:
   colour alone is not a status, and a list is scanned rather than read. */
.ship-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  border: 0.5px solid transparent;
}
.ship-badge.is-ready {
  background: rgba(196, 140, 40, .14);
  color: var(--amber, #9A6A12);
  border-color: rgba(196, 140, 40, .35);
}
.ship-badge.is-shipped {
  background: rgba(var(--brass-rgb), .12);
  color: var(--brass-ink);
  border-color: rgba(var(--brass-rgb), .3);
}

.ship-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  margin-bottom: 14px;
  border-radius: var(--radius-lg);
  border: 0.5px solid var(--line);
  background: var(--surface-2);
}
.ship-banner.is-ready { border-color: rgba(196, 140, 40, .45);
                        background: rgba(196, 140, 40, .06); }
.ship-banner-text { display: flex; flex-wrap: wrap; align-items: center;
                    gap: 10px; font-size: 13.5px; }
.ship-confirm { display: flex; flex-wrap: wrap; align-items: flex-end;
                gap: 10px; }

.ship-state-tabs {
  display: flex;
  gap: 6px;
  margin: 14px 0 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.ship-state-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 0 14px;
  border-radius: 999px;
  border: 0.5px solid var(--line-2);
  color: var(--text-2);
  font-size: 13px;
  white-space: nowrap;
  text-decoration: none;
}
.ship-state-tab:hover { background: var(--surface-2); text-decoration: none; }
.ship-state-tab.is-active {
  background: var(--brass);
  border-color: var(--brass);
  color: var(--on-brass, #fff);
}
.ship-state-count {
  min-width: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: rgba(196, 140, 40, .9);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  line-height: 20px;
}

a.kpi-card-ready { text-decoration: none; color: inherit;
                   border-color: rgba(196, 140, 40, .45); }
a.kpi-card-ready.is-empty { border-color: var(--line); }

.ship-status-choice {
  border: 0;
  padding: 0;
  margin: 14px 0 0;
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.ship-status-choice legend { margin-bottom: 6px; }
.ship-status-option {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: 44px;
  padding: 12px 14px;
  border: 0.5px solid var(--line-2);
  border-radius: var(--radius);
  cursor: pointer;
  background: var(--surface-2);
}
.ship-status-option input { margin-top: 3px; inline-size: 18px;
                            block-size: 18px; flex: 0 0 auto; }
.ship-status-option > span { display: flex; flex-direction: column; gap: 2px;
                             font-size: 13px; }
/* Two rules, never one list: Safari 12 does not know :has(), and an
   unknown selector throws away the WHOLE list it is in - including the
   .is-checked half that xw-inputs.js exists to provide for it. */
.ship-status-option.is-checked {
  border-color: var(--brass);
  box-shadow: 0 0 0 1px var(--brass) inset;
}
.ship-status-option:has(input:checked) {
  border-color: var(--brass);
  box-shadow: 0 0 0 1px var(--brass) inset;
}

/* === Print view (?print=1) ===========================================
   The page as it will print, on screen - so a phone shows a real preview
   before the print dialog, and a phone whose app will not open the
   dialog still shows a sheet worth screenshotting or sharing. Mirrors
   the @media print rules above, on screen. */
.is-print-view { background: #fff; }
.is-print-view .topbar,
.is-print-view .sidebar,
.is-print-view .nav-burger,
.is-print-view .nav-backdrop,
.is-print-view .toast-stack,
.is-print-view .filter-drawer,
.is-print-view .filter-bar,
.is-print-view .row-actions,
.is-print-view .btn-row,
.is-print-view .active-filters,
.is-print-view .table-nav,
.is-print-view .tbl-search,
.is-print-view .chat-widget,
.is-print-view .chat-bubble,
.is-print-view .chat-panel,
.is-print-view .print-toolbar,
.is-print-view .no-print:not(.print-bar) {
  display: none !important;
}
.is-print-view .print-only { display: block !important; }
.is-print-view .print-letterhead { display: flex !important; }
/* The letterhead's own sizing lives in @media print above; the on-screen
   print view needs it too, or the logo renders at its natural size. */
.is-print-view .print-letterhead {
  align-items: center; gap: 14px; padding: 0 0 12px; margin-bottom: 14px;
  border-bottom: 1px solid var(--line-2);
}
.is-print-view .print-letterhead img { height: 40px; width: auto; }
.is-print-view .print-letterhead .print-company {
  font-family: var(--serif); font-size: 22px; letter-spacing: 2px;
}
.is-print-view .print-letterhead .print-title {
  margin-inline-start: auto; font-size: 14px; color: var(--text-2);
}
.is-print-view .layout { grid-template-columns: 1fr !important; }
.is-print-view .content {
  /* width, not just max-width: an auto-margined grid item shrinks to fit
     its content, so one wide table made the whole sheet scroll sideways
     on a phone. */
  width: 100%;
  box-sizing: border-box;
  max-width: 210mm;              /* an A4 sheet's width, on screen */
  margin: 0 auto;
  padding: 12px 14px 40px !important;
  background: #fff;
  color: #111;
}

.print-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 0 12px;
  margin-bottom: 12px;
  background: #fff;
  border-bottom: 0.5px solid var(--line);
}
.print-bar-go { flex: 1 1 220px; min-height: 48px; font-size: 15px; }
.print-bar-hint { flex: 1 1 100%; font-size: 12.5px; line-height: 1.6; }

/* A sheet on paper. Printing from the print view drops its toolbar, and
   A4 with a modest margin is what every printer in the building takes. */
@page { size: A4; margin: 12mm; }
@media print {
  .print-bar { display: none !important; }
  .is-print-view .content { max-width: none; padding: 0 !important; }
  /* Tables that scroll on a phone screen print full width instead of
     being cut at the scroll edge. */
  .tbl-card { overflow: visible !important; }
  .tbl { min-width: 0 !important; width: 100% !important; }
  thead { display: table-header-group; }   /* repeat on every page */
  tr, .card { page-break-inside: avoid; break-inside: avoid; }
}

/* =====================================================================
   Load planner
   Drawings sit on a pale "drafting board" in both themes: the vehicle
   renders are painted on white, and a load is read by carton colour,
   which needs a light ground to be judged at all.
   ===================================================================== */
.lp-card { margin-top: 14px; }
.lp-h2 { font-family: var(--serif); font-weight: 500; font-size: 20px;
         margin: 0 0 12px; display: flex; align-items: center; gap: 10px; }
.lp-h3 { font-size: 14px; font-weight: 600; margin: 0; }
.lp-note { font-size: 12.5px; margin: 8px 0 0; }
.lp-small { font-size: 12px; }
.lp-step-no { display: inline-flex; align-items: center; justify-content: center;
              width: 26px; height: 26px; border-radius: 50%; font-family: var(--sans);
              font-size: 13px; font-weight: 700; color: #fff; background: var(--brass); }

.lp-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
.lp-tab { padding: 8px 14px; min-height: 40px; display: inline-flex; align-items: center;
          border: 0.5px solid var(--line-2); border-radius: 999px; color: var(--text-2);
          text-decoration: none; font-size: 13px; }
.lp-tab.is-active { background: rgba(var(--brass-rgb), .14); border-color: var(--brass);
                    color: var(--text); font-weight: 600; }
.lp-pickrow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.lp-pickrow select { flex: 1 1 260px; min-height: 44px; }
.lp-pickrow .field-label { margin: 0; }

.lp-alert { border: 0.5px solid var(--line-2); border-radius: var(--radius-lg);
            padding: 12px 16px; margin-top: 14px; font-size: 13.5px; line-height: 1.55; }
.lp-alert ul { margin: 6px 0 0; padding-inline-start: 18px; }
.lp-alert.is-bad  { border-color: rgba(var(--bad-rgb), .5); background: rgba(var(--bad-rgb), .08); }
.lp-alert.is-warn { border-color: rgba(var(--amber-rgb), .5); background: rgba(var(--amber-rgb), .08); }
.lp-alert.is-info { border-color: rgba(var(--info-rgb), .45); background: rgba(var(--info-rgb), .08); }

.lp-flag { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px;
           border-radius: 999px; font-size: 11.5px; font-weight: 600; white-space: nowrap;
           background: rgba(var(--brass-rgb), .08); color: var(--text-2);
           border: 0.5px solid var(--line-2); }
.lp-flag.is-good { background: rgba(var(--good-rgb), .16); color: var(--good); border-color: transparent; }
.lp-flag.is-warn { background: rgba(var(--amber-rgb), .16); color: var(--amber); border-color: transparent; }
.lp-flag.is-bad  { background: rgba(var(--bad-rgb), .16); color: var(--bad); border-color: transparent; }

/* Line keys: the cargo colour a carton is drawn in, with its key on it. */
.lp-key { display: inline-flex; align-items: center; justify-content: center;
          min-width: 28px; height: 20px; padding: 0 5px; border-radius: 5px;
          font: 700 11px/1 var(--mono); color: #fff; vertical-align: middle;
          text-shadow: 0 1px 1px rgba(0,0,0,.25);
          -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.lp-key-wood { background: #C6A173; min-width: 20px; }
.lp-key-part { background: transparent; border: 1.5px dashed var(--text-3); min-width: 20px; }

/* Lines */
.lp-lines-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; margin-bottom: 10px; }
.lp-lines { display: flex; flex-direction: column; gap: 6px; }
.lp-line { border: 0.5px solid var(--line-2); border-radius: var(--radius); }
.lp-line.is-missing { border-color: rgba(var(--bad-rgb), .5); }
.lp-line.is-touched { border-color: rgba(var(--amber-rgb), .6); }
.lp-line > summary { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
                     padding: 10px 12px; cursor: pointer; list-style: none; min-height: 44px; }
.lp-line > summary::-webkit-details-marker { display: none; }
.lp-line-name { flex: 1 1 160px; font-weight: 500; }
.lp-line-qty { font-weight: 700; }
.lp-line-meta { font-size: 12px; flex: 1 1 100%; }
.lp-line-stock { font-size: 12px; }
.lp-line-edit, .lp-manual-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
                                gap: 8px 10px; padding: 4px 12px 12px; align-items: end; }
.lp-line-edit .field, .lp-manual-row .field { margin: 0; }
.lp-manual { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.lp-manual-row { border: 0.5px solid var(--line-2); border-radius: var(--radius); padding: 10px 12px; }
.lp-manual-row .lp-f-wide { grid-column: 1 / -1; }

/* Vehicle and pallet cards */
.lp-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.lp-cards-sm { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
.lp-pick { position: relative; display: flex; flex-direction: column; gap: 3px;
           padding: 8px 10px 10px; border: 1px solid var(--line-2); border-radius: var(--radius-lg);
           cursor: pointer; background: var(--surface); transition: border-color .15s, box-shadow .15s; }
.lp-pick > input[type=radio] { position: absolute; opacity: 0; pointer-events: none; }
.lp-pick-img { display: block; height: 0; padding-bottom: 64%; position: relative; border-radius: 8px;
               overflow: hidden; background: #F4F1EC; margin-bottom: 4px; }
.lp-pick-img img { position: absolute; top: 0; left: 0; display: block; width: 100%; height: 100%; object-fit: contain; }
.lp-pick-name { font-weight: 600; font-size: 13px; }
.lp-pick-fig { font-size: 11px; color: var(--text-3); }
.lp-pick .lp-flag { align-self: flex-start; margin-top: 2px; }
.lp-pick.is-checked { border-color: var(--brass); box-shadow: 0 0 0 2px rgba(var(--brass-rgb), .35); }
.lp-pick:has(input:checked) { border-color: var(--brass); box-shadow: 0 0 0 2px rgba(var(--brass-rgb), .35); }
.lp-pick:focus-within { outline: 2px solid var(--brass); outline-offset: 2px; }
.lp-pick.is-disabled { opacity: .55; cursor: not-allowed; }
.lp-pick.is-scenario { border-style: dashed; }

.lp-seg { display: inline-flex; border: 0.5px solid var(--line-2); border-radius: 999px;
          padding: 3px; margin-bottom: 12px; }
.lp-seg-opt { position: relative; padding: 9px 16px; min-height: 40px; border-radius: 999px;
              cursor: pointer; font-size: 13.5px; display: inline-flex; align-items: center; }
.lp-seg-opt input { position: absolute; opacity: 0; pointer-events: none; }
.lp-seg-opt.is-checked { background: var(--brass); color: #fff; font-weight: 600; }
.lp-seg-opt:has(input:checked) { background: var(--brass); color: #fff; font-weight: 600; }
.lp-seg-opt:focus-within { outline: 2px solid var(--brass); outline-offset: 2px; }
.lp-check { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 12px;
            font-size: 13.5px; min-height: 44px; }
.lp-check .dim { flex: 1 1 100%; font-size: 12px; padding-inline-start: 26px; }
.lp-clear { max-width: 260px; margin: 12px 0 0; }
.lp-mix { margin-top: 12px; }
.lp-mix .lp-seg { margin: 4px 0; }

.lp-actions { position: sticky; bottom: 0; z-index: 5; display: flex; flex-wrap: wrap; gap: 10px; padding-inline-end: 64px;
              padding-top: 12px; padding-bottom: 12px; margin-top: 6px; background: var(--bg, var(--surface)); }
.lp-go { flex: 1 1 200px; min-height: 48px; font-size: 15px; }
.lp-go.is-busy { opacity: .7; pointer-events: none; }

/* Result */
.lp-summary { margin-top: 18px; }
.lp-verdict { border-radius: var(--radius-lg); padding: 16px 18px;
              border: 1px solid rgba(var(--good-rgb), .45); background: rgba(var(--good-rgb), .08); }
.lp-verdict.is-multi { border-color: rgba(var(--amber-rgb), .5); background: rgba(var(--amber-rgb), .07); }
.lp-verdict.is-short { border-color: rgba(var(--bad-rgb), .55); background: rgba(var(--bad-rgb), .08); }
.lp-verdict-main { font-family: var(--serif); font-size: 26px; line-height: 1.2; }
.lp-verdict-sub { color: var(--text-2); font-size: 13px; margin-top: 4px; }
.lp-flags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.lp-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-top: 10px; }
.lp-kpi { background: var(--surface-2); border: 0.5px solid var(--line); border-radius: var(--radius-lg); padding: 12px 14px; }
.lp-kpi-v { font-size: 22px; font-weight: 600; overflow-wrap: anywhere; }
.lp-kpi-of { font-size: 13px; color: var(--text-3); font-weight: 400; }
.lp-kpi-l { font-size: 11.5px; color: var(--text-3); text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; }

.lp-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.lp-option { border: 0.5px solid var(--line-2); border-radius: var(--radius-lg); padding: 12px 14px;
             display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.lp-option.is-current { border-color: var(--brass); background: rgba(var(--brass-rgb), .06); }
.lp-option-name { font-weight: 600; font-size: 13.5px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.lp-option-fig { font-size: 15px; }
.lp-option-note { font-size: 12px; }

.lp-trip-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 14px; }
.lp-trip-head .lp-h2 { margin-bottom: 6px; }
.lp-trip-figs { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 13px; color: var(--text-2); }
.lp-meters { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px 16px; margin: 6px 0 12px; }
.lp-meter-l { font-size: 12px; color: var(--text-2); display: flex; justify-content: space-between; }
.lp-meter-bar { height: 8px; border-radius: 999px; background: var(--line-2); overflow: hidden; margin-top: 4px; }
.lp-meter-bar span { display: block; height: 100%; background: var(--brass); border-radius: inherit;
                     -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.lp-meter.is-near .lp-meter-bar span { background: var(--amber); }
.lp-meter.is-over .lp-meter-bar span { background: var(--bad); }

.lp-views { display: flex; gap: 6px; margin-bottom: 8px; flex-wrap: wrap; }
.lp-view-btn { min-height: 40px; padding: 8px 14px; border-radius: 999px; font-size: 13px;
               border: 0.5px solid var(--line-2); background: transparent; color: var(--text-2); cursor: pointer; }
.lp-view-btn.is-active { background: var(--text); color: var(--surface); border-color: var(--text); font-weight: 600; }
.lp-stage { direction: ltr; background: #F7F5F0; border-radius: var(--radius-lg); padding: 8px;
            border: 0.5px solid rgba(16,20,19,.08);
            -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.lp-panel { display: none; margin: 0; }
.lp-panel.is-active { display: block; }
.lp-panel figcaption { font-size: 11.5px; color: #5c6168; text-align: center; padding: 4px 0 2px; }
.lp-svg { display: block; width: 100%; height: auto; max-height: 62vh; }
.lp-svg .lp-lbl { font-family: var(--mono), monospace; font-weight: 700; fill: #fff;
                  paint-order: stroke; stroke: rgba(0,0,0,.35); stroke-width: 2px; }
.lp-scrub { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 10px; font-size: 13px; }
.lp-scrub input[type=range] { flex: 1 1 180px; min-height: 36px; accent-color: var(--brass); }
.lp-legend { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 16px; padding: 0; margin: 12px 0 0; font-size: 12.5px; }
.lp-legend li { display: inline-flex; align-items: center; gap: 6px; }
.lp-seq { margin-top: 12px; }
.lp-seq ol { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 0; padding: 0; list-style: none; counter-reset: lp; }
.lp-seq li { counter-increment: lp; display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-3); }
.lp-seq li::before { content: counter(lp) "."; }
.lp-pno { display: inline-flex; align-items: center; justify-content: center; min-width: 30px; height: 22px;
          padding: 0 6px; border-radius: 6px; background: #23303E; color: #fff; font-size: 11.5px; font-weight: 700;
          -webkit-print-color-adjust: exact; print-color-adjust: exact; }

.lp-pallets { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; }
.lp-pallet-card { border: 0.5px solid var(--line-2); border-radius: var(--radius-lg); padding: 10px; display: flex; flex-direction: column; gap: 8px; }
.lp-pallet-draw { background: #F7F5F0; border-radius: 8px; padding: 8px; direction: ltr;
                  -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.lp-pallet-title { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; font-size: 13px; }
.lp-pallet-meta dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 12px; margin: 6px 0; font-size: 12.5px; }
.lp-pallet-meta dt { color: var(--text-3); }
.lp-pallet-meta dd { margin: 0; }
.lp-pallet-skus { display: flex; flex-wrap: wrap; gap: 4px; }
.lp-unplaced { margin: 0; padding-inline-start: 18px; display: flex; flex-direction: column; gap: 8px; font-size: 13.5px; }
.lp-assume > summary { cursor: pointer; list-style: none; }
.lp-assume > summary::-webkit-details-marker { display: none; }
.lp-assume ul { margin: 0; padding-inline-start: 18px; display: flex; flex-direction: column; gap: 6px; font-size: 13px; line-height: 1.55; }
.lp-search input { min-height: 44px; min-width: 200px; }
.lp-empty { text-align: center; padding: 32px 20px; }
.lp-delete { margin-top: 18px; }
.lp-signoff { display: flex; flex-wrap: wrap; gap: 18px 32px; margin-top: 18px; font-size: 13px; }

@media (max-width: 640px) {
  .lp-verdict-main { font-size: 21px; }
  .lp-kpis { grid-template-columns: repeat(2, 1fr); }
  .lp-cards { grid-template-columns: repeat(2, 1fr); }
  .lp-seg { display: flex; }
  .lp-seg-opt { flex: 1 1 0; justify-content: center; }
  .lp-svg { max-height: none; }
}

/* On paper every view prints, one under the other: a sheet has no
   buttons to press. */
.is-print-view .lp-views, .is-print-view .lp-scrub, .is-print-view .lp-actions { display: none; }
.is-print-view .lp-panel { display: block; }
@media print {
  .lp-views, .lp-scrub, .lp-actions { display: none !important; }
  .lp-panel { display: block !important; break-inside: avoid; page-break-inside: avoid; }
  .lp-svg { max-height: 120mm; }
  .lp-stage, .lp-pallet-draw { background: #fff; border: 0.5px solid #ccc; }
}

/* =====================================================================
   A form that is not ready says why, instead of losing what was typed.
   See static/js/xw-validate.js.
   ===================================================================== */
.xw-form-errors { border: 1px solid rgba(var(--bad-rgb), .55); background: rgba(var(--bad-rgb), .08);
                  border-radius: var(--radius-lg); padding: 12px 16px; margin: 0 0 14px; outline: none; }
.xw-form-errors-title { font-weight: 600; font-size: 14px; color: var(--bad); }
.xw-form-errors ul { margin: 6px 0 0; padding-inline-start: 18px; }
.xw-form-errors li { margin: 2px 0; }
.xw-form-errors button { background: none; border: 0; padding: 4px 0; min-height: 32px; color: var(--text);
                         font: inherit; font-size: 13.5px; text-align: start; cursor: pointer;
                         text-decoration: underline; text-decoration-color: rgba(var(--bad-rgb), .5); }
input.is-invalid, select.is-invalid, textarea.is-invalid,
.has-error input, .has-error select, .has-error textarea {
  border-color: var(--bad) !important;
  box-shadow: 0 0 0 2px rgba(var(--bad-rgb), .22) !important;
}
.has-error .field-label { color: var(--bad); }
select.is-invalid ~ .xw-sel-btn, .has-error .xw-sel-btn {
  border-color: var(--bad) !important;
  box-shadow: 0 0 0 2px rgba(var(--bad-rgb), .22) !important;
}

/* ---- Load planner 2.9.8: rows, vehicle modes, comparison, builder ---- */
.lp-start-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; margin-bottom: 10px; }
.lp-start-head .lp-h2 { margin: 0; }
.lp-start-picks { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px 14px; }
.lp-start-pick { display: flex; flex-direction: column; gap: 4px; margin: 0; }
.lp-start-pick select { min-height: 44px; }

.lp-rows { display: flex; flex-direction: column; gap: 10px; }
.lp-row { display: grid; grid-template-columns: minmax(0, 1fr) 120px 44px; gap: 6px 10px; align-items: end;
          padding: 12px; border: 0.5px solid var(--line-2); border-radius: var(--radius-lg); background: var(--surface); }
.lp-row .field { margin: 0; }
.lp-row-product { min-width: 0; }
.lp-row-qty input { text-align: end; font-family: var(--mono); font-size: 15px; }
.lp-row-del { width: 44px; height: 44px; border-radius: 10px; border: 0.5px solid var(--line-2); background: transparent;
              color: var(--text-3); font-size: 22px; line-height: 1; cursor: pointer; }
.lp-row-del:hover { color: var(--bad); border-color: rgba(var(--bad-rgb), .5); }
.lp-row-info { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12.5px; min-height: 0; }
.lp-row-info:empty { display: none; }
.lp-row-fig b { font-weight: 700; color: var(--text); }
.lp-row-more { grid-column: 1 / -1; }
.lp-row-more > summary { cursor: pointer; font-size: 12px; color: var(--text-3); list-style: none; padding: 2px 0; }
.lp-row-more > summary::-webkit-details-marker { display: none; }
.lp-row-more > summary::before { content: "+ "; }
.lp-row-more[open] > summary::before { content: "− "; }
.lp-row-edit { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px 10px; margin-top: 8px; }
.lp-row-edit .field { margin: 0; }
.lp-row-edit .lp-f-wide { grid-column: 1 / -1; }
.lp-rows-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-top: 12px; }
.lp-totals { font-size: 14px; color: var(--text-2); }
.lp-totals b { color: var(--text); }

.lp-vmode-note { display: none; margin: 0 0 12px; }
.lp-vehicles[data-vmode="auto"] .lp-vmode-note[data-for="auto"],
.lp-vehicles[data-vmode="one"] .lp-vmode-note[data-for="one"],
.lp-vehicles[data-vmode="fleet"] .lp-vmode-note[data-for="fleet"] { display: block; }
/* Cards stay visible in every mode: they are where the load meets the
   vehicle, even before anything is calculated. */
.lp-stepper { display: none; align-items: center; gap: 4px; margin-top: 6px; }
.lp-vehicles[data-vmode="fleet"] .lp-stepper { display: inline-flex; }
.lp-vehicles[data-vmode="fleet"] .lp-pick.is-checked { border-color: var(--line-2); box-shadow: none; }
.lp-vehicles[data-vmode="fleet"] .lp-pick.has-count { border-color: var(--brass); box-shadow: 0 0 0 2px rgba(var(--brass-rgb), .35); }
.lp-stepper input { width: 46px; min-height: 36px; text-align: center; font-family: var(--mono); padding: 4px; }
.lp-step-btn { width: 36px; height: 36px; border-radius: 8px; border: 0.5px solid var(--line-2); background: var(--surface-2);
               color: var(--text); font-size: 18px; cursor: pointer; }

.lp-rules { margin-top: 10px; }
.lp-rules > summary { cursor: pointer; font-size: 13px; color: var(--text-2); min-height: 36px; display: flex; align-items: center; }
.lp-rules-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px 12px; margin: 8px 0; }
.lp-rules-grid .field { margin: 0; }

.lp-compare-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; }
.lp-compare-list { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.lp-compare-row { display: grid; grid-template-columns: 76px minmax(0, 1.4fr) minmax(0, 1fr) minmax(90px, 1fr) auto;
                  align-items: center; gap: 6px 12px; padding: 8px 10px; border: 0.5px solid var(--line-2); border-radius: var(--radius); }
.lp-compare-row.is-best { border-color: rgba(var(--good-rgb), .6); background: rgba(var(--good-rgb), .06); }
.lp-compare-row.is-current { border-color: var(--brass); }
.lp-compare-img { display: block; height: 44px; background: #F4F1EC; border-radius: 6px; overflow: hidden; }
.lp-compare-img img { display: block; width: 100%; height: 100%; object-fit: contain; }
.lp-compare-name { font-weight: 600; font-size: 13.5px; display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center; }
.lp-compare-fig { font-size: 13px; }
.lp-compare-fill { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.lp-compare-fill .lp-meter-bar { flex: 1 1 auto; margin: 0; }
.lp-fleet-tip { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 14px;
                margin-top: 12px; padding: 12px 14px; border-radius: var(--radius); background: rgba(var(--info-rgb), .08);
                border: 0.5px solid rgba(var(--info-rgb), .45); font-size: 13.5px; }
.lp-fleet-chip { display: inline-block; padding: 2px 9px; border-radius: 999px; background: var(--surface-2); border: 0.5px solid var(--line-2); font-size: 12.5px; }

.lp-tripstrip { display: flex; gap: 10px; overflow-x: auto; margin-top: 14px; padding-bottom: 4px; -webkit-overflow-scrolling: touch; }
.lp-tripchip { flex: 0 0 170px; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: var(--radius-lg);
               border: 0.5px solid var(--line-2); background: var(--surface-2); text-decoration: none; color: var(--text); }
.lp-tripchip img { width: 100%; height: 54px; object-fit: contain; background: #F4F1EC; border-radius: 6px; margin-bottom: 4px; }
.lp-tripchip-n { font-weight: 700; font-size: 13px; }
.lp-tripchip-v { font-size: 12.5px; }
.lp-tripchip-f { font-size: 11.5px; color: var(--text-3); }
.lp-trip-vehicle { font-family: var(--sans); font-size: 13px; font-weight: 600; padding: 3px 10px; border-radius: 999px;
                   background: rgba(var(--brass-rgb), .1); color: var(--text-2); }

.lp-pallet-draw-3d { background: #F7F5F0; }
.lp-pallet-iso { max-height: 260px; }

.lp-pb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px 14px; }
.lp-pb-grid .field { margin: 0; }
.lp-pb-grid .lp-f-wide { grid-column: 1 / -1; }
.lp-pb-layout { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 18px; align-items: start; }
.lp-pb-stage .lp-svg { max-height: 460px; }
.lp-pb-facts { display: flex; flex-direction: column; gap: 12px; }
.lp-pb-facts .lp-kpis { margin-top: 0; }
.lp-row-current td { background: rgba(var(--brass-rgb), .06); }

.lp-est { display: flex; flex-direction: column; gap: 10px; }
.lp-est-figs { display: flex; flex-wrap: wrap; gap: 10px 20px; align-items: flex-end; }
.lp-est-figs > div { display: flex; flex-direction: column; }
.lp-est-v { font-size: 20px; font-weight: 700; }
.lp-est-l { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: .04em; }
.lp-est-vehicle .lp-est-v { font-family: var(--sans); font-size: 16px; }
.lp-est-stage .lp-svg { max-height: 220px; }

@media (max-width: 640px) {
  .lp-row { grid-template-columns: minmax(0, 1fr) 96px 44px; padding: 10px; }
  .lp-compare-row { grid-template-columns: 64px minmax(0, 1fr) auto; }
  .lp-compare-fig, .lp-compare-fill { grid-column: 2 / -1; }
  .lp-compare-row .btn { grid-column: 1 / -1; width: 100%; }
  .lp-pb-layout { grid-template-columns: 1fr; }
}
@media print {
  .lp-compare, .lp-tripstrip, .lp-row-del, .lp-row-more { display: none !important; }
}
.lp-compare-more { margin-top: 6px; }
.lp-compare-more > summary { cursor: pointer; font-size: 13px; color: var(--text-2); min-height: 40px; display: flex; align-items: center; }

/* Live fit on each vehicle card - an estimate from the rows above. */
.lp-fit { display: none; flex-direction: column; gap: 3px; margin-top: 6px; }
.lp-fit.is-on { display: flex; }
.lp-fit-row { display: grid; grid-template-columns: 44px 1fr 38px; align-items: center; gap: 6px; font-size: 10.5px; color: var(--text-3); }
.lp-fit-bar { height: 6px; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.lp-fit-bar i { display: block; height: 100%; width: 0; background: var(--brass); border-radius: inherit; }
.lp-fit-bar i.is-near { background: var(--amber); }
.lp-fit-bar i.is-over { background: var(--bad); }
.lp-fit-v { text-align: end; color: var(--text-2); }
.lp-fit-trips { font-size: 11.5px; font-weight: 600; color: var(--text-2); }
.lp-pick.is-best { border-color: rgba(var(--good-rgb), .8); }
.lp-pick.is-best .lp-fit-trips { color: var(--good); }
.lp-fit-note { margin: 0 0 10px; }
.lp-meter-sub { font-size: 11px; color: var(--text-3); margin-top: 3px; }
.lp-row-boxpick .lp-check { min-height: 32px; margin-top: 6px; }
.lp-persp { max-height: 70vh; }

/* Plan list header: search and "New plan" stack on a phone instead of
   overlapping. */
.lp-search { flex: 1 1 220px; margin: 0; min-width: 0; }
.lp-search input { width: 100%; box-sizing: border-box; }
/* The search box is allowed to give way; the button is not - it kept
   shrinking until "+ New plan" wrapped onto two lines. */
.lp-head .btn-row > .btn,
.lp-head .btn-row > .btn-brass { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 640px) {
  .lp-head .btn-row { width: 100%; }
  .lp-head .btn-row > .lp-search { flex: 1 1 100%; }
  .lp-search input { min-width: 0; }
  .lp-head .btn-row > .btn, .lp-head .btn-row > .btn-brass { flex: 1 1 auto; }
  .lp-fit-row { grid-template-columns: 38px 1fr 34px; }
}

/* "Working out the load..." while Calculate is on the server. */
.lp-busy { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 60; display: flex;
           align-items: center; justify-content: center; background: rgba(16, 20, 19, .28); }
.lp-busy[hidden] { display: none; }
.lp-busy-box { display: flex; align-items: center; gap: 12px; padding: 16px 22px; border-radius: var(--radius-lg);
               background: var(--surface-2); color: var(--text); font-size: 14.5px; box-shadow: 0 10px 30px rgba(0,0,0,.25); }
.lp-busy-spin { width: 20px; height: 20px; border-radius: 50%; border: 3px solid var(--line-2);
                border-top-color: var(--brass); animation: lp-spin .8s linear infinite; }
@keyframes lp-spin { to { transform: rotate(360deg); } }
@media print { .lp-busy { display: none !important; } }


/* === Order screens (v2.9.13) ==========================================
   Orders list, order detail, order + line forms and their confirm pages,
   in the soft-surface language above. The detail page leads with who and
   when, then the figures (fulfilled is the hero), then the lines with a
   fulfilment capsule each. One primary action; Cancel and Delete live in
   the "more" menu and still go through their confirm pages.
   New flex rows space with margins, not `gap` (iOS 12); grids use gap.
   ===================================================================== */

/* --- breadcrumb ---------------------------------------------------------- */
.od-crumb { font-size: 12.5px; color: var(--text-3); margin-bottom: 10px; }
.od-crumb a { color: var(--text-2); }
.od-crumb a:hover { color: var(--brass-ink); }
.od-crumb-sep { margin: 0 7px; opacity: 0.6; }

/* --- header: identity first, actions after -------------------------------- */
.od-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  margin-bottom: 18px;
}
.od-id { flex: 1 1 360px; min-inline-size: 0; margin-bottom: 4px; }
.od-title-row { display: flex; flex-wrap: wrap; align-items: center; }
.od-title {
  font-family: var(--mono); font-size: 26px; font-weight: 650; letter-spacing: -0.02em;
  line-height: 1.15; margin: 0 12px 0 0; color: var(--text);
}
[dir="rtl"] .od-title { margin: 0 0 0 12px; }
.od-title-row .chip { margin: 4px 6px 4px 0; font-size: 11.5px; padding: 3px 10px; }
[dir="rtl"] .od-title-row .chip { margin: 4px 0 4px 6px; }
.chip.is-quiet { background: var(--seg-bg); color: var(--text-2); }
.od-meta { display: flex; flex-wrap: wrap; margin: 12px 0 0; }
.od-meta-item { margin: 0 28px 4px 0; min-inline-size: 0; }
[dir="rtl"] .od-meta-item { margin: 0 0 4px 28px; }
.od-meta dt { font-size: 11.5px; color: var(--text-3); margin: 0 0 2px; }
.od-meta dd { margin: 0; font-size: 14px; font-weight: 600; color: var(--text); }
.od-meta dd a { color: var(--text); text-decoration: underline; text-decoration-color: var(--line-2); text-underline-offset: 3px; }
.od-meta dd a:hover { color: var(--brass-ink); text-decoration-color: currentColor; }

.od-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; margin-top: 10px; }
.od-actions > .btn, .od-actions > .btn-brass, .od-actions > .od-more { margin: 0 0 6px 8px; }
[dir="rtl"] .od-actions > .btn, [dir="rtl"] .od-actions > .btn-brass, [dir="rtl"] .od-actions > .od-more { margin: 0 8px 6px 0; }
.od-actions .btn, .od-actions .btn-brass { display: inline-flex; align-items: center; }
.od-actions .xw-icon { inline-size: 16px; block-size: 16px; }
.od-btn-text { margin-inline-start: 7px; }

/* the overflow menu */
.od-more { position: relative; }
.od-actions .od-more-btn { justify-content: center; min-inline-size: 40px; padding-inline-start: 10px; padding-inline-end: 10px; }
.od-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  min-inline-size: 250px; padding: 6px;
  background: var(--surface-2); border: 0.5px solid var(--line-2); border-radius: 14px;
  box-shadow: 0 18px 40px -18px rgba(10, 14, 20, 0.45);
}
[dir="rtl"] .od-menu { right: auto; left: 0; }
.od-menu-item {
  display: block; padding: 9px 12px; border-radius: 10px;
  text-decoration: none; color: var(--text);
  transition: background-color 200ms var(--ease-spring);
}
.od-menu-item:hover, .od-menu-item:focus-visible { background: var(--row-hover); text-decoration: none; }
.od-menu-item + .od-menu-item { margin-top: 2px; }
.od-menu-label { display: flex; align-items: center; font-size: 13.5px; font-weight: 600; }
.od-menu-label .xw-icon { inline-size: 15px; block-size: 15px; margin-inline-end: 6px; }
.od-menu-hint { display: block; font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.od-menu-item.is-warn .od-menu-label { color: var(--amber); }
.od-menu-item.is-bad .od-menu-label { color: var(--bad); }
.od-menu-item.is-bad:hover { background: rgba(var(--bad-rgb), 0.10); }

.od-banner { margin-bottom: 14px; }

/* --- figures ---------------------------------------------------------------- */
.od-kpis {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-gap: 14px; gap: 14px; margin-bottom: 16px;
}
.od-kpis.has-value { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.od-kpis > .card + .card { margin-top: 0; }
.od-kpi { padding: 16px 18px; min-inline-size: 0; display: flex; flex-direction: column; }
.od-kpi-label { font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.od-kpi-value {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 28px; font-weight: 650; letter-spacing: -0.02em; line-height: 1.1;
  margin-top: 10px; color: var(--text); white-space: nowrap;
}
.od-kpi-value.is-bad { color: var(--bad); }
.od-kpi-value .kpi-unit { font-size: 13px; font-weight: 500; letter-spacing: 0; }
.od-kpi-unit { font-size: 15px; font-weight: 600; margin-inline-start: 2px; opacity: 0.7; }
.od-kpi-sub { margin-top: auto; padding-top: 10px; font-size: 12px; line-height: 1.45; color: var(--text-2); }
.od-meter { block-size: 8px; margin-top: 12px; border-radius: 999px; background: var(--cap-hatch); overflow: hidden; }
.od-meter-fill { display: block; block-size: 100%; border-radius: inherit; background: var(--cap-strong); }
/* the hero: an accent tile, the same treatment as a dashboard's first figure */
.od-kpi.is-hero {
  background-color: var(--hero-bg);
  background-image: linear-gradient(140deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 58%);
  border-color: transparent; color: var(--hero-ink);
}
.od-kpi.is-hero .od-kpi-label,
.od-kpi.is-hero .od-kpi-value,
.od-kpi.is-hero .od-kpi-sub { color: inherit; }
.od-kpi.is-hero .od-kpi-sub { opacity: 0.85; }
.od-kpi.is-hero .od-meter { background: rgba(255, 255, 255, 0.24); }
.od-kpi.is-hero .od-meter-fill { background: var(--hero-ink); }

.od-notes { margin-bottom: 16px; padding: 14px 18px; }
.od-notes-body { white-space: pre-wrap; font-size: 13.5px; line-height: 1.55; margin-top: 6px; max-inline-size: 75ch; }

/* --- the lines card ------------------------------------------------------- */
.od-lines { margin-bottom: 18px; }
.od-sec-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 12px; }
.od-sec-title { font-family: inherit; font-size: 15px; font-weight: 650; margin: 0; display: flex; align-items: center; }
.od-count {
  display: inline-block; margin-inline-start: 8px; padding: 1px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; color: var(--text-2); background: var(--seg-bg);
}
.od-tbl-scroll { position: relative; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ol-table td { vertical-align: middle; }
.ol-table .ol-mould { font-weight: 650; }
.ol-label { display: block; font-size: 12px; color: var(--text-3); margin-top: 2px; }
.ol-prog-h { min-inline-size: 150px; }
.ol-prog, .ord-prog { min-inline-size: 140px; }
.ol-prog-track {
  position: relative; block-size: 8px; border-radius: 999px; overflow: hidden;
  background: var(--cap-hatch); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.16);
}
.ol-prog-fill { display: block; block-size: 100%; border-radius: inherit; background: var(--cap-strong); }
.ol-row.is-done .ol-prog-fill { background: var(--good); }
.ol-row.is-overdue .ol-prog-track { box-shadow: inset 0 0 0 1px rgba(var(--bad-rgb), 0.45); }
.ol-prog-fig { font-size: 12px; margin-top: 5px; white-space: nowrap; }
.ol-due { white-space: nowrap; }
.ol-due .chip { margin-inline-start: 6px; }
.ol-row.is-overdue .ol-due .mono { color: var(--bad); font-weight: 600; }
.ol-actions { white-space: nowrap; text-align: end; inline-size: 1%; }
.ol-icon {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 34px; block-size: 34px; border-radius: 999px;
  color: var(--text-2); text-decoration: none;
  transition: background-color 200ms var(--ease-spring), color 200ms var(--ease-spring), transform 200ms var(--ease-spring);
}
.ol-icon + .ol-icon { margin-inline-start: 2px; }
.ol-icon .xw-icon { inline-size: 17px; block-size: 17px; }
.ol-icon:hover { background: var(--chip-bg); color: var(--text); text-decoration: none; }
.ol-icon.is-bad:hover { background: rgba(var(--bad-rgb), 0.12); color: var(--bad); }
.ol-icon:active { transform: scale(0.94); }
.ol-icon:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
@media (pointer: coarse) { .ol-icon { inline-size: 44px; block-size: 44px; } }
.ol-table tfoot td { color: var(--text-2); }
.ol-table tfoot td:first-child { color: var(--text); }

/* --- empty states ------------------------------------------------------------ */
.od-empty { text-align: center; padding: 36px 20px 40px; }
.od-empty.is-inline { padding: 26px 12px 28px; }
.od-empty-icon {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 52px; block-size: 52px; border-radius: 16px;
  background: var(--chip-bg); color: var(--brass-ink); margin-bottom: 12px;
}
.od-empty-icon .xw-icon { inline-size: 24px; block-size: 24px; }
.od-empty-title { font-size: 15px; font-weight: 650; color: var(--text); margin: 0 0 6px; }
.od-empty-text { font-size: 13px; max-inline-size: 46ch; margin: 0 auto 16px; line-height: 1.5; }
.od-empty .btn, .od-empty .btn-brass { margin-top: 6px; }

/* --- list: status pills and the fulfilment column ------------------------- */
.ord-status-seg { margin-bottom: 12px; }
.ord-no { font-weight: 650; color: var(--brass-ink); }
.ord-list .ol-prog-track { max-inline-size: 180px; }

/* --- forms and confirm pages ----------------------------------------------- */
.of-card { padding: 20px 22px; }
.of-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-gap: 14px 18px; gap: 14px 18px; }
.of-grid .field select,
.of-grid .field textarea,
.of-grid .field input[type="text"],
.of-grid .field input[type="number"],
.of-grid .field input[type="date"] { inline-size: 100%; box-sizing: border-box; }
.field-help { font-size: 11.5px; margin-top: 4px; }
.of-lines-card { margin-top: 14px; }
.of-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; margin-top: 18px; }
.of-actions > .btn, .of-actions > .btn-brass, .of-actions > .btn-bad, .of-actions > .btn-warn { margin: 0 0 6px 8px; }
[dir="rtl"] .of-actions > .btn, [dir="rtl"] .of-actions > .btn-brass,
[dir="rtl"] .of-actions > .btn-bad, [dir="rtl"] .of-actions > .btn-warn { margin: 0 8px 6px 0; }
/* The order form's Save bar stays in reach on a long multi-line order. */
.of-sticky {
  position: -webkit-sticky; position: sticky; bottom: 0; z-index: 5;
  padding: 12px 0 6px; background: var(--bg);
  box-shadow: 0 -10px 16px -12px rgba(0, 0, 0, 0.25);
}
.of-confirm { max-inline-size: 640px; padding: 22px 24px; border-inline-start: 4px solid var(--amber); }
.of-confirm.is-bad { border-inline-start-color: var(--bad); }
.of-confirm-title { font-family: inherit; font-size: 20px; font-weight: 700; margin: 0 0 10px; }
.of-confirm p { font-size: 14px; line-height: 1.55; margin: 0 0 10px; }
.of-confirm-what { display: flex; flex-wrap: wrap; align-items: center; }
.of-confirm-what > * { margin-inline-end: 8px; }
.of-confirm-list { font-size: 13px; line-height: 1.7; color: var(--text-2); padding-inline-start: 18px; margin: 0 0 10px; }
.of-confirm-note { font-weight: 600; color: var(--brass-ink); }
.btn-warn { background: transparent; border: 0.5px solid var(--amber); color: var(--amber); border-radius: 999px; }
.btn-warn:hover { background: var(--amber); color: var(--bg); text-decoration: none; }

/* --- iPad and phone --------------------------------------------------------- */
@media (max-width: 1100px) {
  .od-kpis.has-value { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .od-head { display: block; }
  .od-actions { justify-content: flex-start; }
  .od-actions > .btn, .od-actions > .btn-brass, .od-actions > .od-more { margin: 0 8px 6px 0; }
  [dir="rtl"] .od-actions > .btn, [dir="rtl"] .od-actions > .btn-brass, [dir="rtl"] .od-actions > .od-more { margin: 0 0 6px 8px; }
  .od-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .od-kpis > .is-hero { grid-column: 1 / -1; }
  .od-kpis.has-value > .is-hero { grid-column: auto; }
}
@media (max-width: 720px) {
  .od-title { font-size: 22px; }
  .od-kpi { padding: 14px; }
  .od-kpi-value { font-size: 22px; }
  .of-grid { grid-template-columns: 1fr; }
  .od-menu { right: auto; left: 0; }
  [dir="rtl"] .od-menu { left: auto; right: 0; }
  /* lines become cards: label and value pairs, the capsule full width */
  .ol-table, .ol-table tbody, .ol-table tr, .ol-table td { display: block; inline-size: 100%; box-sizing: border-box; }
  .ol-table thead, .ol-table tfoot { display: block; position: absolute; top: 0; left: 0; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .ol-table tbody { position: relative; }
  .ol-table tbody tr { position: relative; padding: 12px 14px; border-top: 0.5px solid var(--line); }
  .ol-table tbody tr td,
  .ol-table tbody tr:nth-child(odd) td,
  .ol-table tbody tr:hover td,
  .ol-table tbody tr:nth-child(odd):hover td { background: transparent; }
  .ol-table tbody td { border-top: 0; padding: 3px 0; }
  .tbl-card .ol-table td:first-child, .tbl-card .ol-table td:last-child { padding-inline-start: 0; padding-inline-end: 0; }
  .ol-table td[data-label] { display: flex; justify-content: space-between; align-items: baseline; text-align: end; }
  .ol-table td[data-label]::before { content: attr(data-label); color: var(--text-3); font-size: 12px; font-weight: 400; margin-inline-end: 12px; text-align: start; }
  .ol-table td.ol-mould { display: block; font-size: 16px; text-align: start; }
  .ol-table td.ol-mould::before { display: none; }
  .tbl-card .ol-table td.ol-mould:first-child { padding-inline-end: 96px; }
  .ol-table td.ol-prog { display: block; padding: 8px 0 6px; text-align: start; }
  .ol-table td.ol-prog::before { display: block; margin-bottom: 6px; }
  .ol-table .ol-label { display: inline; margin-inline-start: 6px; }
  .ol-table td.ol-actions { position: absolute; top: 6px; right: 8px; inline-size: auto; padding: 0; }
  [dir="rtl"] .ol-table td.ol-actions { right: auto; left: 8px; }
  .tbl-card .ol-table td.ol-actions:last-child { padding: 0; }
  .od-sec-head { padding: 12px 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .ol-icon, .od-menu-item { transition: none; }
}
.ol-table th { white-space: nowrap; }
.ol-table td { padding-top: 11px; padding-bottom: 11px; }
.ol-table td .xw-color, .ol-table td:nth-child(3) { white-space: nowrap; }
.od-meta dd.is-bad { color: var(--bad); }
.ol-prog-fig b { font-weight: 650; color: var(--text); }
/* Narrower content (iPad, or a desktop beside the sidebar): the unit
   price is on the edit form and in the line total, so it drops first;
   then the print cell wraps and the due chip goes under its date. */
@media (max-width: 1280px) { .ol-table .ol-col-unit { display: none; } }
@media (max-width: 1100px) {
  .ol-table td:nth-child(3), .ol-table td .xw-color { white-space: normal; }
  .ol-table th, .ol-table td { padding-inline-start: 8px; padding-inline-end: 8px; }
  .ol-table th { white-space: normal; }
  .ol-icon + .ol-icon { margin-inline-start: 0; }
  .ol-prog, .ol-prog-h { min-inline-size: 118px; }
  .ol-due .mono { display: block; }
  .ol-due .chip { margin: 4px 0 0; }
}
.ol-frac { direction: ltr; unicode-bidi: isolate; display: inline-block; }
@media (max-width: 720px) {
  .ol-table td[data-label] { justify-content: flex-start; }
  .ol-table td[data-label]::before { margin-inline-end: auto; padding-inline-end: 12px; }
  .ol-table td[data-label] > * + * { margin-inline-start: 6px; }
  .ol-table td.ol-prog::before { padding-inline-end: 0; }
  .ol-table .ol-due .chip { display: inline-block; margin: 0 0 0 6px; }
  [dir="rtl"] .ol-table .ol-due .chip { margin: 0 6px 0 0; }
}

/* === Forecast page (v2.9.13) ===========================================
   Summary first, then one folding section per recommendation (Make now
   and Plan soon open, Overstocked and OK folded), 25 SKUs a page, SKUs
   grouped under their mould. Each row: a 12-week sparkline (shipped
   solid, forecast dashed), one cover capsule in place of free stock /
   days of cover / reorder point, and the action as a chip with its
   quantity. Desktop: the header row sticks while the page scrolls.
   iPad/phone: the table scrolls sideways under a sticky SKU column.
   ===================================================================== */
.fc-kpis {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-gap: 14px; gap: 14px; margin: 14px 0 16px;
}
.fc-kpis > .card + .card { margin-top: 0; }
.od-kpi-value.is-warn { color: var(--amber); }
.fc-acc .gauge { margin-top: 2px; max-inline-size: 150px; }
.fc-acc .gauge-figure b { font-size: 22px; }
.fc-banner { margin-bottom: 14px; }
.fc-filters .filter-field.fc-num { max-inline-size: 130px; }

.fc-act {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 650; line-height: 1.45; white-space: nowrap;
  background: var(--seg-bg); color: var(--text-2);
}
.fc-act-make { background: rgba(var(--bad-rgb), 0.14); color: var(--bad); }
.fc-act-plan { background: rgba(var(--amber-rgb), 0.16); color: var(--amber); }
.fc-act-over { background: rgba(var(--info-rgb), 0.16); color: var(--info); }
.fc-act-ok   { background: rgba(var(--good-rgb), 0.14); color: var(--good); }
.fc-trust-good { color: var(--good); }
.fc-trust-fair { color: var(--amber); }
.fc-trust-poor { color: var(--bad); }

/* the drill-down */
.fc-detail { margin-bottom: 16px; padding: 18px 20px; }
.fc-detail-head { display: flex; justify-content: space-between; align-items: flex-start; }
.fc-detail-head .od-sec-title { flex-wrap: wrap; }
.fc-detail-head .od-sec-title .fc-act { margin-inline-start: 10px; }
.fc-detail-sub { font-size: 12px; margin-top: 4px; max-inline-size: 80ch; }
.fc-facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-gap: 10px; gap: 10px; margin: 14px 0; }
.fc-fact { display: flex; flex-direction: column; padding: 10px 12px; border-radius: 12px; background: var(--seg-bg); font-size: 11.5px; color: var(--text-3); min-inline-size: 0; }
.fc-fact b { font-size: 18px; font-weight: 650; color: var(--text); margin: 2px 0; }
.fc-legend { list-style: none; padding: 0; margin: 0 0 4px; font-size: 11.5px; }
.fc-legend li { display: inline-block; margin-inline-end: 16px; }
.fc-leg-line { display: inline-block; inline-size: 18px; block-size: 3px; border-radius: 2px; margin-inline-end: 5px; vertical-align: middle; }
.fc-leg-line.is-shipped { background: var(--info); }
.fc-leg-line.is-forecast { background: var(--amber); }
.fc-line-chart { display: block; inline-size: 100%; block-size: auto; margin-top: 6px; }
.fc-band { fill: var(--text); opacity: .045; }
.fc-grid { stroke: var(--line-2); stroke-width: 1; }
.fc-axis { font: 10.5px var(--mono); fill: var(--text-3); }
.fc-readout { font-size: 11px; margin-top: 6px; min-block-size: 16px; }
.fc-readout b { color: var(--text); margin-inline-end: 10px; }
.fc-readout-v { margin-inline-end: 12px; white-space: nowrap; }
.fc-note { font-size: 11.5px; margin-top: 8px; }

/* policy line above the sections */
.fc-policy { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin: 4px 0 10px; }
.fc-policy-title { font-size: 15px; font-weight: 650; margin: 0 12px 6px 0; }
[dir="rtl"] .fc-policy-title { margin: 0 0 6px 12px; }

/* sections */
.fc-sec { margin-bottom: 14px; }
.tbl-card.fc-sec { overflow: visible; }
.fc-sec-head {
  display: flex; align-items: center; flex-wrap: wrap;
  padding: 14px 16px; cursor: pointer; list-style: none;
  border-radius: var(--card-radius);
}
.fc-sec-head::-webkit-details-marker { display: none; }
.fc-sec-head:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.fc-sec-head > * { margin-inline-end: 10px; }
.fc-sec-head .od-count { margin-inline-start: 0; }
.fc-sec-caret {
  display: inline-block; inline-size: 8px; block-size: 8px;
  border-inline-end: 2px solid var(--text-3); border-block-end: 2px solid var(--text-3);
  transform: rotate(-45deg); transition: transform 220ms var(--ease-spring);
}
[dir="rtl"] .fc-sec-caret { transform: rotate(135deg); }
.fc-sec[open] > .fc-sec-head .fc-sec-caret { transform: rotate(45deg); }
.fc-sec-sub { font-size: 12.5px; }
.fc-sec:not([open]) > .fc-sec-head:hover { background: var(--row-hover); }

.fc-tbl-wrap { border-top: 0.5px solid var(--line); }
.fc-tbl { min-inline-size: 100%; }
.fc-tbl thead th {
  position: -webkit-sticky; position: sticky; top: 0; z-index: 3;
  background: var(--well); font-size: 12.5px; white-space: nowrap;
  padding-top: 10px; padding-bottom: 10px;
}
.fc-tbl td { padding-top: 9px; padding-bottom: 9px; vertical-align: middle; }
.fc-tbl tbody tr:nth-child(odd) td { background: transparent; }
.fc-tbl tbody tr.fc-row:hover td { background: var(--row-hover); }
.fc-group-head th {
  padding: 12px 16px 4px; background: transparent; text-align: start;
  border-top: 0.5px solid var(--line);
}
.fc-tbl .fc-group:first-of-type .fc-group-head th { border-top: 0; }
.fc-group-in { display: inline-flex; align-items: baseline; }
.fc-group-in a { font-size: 13.5px; font-weight: 700; color: var(--text); margin-inline-end: 8px; }
.fc-group-in a:hover { color: var(--brass-ink); }
.fc-group-in .dim { font-size: 11.5px; font-weight: 500; }
.fc-tbl .fc-row td { border-top: 0; }
.fc-tbl tbody td:first-child { font-weight: 500; }
.fc-c-sku { min-inline-size: 150px; }
.tbl-card .fc-tbl td.fc-c-sku { padding-inline-start: 28px; }
.fc-sku { color: var(--text); }
.fc-sku:hover { color: var(--brass-ink); text-decoration: none; }
.fc-print { display: block; font-size: 11.5px; margin-top: 1px; padding-inline-start: 22px; }
.fc-demand { white-space: nowrap; }
.fc-spark { display: inline-block; vertical-align: middle; overflow: visible; margin-inline-end: 8px; }
.fc-spark-ship { fill: none; stroke: var(--info); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.fc-spark-fc { fill: none; stroke: var(--amber); stroke-width: 1.2; stroke-dasharray: 3 2; opacity: 0.9; }
.fc-spark-dot { fill: var(--info); }
[dir="rtl"] .fc-spark { transform: scaleX(-1); }
.fc-pattern { font-size: 12px; color: var(--text-2); }
.fc-c-cover { min-inline-size: 150px; }
.fc-cover {
  position: relative; block-size: 8px; max-inline-size: 160px; border-radius: 999px;
  background: var(--cap-hatch); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.16);
}
.fc-cover-fill { position: absolute; top: 0; bottom: 0; left: 0; display: block; border-radius: inherit; background: var(--good); }
[dir="rtl"] .fc-cover { transform: scaleX(-1); }
.fc-cover.is-low .fc-cover-fill { background: var(--amber); }
.fc-cover.is-over .fc-cover-fill { background: var(--info); }
.fc-cover.is-short { box-shadow: inset 0 0 0 1px rgba(var(--bad-rgb), 0.6); background: rgba(var(--bad-rgb), 0.10); }
.fc-cover-rop { position: absolute; top: -3px; bottom: -3px; inline-size: 2px; margin-left: -1px; border-radius: 1px; background: var(--text); opacity: 0.7; }
.fc-cover-fig { font-size: 12px; margin-top: 5px; white-space: nowrap; }
.fc-c-act { white-space: nowrap; }
.fc-order { display: inline-block; margin: 0; vertical-align: middle; }
.fc-order .ol-icon { border: 0; background: transparent; cursor: pointer; inline-size: 30px; block-size: 30px; margin-inline-start: 2px; }
.fc-order .ol-icon:hover { background: var(--chip-bg); color: var(--brass-ink); }

.fc-pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; padding: 10px 16px 14px; border-top: 0.5px solid var(--line); font-size: 12px; }
.fc-pager-btns { display: inline-flex; align-items: center; }
.fc-pager-btns > * { margin-inline-start: 8px; }
.fc-pager-n { color: var(--text-2); }
.fc-showall { margin: 4px 0 16px; text-align: center; }
.fc-about { margin-top: 6px; }
.fc-about > summary { cursor: pointer; list-style: none; }
.fc-about > summary::-webkit-details-marker { display: none; }
.fc-about > summary::before { content: "+"; display: inline-block; inline-size: 18px; color: var(--text-3); }
.fc-about[open] > summary::before { content: "\2212"; }
.fc-about-body { font-size: 13px; line-height: 1.65; margin-top: 10px; max-inline-size: 90ch; color: var(--text-2); }
.fc-about-body p { margin: 0 0 9px; }
.fc-about-body b { color: var(--text); }

@media (max-width: 1100px) {
  .fc-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fc-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* the table scrolls sideways under a pinned SKU column */
  .fc-tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .fc-tbl thead th { position: static; }
  .fc-tbl .fc-c-sku {
    position: -webkit-sticky; position: sticky; left: 0; z-index: 2;
    background: var(--surface-2); box-shadow: 1px 0 0 var(--line);
  }
  [dir="rtl"] .fc-tbl .fc-c-sku { left: auto; right: 0; box-shadow: -1px 0 0 var(--line); }
  .fc-tbl thead .fc-c-sku { background: var(--well); }
  .fc-group-in { position: -webkit-sticky; position: sticky; left: 16px; }
  [dir="rtl"] .fc-group-in { left: auto; right: 16px; }
}
@media (max-width: 640px) {
  .fc-kpis { grid-gap: 10px; gap: 10px; }
  .fc-kpis .od-kpi { padding: 12px 14px; }
  .fc-kpis .od-kpi-value { font-size: 22px; }
  .fc-acc .gauge { max-inline-size: 120px; }
  .fc-sec-sub { flex-basis: 100%; margin-top: 4px; }
}
@media (prefers-reduced-motion: reduce) { .fc-sec-caret { transition: none; } }
.fc-solo-mould { font-weight: 700; color: var(--text); margin-inline-end: 10px; }
.fc-solo-mould:hover { color: var(--brass-ink); text-decoration: none; }
.tbl-card .fc-tbl tr.is-solo td.fc-c-sku { padding-inline-start: 16px; }
.fc-tbl tr.is-solo td { border-top: 0.5px solid var(--line); }
.fc-tbl tr.is-solo .fc-print { padding-inline-start: 0; display: inline; margin-inline-start: 8px; }
.fc-c-sku > a { vertical-align: middle; }
.fc-cover-fig .mono, .fc-detail .fc-fact b.mono { direction: ltr; unicode-bidi: isolate; display: inline-block; }
.od-kpi-unit { font-family: var(--sans); letter-spacing: 0; }
[lang="fa"] .od-kpi-unit, [lang="fa"] .od-kpi-value .kpi-unit { font-family: var(--sans-fa); }
.fc-group-head th, .tbl-card .fc-tbl .fc-group-head th { background: var(--seg-bg); }

/* === Production plan page (v2.9.13) ====================================
   Header like the order page; headline tiles with customer orders as the
   hero; one strip per press with campaigns as spans (customer orders
   solid accent, stock hatched); how each order line is met as a stacked
   capsule; the setup crew's list as compact window cards; the stock
   tables with chips; the generator log split into headline, needs
   attention, and a folded per-change list. Was a page-local <style>.
   ===================================================================== */
.sp-title { font-family: inherit; }
.sp-inline-form { display: inline-block; margin: 0 0 6px 8px; }
[dir="rtl"] .sp-inline-form { margin: 0 8px 6px 0; }
.sp-inline-form .btn-brass { display: inline-flex; align-items: center; }
.sp-banner { margin-bottom: 12px; }
.sched-send { margin-bottom: 16px; padding: 18px 20px; }

.sp-kpis {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  grid-gap: 14px; gap: 14px; margin-bottom: 16px;
}
.sp-kpis > .card + .card { margin-top: 0; }
.sp-kpi .od-kpi-sub .chip { margin-top: 4px; }
.sp-kpi-link { font-weight: 600; white-space: nowrap; }
.od-kpi.is-hero .sp-kpi-link { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.od-kpi.is-hero .chip.is-bad { background: rgba(255, 255, 255, 0.92); color: var(--bad); }

.sp-sec { margin-top: 16px; padding: 18px 20px; }
.sp-sec-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.sp-sec-head .od-sec-title .chip { margin-inline-start: 10px; }
.sp-lead { font-size: 12.5px; line-height: 1.55; margin: 0 0 12px; max-inline-size: 95ch; }
.sp-foot { font-size: 12px; margin: 10px 0 0; }
.sp-tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 -20px; }
.sp-tbl { min-inline-size: 100%; font-size: 13.5px; }
.sp-tbl th { font-size: 12.5px; white-space: nowrap; padding-top: 10px; padding-bottom: 10px; }
.sp-tbl td { padding-top: 10px; padding-bottom: 10px; vertical-align: middle; }
.sp-tbl tbody tr:nth-child(odd) td { background: transparent; }
.sp-tbl th:first-child, .sp-tbl td:first-child { padding-inline-start: 20px; }
.sp-tbl th:last-child, .sp-tbl td:last-child { padding-inline-end: 20px; }
.sp-tbl tbody td:first-child { font-weight: 500; }
.sp-sub-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin: 22px 0 6px; padding-top: 16px; border-top: 0.5px solid var(--line); }
.sp-h3 { font-size: 14px; font-weight: 650; margin: 0 12px 6px 0; }
[dir="rtl"] .sp-h3 { margin: 0 0 6px 12px; }
.sp-diff { min-inline-size: 44px; text-align: center; }
.sp-why { font-size: 12.5px; color: var(--text-2); }
.sp-notes { white-space: pre-wrap; font-size: 12.5px; line-height: 1.7; }
.sp-more { margin-top: 12px; }
.sp-more > summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--brass-ink); list-style: none; padding: 6px 0; }
.sp-more > summary::-webkit-details-marker { display: none; }
.sp-more > summary::before { content: "+ "; }
.sp-more[open] > summary::before { content: "\2212  "; }

/* --- the timeline ----------------------------------------------------- */
.sp-timeline { margin-bottom: 4px; }
.sp-tl-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; margin: 4px 0 10px; }
.sp-tl-head .od-sec-title { margin-inline-end: 16px; }
.sched-legend { list-style: none; display: flex; flex-wrap: wrap; margin: 0; padding: 0; font-size: 12px; color: var(--text-2); }
.sched-legend li { display: inline-flex; align-items: center; margin: 4px 0 4px 16px; }
[dir="rtl"] .sched-legend li { margin: 4px 16px 4px 0; }
.sched-key { display: inline-block; inline-size: 18px; block-size: 10px; border-radius: 999px; margin-inline-end: 7px; }
.sched-key.is-order { background: var(--cap-strong); }
.sched-key.is-stock { background: var(--cap-hatch); box-shadow: inset 0 0 0 1px rgba(var(--brass-rgb), 0.35); }
.sched-key.is-change { background: rgba(var(--amber-rgb), 0.85); inline-size: 10px; }
.sched-key.is-idle { background: transparent; box-shadow: inset 0 0 0 1px var(--line-2); }

.sched-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.sched-tbl { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; }
.sched-tbl th, .sched-tbl td { border-bottom: 0.5px solid var(--line); }
.sched-tbl tbody tr:last-child th, .sched-tbl tbody tr:last-child td { border-bottom: 0; }
.sched-tbl tbody tr:nth-child(odd) td, .sched-tbl tbody tr:hover td { background: transparent; }
.sched-machine-col {
  width: 150px; min-width: 150px;
  position: -webkit-sticky; position: sticky; left: 0; z-index: 2;
  background: var(--surface-2);
  box-shadow: 1px 0 0 var(--line);
  padding: 10px 14px;
  text-align: start; vertical-align: middle; font-weight: 400;
}
[dir="rtl"] .sched-machine-col { left: auto; right: 0; box-shadow: -1px 0 0 var(--line); }
thead .sched-machine-col { background: var(--well); font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.sched-mc-code { font-size: 13.5px; font-weight: 700; color: var(--text); }
.sched-mc-name { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }
.sched-mc-load { display: flex; align-items: center; margin-top: 6px; font-size: 10.5px; color: var(--text-3); }
.sched-mc-meter { position: relative; flex: 1 1 auto; block-size: 4px; border-radius: 999px; background: var(--seg-bg); overflow: hidden; margin-inline-end: 8px; max-inline-size: 70px; }
.sched-mc-meter > span { display: block; block-size: 100%; border-radius: inherit; background: var(--cap-strong); }
.sched-day-col {
  width: 78px; min-width: 78px;
  padding: 6px 4px 7px; vertical-align: bottom;
  text-align: center; font-size: 12px; font-weight: 600;
  color: var(--text-2); background: var(--well); white-space: nowrap;
}
.sched-month { display: block; font-size: 10px; font-weight: 500; color: var(--text-3); text-align: start; }
.sched-dom { display: block; }
.sched-day-col.is-change-day .sched-dom { color: var(--amber); }
.sched-cell { padding: 6px 3px; vertical-align: top; }

.sched-idle {
  display: flex; align-items: center; justify-content: center;
  min-block-size: 64px; border-radius: 12px;
  color: var(--text-3); font-size: 12px;
  border: 1px dashed var(--line-2); background: transparent;
}
.sched-edit-hint { margin-inline-start: 6px; font-size: 11px; color: var(--brass-ink); }

.sched-bar {
  position: relative; min-block-size: 64px; padding: 8px 10px 8px;
  border-radius: 12px; overflow: hidden;
  background: var(--surface); border: 0.5px solid var(--line-2);
  border-inline-start: 3px solid var(--cap-strong);
  color: var(--text);
  transition: transform 200ms var(--ease-spring), box-shadow 200ms var(--ease-spring);
}
.sched-bar.is-stock { border-inline-start-color: rgba(var(--brass-rgb), 0.45); background-image: repeating-linear-gradient(135deg, rgba(var(--brass-rgb), 0.07) 0, rgba(var(--brass-rgb), 0.07) 2px, transparent 2px, transparent 8px); }
.sched-bar.is-mixed { border-inline-start-color: var(--cap-strong); }
.sched-bar.is-none { border-inline-start-color: var(--line-2); }
.sched-bar.is-editable { cursor: pointer; }
.sched-bar.is-editable:hover { box-shadow: 0 8px 20px -14px rgba(10, 14, 20, 0.6); transform: translateY(-1px); }
.sched-bar.is-editable:focus-visible, .sched-idle.is-editable:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.sched-idle.is-editable { cursor: pointer; }
.sched-idle.is-editable:hover { background: var(--row-hover); color: var(--text-2); }
.sched-bar-top { display: flex; align-items: center; white-space: nowrap; }
.sched-mould { font-size: 13.5px; font-weight: 700; margin-inline-end: auto; padding-inline-end: 8px; }
.sched-qty { font-size: 12px; color: var(--text-2); }
.sched-change {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  inline-size: 18px; block-size: 18px; border-radius: 6px; margin-inline-end: 6px;
  background: rgba(var(--amber-rgb), 0.18); color: var(--amber);
}
.sched-change svg { inline-size: 12px; block-size: 12px; }
.sched-bar-sub { margin-top: 3px; font-size: 11px; color: var(--text-3); white-space: nowrap; }
.sched-idle-note { color: var(--amber); }
.sched-colours { display: flex; flex-wrap: wrap; margin-top: 5px; }
.sched-colours .xw-swatch { margin-inline-end: 3px; }
.sched-split { display: flex; block-size: 4px; margin-top: 6px; border-radius: 999px; overflow: hidden; background: var(--seg-bg); }
.sched-split > .is-order { background: var(--cap-strong); }
.sched-split > .is-stock { background: var(--cap-hatch); }
.sched-bar-parts { margin-top: 5px; }
.sched-bar-orders { font-size: 10.5px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sched-bar-orders.is-stock { color: var(--text-3); }

.modal-overlay:empty { display: none; }
.modal-overlay:not(:empty) {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 60;
  background: rgba(0, 0, 0, 0.5);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}

/* --- how each order line is met -------------------------------------- */
.cov-legend { list-style: none; display: flex; flex-wrap: wrap; margin: 0 0 10px; padding: 0; font-size: 12px; color: var(--text-2); }
.cov-legend li { display: inline-flex; align-items: center; margin: 0 14px 4px 0; }
[dir="rtl"] .cov-legend li { margin: 0 0 4px 14px; }
.cov-key { display: inline-block; inline-size: 10px; block-size: 10px; border-radius: 3px; margin-inline-end: 6px; vertical-align: -1px; }
.cov-key.is-shipped, .cov-seg.is-shipped { background: var(--text-3); }
.cov-key.is-from_stock, .cov-seg.is-from_stock { background: var(--good); }
.cov-key.is-from_plans, .cov-seg.is-from_plans { background: var(--info); }
.cov-key.is-scheduled, .cov-seg.is-scheduled { background: var(--cap-strong); }
.cov-key.is-short, .cov-seg.is-short { background: repeating-linear-gradient(135deg, var(--bad) 0, var(--bad) 2px, rgba(var(--bad-rgb), 0.25) 2px, rgba(var(--bad-rgb), 0.25) 6px); }
.cov-c-bar { min-inline-size: 190px; }
.cov-bar { display: flex; block-size: 10px; border-radius: 999px; overflow: hidden; background: var(--seg-bg); max-inline-size: 260px; }
.cov-seg { display: block; block-size: 100%; }
.cov-seg + .cov-seg { box-shadow: inset 1px 0 0 var(--surface-2); }
.cov-figs { display: flex; flex-wrap: wrap; margin-top: 5px; font-family: var(--mono); font-size: 11px; color: var(--text-2); }
.cov-figs > span { display: inline-flex; align-items: center; margin-inline-end: 10px; white-space: nowrap; }
.cov-figs .cov-key { inline-size: 7px; block-size: 7px; margin-inline-end: 4px; }
.cov-prod { white-space: nowrap; }
.cov-prod .xw-color { display: inline-flex; margin-inline-start: 6px; }
.cov-when { font-size: 12.5px; white-space: nowrap; }
.cov-when .chip { margin-top: 4px; }
.cov-why { font-size: 11.5px; color: var(--text-2); margin-top: 4px; max-inline-size: 180px; margin-inline-start: auto; white-space: normal; }
.cov-tbl tr.is-short td:first-child { box-shadow: inset 3px 0 0 var(--bad); }
[dir="rtl"] .cov-tbl tr.is-short td:first-child { box-shadow: inset -3px 0 0 var(--bad); }
.od-plan-coverage .cov { padding: 0 0 4px; }
.od-plan-coverage .sp-lead, .od-plan-coverage .cov-legend, .od-plan-from { padding: 0 16px; }
.od-plan-from { font-size: 12.5px; margin: -4px 0 8px; }
.od-plan-coverage .sp-tbl-wrap { margin: 0; }
.od-plan-coverage .od-sec-title .chip { margin-inline-start: 10px; }

/* --- the setup crew --------------------------------------------------- */
.crew-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-gap: 12px; gap: 12px; }
.crew-card { border-radius: 14px; background: var(--seg-bg); padding: 12px 14px; min-inline-size: 0; }
.crew-card.is-warn { box-shadow: inset 0 0 0 1px rgba(var(--amber-rgb), 0.55); }
.crew-card-head { display: flex; align-items: flex-start; justify-content: space-between; }
.crew-when { font-size: 12.5px; font-weight: 650; color: var(--text); line-height: 1.4; }
.crew-card-head .od-count { flex: none; background: var(--surface-2); }
.crew-warn { margin: 4px 0 0; font-size: 12px; color: var(--amber); }
.crew-list { list-style: none; margin: 8px 0 0; padding: 0; }
.crew-row {
  display: grid; grid-template-columns: 92px minmax(0, 1fr) minmax(0, 1.2fr);
  grid-column-gap: 10px; column-gap: 10px; align-items: baseline;
  padding: 6px 0; border-top: 0.5px solid var(--line); font-size: 12.5px;
}
.crew-row:first-child { border-top: 0; }
.crew-press { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crew-moulds { white-space: nowrap; }
.crew-arrow { color: var(--text-3); margin: 0 3px; }
.crew-colour { color: var(--text-2); min-inline-size: 0; }
.crew-note { grid-column: 1 / -1; font-size: 11.5px; }
.crew-note:empty { display: none; }
.crew-note .chip { margin-inline-end: 6px; }

/* --- the generator log --------------------------------------------------- */
.genlog { list-style: none; margin: 4px 0 0; padding: 0; font-size: 13px; line-height: 1.65; }
.genlog-item { position: relative; padding: 8px 0 8px 18px; border-top: 0.5px solid var(--line); color: var(--text-2); }
[dir="rtl"] .genlog-item { padding: 8px 18px 8px 0; }
.genlog-item:first-child { border-top: 0; }
.genlog-item::before { content: ""; position: absolute; top: 15px; left: 2px; inline-size: 7px; block-size: 7px; border-radius: 999px; background: var(--cap-strong); }
[dir="rtl"] .genlog-item::before { left: auto; right: 2px; }
.genlog.is-warn .genlog-item::before { background: var(--amber); }
.genlog.is-detail { font-size: 12px; line-height: 1.5; }
.genlog.is-detail .genlog-item { padding-top: 4px; padding-bottom: 4px; }
.genlog.is-detail .genlog-item::before { top: 10px; inline-size: 5px; block-size: 5px; background: var(--text-3); }
.genlog code { color: var(--text-2); font-family: var(--mono); font-size: 0.92em; }
.genlog b { color: var(--text); }
.genlog .signed { display: inline-block; padding: 0 7px; margin-inline-start: 4px; border-radius: 999px; background: var(--chip-bg); color: var(--brass-ink); font-weight: 650; font-size: 12px; }
.genlog-warn { color: var(--amber); }
.genlog-ok { color: var(--good); }
.genlog-h { display: flex; align-items: center; font-size: 13.5px; font-weight: 650; margin: 16px 0 2px; }
.genlog-h .chip { margin-inline-end: 8px; }
.genlog-more { margin-top: 12px; border-top: 0.5px solid var(--line); padding-top: 8px; }
.genlog-more > summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--brass-ink); list-style: none; padding: 6px 0; }
.genlog-more > summary::-webkit-details-marker { display: none; }
.genlog-more > summary::before { content: "+ "; }
.genlog-more[open] > summary::before { content: "\2212  "; }

@media (max-width: 1280px) {
  .sp-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1100px) {
  .crew-grid { grid-template-columns: 1fr; }
}
@media (max-width: 900px) {
  .sp-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sp-kpis > .is-hero { grid-column: 1 / -1; }
  .sp-inline-form { margin: 0 8px 6px 0; }
  [dir="rtl"] .sp-inline-form { margin: 0 0 6px 8px; }
}
@media (max-width: 640px) {
  .sp-sec { padding: 14px; }
  .sp-tbl-wrap { margin: 0 -14px; }
  .sp-tbl th:first-child, .sp-tbl td:first-child { padding-inline-start: 14px; }
  .sched-machine-col { width: 96px; min-width: 96px; padding: 8px 10px; }
  .sched-mc-name, .sched-mc-load { display: none; }
  .sched-day-col { width: 64px; min-width: 64px; }
  .crew-row { grid-template-columns: 74px minmax(0, 1fr); }
  .crew-colour { grid-column: 2; }
}
@media (prefers-reduced-motion: reduce) { .sched-bar { transition: none; } }
.sp-kpi-link { white-space: normal; }

/* === Plan print sheet (v2.9.13) ========================================
   The production plan on paper. Plain greys and black, never theme
   tokens (a dark theme would print as a black slab); order vs stock,
   short and late carried by weight, border and words so a
   black-and-white printer loses nothing. A4 landscape; each press starts
   a new page so its sheet can be handed to whoever runs it. Was a
   page-local <style>.
   ===================================================================== */
.print-toolbar { display: flex; justify-content: flex-end; margin-bottom: 14px; }
.print-toolbar > * { margin-inline-start: 8px; }
.sheet {
  background: #fff; color: #111;
  max-width: 1040px; margin: 0 auto; padding: 28px 32px;
  border-radius: 14px; box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.5);
  font-size: 12px; line-height: 1.5;
  --text: #111; --text-2: #333; --text-3: #666; --line: #ddd; --line-2: #999;
  --amber: #111; --good: #111; --bad: #111; --brass-ink: #111; --cap-strong: #111;
  --chip-bg: transparent; --seg-bg: transparent; --surface-2: #fff;
}
.sheet a { color: #111; text-decoration: none; }
.sheet-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  border-bottom: 2px solid #111; padding-bottom: 12px; margin-bottom: 14px;
}
.sheet-brand { font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: #555; }
[lang="fa"] .sheet-brand { letter-spacing: 0; }
.sheet h1 { font-family: inherit; font-size: 24px; margin: 2px 0 0; color: #111; font-weight: 700; }
.sheet-meta { display: flex; flex-wrap: wrap; margin: 0; font-size: 11px; }
.sheet-meta > div { margin-inline-start: 22px; }
.sheet-meta dt { color: #666; font-size: 10px; }
.sheet-meta dd { margin: 0; font-weight: 700; font-family: var(--mono); font-size: 12.5px; }

.sheet-tiles { display: flex; flex-wrap: wrap; margin: 0 0 16px; }
.sheet-tile {
  flex: 1 1 150px; margin: 0 10px 8px 0; padding: 10px 12px;
  border: 1px solid #bbb; border-radius: 10px; min-inline-size: 0;
}
[dir="rtl"] .sheet-tile { margin: 0 0 8px 10px; }
.sheet-tile:last-child { margin-inline-end: 0; }
.sheet-tile.is-key { border: 2px solid #111; }
.sheet-tile > span { display: block; font-size: 10.5px; color: #555; }
.sheet-tile > b { display: block; font-family: var(--mono); font-size: 22px; line-height: 1.2; margin: 2px 0; }
.sheet-tile > small { display: block; font-size: 10.5px; color: #444; line-height: 1.35; }

.mc { margin-bottom: 18px; }
.mc h2 {
  font-family: inherit; font-size: 14px; margin: 0 0 6px; padding-bottom: 4px;
  border-bottom: 1.5px solid #111; font-weight: 700;
}
.mc h2 span { font-weight: 400; color: #555; }
.mc .lead { margin: 10px 0 4px; color: #333; font-size: 11.5px; font-weight: 600; }
.sheet table { width: 100%; border-collapse: collapse; }
.sheet th, .sheet td { border-bottom: 1px solid #ddd; padding: 5px 7px; text-align: start; vertical-align: top; }
.sheet th { font-size: 10px; font-weight: 600; color: #555; border-bottom: 1px solid #999; white-space: nowrap; }
.sheet td.n, .sheet th.n { text-align: end; }
.sheet .mono { font-family: var(--mono); }
.sheet .b { font-weight: 700; }
.sheet .sub { color: #777; font-weight: 400; margin-inline-start: 4px; }
.sheet .tag {
  display: inline-block; margin-inline-start: 5px; padding: 0 6px;
  border: 1px solid #555; border-radius: 999px;
  font-size: 9px; font-family: inherit; font-weight: 600; color: #222;
}
.sheet .flag { display: inline-block; padding: 0 6px; border: 1.5px solid #111; border-radius: 999px; }
.sheet tr.is-short td:first-child { box-shadow: inset 3px 0 0 #111; }
[dir="rtl"] .sheet tr.is-short td:first-child { box-shadow: inset -3px 0 0 #111; }
.sheet .for-order { font-weight: 600; }
.sheet .for-stock { font-style: italic; color: #444; }
.sheet tr.run.is-order td:first-child { box-shadow: inset 3px 0 0 #111; }
.sheet tr.run.is-mixed td:first-child { box-shadow: inset 3px 0 0 #777; }
[dir="rtl"] .sheet tr.run.is-order td:first-child { box-shadow: inset -3px 0 0 #111; }
[dir="rtl"] .sheet tr.run.is-mixed td:first-child { box-shadow: inset -3px 0 0 #777; }
.sheet .tick { inline-size: 22px; }
.sheet .box { display: inline-block; inline-size: 11px; block-size: 11px; border: 1.2px solid #333; border-radius: 2px; vertical-align: -1px; }
.sheet .xw-swatch { border: 1px solid #999; }
.notes { white-space: pre-wrap; font-family: inherit; font-size: 11px; color: #333; margin: 0; }
.none { color: #666; }
.sheet-why .genlog { font-size: 11.5px; }
.sheet-why .genlog-item { color: #333; padding-top: 4px; padding-bottom: 4px; }
.sheet-why .genlog-item::before { background: #111; top: 10px; }
.sheet-why .genlog.is-warn .genlog-item::before { background: transparent; border: 1.5px solid #111; }
.sheet-why .genlog-more { display: none; }
.sheet-why .genlog-h .chip { border: 1px solid #111; color: #111; background: none; }
.sheet-why .signed { border: 1px solid #111; background: none; color: #111; }
.sheet-foot { margin-top: 18px; padding-top: 8px; border-top: 1px solid #bbb; font-size: 10px; color: #777; text-align: center; }

@media print {
  .topbar, .sidebar, .print-toolbar, .toast-stack, footer.login-footer { display: none !important; }
  body, .app, main, .content { background: #fff !important; margin: 0 !important; padding: 0 !important; }
  .sheet { max-width: none; margin: 0; padding: 0; border-radius: 0; box-shadow: none; font-size: 11px; }
  @page { size: A4 landscape; margin: 12mm; }
  .mc-press, .mc-crew { break-before: page; page-break-before: always; }
  .mc-press { break-inside: avoid; page-break-inside: avoid; }
  .keep, .crew-block, .sheet-tiles { break-inside: avoid; page-break-inside: avoid; }
  thead { display: table-header-group; }
  tr { break-inside: avoid; page-break-inside: avoid; }
  .sheet-foot { break-before: avoid; }
}

/* Dates, codes and from-to pairs read left to right inside RTL text. */
.ltr-run, .crew-moulds, .crew-colour { direction: ltr; unicode-bidi: isolate; }
[dir="rtl"] .crew-moulds, [dir="rtl"] .crew-colour { text-align: end; }
.cov-tbl td.mono, .cov-tbl td.cov-prod { white-space: nowrap; }
.sp-tbl td { padding-top: 7px; padding-bottom: 7px; }
.sp-more .sp-tbl-wrap { margin-top: 6px; }

/* === Forecast on tablets and phones (v2.9.13b) =========================
   Below 1100px the forecast table becomes cards - two columns on an iPad,
   one on a phone. The earlier answer, a sideways-scrolling table with a
   pinned SKU column, overlapped on iOS: WebKit paints an inline SVG (the
   sparkline) above a sticky table cell, so sparklines drew across the
   colour names and the next column's labels were clipped. Cards have no
   sticky cells and no sideways scroll at all.
   ===================================================================== */
@media (max-width: 1100px) {
  .fc-tbl-wrap { overflow: visible; border-top: 0; }
  .fc-tbl, .fc-tbl tbody, .fc-tbl tr, .fc-tbl td, .fc-tbl th { display: block; }
  .fc-tbl { min-inline-size: 0; inline-size: 100%; }
  .fc-tbl thead {
    position: absolute; top: 0; left: 0; inline-size: 1px; block-size: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .fc-tbl .fc-c-sku,
  .fc-tbl thead .fc-c-sku,
  [dir="rtl"] .fc-tbl .fc-c-sku { position: static; box-shadow: none; background: transparent; }
  .fc-group-in, [dir="rtl"] .fc-group-in { position: static; }

  .fc-tbl tbody.fc-group {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-gap: 10px; gap: 10px; padding: 12px 14px;
    border-top: 0.5px solid var(--line);
  }
  .fc-tbl .fc-group:first-of-type { border-top: 0.5px solid var(--line); }
  .fc-tbl .fc-group-head { grid-column: 1 / -1; }
  .fc-tbl .fc-group-head th,
  .tbl-card .fc-tbl .fc-group-head th { padding: 0; background: transparent; border: 0; }
  .fc-group-in a { font-size: 14px; }

  /* the card */
  .fc-tbl tr.fc-row {
    display: grid; min-inline-size: 0;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas:
      "sku sku act"
      "demand demand demand"
      "cover cover cover"
      "fc acc build"
      "money money money";
    grid-column-gap: 10px; column-gap: 10px;
    padding: 12px 14px; border-radius: 14px;
    background: var(--seg-bg); border: 0;
  }
  .fc-tbl tr.fc-row td,
  .fc-tbl tr.is-solo td,
  .tbl-card .fc-tbl tr.fc-row td,
  .tbl-card .fc-tbl tr.is-solo td.fc-c-sku,
  .tbl-card .fc-tbl td.fc-c-sku {
    padding: 0; border: 0; background: transparent; min-inline-size: 0; text-align: start;
  }
  .fc-tbl tbody tr.fc-row:hover td { background: transparent; }
  .fc-tbl .fc-c-sku { grid-area: sku; align-self: center; font-weight: 600; overflow: hidden; }
  .fc-tbl .fc-c-act { grid-area: act; justify-self: end; align-self: center; white-space: nowrap; }
  .fc-tbl .fc-demand { grid-area: demand; display: flex; align-items: center; margin-top: 8px; white-space: normal; }
  .fc-tbl .fc-c-cover { grid-area: cover; margin-top: 10px; }
  .fc-tbl .fc-c-fc { grid-area: fc; }
  .fc-tbl .fc-c-acc { grid-area: acc; }
  .fc-tbl .fc-c-build { grid-area: build; }
  .fc-tbl .fc-c-money { grid-area: money; margin-top: 8px; }
  .fc-tbl .fc-c-fc, .fc-tbl .fc-c-acc, .fc-tbl .fc-c-build { margin-top: 10px; font-size: 13.5px; }
  .fc-tbl td[data-label]::before {
    content: attr(data-label); display: block; margin-bottom: 2px;
    font-family: var(--sans); font-size: 10.5px; font-weight: 500; color: var(--text-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  [lang="fa"] .fc-tbl td[data-label]::before { font-family: var(--sans-fa); }
  .fc-tbl .fc-c-cover::before { margin-bottom: 5px; }
  .fc-cover { max-inline-size: none; }
  .fc-spark { flex: 1 1 auto; inline-size: 100%; block-size: 26px; margin-inline-end: 10px; }
  .fc-spark-dot { display: none; }
  .fc-pattern {
    flex: none; padding: 2px 8px; border-radius: 999px;
    background: var(--surface-2); font-size: 11px; white-space: nowrap;
  }
  .fc-solo-mould { margin-inline-end: 8px; }
  .fc-sku .xw-color { max-inline-size: 100%; }
  .fc-print, .fc-tbl tr.is-solo .fc-print { display: block; padding: 0; margin: 2px 0 0; font-weight: 400; }
  .fc-act { font-size: 11px; padding: 2px 8px; }
  .fc-order .ol-icon { inline-size: 32px; block-size: 32px; }
}
@media (max-width: 760px) {
  .fc-tbl tbody.fc-group { grid-template-columns: minmax(0, 1fr); padding: 10px; }
}
@media (max-width: 380px) {
  .fc-tbl tr.fc-row { grid-template-areas: "sku sku sku" "act act act" "demand demand demand" "cover cover cover" "fc acc build" "money money money"; }
  .fc-tbl .fc-c-act { justify-self: start; margin-top: 6px; }
}
/* One grid for the whole section, so single-colour moulds pack two to a
   row on an iPad instead of each sitting alone in its own group. */
@media (max-width: 1100px) {
  .fc-tbl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-gap: 10px; gap: 10px; padding: 12px 14px; box-sizing: border-box; }
  .fc-tbl tbody.fc-group { display: contents; }
  .fc-tbl .fc-group-head { margin-top: 6px; }
  .fc-tbl .fc-group:first-of-type .fc-group-head { margin-top: 0; }
}
@media (max-width: 760px) {
  .fc-tbl { grid-template-columns: minmax(0, 1fr); padding: 10px; }
}

/* === Phones and tablets: cards, timelines, scroll hints (v2.9.13b) ======
   .tbl-cards - a list table that becomes one card per row on a phone:
   the row's first cell (.c-title) is the heading, .c-end sits beside it
   (a status chip), every other cell shows its own label (data-label)
   above its value in a three-column grid, .c-wide takes a full row and
   .c-hide-sm drops out (it repeats what the title already says).
   No sideways scrolling, nothing sticky.
   ===================================================================== */
@media (max-width: 760px) {
  .tbl-cards, .tbl-cards tbody, .tbl-cards tfoot, .tbl-cards tr, .tbl-cards td { display: block; }
  .tbl-cards { min-inline-size: 0; inline-size: 100%; }
  .tbl-card .tbl-cards { min-width: 0; }
  .tbl-cards thead {
    position: absolute; top: 0; left: 0; inline-size: 1px; block-size: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .tbl-cards tbody tr, .tbl-cards tfoot tr {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-gap: 10px 12px; gap: 10px 12px;
    padding: 14px 16px; border-top: 0.5px solid var(--line);
  }
  .tbl-cards tbody tr:first-child { border-top: 0; }
  .tbl-card .tbl-cards td,
  .tbl-card .tbl-cards td:first-child,
  .tbl-card .tbl-cards td:last-child,
  .tbl-cards tbody tr:nth-child(odd) td,
  .tbl-cards tbody tr:hover td {
    padding: 0; border: 0; background: transparent; text-align: start; min-inline-size: 0;
    white-space: normal;
  }
  .tbl-cards td[data-label]::before {
    content: attr(data-label); display: block; margin-bottom: 2px;
    font-family: var(--sans); font-size: 10.5px; font-weight: 500; letter-spacing: 0;
    color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  [lang="fa"] .tbl-cards td[data-label]::before { font-family: var(--sans-fa); }
  .tbl-cards .c-title { grid-column: 1 / 3; -webkit-order: -2; order: -2; font-size: 15px; font-weight: 650; align-self: center; }
  .tbl-cards .c-end { grid-column: 3; -webkit-order: -1; order: -1; justify-self: end; align-self: center; text-align: end; }
  .tbl-card .tbl-cards td.c-end { text-align: end; }
  .tbl-cards .c-wide, .tbl-cards td[colspan] { grid-column: 1 / -1; }
  .tbl-cards .c-wide2 { grid-column: span 2; }
  .tbl-cards .c-hide-sm { display: none; }
  .tbl-cards .c-link { padding-top: 2px; }
  .tbl-cards tfoot tr td:not([data-label]) { grid-column: 1 / -1; }
  .tbl-cards tfoot td[colspan="4"]:empty, .tbl-cards tfoot td:empty { display: none; }
  .tbl-cards .ord-prog, .tbl-cards .ord-prog .ol-prog-track { min-inline-size: 0; max-inline-size: none; }
}

/* --- the plan's campaigns on a phone: one list per press ---------------
   A 45-day timeline cannot be read 360px wide, and a pinned machine
   column over a sideways-scrolling grid overlaps in WebKit. Below 760px
   each press is a heading with its campaigns stacked under it, each
   with its own dates. */
.sched-when { display: none; }
@media (max-width: 760px) {
  .sched-wrap { overflow: visible; }
  .sched-tbl, .sched-tbl tbody, .sched-tbl tr, .sched-tbl th, .sched-tbl td { display: block; }
  .sched-tbl { width: 100%; min-width: 0; }
  .sched-tbl thead { position: absolute; top: 0; left: 0; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .sched-tbl tbody tr { padding: 10px 12px 12px; border-top: 0.5px solid var(--line); }
  .sched-tbl tbody tr:first-child { border-top: 0; }
  .sched-tbl th, .sched-tbl td { border: 0; }
  .sched-tbl .sched-machine-col,
  [dir="rtl"] .sched-tbl .sched-machine-col {
    position: static; width: auto; min-width: 0; box-shadow: none; background: transparent;
    padding: 2px 0 8px; display: flex; align-items: baseline; flex-wrap: wrap;
  }
  .sched-tbl .sched-mc-code { font-size: 15px; margin-inline-end: 8px; }
  .sched-tbl .sched-mc-name { display: inline; margin: 0 8px 0 0; }
  [dir="rtl"] .sched-tbl .sched-mc-name { margin: 0 0 0 8px; }
  .sched-tbl .sched-mc-load { display: flex; margin: 0; margin-inline-start: auto; }
  .sched-tbl .sched-cell { padding: 0 0 8px; }
  .sched-tbl .sched-bar { min-block-size: 0; }
  .sched-tbl .sched-idle { min-block-size: 0; padding: 8px 10px; justify-content: flex-start; }
  .sched-when { display: block; font-size: 11px; color: var(--text-2); margin-top: 2px; }
  .sched-idle .sched-when { display: inline; margin: 0 0 0 8px; }
  [dir="rtl"] .sched-idle .sched-when { margin: 0 8px 0 0; }
  .sched-bar-orders { white-space: normal; }
  .sp-timeline .scroll-hint { display: none; }
}

/* --- sticky first columns that must scroll (tablets) -------------------
   WebKit can paint later cells - an inline SVG especially - over a
   sticky table cell. Giving the sticky cell its own compositing layer and
   an opaque background keeps it on top. */
.sched-machine-col, .cov-tbl th:first-child, .cov-tbl td:first-child {
  -webkit-transform: translateZ(0); transform: translateZ(0);
}
@media (max-width: 1100px) {
  .cov-tbl th:first-child, .cov-tbl td:first-child {
    position: -webkit-sticky; position: sticky; left: 0; z-index: 3;
    background: var(--surface-2); box-shadow: 1px 0 0 var(--line);
  }
  .cov-tbl thead th:first-child { background: var(--well); }
  [dir="rtl"] .cov-tbl th:first-child, [dir="rtl"] .cov-tbl td:first-child { left: auto; right: 0; box-shadow: -1px 0 0 var(--line); }
}

/* --- a visible hint where a table still scrolls sideways ---------------
   Shadows at whichever edge has more to see: the cover gradients scroll
   with the content (local), the shadows stay put (scroll), so a shadow
   shows only while there is something beyond it. */
.scroll-hint { display: none; margin: 0 0 6px; font-size: 11.5px; color: var(--text-3); }
.scroll-hint-arrow { display: inline-block; }
[dir="rtl"] .scroll-hint-arrow { transform: scaleX(-1); }
@media (max-width: 1100px) {
  .scroll-hint { display: block; }
  .sched-wrap, .sp-tbl-wrap, .od-tbl-scroll {
    background:
      linear-gradient(to right, var(--surface-2) 30%, rgba(0, 0, 0, 0)) left center / 28px 100% no-repeat local,
      linear-gradient(to left, var(--surface-2) 30%, rgba(0, 0, 0, 0)) right center / 28px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0)) left center / 12px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0)) right center / 12px 100% no-repeat scroll;
    background-color: var(--surface-2);
  }
}
/* The fulfilment figure line can carry stock / ready / short notes; let
   it wrap instead of pushing the card or the column wider. */
.ol-prog-fig { white-space: normal; line-height: 1.6; }
.ol-prog-fig .chip { white-space: nowrap; }
@media (max-width: 720px) { .ol-table td.ol-prog { overflow: hidden; } }
/* Order pages: units ready in stock are striped, so "on the shelf" reads
   apart from "already shipped" (solid) at a glance. */
.od-kpis .cov-seg.is-from_stock, .ol-table .cov-seg.is-from_stock, .ord-list .cov-seg.is-from_stock {
  background: repeating-linear-gradient(135deg, var(--good) 0, var(--good) 3px, rgba(var(--good-rgb), 0.35) 3px, rgba(var(--good-rgb), 0.35) 6px);
}
.od-kpi.is-hero .cov-seg.is-from_stock { background: repeating-linear-gradient(135deg, var(--hero-ink) 0, var(--hero-ink) 3px, rgba(255, 255, 255, 0.35) 3px, rgba(255, 255, 255, 0.35) 6px); }
.od-kpi.is-hero .cov-seg.is-shipped { background: var(--hero-ink); }
.od-cov { max-inline-size: none; margin-top: 12px; background: rgba(255, 255, 255, 0.24); }
.ol-table .cov-bar, .ord-list .cov-bar { max-inline-size: none; }
/* ...so "short" is solid there, not a second stripe. */
.ol-table .cov-seg.is-short, .ord-list .cov-seg.is-short { background: rgba(var(--bad-rgb), 0.55); }
.od-kpi.is-hero .cov-seg.is-short { background: transparent; }
.od-kpi.is-hero .cov-seg.is-scheduled { background: rgba(255, 255, 255, 0.6); }

/* --- no access: another section's widget the user may not see ---------
   apps.core.permissions.can_view / templates/partials/_no_access.html.
   Soft and quiet on purpose: it is a fact about permissions, not an
   error. Nothing is drawn underneath - the data was never sent. */
.xw-noaccess { display: flex; flex-direction: column; gap: 8px; min-block-size: 110px; }
.xw-noaccess.kpi-tile { min-block-size: 0; }
.xw-noaccess-title { font-size: 12px; font-weight: 600; color: var(--text-2); }
.xw-noaccess-body {
  flex: 1 1 auto;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px 10px; text-align: center;
  border: 1px dashed var(--line-2); border-radius: var(--radius);
  color: var(--text-3);
}
.xw-noaccess-msg { margin: 0; font-size: 13px; color: var(--text-2); line-height: 1.45; }
.xw-noaccess-icon { flex: 0 0 auto; color: var(--text-3); }
.xw-noaccess-inline {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; color: var(--text-3); white-space: nowrap;
}

/* Support session banner (multi-company P2b). Red, fixed to the top of the
   page and never dismissible: someone from the platform is looking in. */
.support-banner {
  position: sticky; top: 0; z-index: 1000;
  background: #9b1c1c; color: #fff; font-size: 13px;
  padding: 7px 16px; text-align: center;
}
.support-banner strong { letter-spacing: 0.5px; text-transform: uppercase; }
.support-banner-end { display: inline; margin-inline-start: 12px; }
.support-banner-end button {
  background: #fff; color: #9b1c1c; border: 0; border-radius: 4px;
  padding: 2px 10px; font: inherit; font-weight: 600; cursor: pointer;
}

/* Two-factor sign-in step (company app, P2b add-ons). One card, centred;
   the QR is black on white whatever the theme so every camera reads it. */
.login-shell-single { display: flex; justify-content: center; }
.login-shell-single .login-card { max-width: 440px; width: 100%; }
.twofactor-steps { padding-inline-start: 20px; line-height: 1.6; }
.twofactor-qr { margin: 8px 0; }
.twofactor-qr svg { display: block; width: 190px; height: auto; border-radius: 6px; }
.twofactor-key { font-size: 18px; letter-spacing: 2px; }
.twofactor-copy { display: flex; align-items: stretch; max-width: 380px; margin: 4px 0 6px; }
.twofactor-copy-field {
  flex: 1 1 auto; min-width: 0; min-height: 44px; box-sizing: border-box; padding: 0 12px;
  font-size: 15px; letter-spacing: 0.06em; direction: ltr; text-align: left;
}
.twofactor-copy .btn { flex: 0 0 auto; margin-inline-start: 8px; }
.twofactor-copy .btn.is-done { color: var(--good); border-color: var(--good); }
.twofactor-hint { font-size: 13px; color: var(--text-3); }
