/*
 * LOCALISATION / RTL FOUNDATION
 * ─────────────────────────────────────────────────────────────────────────────
 *
 * Phase 03. This stylesheet carries ONLY what localisation needs: direction,
 * typography, the currency glyph, bidi value classes and the accessibility
 * primitives the markup depends on. It is deliberately not a design system —
 * visual design belongs to the UI phases.
 *
 * IT IS WRITTEN IN CSS LOGICAL PROPERTIES THROUGHOUT. `margin-inline-start`,
 * not `margin-left`. That is the whole RTL strategy in one sentence: a logical
 * property flips with `dir` automatically, so there is no second RTL stylesheet
 * to keep in sync and no `[dir="rtl"]` override that someone forgets to add.
 * The Phase 03 brief warns against exactly the failure this avoids — "Do not
 * simply apply direction: rtl; and consider the job complete."
 *
 * NO BUILD STEP. Served directly from /public. Phase 03 produces no JavaScript
 * and no component library, so a bundler would add a dependency that every later
 * verification would have to keep alive for no benefit yet. `resources/css/app.css`
 * (Tailwind) is left in place, unwired, for the UI phase that needs it.
 */

/* ═══════════════════════════════════════════════════════════════════════════
   THE ARABIC TYPEFACE — Noto Sans Arabic, self-hosted
   ═══════════════════════════════════════════════════════════════════════════

   Chosen after verifying six free Arabic families against this application's
   actual requirements. What decided it was not taste, it was the digits.

   This is a point of sale. Money stands in columns, and a column only reads as
   a column when every digit is the same width. Noto Sans Arabic is the only
   family of the six whose figures are uniform-width in BOTH numeral systems —
   every Western digit and every Arabic-Indic digit carries an advance of
   572/1000 — so an invoice register aligns whichever numerals the tenant uses,
   with no feature flag and nothing for a print path to ignore. Cairo, IBM Plex
   Sans Arabic, Tajawal and Zain all have proportional Arabic-Indic digits;
   Tajawal's Western digits are proportional too, spanning 17.8% of an em,
   which is a broken money column by construction.

   The other decisive fact is weight. This UI asks for 800 in twenty-odd
   places. IBM Plex Sans Arabic — the better-drawn face by most accounts, and
   the one Arabic typographers name first — simply has no 800 and no axis to
   reach one, and a renderer asked for a weight a family does not have will
   smear the Arabic joins to fake it. Noto carries 400, 600, 700 and 800 as
   real drawn masters on a single variable axis.

   It is also the Arabic face of Android and ChromeOS and Chromium's Arabic
   fallback, so it is what a Saudi operator already reads all day, and it is
   the safest thing to degrade to if a file ever fails to load.

   ⚠ SELF-HOSTED, BECAUSE THE POLICY LEAVES NO CHOICE. `SecurityHeaders`
   declares `font-src 'self'`, so a face fetched from any other origin is
   blocked outright — not degraded, blocked. Both files sit under /public and
   are served by the same origin as the page. No build step, no CDN, no
   external request, and the policy is untouched.

   ⚠ TWO FAMILIES RATHER THAN ONE, DELIBERATELY. Google publishes this face as
   five files split by codepoint block, bound together with a CSS keyword this
   stylesheet is FORBIDDEN to contain: `SaudiRiyalSignTest` reads the raw file
   and fails if that keyword appears anywhere in it, including inside a comment
   — a ratchet left behind by the undelivered vendor glyph font. Declaring both
   subsets under one family name would be worse than merely forbidden, it would
   be wrong: with nothing to arbitrate between them the later face would win
   outright and the earlier one would never render at all.

   So each subset keeps its own name and the CASCADE does the arbitration,
   which is what font stacks have always been for. A browser resolves a stack
   per CHARACTER, not per element: an Arabic run finds its glyphs in the first
   family, and the English product name on the same line falls through to the
   second — same typeface, same metrics, same design. This is precisely the
   arrangement the tokens below already described.
   ═══════════════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "Noto Sans Arabic";
  font-style: normal;
  /* One variable file spans the whole range, so 400, 600, 700 and 800 are all
     drawn instances rather than four downloads or four synthetic weights. */
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/noto-sans-arabic-arabic.woff2") format("woff2");
}

@font-face {
  font-family: "Noto Sans";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/noto-sans-arabic-latin.woff2") format("woff2");
}

/* ─────────────────────────────────────────────── design tokens */

:root {
  /*
   * ARABIC TYPOGRAPHY.
   *
   * ⚠ THIS WAS A SYSTEM STACK AND IS NOW A BUNDLED FACE. The reason it was a
   * system stack is still true and worth keeping on the record: no specific
   * Arabic font is evidenced anywhere in the recovered corpus, so nothing here
   * claims to reproduce the audited product's typography. It is a choice, made
   * on the merits set out above the faces, at the owner's request.
   *
   * Segoe UI stays in the stack behind the bundled face, and Tahoma behind it,
   * because the deployment target is a Windows counter terminal and those are
   * what it falls back to if a font file is ever missing. The stack still ends
   * where it always ended.
   *
   * ORDER MATTERS AND IS NOT ALPHABETICAL. Each stack leads with the script it
   * is for and carries the other immediately behind it, because a browser
   * resolves a stack per character: an Arabic label and an English product
   * name on the same line each find the right file, in the same typeface.
   */
  --font-arabic: "Noto Sans Arabic", "Noto Sans", "Segoe UI", "Noto Naskh Arabic", "Geeza Pro", Tahoma, "Arial", sans-serif;
  --font-latin: "Noto Sans", "Noto Sans Arabic", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;

  /*
   * Tabular figures keep money columns aligned regardless of digit widths.
   *
   * With the bundled face this token is belt AND braces rather than the belt:
   * every digit in Noto Sans Arabic is already 572/1000 in both numeral
   * systems, so alignment no longer depends on a font feature being honoured.
   */
  --font-numeric: "Noto Sans", "Noto Sans Arabic", "Segoe UI", Consolas, "SF Mono", Menlo, monospace;

  --ink: #1a1d21;
  --ink-muted: #5b6470;
  --surface: #ffffff;
  --surface-sunken: #f4f6f8;
  --line: #dfe3e8;
  --accent: #0f7b6c;
  --danger: #b3261e;
  --warning: #8a6100;
  --success: #1b6e3c;

  --radius: 6px;
  --gap: 1rem;
}

/* ─────────────────────────────────────────────── document */

html {
  font-family: var(--font-latin);
  color: var(--ink);
  background: var(--surface-sunken);
}

/*
 * The Arabic face is selected by the LANG attribute, not by direction.
 * They are different questions: an Arabic string can appear inside an LTR page,
 * and a Latin string inside an RTL one. Keying on `lang` means each RUN of text
 * gets the right face even when the two are mixed in one line.
 */
html[lang|="ar"],
[lang|="ar"] {
  font-family: var(--font-arabic);
}

html[lang|="en"],
[lang|="en"] {
  font-family: var(--font-latin);
}

body {
  margin: 0;
  min-height: 100vh;
  line-height: 1.6;
  background: var(--surface-sunken);
}

/* ─────────────────────────────────────────────── accessibility primitives */

/*
 * sr-only — visible to a screen reader, not to the eye.
 *
 * This is load-bearing, not decoration: MoneyPresenter renders the SAR glyph
 * `aria-hidden` and puts the readable `ر.س` in an sr-only span beside it. With
 * no rule for this class BOTH would be visible and every amount would read
 * "150.00 [glyph] ر.س" — the currency named twice.
 */
.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;
}

/* A skip link must become visible when it takes focus, or it helps nobody. */
.skip-link {
  position: absolute;
  inset-inline-start: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--accent);
  color: #fff;
  text-decoration: none;
  border-end-end-radius: var(--radius);
}

.skip-link:focus {
  inset-inline-start: 0;
}

/*
 * A visible focus ring on every interactive element. The audited product's
 * accessibility was recorded as near-absent; removing focus outlines is the
 * single most common way a keyboard user is locked out, so it is stated here
 * rather than left to a later stylesheet to preserve.
 */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ─────────────────────────────────────────────── direction-aware values */

/*
 * Values that must not be reordered by the bidi algorithm — see App\Support\
 * Format\Bidi for what goes wrong without this. `<bdi>` already isolates; the
 * explicit `dir` and `unicode-bidi` make the intent visible and survive a
 * browser that treats bdi loosely.
 */
bdi.ltr-value,
.ltr-value {
  direction: ltr;
  unicode-bidi: isolate;
}

bdi.rtl-value,
.rtl-value {
  direction: rtl;
  unicode-bidi: isolate;
}

bdi.auto-value {
  unicode-bidi: isolate;
}

/* Numbers, money and dates share one numeric treatment. */
.money,
.ltr-value,
time,
.tabular {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.money {
  direction: ltr;
  unicode-bidi: isolate;
  white-space: nowrap;
}

/*
 * THE OFFICIAL SAUDI RIYAL SIGN.
 *
 * Drawn from SAMA's own published SVG, referenced once per amount out of a
 * sprite defined in the layout. See `App\Support\Format\SaudiRiyalSign`.
 *
 * There is no @font-face here any more, and that is the point. The previous
 * implementation pointed at U+E900 in a vendor icon font that was never
 * delivered, so the sign was never drawn at all and every amount fell back to
 * the `ر.س` text mark. Artwork carries its own geometry and cannot fail that
 * way — it needs no font on the host, in any browser, on any printer.
 *
 * Sized in `em` so it tracks the surrounding type, and filled with
 * `currentColor` so it inherits colour from the text it sits in — including
 * inverted headers, the thermal receipt, and print.
 */
/*
 * The sizing follows SAMA's own Usage Rules:
 *
 *   Alignment    "The height of the symbol should be aligned with the height
 *                of the text" — hence `height: 1em`, and a width that keeps
 *                SAMA's 1124.14 : 1256.39 aspect ratio (0.895) exactly.
 *   Proportions  "The shape of the symbol should be maintained" — the width is
 *                derived from that ratio, never rounded to a convenient value,
 *                and nothing here scales one axis independently.
 *   Spacing      "A space is required between the symbol and the numeral" —
 *                that space is the `&nbsp;` the presenter emits, so it survives
 *                copy-and-paste out of the page. It is deliberately NOT ALSO a
 *                margin here: doubling the gap loosens every figure in a dense
 *                register and risks wrapping the 80mm thermal totals column.
 *   NegativeSpace "A clear space equal to one third of the symbol's height is
 *                required around the shapes" — the space above, and the line
 *                box, which at `height: 1em` leaves the sign uncrowded.
 *   Contrast     `currentColor` inherits the colour of the text it sits in, so
 *                the contrast is whatever the surrounding type already has.
 */
.sar {
  height: 1em;
  width: 0.895em;
  fill: currentColor;
  vertical-align: -0.12em;
  flex: none;
}

/* ─────────────────────────────────────────────── layout chrome */

.app-shell {
  display: grid;
  grid-template-columns: 260px 1fr;
  min-height: 100vh;
}

/*
 * The sidebar occupies the RTL-LEADING edge — the specification's own wording.
 * `grid-template-columns` places it first in SOURCE order, and the grid's
 * inline axis follows `dir`, so it lands on the right in Arabic and the left in
 * English with no override.
 */
.app-sidebar {
  background: var(--surface);
  border-inline-end: 1px solid var(--line);
  padding: var(--gap);
}

.app-main {
  padding: calc(var(--gap) * 1.5);
  min-width: 0; /* lets long LTR values scroll rather than blow out the grid */
}

.app-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.app-nav a {
  display: block;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  color: var(--ink);
  text-decoration: none;
  /* Logical: indents from the reading edge in both directions. */
  padding-inline-start: 0.75rem;
}

.app-nav a:hover {
  background: var(--surface-sunken);
}

.app-nav a[aria-current="page"] {
  background: var(--surface-sunken);
  font-weight: 600;
  border-inline-start: 3px solid var(--accent);
}

/* ─────────────────────────────────────────────── directional icons */

/*
 * Icons whose MEANING is directional — "back", "next", chevrons — must mirror.
 * Icons whose meaning is not — a clock, a printer — must NOT. Marking the
 * mirroring ones explicitly is what keeps the second group from being flipped
 * by a blanket rule.
 */
[dir="rtl"] .icon-directional {
  transform: scaleX(-1);
}

/* ─────────────────────────────────────────────── forms */

.field {
  margin-block-end: var(--gap);
}

.field label {
  display: block;
  margin-block-end: 0.25rem;
  font-weight: 600;
}

.field .required-mark {
  color: var(--danger);
  margin-inline-start: 0.25rem;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
  /* Inherits the document direction; overridden per-input for LTR values. */
  background: var(--surface);
}

/*
 * An input holding an LTR value — phone, email, a document number — is set LTR
 * with start-aligned text, so the caret behaves as the user expects while typing
 * a number inside an otherwise RTL form.
 */
.field input[dir="ltr"] {
  text-align: start;
}

.field .error {
  display: block;
  margin-block-start: 0.25rem;
  color: var(--danger);
  font-size: 0.875rem;
}

.field input[aria-invalid="true"],
.field select[aria-invalid="true"] {
  border-color: var(--danger);
}

/* ─────────────────────────────────────────────── tables */

.data-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
}

/*
 * `text-align: start` — not `right`. The header and cells follow the document
 * direction, so one rule serves both locales.
 */
.data-table th,
.data-table td {
  text-align: start;
  padding: 0.6rem 0.75rem;
  border-block-end: 1px solid var(--line);
}

.data-table th {
  background: var(--surface-sunken);
  font-weight: 600;
}

/* Numeric columns align to the END edge in both directions. */
.data-table .numeric {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

/* Wide tables scroll inside their own container rather than the page. */
.table-scroll {
  overflow-x: auto;
}

/* ─────────────────────────────────────────────── toast */

/*
 * The global toast. The roadmap's localisation phase names it and its ARIA
 * explicitly: `success`/`error`/`info` with `role="status"` / `role="alert"` —
 * and records that "the current product has no ARIA on it".
 *
 * Positioned on the LEADING edge via a logical property, so it appears
 * top-right in Arabic and top-left in English, matching the reading start.
 */
.toast-region {
  position: fixed;
  top: 1rem;
  inset-inline-start: 1rem;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.toast {
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  background: var(--surface);
  border-inline-start: 4px solid var(--line);
  box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
  max-width: 22rem;
}

.toast--success { border-inline-start-color: var(--success); }
.toast--error   { border-inline-start-color: var(--danger); }
.toast--info    { border-inline-start-color: var(--accent); }
.toast--warning { border-inline-start-color: var(--warning); }

/* ─────────────────────────────────────────────── misc */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--gap);
  margin-block-end: var(--gap);
}

.muted { color: var(--ink-muted); }

.definition-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.4rem 1rem;
  margin: 0;
}

.definition-list dt { font-weight: 600; color: var(--ink-muted); }
.definition-list dd { margin: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 10 — the global sidebar (spec §1.1)

   Phase 03 built `.app-shell`, `.app-sidebar` and `.app-nav`; this adds the
   `gsb-*` layer the recovered specification names, including the bespoke
   two-stop gradient each entry carries. The gradient values arrive as
   `--gsb-from` / `--gsb-to` custom properties from the template, so the data
   lives in the registry and the paint lives here.
   ═══════════════════════════════════════════════════════════════════════════ */

.gsb {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.gsb-toggle {
  align-self: flex-start;
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 0.4rem 0.6rem;
}

.gsb-section {
  color: var(--ink-muted, #6b7280);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin: var(--gap) 0 0.35rem;
  text-transform: uppercase;
}

.gsb-item {
  align-items: center;
  display: flex;
  gap: 0.55rem;
}

/*
 * The per-entry gradient. Rendered as a small leading swatch rather than as the
 * row's background: the recovered design puts a coloured icon tile beside each
 * label, and a full-bleed gradient behind Arabic text at these hues would fail
 * contrast on at least half the twenty-one.
 */
.gsb-swatch {
  background-image: linear-gradient(135deg, var(--gsb-from, #6366f1), var(--gsb-to, #4338ca));
  border-radius: 6px;
  flex: 0 0 auto;
  height: 1.25rem;
  width: 1.25rem;
}

.gsb-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The logout control sits at the end of the rail, away from navigation, so it
   is never the neighbour of a mis-tap. Phase 10 Additional DoD. */
.gsb-logout {
  border-top: 1px solid var(--line);
  margin-top: auto;
  padding-top: var(--gap);
}

.gsb-item--logout {
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
  font: inherit;
  padding: 0.5rem 0.75rem;
  text-align: start;
  width: 100%;
}

.gsb-item--logout:hover {
  background: var(--surface-sunken);
}

/*
 * COLLAPSED. The state is persisted in `localStorage.sidebar_collapsed` as
 * '1'/'0' (spec §1.1) and applied as an attribute on the shell, so the grid
 * column narrows and the labels drop out while the swatches stay as an icon
 * rail — collapsing to nothing would remove the navigation rather than
 * condense it.
 */
.app-shell[data-sidebar-collapsed='1'] {
  grid-template-columns: 4rem 1fr;
}

.app-shell[data-sidebar-collapsed='1'] .gsb-label,
.app-shell[data-sidebar-collapsed='1'] .gsb-section {
  display: none;
}

/* ─────────────────────────────────────────────── shared modal patterns (§1.7) */

.modal-overlay {
  align-items: center;
  background: rgb(15 23 42 / 55%);
  display: flex;
  inset: 0;
  justify-content: center;
  padding: var(--gap);
  position: fixed;
  z-index: 50;
}

.modal {
  background: var(--surface);
  border-radius: var(--radius);
  max-height: 90vh;
  max-width: 32rem;
  overflow: auto;
  padding: calc(var(--gap) * 1.5);
  width: 100%;
}

.modal-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: var(--gap);
}

/* Danger styling is deliberately distinct from the ordinary confirm: §1.7
   records `btn-modal-danger` as its own pattern for destructive actions. */
.btn-danger,
.btn-modal-danger {
  background: #dc2626;
  border: 0;
  border-radius: var(--radius);
  color: #fff;
  cursor: pointer;
  padding: 0.5rem 0.9rem;
}

/* ─────────────────────────────────── responsive: the rail folds under 60rem */

@media (max-width: 60rem) {
  .app-shell {
    grid-template-columns: 1fr;
  }

  .app-sidebar {
    border-inline-end: 0;
    border-block-end: 1px solid var(--line);
  }

  .gsb-logout {
    margin-top: var(--gap);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHASE 11 — SCR-002, the dashboard
   ══════════════════════════════════════════════════════════════════════════

   Scoped to this screen. STEP 17 of the phase brief is explicit that DF-10-06
   (the shared components Phase 10 left without styling) "must NOT be solved
   globally … do not create an unofficial Phase 10.5 or Phase 11.5", so nothing
   below touches a class this screen does not use.

   LOGICAL PROPERTIES THROUGHOUT — `inline-start`, `inline-end`, `margin-inline`
   — as Phase 03 established. There is not one `left` or `right` here, which is
   what lets the same rules serve both directions without a mirrored stylesheet.
*/

.dash-header {
  align-items: center;
  display: flex;
  gap: var(--gap);
  margin-block-end: calc(var(--gap) * 1.5);
}

/*
   THE BRAND SITS AT THE END EDGE.

   In the capture the user controls (greeting · EN · logout) sit at the
   RTL-leading edge beside the sidebar, and the brand sits at the far edge.
   `margin-inline-start: auto` expresses exactly that and stays correct in
   English, where both ends swap by themselves.
*/
.dash-brand {
  align-items: center;
  color: var(--ink);
  display: flex;
  font-weight: 600;
  gap: 0.5rem;
  margin: 0;
  margin-inline-start: auto;
}

.dash-brand-mark,
.dash-greeting-avatar,
.lang-btn-globe {
  background: var(--line);
  border-radius: 50%;
  display: inline-block;
  block-size: 1.5rem;
  inline-size: 1.5rem;
}

/*
   The greeting chip. Pill-shaped, as captured.

   The text inside is the recovered `مرحباً، المدير` — see the note in
   dashboard.blade.php about why it is not personalised.
*/
.dash-greeting {
  align-items: center;
  background: var(--surface-2, #eef2ff);
  border-radius: 999px;
  color: var(--ink);
  display: flex;
  gap: 0.5rem;
  margin: 0;
  padding: 0.35rem 0.75rem;
}

.lang-btn {
  align-items: center;
  background: var(--surface-2, #eef2ff);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
  cursor: pointer;
  display: flex;
  font: inherit;
  gap: 0.5rem;
  padding: 0.35rem 0.75rem;
}

.lang-btn:hover {
  border-color: var(--accent, #6366f1);
}

/* ─────────────────────────────────── the grid

   `auto-fill` with a floor rather than a fixed 5 columns: the capture is five
   across at that viewport, and hard-coding five would force a horizontal
   scrollbar on a counter terminal running a narrower screen. The tile ORDER is
   never touched — it is the captured order, and the grid only decides how many
   fit on a line.
*/
.menu-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}

.menu-card {
  align-items: center;
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgb(0 0 0 / 6%);
  color: inherit;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  block-size: 100%;
  padding: calc(var(--gap) * 1.25) var(--gap);
  text-align: center;
  text-decoration: none;
}

.menu-card:hover {
  border-color: var(--accent, #6366f1);
  box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
}

/* A tile is a link, and a keyboard user must be able to see which one they are
   on. `:focus-visible` rather than `:focus` so a mouse click does not paint a
   ring the pointer user did not ask for. */
.menu-card:focus-visible {
  outline: 2px solid var(--accent, #6366f1);
  outline-offset: 2px;
}

.menu-card-icon {
  background: var(--line);
  border-radius: var(--radius);
  block-size: 3rem;
  inline-size: 3rem;
  margin-block-end: 0.5rem;
}

.menu-card-title {
  font-size: 1.05rem;
  font-weight: 700;
}

.menu-card-desc {
  color: var(--ink-muted, #6b7280);
  font-size: 0.85rem;
  line-height: 1.5;
}

/* ─────────────────────────────────── the empty state

   Centred and quiet. It is not an error, and it must not look like one — no
   red, no icon, no border. A cashier whose account is scoped to nothing has
   not done anything wrong.
*/
.dashboard-empty {
  margin-inline: auto;
  max-inline-size: 32rem;
  padding-block: calc(var(--gap) * 3);
  text-align: center;
}

.dashboard-empty-title {
  font-size: 1.15rem;
  margin: 0 0 0.5rem;
}

.dashboard-empty-body {
  color: var(--ink-muted, #6b7280);
  line-height: 1.7;
  margin: 0;
}

@media (max-width: 40rem) {
  .dash-header {
    flex-wrap: wrap;
  }

  .dash-brand {
    margin-inline-start: 0;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHASE 12 — SCR-021, the settings screen
   ══════════════════════════════════════════════════════════════════════════

   Scoped to this screen's own classes. Logical properties throughout, as
   Phase 03 established, so the rail sits on the RTL-leading edge in Arabic and
   the LTR-leading edge in English without a mirrored stylesheet.
*/

.settings-layout {
  display: grid;
  gap: var(--gap);
  grid-template-columns: 14rem 1fr;
}

/* ─────────────────────────────────── the tab rail */

.settings-tabs {
  border-inline-end: 1px solid var(--line);
  padding-inline-end: var(--gap);
}

.settings-tabs-heading {
  color: var(--ink-muted, #6b7280);
  font-size: 0.8rem;
  margin: 0 0 0.5rem;
}

.settings-tabs ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.settings-tab {
  border-radius: var(--radius);
  color: inherit;
  display: block;
  padding: 0.55rem 0.75rem;
  text-decoration: none;
}

.settings-tab:hover {
  background: var(--surface-2, #eef2ff);
}

/* The active panel. `aria-current` rather than a class: the attribute is the
   accessible state, so styling it directly keeps the two from drifting. */
.settings-tab[aria-current="page"] {
  background: var(--surface-2, #eef2ff);
  color: var(--accent, #4338ca);
  font-weight: 700;
}

.settings-tab:focus-visible {
  outline: 2px solid var(--accent, #6366f1);
  outline-offset: 2px;
}

/* ─────────────────────────────────── the cards */

.settings-panels {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  min-inline-size: 0;   /* lets long values wrap instead of stretching the grid */
}

.settings-form {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.settings-card {
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--gap);
}

.settings-card-head {
  border-block-end: 1px solid var(--line);
  margin-block-end: var(--gap);
  padding-block-end: 0.6rem;
}

.settings-card-head h2 {
  font-size: 1rem;
  margin: 0;
}

.settings-card-head p {
  color: var(--ink-muted, #6b7280);
  font-size: 0.82rem;
  margin: 0.15rem 0 0;
}

.settings-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.settings-grid--three {
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}

.field--full {
  grid-column: 1 / -1;
}

.settings-hint {
  color: var(--ink-muted, #6b7280);
  font-size: 0.78rem;
  line-height: 1.6;
  margin: 0.35rem 0 0;
}

/* ─────────────────────────────────── the logo card */

.settings-logo {
  align-items: flex-start;
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
}

.settings-logo-preview {
  align-items: center;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  display: flex;
  justify-content: center;
  min-block-size: 6rem;
  inline-size: 8rem;
  padding: 0.5rem;
}

.settings-logo-preview img {
  max-block-size: 5rem;
  max-inline-size: 100%;
}

.settings-logo-empty {
  color: var(--ink-muted, #6b7280);
  font-size: 0.78rem;
  margin: 0;
  text-align: center;
}

.settings-logo-actions {
  display: flex;
  flex: 1 1 18rem;
  flex-direction: column;
  gap: 0.6rem;
}

.settings-logo-actions form {
  align-items: center;
  display: flex;
  gap: 0.5rem;
}

/* ─────────────────────────────────── buttons

   `btn-save` is the sticky action each panel carries. The capture shows it
   pinned at the panel's foot on the LTR-trailing edge; `align-self` keeps it
   there in both directions without a hard-coded side.
*/
.btn-save {
  align-self: flex-start;
  background: var(--accent, #4338ca);
  border: 0;
  border-radius: var(--radius);
  color: #fff;
  cursor: pointer;
  font: inherit;
  padding: 0.55rem 1.25rem;
}

.btn-outline {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: inherit;
  cursor: pointer;
  font: inherit;
  padding: 0.45rem 0.9rem;
}

.btn-danger-outline {
  background: transparent;
  border: 1px solid #dc2626;
  border-radius: var(--radius);
  color: #dc2626;
  cursor: pointer;
  font: inherit;
  padding: 0.45rem 0.9rem;
}

@media (max-width: 55rem) {
  .settings-layout {
    grid-template-columns: 1fr;
  }

  .settings-tabs {
    border-inline-end: 0;
    border-block-end: 1px solid var(--line);
    padding-inline-end: 0;
    padding-block-end: var(--gap);
  }

  .settings-tabs ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   UI RECONSTRUCTION — PHASE B FOUNDATION

   Appended, not rewritten. Everything above this line is Phase 03's
   localisation layer and the rules later phases added to it; it is left byte
   for byte alone, because `VisualVerificationTest` reads this file and because
   those rules already work.

   ⚠ LOGICAL PROPERTIES ONLY. `VisualVerificationTest::the_stylesheet_uses_
   logical_properties_not_physical_ones` forbids `margin-left`, `margin-right`,
   `padding-left`, `padding-right`, `border-left:`, `border-right:`,
   `text-align: left` and `text-align: right` anywhere in this file. That
   ratchet is the RTL strategy, and it is why this layer uses
   `*-inline-start/end` throughout.

   ⚠ EVERY VALUE BELOW WAS SAMPLED FROM `002_Dashboard.png`, NOT CHOSEN.
   Canvas #e9eef6, card #ffffff, sidebar #ffffff, active pill #f0f1f2,
   POS pill #2fbf64; tiles 256px wide on a 20px gap, inset 36px.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* — surfaces, sampled — */
  --ui-canvas: #e9eef6;
  --ui-canvas-2: #eaeff7;
  --ui-surface: #ffffff;
  --ui-header: #fdfeff;
  --ui-active: #f0f1f2;

  /* — ink — */
  --ui-ink: #1f2937;
  --ui-ink-muted: #8b93a1;
  --ui-ink-soft: #6b7280;

  /* — line & shadow — */
  --ui-line: #e6eaf1;
  --ui-shadow-card: 0 1px 2px rgba(16, 24, 40, .04), 0 6px 16px rgba(16, 24, 40, .06);
  --ui-shadow-pop: 0 12px 32px rgba(16, 24, 40, .16);

  /* — accents, sampled — */
  --ui-green: #2fbf64;
  --ui-blue: #2563eb;
  --ui-blue-soft: #ecf1fa;
  --ui-red: #e5484d;
  --ui-amber: #f5a524;

  /* — geometry, measured — */
  --ui-radius: 16px;
  --ui-radius-sm: 10px;
  --ui-radius-pill: 999px;
  --ui-sidebar-w: 245px;
  --ui-header-h: 60px;
  --ui-tile-w: 256px;
  --ui-gap: 20px;
  --ui-inset: 36px;
}

/* ── shell ──────────────────────────────────────────────────────────────── */

body {
  background: var(--ui-canvas);
  color: var(--ui-ink);
}

.app-shell {
  display: grid;
  /* ⚠ SIDEBAR FIRST. It is the first DOM child of `.app-shell`, and in RTL
     grid column 1 is placed at the RIGHT — which is where the reference
     puts it. Writing `1fr var(--ui-sidebar-w)` gave the sidebar the free
     track and squeezed `main` to 245px; the dashboard tiles then stacked
     in a single narrow column. Caught by screenshot comparison, not by
     the pixel metric, which barely moved. */
  grid-template-columns: var(--ui-sidebar-w) 1fr;
  min-height: 100vh;
  background: var(--ui-canvas);
}

.app-main {
  min-width: 0;
  padding: var(--ui-gap) var(--ui-inset) 40px;
}

/* ── sidebar ────────────────────────────────────────────────────────────── */

.app-sidebar {
  background: var(--ui-surface);
  border-inline-start: 1px solid var(--ui-line);
  padding: 14px 0;
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  overflow-y: auto;
}

.gsb,
.gsb-group {
  list-style: none;
  margin: 0;
  padding: 0;
}

.gsb-section {
  font-size: 11px;
  font-weight: 700;
  color: var(--ui-ink-muted);
  padding: 14px 18px 6px;
  text-align: start;
}

.gsb-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 14px;
  margin: 1px 8px;
  border-radius: var(--ui-radius-sm);
  color: var(--ui-ink);
  text-decoration: none;
  font-size: 13.5px;
  font-weight: 600;
}

.gsb-item:hover { background: #f7f8fa; }

.gsb-item[aria-current="page"],
.gsb-item.is-active { background: var(--ui-active); }

.gsb-label { text-align: start; }

/* The coloured rounded-square chip that carries each module's icon. */
.gsb-swatch {
  inline-size: 34px;
  block-size: 34px;
  border-radius: var(--ui-radius-sm);
  flex: 0 0 34px;
  background: linear-gradient(145deg, #dfe3ea, #cdd4de);
  display: inline-block;
}

.gsb-toggle {
  border: 0;
  background: transparent;
  color: var(--ui-ink-soft);
  cursor: pointer;
  padding: 6px 10px;
}

.gsb-logout,
.gsb-item--logout { color: var(--ui-red); }

/* ── header ─────────────────────────────────────────────────────────────── */

.dash-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  block-size: var(--ui-header-h);
  background: var(--ui-header);
  padding: 0 var(--ui-inset) 0 20px;
  margin: calc(var(--ui-gap) * -1) calc(var(--ui-inset) * -1) var(--ui-gap);
  border-block-end: 1px solid var(--ui-line);
}

.dash-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  order: 3;
}

.dash-brand-mark {
  inline-size: 34px;
  block-size: 34px;
  border-radius: var(--ui-radius-sm);
  background: linear-gradient(145deg, #dbeafe, #bfdbfe);
  display: inline-block;
}

.dash-greeting {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  order: 1;
  background: var(--ui-blue-soft);
  border-radius: var(--ui-radius-pill);
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
}

.dash-greeting-avatar {
  inline-size: 24px;
  block-size: 24px;
  border-radius: var(--ui-radius-pill);
  background: linear-gradient(145deg, #a78bfa, #7c5cf5);
  display: inline-block;
}

.dash-lang { order: 2; margin: 0; }

.lang-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  cursor: pointer;
  background: var(--ui-blue-soft);
  color: var(--ui-blue);
  border-radius: var(--ui-radius-pill);
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 700;
}

.lang-btn-globe {
  inline-size: 22px;
  block-size: 22px;
  border-radius: var(--ui-radius-pill);
  background: linear-gradient(145deg, #60a5fa, #2563eb);
  display: inline-block;
}

/* ── dashboard tiles ────────────────────────────────────────────────────── */

.menu-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, var(--ui-tile-w));
  gap: var(--ui-gap);
  justify-content: center;
}

.menu-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
  padding: 26px 18px 22px;
  min-block-size: 176px;
  text-decoration: none;
  color: inherit;
}

.menu-card-icon {
  inline-size: 72px;
  block-size: 72px;
  border-radius: 18px;
  background: linear-gradient(145deg, #eef2f7, #dfe5ee);
  display: inline-block;
  margin-block-end: 6px;
}

.menu-card-title {
  font-size: 15px;
  font-weight: 800;
  text-align: center;
}

.menu-card-desc {
  font-size: 11.5px;
  color: var(--ui-ink-muted);
  text-align: center;
  line-height: 1.5;
}

/* ── cards, screens, tables ─────────────────────────────────────────────── */

.screen-header,
.settings-card,
.card,
.panel {
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
  padding: 18px 20px;
  margin-block-end: var(--ui-gap);
}

.data-table,
.doc-table,
.deferred-table {
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
  overflow: hidden;
}

.data-table th,
.doc-table th,
.deferred-table th {
  background: #f7f9fc;
  font-size: 12.5px;
  font-weight: 800;
  color: var(--ui-ink-soft);
  padding: 12px 14px;
  text-align: start;
}

.data-table td,
.doc-table td,
.deferred-table td {
  padding: 12px 14px;
  font-size: 13px;
  border-block-start: 1px solid var(--ui-line);
  text-align: start;
}

.data-table tbody tr:hover,
.doc-table tbody tr:hover { background: #fafbfd; }

/* ── controls ───────────────────────────────────────────────────────────── */

.btn-primary,
.btn-save {
  background: var(--ui-blue);
  color: #fff;
  border: 0;
  border-radius: var(--ui-radius-sm);
  padding: 9px 18px;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

.btn-cancel,
.btn-secondary-sm {
  background: #eef1f6;
  color: var(--ui-ink);
  border: 0;
  border-radius: var(--ui-radius-sm);
  padding: 9px 16px;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

.btn-modal-danger { background: var(--ui-red); color: #fff; }

.field-input,
.field input,
.field select,
.field textarea {
  inline-size: 100%;
  background: #fff;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-sm);
  padding: 9px 12px;
  font: inherit;
  font-size: 13.5px;
}

.field-error,
.modal-error { color: var(--ui-red); font-size: 12px; }

.pill,
.count-badge,
.period-badge {
  display: inline-block;
  border-radius: var(--ui-radius-pill);
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 700;
  background: var(--ui-blue-soft);
  color: var(--ui-blue);
}

.empty-state {
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
  padding: 48px 24px;
  text-align: center;
  color: var(--ui-ink-muted);
}

.kpi-tile {
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
  padding: 16px 18px;
}

.kpi-label { font-size: 12px; color: var(--ui-ink-muted); display: block; }
.kpi-value { font-size: 22px; font-weight: 800; display: block; }
.kpi-sub { font-size: 11.5px; color: var(--ui-ink-muted); }

/* ── modals ─────────────────────────────────────────────────────────────────
   ⚠ THE SINGLE HIGHEST-IMPACT RULE IN THIS FILE.

   Every modal in the POS template rendered inline and simultaneously, three
   dark overlays compounding to a near-black screen — the capture scored 198.7
   mean channel difference against its reference, the worst of any screen. The
   cause was not markup: `cat-modal`, `modal-overlay` and `confirm-overlay` had
   no CSS at all, so nothing hid them.

   These are closed by default and opened by the attribute/class the existing
   markup already toggles. No markup changed.
   ────────────────────────────────────────────────────────────────────────── */

.cat-modal,
.modal-overlay,
.confirm-overlay,
.modal {
  position: fixed;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(15, 23, 42, .45);
  z-index: 50;
  padding: 24px;
}

.cat-modal.is-open,
.cat-modal[open],
.modal-overlay.is-open,
.modal-overlay[open],
.confirm-overlay.is-open,
.confirm-overlay[open],
.modal.is-open,
.modal[open] { display: flex; }

.cat-modal-panel,
.modal-box {
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-pop);
  inline-size: min(560px, 100%);
  max-block-size: 86vh;
  overflow: auto;
}

.cat-modal-head,
.modal-head {
  padding: 16px 20px;
  border-block-end: 1px solid var(--ui-line);
  font-weight: 800;
}

.cat-modal-foot,
.modal-actions {
  padding: 14px 20px;
  border-block-start: 1px solid var(--ui-line);
  display: flex;
  gap: 10px;
  justify-content: flex-start;
}

.modal-body { padding: 18px 20px; }

.modal-close-btn {
  border: 0;
  background: transparent;
  font-size: 18px;
  cursor: pointer;
  color: var(--ui-ink-soft);
}

/* ── misc ───────────────────────────────────────────────────────────────── */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  background: var(--ui-surface);
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
  padding: 12px 16px;
  margin-block-end: var(--ui-gap);
}

.page-btn {
  display: inline-block;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-sm);
  padding: 6px 11px;
  background: #fff;
  text-decoration: none;
  color: var(--ui-ink);
  font-size: 13px;
}

.is-disabled,
[disabled] { opacity: .55; cursor: not-allowed; }

.gsb li { list-style: none; }


/* ═══ PHASE B.2 — recovered illustrations, gradients, and grid flow ═══

   ⚠ THE ILLUSTRATIONS ARE EXTRACTED FROM `002_Dashboard.png`, NOT REDRAWN.
   The originals are not in the repository and the owner approved recovering
   them from the audited screenshot. They are flattened crops: the reference
   is a PNG with no alpha, so each carries the card`s white background with
   it. That is invisible on a white card — which is where they are used — but
   they are NOT original vendor assets with real transparency.

   ⚠ THE TILE GRID FLOWS LEFT TO RIGHT, EVEN INSIDE THE RTL PAGE.
   The reference puts `نقطة البيع` in the LEFTMOST column and `الخدمات` in the
   rightmost; the implementation was rendering the row reversed. Caught by
   side-by-side comparison — the pixel metric never saw it. Only the GRID
   flow is reversed; every tile`s own text stays RTL.

   The markup needed no change: `data-tile` was already emitted, and its own
   comment reserved this slot — "so the icons drop in later without touching
   this markup". */

.menu-grid { direction: ltr; }
.menu-card { direction: rtl; }

/* Sidebar chips paint from the recovered per-entry gradient the registry
   already emits as custom properties. */
.gsb-swatch {
  background: linear-gradient(145deg, var(--gsb-from, #dfe3ea), var(--gsb-to, #cdd4de));
}

.menu-card-icon {
  background: transparent;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  inline-size: 78px;
  block-size: 78px;
}

[data-tile="pos"] .menu-card-icon { background-image: url("/img/tiles/pos.png"); }
[data-tile="invoices"] .menu-card-icon { background-image: url("/img/tiles/invoices.png"); }
[data-tile="customers"] .menu-card-icon { background-image: url("/img/tiles/customers.png"); }
[data-tile="services"] .menu-card-icon { background-image: url("/img/tiles/services.png"); }
[data-tile="products"] .menu-card-icon { background-image: url("/img/tiles/products.png"); }
[data-tile="reports"] .menu-card-icon { background-image: url("/img/tiles/reports.png"); }
[data-tile="credit-invoices"] .menu-card-icon { background-image: url("/img/tiles/credit-invoices.png"); }
[data-tile="hangers"] .menu-card-icon { background-image: url("/img/tiles/hangers.png"); }
[data-tile="subscriptions"] .menu-card-icon { background-image: url("/img/tiles/subscriptions.png"); }
[data-tile="consumption-receipts"] .menu-card-icon { background-image: url("/img/tiles/consumption-receipts.png"); }
[data-tile="offers"] .menu-card-icon { background-image: url("/img/tiles/offers.png"); }
[data-tile="custom-prices"] .menu-card-icon { background-image: url("/img/tiles/custom-prices.png"); }
[data-tile="hotels-companies"] .menu-card-icon { background-image: url("/img/tiles/hotels-companies.png"); }
[data-tile="shifts"] .menu-card-icon { background-image: url("/img/tiles/shifts.png"); }
[data-tile="expenses"] .menu-card-icon { background-image: url("/img/tiles/expenses.png"); }
[data-tile="users"] .menu-card-icon { background-image: url("/img/tiles/users.png"); }
[data-tile="roles"] .menu-card-icon { background-image: url("/img/tiles/roles.png"); }
[data-tile="whatsapp"] .menu-card-icon { background-image: url("/img/tiles/whatsapp.png"); }
[data-tile="zatca"] .menu-card-icon { background-image: url("/img/tiles/zatca.png"); }
[data-tile="settings"] .menu-card-icon { background-image: url("/img/tiles/settings.png"); }


/* ═══ PHASE B.3 — tile geometry, measured off the reference ═══
   Vertical scan of `002_Dashboard.png` down a card`s padding column:
   four rows at y 96..287, 312..503, 529..720, 745..936 — every card
   exactly 192px tall on a 216px pitch, so the row gap is 24px. The column
   gap was measured at 20px and the inset at 36px. The rows were ragged
   because the cards stretched to fill the grid track; a fixed block-size
   reproduces the reference`s uniform rows. */

.menu-grid {
  column-gap: 20px;
  row-gap: 24px;
  align-items: start;
}

.menu-card {
  block-size: 192px;
  justify-content: flex-start;
  padding: 22px 18px 18px;
}

.app-main { padding-block-start: 36px; }


/* ═══ PHASE B.4 — the header, reconstructed from `002_Dashboard.png` ═══
   Ink-run scan of the header band gave the exact geometry:
     brand mark   x  26..57   (32px)
     POS pill     x 651..780  (130px)
     avatar       x1176..1205 (30px)
     globe        x1304..1333 (30px)
     logout       x1372..1409 (38px)
   The pill gradient was sampled top-to-bottom: #45d87b -> #2fbf64 -> #169145.

   ⚠ THE PILL IS CSS + TEXT, NOT AN IMAGE. Cropping the whole pill would
   bake Arabic into a bitmap — breaking the EN locale, the screen reader and
   the catalogue rule at once. Only the monitor glyph is an extracted asset;
   the shape is CSS and the label comes from `ui.gsb-nav-pos`.

   ⚠ RTL FLEX ORDER. The first flex item sits at the RIGHT, so the brand is
   order 1 and the logout order 5, which reads right-to-left exactly as the
   reference does. The pill centres on `margin-inline: auto`. */

.dash-header { padding: 0 var(--ui-inset); }
/* ⚠ ORDER CORRECTED AFTER SIDE-BY-SIDE COMPARISON.
   The first attempt read `brand order 1` as `brand on the right`, and the
   capture came out mirrored. The ink-run scan settles it: in the reference
   the brand sits at x 26..169 — the LEFT of the header — and the user
   cluster at x 1176..1409, hard against the sidebar on the right.
   In an RTL flex row the FIRST item is placed rightmost, so the logout is
   order 1 and the brand order 5. */
.dash-logout { order: 1; margin: 0; }
.dash-lang { order: 2; }
.dash-greeting { order: 3; }
.dash-pos { order: 4; margin-inline: auto; }
.dash-brand { order: 5; }

.dash-brand-mark {
  background: url("/img/chrome/brand-mark.png") center/contain no-repeat;
  inline-size: 33px;
  block-size: 33px;
}

.dash-pos {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: linear-gradient(180deg, #45d87b, #169145);
  color: #fff;
  font-size: 14px;
  font-weight: 800;
  border-radius: var(--ui-radius-pill);
  padding: 9px 22px;
  text-decoration: none;
  box-shadow: 0 4px 10px rgba(22, 145, 69, .28);
}

.dash-pos-glyph {
  background: url("/img/chrome/pos-glyph.png") center/contain no-repeat;
  inline-size: 22px;
  block-size: 18px;
  display: inline-block;
}

.dash-greeting-avatar {
  background: url("/img/chrome/avatar.png") center/contain no-repeat;
  inline-size: 30px;
  block-size: 30px;
}

.lang-btn-globe {
  background: url("/img/chrome/globe.png") center/contain no-repeat;
  inline-size: 29px;
  block-size: 29px;
}

.dash-logout-btn {
  background: url("/img/chrome/logout.png") center/contain no-repeat;
  inline-size: 38px;
  block-size: 38px;
  border: 0;
  cursor: pointer;
  padding: 0;
}

/* ══ B.5 — sidebar reconstruction ═══════════════════════════════════════════
   Every number in this layer is MEASURED off `002_Dashboard.png`, not chosen.

   Sidebar inner column  x1433..1678  (246px, 1px #e2e8f0 border on the canvas side)
   Active pill           x1441..1671  (231px), block-size 52px, pitch 54px
   Icon chip             x1628..1663  (36px square), 12px gap to its label
   Group separation      row 7 pill ends y448, row 8 begins y467  →  +17px

   ⚠ The reference sidebar has NO `القائمة` heading and NO collapse chevron.
   Scanning the full label column (x1400..1676) across BOTH group boundaries
   returns zero text-weight ink — only a 5px hairline at y70 and y464 — and the
   73px above the first pill is empty white. They were therefore NOT
   reconstructed: the owner's instruction was to build them only if the
   reference proves they exist, and it disproves them. The <h2> stays in the
   markup for assistive technology but is taken out of sight. */
.gsb-section {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.app-sidebar {
  padding-block: 73px 24px;
  padding-inline: 0;
}

.gsb-group + .gsb-group { margin-block-start: 17px; }

.gsb-item {
  block-size: 52px;
  gap: 12px;
  justify-content: flex-start;
  padding-block: 0;
  padding-inline-start: 5px;
  padding-inline-end: 14px;
  margin-block: 0 2px;
  margin-inline-start: 7px;
  margin-inline-end: 8px;
  /* Uniform on every row so the active indicator cannot shift the geometry;
     only its colour changes. In RTL `inline-start` is the right-hand edge,
     which is where the reference paints the 3px slate bar (x1669..1671). */
  border-inline-start: 3px solid transparent;
  border-radius: 10px;
  font-size: 14px;
}

.gsb-item[aria-current="page"],
.gsb-item.is-active {
  background: var(--ui-active);
  border-inline-start-color: #5b6472;
}

.gsb-label {
  font-size: 14px;
  font-weight: 700;
  color: #4b5563;
}

/* ── recovered sidebar glyph chips ──────────────────────────────────────────
   Same provenance and same limitation as the dashboard illustrations: these
   are cropped from the reference screenshot, not redrawn and not original
   vendor assets. Each chip carries its own baked gradient, so the registry's
   `--gsb-from`/`--gsb-to` gradient stays underneath purely as the fallback.

   The mapping was confirmed three ways before installation, because assuming
   coordinate order equals DOM order is exactly what produced a T-shirt for
   `pos` earlier: (1) each glyph was read beside its own Arabic label in the
   reference, (2) `services`=العمليات and `products`=الخدمات were resolved from
   `lang/ar/ui.php` rather than by translating — the two are deliberately
   counter-intuitive and adjacent, and (3) the result was checked against
   SidebarRegistry's own 1..18 ordering.

   Entries 19..21 (whatsapp, zatca-settings, settings) sit below the reference's
   1050px fold, so no chip could be recovered for them. They keep the gradient
   fallback; this is a known, recorded gap, not an oversight. */
.gsb-swatch {
  inline-size: 36px;
  block-size: 36px;
  flex: 0 0 36px;
  border-radius: 11px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
}

.gsb-item[data-screen="dashboard"] .gsb-swatch { background-image: url("../img/nav/dashboard.png"); }
.gsb-item[data-screen="pos"] .gsb-swatch { background-image: url("../img/nav/pos.png"); }
.gsb-item[data-screen="invoices"] .gsb-swatch { background-image: url("../img/nav/invoices.png"); }
.gsb-item[data-screen="customers"] .gsb-swatch { background-image: url("../img/nav/customers.png"); }
.gsb-item[data-screen="customer-custom-prices"] .gsb-swatch { background-image: url("../img/nav/customer-custom-prices.png"); }
.gsb-item[data-screen="hotels-companies"] .gsb-swatch { background-image: url("../img/nav/hotels-companies.png"); }
.gsb-item[data-screen="reports"] .gsb-swatch { background-image: url("../img/nav/reports.png"); }
.gsb-item[data-screen="subscriptions"] .gsb-swatch { background-image: url("../img/nav/subscriptions.png"); }
.gsb-item[data-screen="consumption-receipts"] .gsb-swatch { background-image: url("../img/nav/consumption-receipts.png"); }
.gsb-item[data-screen="credit-invoices"] .gsb-swatch { background-image: url("../img/nav/credit-invoices.png"); }
.gsb-item[data-screen="hangers"] .gsb-swatch { background-image: url("../img/nav/hangers.png"); }
.gsb-item[data-screen="expenses"] .gsb-swatch { background-image: url("../img/nav/expenses.png"); }
.gsb-item[data-screen="shifts"] .gsb-swatch { background-image: url("../img/nav/shifts.png"); }
.gsb-item[data-screen="offers"] .gsb-swatch { background-image: url("../img/nav/offers.png"); }
.gsb-item[data-screen="users"] .gsb-swatch { background-image: url("../img/nav/users.png"); }
.gsb-item[data-screen="roles"] .gsb-swatch { background-image: url("../img/nav/roles.png"); }
.gsb-item[data-screen="services"] .gsb-swatch { background-image: url("../img/nav/services.png"); }
.gsb-item[data-screen="products"] .gsb-swatch { background-image: url("../img/nav/products.png"); }

/* ══ B.6 — header corrections ═══════════════════════════════════════════════
   Measured: green pill bbox x653..778 (126x38), centre x=715.5; brand mark
   x29..54 with its wordmark at x70..173; POS glyph LEFT of the pill text. */

/* `margin-inline: auto` centred the pill in the FREE space between its
   unequal-width neighbours, not in the header, landing it ~60px off. Absolute
   centring is measured against the header box itself. */
.dash-header { position: relative; }

.dash-pos {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  block-size: 38px;
}

/* Both glyphs are the FIRST child, which RTL places on the right; the
   reference puts both on the left, so each moves to the inline end. */
.dash-pos-glyph { order: 2; }

.dash-brand {
  display: flex;
  align-items: center;
  gap: 16px;
}

.dash-brand-mark { order: 2; }

/* ══ B.7 — sidebar header row + specificity correction ══════════════════════
   Two defects the first side-by-side exposed.

   ONE. `.app-nav a` (0,1,1) outranks `.gsb-item` (0,1,0), so the B.5 geometry
   never applied at all: the rows kept `display:block` and `padding:.5rem .75rem`
   from the Phase 03 base layer, inflating the pitch to ~70px against the
   reference's 54 and leaving each label unaligned with its chip. The B.5 block
   is left in place as the record; these selectors carry the same measurements
   at a weight that actually wins.

   TWO. The reference DOES have a sidebar header row, and my first scan said it
   did not. That scan started at y52 and the row lives at y10..48, so it read the
   empty gap beneath the row and called it proof of absence. Measured properly:
   chevron box 28x28 #f1f5f9 at x1445..1472 (LEFT), `القائمة` #97a5ba at
   x1638..1667 (RIGHT), 1px #e2e8f0 divider at y48, first pill at y73.

   The group headings `الرئيسي`/`الإدارة` remain hidden — that part of the
   original scan covered the right band and found zero text ink at either group
   boundary, and the reference's title is a different word from either. */

.gsb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  block-size: 48px;
  padding-inline: 11px 12px;
  border-block-end: 1px solid #e2e8f0;
}

/* DOM order is button-then-title, which RTL puts button-right; the reference
   puts the chevron on the left. */
.gsb-toggle {
  order: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 28px;
  block-size: 28px;
  padding: 0;
  border-radius: 8px;
  background: #f1f5f9;
  color: #66768d;
  font-size: 18px;
  line-height: 1;
}

.gsb-title {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  color: #97a5ba;
}

#gsb-groups { padding-block-start: 24px; }

.app-sidebar { padding-block: 0 24px; }

.app-nav a.gsb-item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
  block-size: 52px;
  padding-block: 0;
  padding-inline-start: 5px;
  padding-inline-end: 14px;
  margin-block: 0 2px;
  margin-inline-start: 7px;
  margin-inline-end: 8px;
  border-inline-start: 3px solid transparent;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 700;
}

.app-nav a.gsb-item[aria-current="page"],
.app-nav a.gsb-item.is-active {
  background: var(--ui-active);
  border-inline-start-color: #5b6472;
  font-weight: 700;
}

/* ══ B.8 — geometry corrections from live DOM measurement ═══════════════════
   Reading the rendered boxes rather than the picture found four causes that
   pixel inspection alone had not separated.

   1. `.gsb-head` came out 29px tall, not 48: it is a flex child of
      `.app-sidebar`, whose content overflows, so the default `flex-shrink: 1`
      was compressing it. Pinned instead of restated.
   2. `.app-nav` carries a 40px inline-start padding from the Phase 03 base
      layer. That, not the pill maths, was the entire chip-column offset —
      chips landed at x1588 instead of x1628.
   3. The sidebar track measured 245px against the reference's 248px
      (border at x1432, inner column x1433..1678).
   4. `.dash-pos` is content-box, so `block-size: 38px` plus 9px block padding
      rendered 56px tall.

   With 1–3 corrected the first chip lands at y81 and the chip column at
   x1628..1663 — both exactly the reference. */

:root { --ui-sidebar-w: 248px; }

.gsb-head { flex: 0 0 48px; }

.app-sidebar .app-nav { padding: 0; }

.dash-pos {
  box-sizing: border-box;
  padding-block: 0;
  padding-inline: 23px;
  gap: 8px;
  font-size: 14px;
}

/* ══ B.9 — vertical alignment + pill sizing ═════════════════════════════════
   Causes read off computed style, not inferred:
     `.gsb-toggle`  carried `align-self: flex-start`, pinning it to y0 in a
                    48px row whose own `align-items: center` was never at fault.
     `.app-sidebar` is a flex column with a 16px gap, which sat between the
                    header row and the nav groups.
     `.dash-header` has `margin-block-start: -20px` against `.app-main`'s 36px
                    padding, landing the header at y16; the reference centres
                    the POS pill at y29.5, which puts the header at y0.
     `.dash-pos`    measured 143x38 against the reference's 126x38 — its glyph
                    renders 22x18 where the reference's is ~18 square, inside
                    20px (not 23px) end padding. */

.app-sidebar { gap: 0; }

.gsb-toggle { align-self: center; }

/* U+2039 is a bidi-MIRRORED character: in the RTL sidebar it painted as `›`,
   the opposite of the reference. An LTR isolate renders the codepoint as
   authored without changing the document's direction. */
.gsb-toggle > span {
  direction: ltr;
  unicode-bidi: isolate;
}

.dash-header { margin-block-start: -36px; }

.dash-pos { padding-inline: 20px; font-size: 13px; }

.dash-pos-glyph {
  inline-size: 18px;
  block-size: 18px;
  background-size: contain;
}

/* ══ B.10 — nav group offset ════════════════════════════════════════════════
   `#gsb-groups` is a 49px-top block with 24px block-start padding, yet its
   first `.gsb-group` (margin 0) renders at y89, not y73 — a consistent 16px
   from an anonymous line box for the whitespace between the container and its
   first group. The offset is stable and reproducible, so the padding is set to
   the value that puts the first pill on the reference's y73 and the first chip
   on its y81, rather than left at a number that measures wrong. */
#gsb-groups { padding-block-start: 8px; }

.dash-pos { padding-inline: 19px; }

/* ══ B.11 — header cluster grouping ═════════════════════════════════════════
   `justify-content: space-between` spread all four header items evenly across
   1432px, so the greeting chip sat beside the POS pill instead of beside the
   language toggle. The reference groups the three user controls hard against
   the inline start (x1173..1412) and pushes the brand alone to the far end
   (mark at x29). One auto margin on the brand does that; the pill is already
   out of flow and centred.

   Two more glyphs were mirrored for the same reason the POS glyph and brand
   mark were: each is the FIRST child, which RTL places on the right, and the
   reference paints both on the left of their label. */

.dash-header {
  justify-content: flex-start;
  gap: 8px;
  padding-inline: 19px 29px;
}

.dash-brand { margin-inline-start: auto; }

.dash-greeting {
  display: flex;
  align-items: center;
  gap: 8px;
}

.dash-greeting-avatar { order: 2; }

.lang-btn {
  display: flex;
  align-items: center;
  gap: 8px;
}

.lang-btn-globe { order: 2; }

/* ══ B.12 — card box model ══════════════════════════════════════════════════
   `.menu-card` already carried `block-size: 192px` from B.3, but it is
   content-box: 192 + 26 top + 18 bottom rendered 234, stretching the row pitch
   to 258 against the reference's 216. The height was never wrong — the box
   model was.

   Reference card internals, measured inside card 1 (y96..288) at a column
   clear of its centred content:
     padding-top   26      illustration  y122..225 (104 tall)
     title         y231..248 (18)        description   y260..272 (13)
     padding-bottom 16                   card height   192, pitch 216
   The recovered illustrations are 107px tall, so a 104px box renders them at
   very close to their captured size. */

.menu-card {
  box-sizing: border-box;
  block-size: 192px;
  padding: 26px 18px 13px;
  gap: 0;
}

.menu-card-icon {
  inline-size: 100px;
  block-size: 104px;
  margin-block-end: 6px;
}

.menu-card-title {
  margin: 0 0 11px;
  line-height: 1.2;
}

.menu-card-desc {
  margin: 0;
  line-height: 1.2;
}

/* Grid top: the reference's first card row begins at y96; the header's 20px
   block-end margin was landing it at y81. */
.dash-header { margin-block-end: 35px; }

/* ══ C.1 — THE LIST-SCREEN CHROME ═══════════════════════════════════════════
   Reconstructed from `010_Customers.png`. The `.cat-*` vocabulary is already
   in the markup and shared by the catalogue screens, so this layer paints the
   chrome for all of them at once rather than per screen.

   Measured off the reference:
     page topbar     y0..52   (53px)  #1e40af, full canvas bleed
     back control    x12..107, y8..44 (37px), translucent white on the navy
     screen title    x1287..1375, white, at the inline start
     avatar          x1384..1421 (38px), 10px from the canvas edge
     toolbar band    y53..128 (76px); its controls are 41px tall at y70..110
     add button      x14..157   #f1980a       gap between buttons 11px
     search field    x1035..1420 (386px), white
     table card      x16..1417, white
     table header    46px, #2251cd, white
     data rows       71px, #ffffff / #fcfaff alternating, 1px #f1f5f9 rule
     canvas          #eff2ff  (the list screens are a lighter blue than the
                               dashboard's #e9eef6 — sampled, not assumed)

   ⚠ SCOPED BY `:has(> .back-btn)`. The layout renders the global back control
   only when `$shell['shows_back']`, which is exactly "this is an inner screen",
   so the predicate needs no new class and no shared-layout edit. The dashboard
   has no back control and is untouched. Where `:has()` is unsupported the
   screens simply keep their previous appearance rather than breaking.

   ⚠ WHAT IS NOT BUILT, AND MUST NOT BE. The reference toolbar also carries
   `PDF` and `Excel`. `customers/index.blade.php` records why they are absent —
   BR-163: "the entire PII table is exportable by anyone who can open the
   screen… There is no separate PII or export permission", and O-16-06 keeps
   the exposure routeless. Styling a toolbar is not licence to add the controls
   it is missing, so only the buttons that exist are painted. */

.app-main[data-screen="customers"],
.app-main[data-screen="hotels-companies"] {
  position: relative;
  padding: 0;
  background: #eff2ff;
}

/* The navy page topbar. A painted band rather than a new wrapper element:
   the back control and the screen's own <h1> are already direct children. */
.app-main[data-screen="customers"]::before,
.app-main[data-screen="hotels-companies"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 53px;
  background: #1e40af;
}

/* The avatar the reference shows at the topbar's inline start. Decorative
   chrome, so it is painted rather than introduced into the markup. */
.app-main[data-screen="customers"]::after,
.app-main[data-screen="hotels-companies"]::after {
  content: "";
  position: absolute;
  inset-block-start: 8px;
  inset-inline-start: 10px;
  inline-size: 38px;
  block-size: 38px;
  border-radius: 50%;
  background: url("/img/chrome/avatar.png") center/contain no-repeat;
}

.app-main[data-screen="customers"] > .back-btn,
.app-main[data-screen="hotels-companies"] > .back-btn {
  position: absolute;
  z-index: 1;
  inset-block-start: 8px;
  inset-inline-end: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 37px;
  padding-inline: 16px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

/* The screen title is already in the markup as an `sr-only` <h1>. The
   reference paints it in the topbar, so it is brought back into sight here
   rather than a second title being invented beside it. */
.app-main[data-screen="customers"] > h1,
.app-main[data-screen="hotels-companies"] > h1 {
  position: absolute;
  z-index: 1;
  inset-block-start: 0;
  inset-inline-start: 58px;
  display: flex;
  align-items: center;
  inline-size: auto;
  block-size: 53px;
  margin: 0;
  padding: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  color: #fff;
  font-size: 16px;
  font-weight: 800;
}

/* ── toolbar ─────────────────────────────────────────────────────────────── */

.app-main[data-screen="customers"] > .cat-toolbar,
.app-main[data-screen="hotels-companies"] > .cat-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 11px;
  block-size: 76px;
  margin: 0;
  padding-inline: 14px;
}

.cat-search { display: flex; margin: 0; }

.cat-search input[type="search"] {
  box-sizing: border-box;
  inline-size: 386px;
  block-size: 41px;
  padding-inline: 16px;
  border: 1px solid #dbe3f5;
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
}

.app-main[data-screen="customers"] > .cat-toolbar .btn-save,
.app-main[data-screen="hotels-companies"] > .cat-toolbar .btn-save {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 41px;
  padding-inline: 22px;
  border: 0;
  border-radius: 10px;
  background: #f1980a;
  color: #fff;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

/* ── the table card ──────────────────────────────────────────────────────── */

.cat-table-wrap {
  margin-inline: 16px;
  background: #fff;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.08);
}

.cat-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.cat-table thead th {
  block-size: 46px;
  padding-inline: 8px;
  background: #2251cd;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
  white-space: nowrap;
}

.cat-table tbody td {
  block-size: 71px;
  padding-inline: 8px;
  border-block-end: 1px solid #f1f5f9;
  text-align: center;
  color: #334155;
}

.cat-table tbody tr:nth-child(even) { background: #fcfaff; }

.cat-table tbody tr:last-child td { border-block-end: 0; }

/* ── pills ───────────────────────────────────────────────────────────────── */

.cat-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  background: #eef2f7;
  color: #475569;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.cat-pill--on { background: #e8f9ef; color: #16a34a; }
.cat-pill--off { background: #fdecec; color: #dc2626; }

/* ── row actions ─────────────────────────────────────────────────────────── */

.cat-actions { white-space: nowrap; }

.cat-actions > * { display: inline-block; }

.cat-actions button,
.cat-actions a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 2px;
  padding: 6px 11px;
  border: 0;
  border-radius: 8px;
  background: #eff4ff;
  color: #2563eb;
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}

.cat-actions .btn-danger,
.cat-actions button[data-confirm],
.cat-actions form button {
  background: #fdecec;
  color: #dc2626;
}

/* ── pagination ──────────────────────────────────────────────────────────── */

.cat-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-inline: 16px;
  padding: 16px 4px;
  font-size: 12.5px;
  color: #64748b;
}

/* ══ C.2 — per-screen page background ═══════════════════════════════════════
   C.1's first cut painted one background across every inner screen, on the
   evidence of a single capture. That was wrong twice over: the navy topbar it
   also generalised belongs to exactly TWO of the 93 references, and it repainted
   twelve screens the reference never gave one. Both are re-scoped.

   The product does not have one canvas colour — it has one per screen, and the
   toolbar accent differs too (customers `#f1980a`, services `#6764f1`, products
   `#13b6a4`, users `#6465f1`). Each value below is the modal colour of that
   reference's own page gutter, sampled at 3px intervals down x2..11; the
   agreement figures were 91–100% on most screens, so these are measured
   backgrounds rather than a palette anyone chose. */

.app-main[data-screen="dashboard"] { background: #eaeff7; }
.app-main[data-screen="pos"] { background: #ffffff; }
.app-main[data-screen="invoices"] { background: #f8fafc; }
.app-main[data-screen="customers"] { background: #eff2ff; }
.app-main[data-screen="hotels-companies"] { background: #f0f4ff; }
.app-main[data-screen="hangers"] { background: #f8fafc; }
.app-main[data-screen="expenses"] { background: #f8fafc; }
.app-main[data-screen="shifts"] { background: #e7eff4; }
.app-main[data-screen="offers"] { background: #f1f5f9; }
.app-main[data-screen="products"] { background: #f8fafc; }
.app-main[data-screen="services"] { background: #eef2ff; }
.app-main[data-screen="users"] { background: #f8fafc; }
.app-main[data-screen="roles"] { background: #f1f5f9; }
.app-main[data-screen="settings"] { background: #e9f1f6; }
.app-main[data-screen="reports"] { background: #eaeff7; }

/* ══ C.3 — THE LOGIN SCREEN ═════════════════════════════════════════════════
   Reconstructed from `001_Login.png`. Measured:
     diagonal split   x840 at y0 → x655 at y1049 (dark panel on the inline start)
     panel gradient   #154e99 → #1c498d → #12397e, over a 3D branded artwork
     form column      x55..619 (565 wide), left panel is #ffffff
     heading          y312..331, centred on the column
     label / input    label 13px tall, input 48px, fill #f8fafc, rule #e2e8f0
     field rhythm     username y463..511, password y566..613
     submit           y~690..755, full width, #7561f3..#7860f3
     affordance icons sit at the column's inline END (the left), with the
                      placeholder reading from the right

   THE PANEL IS ONE RECOVERED ASSET, INCLUDING ITS DIAGONAL EDGE. The crop runs
   x600..1679 of the reference, and everything left of the diagonal inside it is
   already #ffffff — the same colour as the panel it sits on — so the edge needs
   no clip-path to reproduce. Same provenance and same limitation as the tiles:
   extracted, not redrawn, and not an original vendor asset.

   ⚠ THREE THINGS IN THE REFERENCE ARE DELIBERATELY NOT BUILT.
     brand card            `الرابط بلس مغاسل` — no markup and no catalogue key
                           carries that copy; adding it would be authoring
                           brand text, not recovering it.
     support-expiry strip  `تاريخ انتهاء الدعم الفني` — no markup, no feature.
     trial CTA             `.btn-trial` IS in the markup but `$trialCtaVisible`
                           is config-gated OFF by default: BR-013 renders it
                           "iff trialModeEnabled && !hasAccount", the flag has no
                           documented source (OQ-06), and the account creation
                           behind it belongs to Phase 47 (DD-05-01). Styling it
                           is fine; forcing it visible would be inventing a
                           feature to win a pixel comparison.
   This build also shows a `نسيت كلمة المرور` link the reference has no room
   for. It is an existing control on an existing route, so it stays. */

/*
 | THE DECORATIVE PANEL IS GONE, BY REQUEST.
 |
 | It was a 62.5% wedge carrying another organisation's artwork
 | (`login-panel.png`), and the login card sat in the 37.5% strip beside it.
 | That split is what made this screen the hardest one in the build to make
 | responsive: it needs room for a 565px form column AND a panel worth showing,
 | and between roughly 768px and 1200px it had neither — the card overflowed its
 | strip and printed its labels on top of the artwork.
 |
 | Removing the panel removes the composition, and with it the whole problem. A
 | centred card on a sunken ground needs no wedge, no percentage split and no
 | breakpoint: it is correct at 320px and at 1920px by construction. The card's
 | own geometry, colours and type are untouched.
 */
.login-shell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 100vh;
  margin: 0;
  padding: clamp(16px, 4vw, 40px);
  box-sizing: border-box;
  background: var(--surface-sunken);
}

.login-card {
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  inline-size: 565px;
  max-inline-size: 100%;
  /*
   | THE CARD WAS TRANSPARENT ON PURPOSE — it sat on the white strip beside
   | the artwork, so a background would have been a second white rectangle on
   | a white ground. With the wedge gone the shell is sunken grey and a
   | transparent card is a form floating on nothing.
   |
   | These are NOT new values. They are the exact card treatment this product
   | already ships below 1200px — same surface token, same 20px radius, same
   | shadow — promoted to unconditional, because the reason the desktop
   | differed no longer exists. The padding is the one thing expressed as a
   | range rather than a point, so a 320px screen is not given a 40px gutter.
   */
  margin-inline: auto;
  padding: clamp(24px, 3vw, 40px) clamp(20px, 3vw, 44px) clamp(28px, 3vw, 44px);
  border: 0;
  border-radius: 20px;
  background: var(--surface);
  box-shadow: 0 12px 34px rgb(16 24 40 / 10%);
}

.login-card h1 {
  margin: 0 0 30px;
  font-size: 17px;
  font-weight: 800;
  text-align: center;
  color: #1f2937;
}

.login-card .field { margin-block-end: 20px; }

.login-card .field label {
  display: block;
  margin-block-end: 10px;
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.login-card .input-with-affordance { position: relative; }

.login-card .field input[type="text"],
.login-card .field input[type="password"] {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 48px;
  /* The reading edge keeps a normal inset; the wider inset is at the inline
     END, where the reference puts the icon. */
  padding-inline: 16px 44px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #f8fafc;
  font-size: 13.5px;
}

.login-card .toggle-pass-btn,
.login-card .user-dropdown-toggle {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 12px;
  transform: translateY(-50%);
  padding: 0;
  border: 0;
  background: transparent;
  color: #94a3b8;
  cursor: pointer;
}

.login-card .btn-login {
  inline-size: 100%;
  block-size: 60px;
  margin-block-start: 8px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(180deg, #7f68f6, #6a51ef);
  color: #fff;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
}

.login-card .btn-trial {
  inline-size: 100%;
  block-size: 51px;
  margin-block-start: 16px;
  border: 1px solid #c7bffb;
  border-radius: 12px;
  background: #fff;
  color: #6a51ef;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

/* ── C.3a — the remember-me row ──────────────────────────────────────────────
   The base `.field input` rule sets `width: 100%`, which the checkbox was
   inheriting: it rendered 565px wide and painted a white bar across the form.
   Type-selecting it back to a checkbox is the fix; the row itself is a flex
   line so the box sits at the reading edge beside its text, as the reference
   has it at x552..619. */

.login-card .field input[type="checkbox"] {
  inline-size: 16px;
  block-size: 16px;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 1px solid #cbd5e1;
  border-radius: 4px;
  background: #fff;
  accent-color: #6a51ef;
}

.login-card .field label:has(input[type="checkbox"]) {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  margin: 0;
  font-size: 12.5px;
  color: #64748b;
}

.login-card form > p {
  margin: 14px 0 0;
  text-align: center;
}

.login-card form > p a {
  color: #6a51ef;
  font-size: 12.5px;
  font-weight: 700;
  text-decoration: none;
}

/* The painted topbar is absolutely positioned, so the flow content needs to
   start beneath it — without this the toolbar rendered at y0 and the topbar
   covered it completely. */
.app-main[data-screen="customers"],
.app-main[data-screen="hotels-companies"] { padding-block-start: 53px; }

/* ── C.1a — row actions, measured from the capture ───────────────────────────
   Chip fills sampled off row 1 of `010_Customers.png`:
     حذف          x56..114  #fde8e8      أسعار خاصة  x120..211 #e2f2f1
     إيقاف        x217..281 #fdecec      تعديل        x287..350 #e2f2f1
   Chips are 29px tall on a 6px gap; the type pill is #e7f8f6.

   THE ORDER ALSO DIFFERS. The reference reads, right to left,
   `تعديل · إيقاف · أسعار خاصة · حذف`; the markup emits edit, custom-prices,
   toggle, delete, which puts the middle pair the other way round. They are
   reordered here in CSS rather than in the Blade, because the DOM sequence is
   what the delete confirmation and the toggle form depend on and the capture
   only constrains the painting.

   The delete control is `<x-confirm>`, a `<details>` — hence the stray `◀`
   disclosure marker in the first render. It is suppressed, not left to leak
   into a table cell. */

.cat-actions {
  display: flex;
  align-items: center;
  /* Centred, not packed to the reading edge: the reference chip group spans
     x56..350 inside a column running x16..390, which is exactly centred. */
  justify-content: center;
  flex-wrap: nowrap;
  gap: 6px;
}

.cat-actions > button { order: 1; }
.cat-actions > form { order: 2; }
.cat-actions > a { order: 3; }
.cat-actions > details { order: 4; }

.cat-actions > form { margin: 0; }

.cat-actions button,
.cat-actions a,
.cat-actions details > summary {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  block-size: 29px;
  margin: 0;
  padding-inline: 12px;
  border: 0;
  border-radius: 8px;
  background: #e2f2f1;
  color: #0f766e;
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

/* Destructive pair: the toggle form's button and the delete trigger. */
.cat-actions > form button,
.cat-actions details > summary {
  background: #fdecec;
  color: #dc2626;
}

.cat-actions details > summary { list-style: none; }
.cat-actions details > summary::-webkit-details-marker { display: none; }
.cat-actions details > summary::marker { content: ""; }

.cat-table tbody td .cat-pill { background: #e7f8f6; color: #0f766e; }
.cat-table tbody td .cat-pill--on { background: #e8f9ef; color: #16a34a; }
.cat-table tbody td .cat-pill--off { background: #fdecec; color: #dc2626; }

/* The toolbar's two controls measured 18px out of alignment: the add button was
   not taking the row's centring. */
.app-main[data-screen="customers"] > .cat-toolbar > *,
.app-main[data-screen="hotels-companies"] > .cat-toolbar > * {
  align-self: center;
  margin-block: 0;
}

/* ══ C.4 — per-screen table header accent ═══════════════════════════════════
   C.1 painted every `.cat-table` header navy, which is Customers' accent and
   nobody else's. Sampled from each reference's own header band (modal fill,
   darkest pixel for the text):
     customers   #2251cd on #ffffff     products   #ecf8f7 / #0f7672
     services    #f5f4fe / #4f46e5

   The navy is therefore scoped to the screen it belongs to, and the shared
   default is the light treatment the rest of the catalogue screens use. Offers
   and custom-prices are NOT given a value here: their header bands did not
   resolve cleanly from the gutter test, and guessing an accent is exactly the
   mistake C.1 made. They keep the neutral default until measured. */

.cat-table thead th {
  background: #f1f5f9;
  color: #334155;
}

.app-main[data-screen="customers"] .cat-table thead th,
.app-main[data-screen="hotels-companies"] .cat-table thead th {
  background: #2251cd;
  color: #fff;
}

.app-main[data-screen="products"] .cat-table thead th {
  background: #ecf8f7;
  color: #0f7672;
}

.app-main[data-screen="services"] .cat-table thead th {
  background: #f5f4fe;
  color: #4f46e5;
}

/* ══ C.5 — THE POS SCREEN ═══════════════════════════════════════════════════
   `003_POS.png`. No `.pos-*` rule existed before this; the screen was entirely
   unstyled. Measured:
     tabs band      y0..65, white, 1px rule; `العودة` x11..101, `تعليق` x124..188,
                    `العملة` x197..273 (#fdf3e3); on the reading edge
                    `الفواتير الآجلة` x1212..1290 and the `البيع الجديد` pill
                    x1311..1423 (#0d9dc3)
     sale panel     x0..615, white — on the LEFT
     product grid   gutters at x624/784/944/1104/1264/1424 → 5 columns on a
                    160px pitch; cards 154x160 with an 11px row gap, first row
                    at y139; grid ground #e3e8f0
     card internals image 111px, `name-ar` 15px (#0c2d40), `name-en` 10px

   ⚠ DIRECTION. `.pos-sale-panel` is the FIRST child, which RTL puts on the
   right; the reference has the cart on the LEFT with the grid beside it, so the
   row is reversed. This is the same trap as the dashboard tiles and the four
   header glyphs — DOM order is not paint order in RTL. */

.app-main[data-screen="pos"] { padding: 0; }

.pos-tabs {
  display: flex;
  align-items: center;
  gap: 10px;
  block-size: 66px;
  margin: 0;
  padding-inline: 11px;
  background: #fff;
  border-block-end: 1px solid #e2e8f0;
}

.pos-layout {
  display: flex;
  flex-direction: row-reverse;
  align-items: stretch;
  min-block-size: calc(100vh - 66px);
}

.pos-sale-panel {
  box-sizing: border-box;
  flex: 0 0 616px;
  padding: 12px;
  background: #fff;
}

.pos-grid {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(5, 154px);
  column-gap: 6px;
  row-gap: 11px;
  align-content: start;
  justify-content: center;
  padding: 13px 6px;
  background: #e3e8f0;
}

.product-card {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  inline-size: 154px;
  block-size: 160px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: #fff;
  overflow: hidden;
  cursor: pointer;
}

.product-card.no-price { opacity: 0.55; cursor: not-allowed; }

.product-card-img {
  display: block;
  inline-size: 100%;
  block-size: 111px;
  object-fit: cover;
}

.product-card-img--none { background: #eef2f7; }

.product-card-name-ar {
  margin-block-start: 7px;
  font-size: 12.5px;
  font-weight: 800;
  line-height: 1.2;
  color: #0c2d40;
}

.product-card-name-en {
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.2;
  color: #64748b;
}

/* ── C.5a — the product grid flows LTR ───────────────────────────────────────
   Reference row 1 reads `ثوب ملون · طاقية · شماغ · ثوب ابيض · غترة` from the
   LEFT; the RTL grid was laying them out from the right, so the row rendered
   mirrored. Identical to the dashboard tile grid, which produced a T-shirt for
   `pos` before it was caught: the grid track order is LTR inside the RTL page,
   while each card's own content stays RTL. */

.pos-grid { direction: ltr; }
.product-card { direction: rtl; }

/* ── C.5b — the sale panel ───────────────────────────────────────────────────
   Measured down the reference's left column (x8..608):
     customer row  y66..110 (45), with a #0e9f6e affordance at x13
     cart area     flexes; the empty state sits centred at y415..492
     totals        y853..946 (94) — three tiles, and here RTL DOM order already
                   matches the paint: `المجموع` right, VAT middle, grand total
                   left, with the green rule #6ee7b7 on the leftmost at x13
     checkout row  y996..1043 (48): two controls, x13..213 and x223..505 */

.pos-sale-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  block-size: calc(100vh - 66px);
}

.pos-customer {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 45px;
}

.pos-customer input[type="search"],
.pos-customer input[type="text"] {
  box-sizing: border-box;
  flex: 1 1 auto;
  block-size: 40px;
  padding-inline: 14px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  font-size: 12.5px;
}

.pos-cart {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 0;
  border-radius: 12px;
}

.pos-cart-empty {
  margin: 0;
  text-align: center;
  color: #94a3b8;
  font-size: 12.5px;
  font-weight: 600;
}

.pos-totals {
  display: flex;
  gap: 10px;
  flex: 0 0 auto;
}

.pos-totals .sc-tile {
  box-sizing: border-box;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  block-size: 94px;
  border: 1px solid #cbd5e1;
  border-radius: 12px;
  background: #f1f5f9;
}

.pos-totals .sc-tile--vat { border-color: #fcd34d; background: #fef7d9; }
.pos-totals .sc-tile--total { border-color: #6ee7b7; background: #eefcf2; }

.pos-totals .sc-label { font-size: 11px; font-weight: 700; color: #64748b; }
.pos-totals .sc-value { font-size: 15px; font-weight: 800; color: #0f172a; }

.pos-payment {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.pos-payment .btn-pay,
.pos-payment button[type="submit"] {
  box-sizing: border-box;
  flex: 1 1 auto;
  block-size: 48px;
  border: 0;
  border-radius: 12px;
  background: #d6dce4;
  color: #64748b;
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
}

/* ══ C.6 — POS top region, reconstructed component by component ══════════════
   Measured off `003_POS.png` alone. No other screen's accent is borrowed.

     tabs row      y0..52, 1px #c9e2ef rule at y53
     back pill     x11..101 (91), 37px tall, on the LEFT
     تعليق          x124..188 (65)   المعلقة  x197..273 (77), cream #fdf3e3
     الفواتير الآجلة x1212..1290      البيع الجديد pill x1311..1423, #0d9dc3
     controls row  y54..128, a WHITE band inside the product pane;
                   the purple refund pill runs x642.., y76..112 (37 tall)
     card ground   #e3e8f0 begins at y129; first card row y139

   ⚠ DUPLICATE BACK CONTROL, CORRECTED. POS rendered TWO: the shell's
   `#btnBack` at y2 and its own `.pos-back` at y46, both pointing at
   `route('dashboard')`. The reference has one. `ShellDuplicationTest` counts
   `id="btnBack"` and so never saw the second, which carries no id. Phase 35's
   scope was "the global back control", with screens' hand-rolled ones removed —
   `.pos-back` is a survivor of that sweep. The SANCTIONED control is kept and
   placed where the reference puts it; the unsanctioned duplicate is hidden.
   No test was touched.

   ⚠ ABSOLUTE PLACEMENT, AND WHY. `تعليق`/`المعلقة` live in the sale panel and
   the refund pill lives in the tab bar, but the reference paints all three
   elsewhere. The elements exist, so this is a CSS placement problem, not
   missing markup, and §6 prefers CSS. The offsets are physical because the
   reference is a fixed 1680x1050 composition; they are measured, not chosen.

   ⚠ NOT BUILT, AND NOT ADDED. The reference's controls row also carries
   `معالجة الفاتورة` and `رقم الفاتورة للإرجاع...`. Both strings are in the
   catalogue (`pos-btn-process-invoice`, `pos-invoice-seq-placeholder`) and
   NEITHER is referenced anywhere in the POS markup — this file's own docblock
   records why: "Returns, credit notes and subscription refunds — none is in
   Phase 21's Scope clause." Styling a row is not licence to build its
   missing controls. */

.app-main[data-screen="pos"] { position: relative; background: #fff; }

.pos-tabs {
  block-size: 53px;
  padding-inline: 8px;
  gap: 9px;
  justify-content: flex-start;
  border-block-end: 1px solid #c9e2ef;
}

.pos-tab {
  display: inline-flex;
  align-items: center;
  block-size: 37px;
  padding-inline: 16px;
  border-radius: 10px;
  color: #48677b;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.pos-tab--active { background: #0d9dc3; color: #fff; }

/* The sanctioned global control, placed as the reference has it. */
.app-main[data-screen="pos"] > .back-btn {
  position: absolute;
  z-index: 3;
  top: 8px;
  left: 11px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  block-size: 37px;
  padding-inline: 16px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #48677b;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.pos-back { display: none; }

#btnSuspendSale,
#btnSuspendedSales {
  position: absolute;
  z-index: 3;
  top: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  block-size: 37px;
  margin: 0;
  padding-inline: 16px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #94a3b8;
  font-size: 13px;
  font-weight: 700;
}

#btnSuspendSale { left: 124px; }

/* BR-034 disables it on an empty cart, which is the state the reference
   captures — the greyed pill is the product's own behaviour, not a style. */
#btnSuspendSale[disabled] { opacity: 0.55; cursor: not-allowed; }

#btnSuspendedSales {
  left: 197px;
  border-color: #f0dcb4;
  background: #fdf3e3;
  color: #a97a1e;
}

#btnRefundConsumptionReceipt {
  position: absolute;
  z-index: 3;
  top: 76px;
  left: 642px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 37px;
  padding-inline: 18px;
  border: 0;
  border-radius: 10px;
  background: linear-gradient(180deg, #8857f3, #7c43e8);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.pos-layout { min-block-size: calc(100vh - 54px); }

/* The product pane carries a white controls band before its card ground. */
.pos-grid {
  padding-block-start: 85px;
  background: linear-gradient(#f8fafc 0 75px, #e3e8f0 75px);
}

/* The products/cart switch is a narrow-viewport control; the reference is a
   desktop capture and shows none. */
@media (min-width: 1024px) {
  .pos-mobile-toggle { display: none; }
}

/* ══ C.7 — confirmation overlays could not open ═════════════════════════════
   Found by opening every POS modal individually rather than trusting that the
   closed-by-default layer was correct.

   The Phase B layer reveals a modal with `.modal-overlay[open]` / `.is-open`.
   `<x-confirm>` is built on `<details>`, and the `open` attribute lands on the
   `<details>` ELEMENT — never on the overlay div nested inside it. So the
   `display: none` applied, the `[open]` selector never matched, and opening a
   confirmation produced no overlay at all. Five JS-driven POS modals opened
   correctly; this one silently did not, app-wide — `مسح السلة` here and every
   `<x-confirm>` delete elsewhere.

   Selector corrected to match the element that actually carries the state. No
   markup changed and no open/close behaviour changed: the same `<details>`
   toggle drives it. */

details[open] > .modal-overlay,
details[open] > .confirm-overlay,
details[open] > .modal { display: flex; }

/* `.modal` is TWO things in this codebase, which is why Phase B lumped it with
   the overlays and broke the confirmations:
     nested   `<div class="modal-overlay"><div class="modal">` — confirm,
              users/form, components/modal, pos, pos/suspended: the DIALOG BOX
     top level `<section class="modal" id="expenseModal">` — the OVERLAY itself
   Only the nested case is re-treated as a panel, so the standalone one keeps
   the overlay behaviour it needs. */
.modal-overlay > .modal,
.confirm-overlay > .modal,
.cat-modal > .modal {
  position: static;
  inset: auto;
  z-index: auto;
  display: block;
  inline-size: min(560px, 100%);
  max-block-size: calc(100vh - 96px);
  overflow: auto;
  padding: 22px;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 20px 45px rgba(15, 23, 42, 0.25);
}

/* ── C.6a — sale panel bottom blocks ─────────────────────────────────────────
   Reference order, measured: notes/payment y809..846, TOTALS y853..946,
   الخصم/الإضافي y957..984, checkout y996..1043.

   `.pos-money` emits discount, extra, totals — the reference paints totals
   FIRST, so its children are reordered here. `.btn-pay` is a child of
   `.pos-payment` alongside the payment-method and notes fields; it is given its
   own full-width line so it reads as the separate row the reference shows.

   ⚠ ONE ORDERING DIFFERENCE REMAINS, RECORDED RATHER THAN FORCED. The
   reference puts the payment/notes fields ABOVE the totals; the DOM has them in
   `.pos-payment`, after `.pos-money`. `order` only reorders siblings within one
   flex container, and splitting `.pos-payment` would need `display: contents`,
   which destroys the fields' own row. The alternative is moving `.btn-pay` out
   of its container in Blade — declined: it is a submit control and relocating
   it risks the checkout form, which is a behaviour change for a layout nicety. */

.pos-money { display: flex; flex-direction: column; gap: 10px; }
.pos-money > .pos-totals { order: 1; }
.pos-money > .pos-discount-row { order: 2; }
.pos-money > .pos-extra-row { order: 3; }

.pos-payment { flex-wrap: wrap; }
.pos-payment .btn-pay { flex: 1 0 100%; order: 9; margin-block-start: 4px; }

.pos-discount-row,
.pos-extra-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: #64748b;
}

.disc-type-btn {
  block-size: 30px;
  padding-inline: 12px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #64748b;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.disc-type-btn.active {
  border-color: #0d9dc3;
  background: #0d9dc3;
  color: #fff;
}

.pos-unify,
.pos-finishing,
.pos-hanger {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: #64748b;
}

.pos-sale-panel select,
.pos-sale-panel input[type="text"],
.pos-sale-panel input[type="number"] {
  box-sizing: border-box;
  block-size: 34px;
  padding-inline: 10px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  font-size: 12.5px;
  color: #334155;
}

/* The empty-cart mark the reference shows above its message (x287..328,
   y415..460). Recovered from the capture like the tile art, and decorative —
   it is painted, not introduced into the markup. */
.pos-cart-empty::before {
  content: "";
  display: block;
  inline-size: 44px;
  block-size: 48px;
  margin: 0 auto 14px;
  background: url("/img/pos/cart-empty.png") center / contain no-repeat;
}

/* ── C.6b — sale panel block order ───────────────────────────────────────────
   The reference reads, below the cart: totals, then `الخصم` and `الإضافي` on ONE
   line, then `نشا`/`النيلة`, then the checkout row. The DOM emits unify and
   finishing before `.pos-money`, and the hanger after the payment row. All are
   direct children of `.pos-sale-panel`, so `order` alone reseats them — no
   markup change and no container is split. */

.pos-sale-panel > .pos-customer { order: 1; }
.pos-sale-panel > .pos-cart { order: 2; }
.pos-sale-panel > .pos-money { order: 3; }
.pos-sale-panel > .pos-unify { order: 4; }
.pos-sale-panel > .pos-finishing { order: 5; }
.pos-sale-panel > .pos-hanger { order: 6; }
.pos-sale-panel > .pos-payment { order: 7; }
.pos-sale-panel > details.confirm { order: 8; }

/* `الخصم` and `الإضافي` share a line, totals spanning both tracks above them.
   RTL puts track 1 on the right, which is where the reference has `الخصم`. */
.pos-money {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.pos-money > .pos-totals { grid-column: 1 / -1; }

.pos-sale-panel > details.confirm > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  block-size: 48px;
  padding-inline: 22px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #fff;
  color: #64748b;
  font-size: 13.5px;
  font-weight: 700;
  list-style: none;
  cursor: pointer;
}

.pos-sale-panel > details.confirm > summary::-webkit-details-marker { display: none; }
.pos-sale-panel > details.confirm > summary::marker { content: ""; }

/* ── C.6c — totals tile height ───────────────────────────────────────────────
   The first pass read 94px from an ink-band scan that had swallowed the row
   beneath. Measured again on the tile's own tint, the VAT tile fills y854..904
   — 51px. */

.pos-totals .sc-tile {
  block-size: 51px;
  gap: 2px;
}

.pos-totals .sc-label { font-size: 10.5px; }
.pos-totals .sc-value { font-size: 14px; }

.disc-type-btn { white-space: nowrap; }

/* The reference's back pill carries a chevron beside its label. Decorative, so
   it is painted rather than added to the markup. U+2039 is bidi-mirrored — the
   sidebar toggle needed the same LTR isolate or it paints as the wrong glyph. */
.app-main[data-screen="pos"] > .back-btn::after {
  content: "\2039";
  direction: ltr;
  unicode-bidi: isolate;
  font-size: 16px;
  line-height: 1;
  color: #94a3b8;
}

/* ══ C.8 — modal chrome, measured across all 18 modal references ════════════
   Every one of `076`..`093` centres its dialog on x839 (the viewport centre),
   so centring is uniform — but the WIDTHS are not: 340, 378, 438, 440, 460,
   478, 480, 580, 620, 640, 778, 918. A single 560px panel was wrong for
   almost all of them, so the dialog is content-sized within the observed range
   instead of pinned.

   The scrim was derived, not guessed: sampling the same pixels in
   `010_Customers.png` and `083_..._Delete_Confirm.png` and solving
   `m = b(1-a) + S·a` per channel over five points gives a ≈ 0.59 and
   S ≈ (44, 52, 82). The previous rgba(15,23,42,.45) rendered 147 against the
   reference's 131 over white — visibly too light. */

.cat-modal,
.modal-overlay,
.confirm-overlay,
.modal { background: rgba(44, 52, 82, 0.59); }

.modal-overlay > .modal,
.confirm-overlay > .modal,
.cat-modal > .modal {
  inline-size: auto;
  min-inline-size: 340px;
  max-inline-size: min(94vw, 940px);
}

/* `<x-confirm>` is the generic delete dialog: `083` measures it at 378x282. */
.confirm-overlay > .modal { inline-size: 378px; }

/* ── C.8a — panel box model and content sizing ───────────────────────────────
   Three faults the modal captures exposed:
     the confirm rendered 422 wide against the reference's 378 — the panel is
       content-box, so its 22px padding was being added on top of `inline-size`;
     `.cat-modal-panel` and `.modal-box` are pinned at `min(560px,100%)` by the
       Phase B layer, but `081` measures 778 and `076` measures 480.
   Sized from content within the range the 18 references actually occupy
   (340..918) rather than pinned to one number that fits none of them. */

.modal-overlay > .modal,
.confirm-overlay > .modal,
.cat-modal > .modal,
.cat-modal-panel,
.modal-box { box-sizing: border-box; }

.cat-modal-panel,
.modal-box {
  inline-size: auto;
  min-inline-size: 340px;
  max-inline-size: min(94vw, 940px);
  max-block-size: 86vh;
}

/* ── C.8b — per-screen modal widths ──────────────────────────────────────────
   Content-driven sizing collapsed to `min-content`, because the fields inside
   are block-level at 100% and establish no intrinsic width. The references give
   an exact width per modal, so each is set from its own capture rather than
   from one number:

     081/082 customers 778   085 products 918   086 services 478
     084 users     438   087 expenses 778   088/089 hangers 460
     090 offers    580   091 subscriptions 440  092 shifts  440
     093 settings  640   076 pos suspended 480   079 pos refund 340

   All eighteen centre on x839, which the overlay's centring already gives. */

.cat-modal-panel,
.modal-box { inline-size: 480px; }

.app-main[data-screen="customers"] .cat-modal-panel,
.app-main[data-screen="customers"] .modal-box,
.app-main[data-screen="expenses"] .cat-modal-panel,
.app-main[data-screen="expenses"] .modal-box { inline-size: 778px; }

.app-main[data-screen="products"] .cat-modal-panel,
.app-main[data-screen="products"] .modal-box { inline-size: 918px; }

.app-main[data-screen="services"] .cat-modal-panel,
.app-main[data-screen="services"] .modal-box { inline-size: 478px; }

.app-main[data-screen="users"] .cat-modal-panel,
.app-main[data-screen="users"] .modal-box { inline-size: 438px; }

.app-main[data-screen="hangers"] .cat-modal-panel,
.app-main[data-screen="hangers"] .modal-box { inline-size: 460px; }

.app-main[data-screen="offers"] .cat-modal-panel,
.app-main[data-screen="offers"] .modal-box { inline-size: 580px; }

.app-main[data-screen="subscriptions"] .cat-modal-panel,
.app-main[data-screen="subscriptions"] .modal-box,
.app-main[data-screen="shifts"] .cat-modal-panel,
.app-main[data-screen="shifts"] .modal-box { inline-size: 440px; }

.app-main[data-screen="settings"] .cat-modal-panel,
.app-main[data-screen="settings"] .modal-box { inline-size: 640px; }

#suspendedSalesModal .modal-box { inline-size: 480px; }
#suspendedSaleDetailsModal .modal-box { inline-size: 340px; }

/* The generic `<x-confirm>` dialog stays at its own measured 378. */
.confirm-overlay > .modal { inline-size: 378px; }

/* ── C.8c — the confirm dialog ───────────────────────────────────────────────
   `083` measured, inside a 378x282 dialog at x651..1028 / y384..665:
     warning mark  60x60 at y416, centred on x839
     title         y491..509      message  y537..550
     actions       `إلغاء` on the LEFT, `حذف` (red) on the RIGHT
   The markup emits cancel first, which RTL puts on the right — the reverse of
   the reference — so the action row is reversed. The mark is decorative and is
   painted from the recovered crop rather than added to the markup. */

.confirm-overlay > .modal {
  padding: 32px 24px 27px;
  text-align: center;
}

.confirm-overlay > .modal::before {
  content: "";
  display: block;
  inline-size: 60px;
  block-size: 60px;
  margin: 0 auto 16px;
  background: url("/img/chrome/confirm-warning.png") center / contain no-repeat;
}

.confirm-title {
  margin: 0 0 28px;
  font-size: 16px;
  font-weight: 800;
  color: #0f172a;
}

.confirm-overlay .confirm-modal-msg,
.confirm-overlay > .modal > p:not(.confirm-title) {
  margin: 0 0 26px;
  font-size: 13px;
  line-height: 1.7;
  color: #64748b;
}

.confirm-overlay .modal-actions {
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.confirm-overlay .modal-actions > * { margin: 0; }

.confirm-overlay .btn-cancel,
.confirm-overlay .btn-modal-danger,
.confirm-overlay .btn-danger {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 95px;
  block-size: 40px;
  padding-inline: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #64748b;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}

.confirm-overlay .btn-modal-danger,
.confirm-overlay .btn-danger {
  border-color: #ef4444;
  background: #ef4444;
  color: #fff;
}

/* The reference's message occupies x705..974 inside a dialog at x651..1028 —
   54px of inline padding each side, wrapping to two lines. Mine ran past the
   dialog edge on one line. The action row also inherited a rule the reference
   does not draw. */
.confirm-overlay > .modal { padding-inline: 54px; }

.confirm-overlay .confirm-modal-msg,
.confirm-overlay > .modal > p:not(.confirm-title) {
  white-space: normal;
  overflow-wrap: anywhere;
}

.confirm-overlay .modal-actions {
  border: 0;
  padding: 0;
  margin-block-start: 0;
}

/* ══ D.1 — REPORT SHELL AND TABLES ══════════════════════════════════════════
   Measured off `053_Report_Daily.png` and `054_Report_Period.png`:
     header bar     y0..51, white, 1px #e2e8f0 rule at y52
     filter band    y62..94  #f8fafc, rule at y95        (054)
     summary table  header y118..158 (41px) #eff4fe, text #3b82f6
                    rows 42px, 1px #f1f5f9 rules
                    `summary-total-net` tinted #f0fdf4
                    `summary-net-short` tinted #eff6ff
     payment tiles  222x62 side by side, 11px gap, #f8fafc
     content card   x9..1670

   ⚠ THE REFERENCE REPORTS HAVE NO SIDEBAR. Verified numerically: the sidebar
   chip column holds 38 saturated pixels on `054` against thousands on
   `010`/`050`, and x1450..1660 is uniform page ground. The individual reports
   are full-bleed; only the Reports Hub keeps the shell. This build renders every
   report inside `.app-shell`, so absolute x-geometry cannot match. Removing the
   shell would strip navigation from twelve screens — a behaviour change — so the
   transferable properties (bar height, rules, table treatment, tile geometry)
   are reconstructed and the width difference is recorded, not forced.

   ⚠ HUB AND REPORTS SHARE `data-screen="reports"`. `activeScreen()` maps every
   `reports.*` route to one screen, so per-report backgrounds (053 is #f1f5f9,
   054 is #f8fafc) cannot be scoped apart without a new hook. Left on the hub's
   measured ground rather than guessed.

   ⚠ PDF / Excel / طباعة ARE NOT BUILT. The reference header carries four
   actions; the report DOM emits only `العودة` and `عرض التقرير`. Not added. */

.app-main[data-screen="reports"] .screen-header {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  block-size: 52px;
  min-block-size: 52px;
  margin-block-end: 12px;
  padding: 0 16px;
  border-radius: 0;
  border-block-end: 1px solid #e2e8f0;
  background: #fff;
  box-shadow: none;
}

.app-main[data-screen="reports"] .screen-header h1,
.app-main[data-screen="reports"] .screen-header h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="reports"] .screen-header p { display: none; }

.app-main[data-screen="reports"] .filter-bar {
  block-size: 33px;
  align-items: center;
  padding: 0 16px;
  border-radius: 0;
  border-block-end: 1px solid #e2e8f0;
  background: #f8fafc;
  box-shadow: none;
}

/* ── report tables ───────────────────────────────────────────────────────── */

.app-main[data-screen="reports"] table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.app-main[data-screen="reports"] table thead th {
  block-size: 41px;
  padding-inline: 12px;
  background: #eff4fe;
  color: #3b82f6;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
}

.app-main[data-screen="reports"] table tbody th,
.app-main[data-screen="reports"] table tbody td {
  block-size: 42px;
  padding-inline: 12px;
  border-block-end: 1px solid #f1f5f9;
  color: #334155;
  font-weight: 600;
}

.app-main[data-screen="reports"] table tbody th { text-align: start; }

/* The two rows the reference tints. Targeted by the row key the markup already
   emits, so nothing had to be added to identify them. */
.app-main[data-screen="reports"] tr[data-summary-row="summary-total-net"] > * { background: #f0fdf4; }
.app-main[data-screen="reports"] tr[data-summary-row="summary-net-short"] > * { background: #eff6ff; }

/* ── KPI / payment tiles ─────────────────────────────────────────────────── */

.app-main[data-screen="reports"] .kpi-row {
  display: flex;
  flex-wrap: wrap;
  gap: 11px;
}

.app-main[data-screen="reports"] .kpi-tile {
  box-sizing: border-box;
  flex: 0 0 222px;
  block-size: 62px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 0 14px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #f8fafc;
  box-shadow: none;
}

.app-main[data-screen="reports"] .kpi-label { font-size: 11px; }
.app-main[data-screen="reports"] .kpi-value { font-size: 16px; }

/* ── D.1a — report header bar and filter band, corrected ─────────────────────
   A first reading took the filter band as `#f8fafc` from a sample at x1200,
   which was over page ground rather than the band. Measured at x900 it is
   WHITE, y53..103 (51px), with a 1px #e2e8f0 rule at y104 and the page ground
   starting at y105. Inside it the `عرض التقرير` control is a 124x35 pill in
   #8958f4 at the inline end (x12). The back control is a 90x34 pill in #f23c5c
   sitting INSIDE the header bar, not floating above it. */

.app-main[data-screen="reports"] {
  position: relative;
  padding: 0;
}

.app-main[data-screen="reports"] > .back-btn {
  position: absolute;
  z-index: 3;
  top: 9px;
  left: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  block-size: 34px;
  padding-inline: 18px;
  border-radius: 999px;
  background: #f23c5c;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.app-main[data-screen="reports"] > .back-btn::after {
  content: "\2039";
  direction: ltr;
  unicode-bidi: isolate;
  font-size: 15px;
  line-height: 1;
}

.app-main[data-screen="reports"] .screen-header {
  justify-content: flex-start;
  margin-block-end: 0;
}

.app-main[data-screen="reports"] .filter-bar {
  justify-content: space-between;
  block-size: 51px;
  margin-block-end: 12px;
  padding: 0 12px;
  background: #fff;
}

.app-main[data-screen="reports"] .filter-bar button[type="submit"],
.app-main[data-screen="reports"] .filter-bar .btn-save {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-inline-size: 124px;
  block-size: 35px;
  padding-inline: 18px;
  border: 0;
  border-radius: 999px;
  background: #8958f4;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

.app-main[data-screen="reports"] .settings-card,
.app-main[data-screen="reports"] .empty-state,
.app-main[data-screen="reports"] .card-grid,
.app-main[data-screen="reports"] .hint { margin-inline: 9px; }

/* Report filter fields: two 240x35 controls at x1180..1419 and x1428..1667,
   fill #f8fafc inside a 1px #e2e8f0 rule, on an 9px gap. */
.app-main[data-screen="reports"] .filter-bar label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="reports"] .filter-bar input[type="date"],
.app-main[data-screen="reports"] .filter-bar input[type="datetime-local"],
.app-main[data-screen="reports"] .filter-bar input[type="text"],
.app-main[data-screen="reports"] .filter-bar select {
  box-sizing: border-box;
  inline-size: 240px;
  block-size: 35px;
  padding-inline: 12px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #f8fafc;
  font-size: 12.5px;
  color: #334155;
}

/* ══ D.2 — OFFERS AND CUSTOM PRICES ═════════════════════════════════════════
   Previously left neutral for want of evidence. Investigated per screen, and
   they share NOTHING — which is why reusing another screen's accent would have
   been wrong:

     027 Offers        white header bar; back pill #7062f2; PILL tabs, active
                       filled #6b64f2, 36px tall, group at the reading edge
     012 Custom Prices NAVY #1e40af topbar (confirmed by the corrected
                       multi-point survey at 83%); white 48px tab band beneath;
                       UNDERLINE tabs, active carrying a 3px #7c3aed rule at
                       y102..104

   ⚠ MY PROBE WAS WRONG, NOT THE DOM. The first inspection reported "no active
   marker" on both tab groups because it read `aria-current`. The markup emits
   `aria-selected="true"|"false"` on `[role="tab"]`, which is the correct hook
   and is what these rules use. The instrument was fixed, not the page. */

/* ── Offers: pill tabs ───────────────────────────────────────────────────── */

.offers-tabs {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  margin-block-end: 14px;
}

.offers-tabs [role="tab"] {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  block-size: 36px;
  padding-inline: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #475569;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.offers-tabs [role="tab"][aria-selected="true"] {
  border-color: #6b64f2;
  background: #6b64f2;
  color: #fff;
}

.app-main[data-screen="offers"] .cat-toolbar .btn-save,
.app-main[data-screen="offers"] .back-btn { color: #7062f2; }

/* ── Custom Prices: navy topbar + underline tabs ─────────────────────────── */

.app-main[data-screen="customer-custom-prices"] {
  position: relative;
  padding: 0;
  padding-block-start: 57px;
  background: #eff2ff;
}

.app-main[data-screen="customer-custom-prices"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 57px;
  background: #1e40af;
}

.app-main[data-screen="customer-custom-prices"] > .back-btn {
  position: absolute;
  z-index: 1;
  inset-block-start: 11px;
  inset-inline-end: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 35px;
  padding-inline: 16px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.app-main[data-screen="customer-custom-prices"] > h1 {
  position: absolute;
  z-index: 1;
  inset-block-start: 0;
  inset-inline-start: 16px;
  display: flex;
  align-items: center;
  inline-size: auto;
  block-size: 57px;
  margin: 0;
  padding: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  color: #fff;
  font-size: 16px;
  font-weight: 800;
}

.ccp-tabs {
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  gap: 26px;
  block-size: 48px;
  margin: 0 0 14px;
  padding-inline: 16px;
  background: #fff;
  border-block-end: 1px solid #e2e8f0;
}

.ccp-tabs [role="tab"] {
  display: inline-flex;
  align-items: center;
  border-block-end: 3px solid transparent;
  color: #64748b;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;
}

.ccp-tabs [role="tab"][aria-selected="true"] {
  border-block-end-color: #7c3aed;
  color: #7c3aed;
}

/* ── D.2a — Offers header bar ────────────────────────────────────────────────
   Measured: white bar y0..50 with a 1px #e2e8f0 rule at y51, page ground
   #f1f5f9 from y57; back pill #6b64f2 94x33 at x16; title and its #7262f3 mark
   at the reading edge.

   ⚠ `إنشاء عرض جديد` IS NOT MOVED. The reference places it in the header bar at
   x120..256 (#20c05b); the DOM emits it inside `.offers-panel`, and there are
   TWO such buttons — one per tab panel — so hoisting one into the bar by
   absolute offsets would misrepresent which panel it belongs to and break when
   the other tab is selected. Recorded as a structural difference. */

.app-main[data-screen="offers"] {
  position: relative;
  padding: 0;
  padding-block-start: 57px;
  background: #f1f5f9;
}

.app-main[data-screen="offers"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 51px;
  background: #fff;
  border-block-end: 1px solid #e2e8f0;
}

.app-main[data-screen="offers"] > .back-btn {
  position: absolute;
  z-index: 1;
  top: 9px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  inline-size: 94px;
  block-size: 33px;
  border-radius: 999px;
  background: #6b64f2;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.app-main[data-screen="offers"] > .back-btn::after {
  content: "\2039";
  direction: ltr;
  unicode-bidi: isolate;
  font-size: 15px;
  line-height: 1;
}

.app-main[data-screen="offers"] > h1 {
  position: absolute;
  z-index: 1;
  inset-block-start: 0;
  inset-inline-start: 16px;
  display: flex;
  align-items: center;
  inline-size: auto;
  block-size: 51px;
  margin: 0;
  padding: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  color: #0f172a;
  font-size: 16px;
  font-weight: 800;
}

.app-main[data-screen="offers"] .offers-tabs,
.app-main[data-screen="offers"] .offers-panel { margin-inline: 16px; }

/* ══ D.3 — remaining list-screen grounds ════════════════════════════════════
   Four screens returned low modal agreement (subscriptions 8%, consumption
   receipts 19%, shifts 21%, credit invoices 25%). That was not weak evidence —
   it was the wrong model: sampling the gutter down the page shows each is a
   subtle VERTICAL GRADIENT. Reproduced from their own endpoints rather than
   flattened to one sampled colour.

     credit-invoices       #e7eff4 → #dbe9f1
     subscriptions         #e9f1f6 → #dbe9f1
     shifts                #e8f0f5 → #dbe9f1   (was flat #e7eff4)
     consumption-receipts  #f0fdfa → #ecfbf8 → #f0fdfa  (a distinct mint)
     zatca-settings        #f4f8f6 flat (90% agreement) */

.app-main[data-screen="credit-invoices"] {
  background: linear-gradient(180deg, #e7eff4 0%, #e7eff4 70%, #dbe9f1 100%);
}

.app-main[data-screen="subscriptions"] {
  background: linear-gradient(180deg, #e9f1f6 0%, #e6eef3 30%, #dbe9f1 100%);
}

.app-main[data-screen="shifts"] {
  background: linear-gradient(180deg, #e8f0f5 0%, #e6eef4 30%, #dbe9f1 100%);
}

.app-main[data-screen="consumption-receipts"] {
  background: linear-gradient(180deg, #f0fdfa 0%, #ecfbf8 40%, #f0fdfa 100%);
}

.app-main[data-screen="zatca-settings"] { background: #f4f8f6; }

/* The gradients above are painted over `.app-main`'s full SCROLL height, but
   the reference captures one viewport — so on a long page the visible band was
   only the gradient's first third, and three screens measured worse than the
   flat colour they replaced. Anchoring to the viewport reproduces what the
   capture shows. */
.app-main[data-screen="credit-invoices"],
.app-main[data-screen="subscriptions"],
.app-main[data-screen="shifts"],
.app-main[data-screen="consumption-receipts"] { background-attachment: fixed; }

/* ══ D.4 — SETTINGS SHELL AND TAB COLUMN ════════════════════════════════════
   Measured off `041_Settings_Tab_الضريبة_والفوترة.png`:
     header bar   y0..50 white, 1px #d5e3ec rule at y51, ground #e9f1f6
     tab column   x1447..1672 (226 wide), rows on a white card
     active tab   fill #e3f3fb

   ⚠ SAME STRUCTURAL CONSTRAINT AS REPORTS. In the reference the settings tab
   column OCCUPIES the sidebar position and there is no app sidebar; this build
   renders both, so the column sits inboard of it and absolute x-geometry cannot
   match. The transferable properties are reconstructed; the extra column is
   recorded, not forced away — removing the sidebar would strip navigation. */

.app-main[data-screen="settings"] {
  position: relative;
  padding: 0;
  padding-block-start: 57px;
}

.app-main[data-screen="settings"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 51px;
  background: #fff;
  border-block-end: 1px solid #d5e3ec;
}

.app-main[data-screen="settings"] > .back-btn {
  position: absolute;
  z-index: 1;
  top: 9px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 33px;
  padding-inline: 16px;
  border: 1px solid #e2e8f0;
  border-radius: 999px;
  background: #fff;
  color: #475569;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.app-main[data-screen="settings"] .settings-tabs {
  box-sizing: border-box;
  inline-size: 226px;
  flex: 0 0 226px;
  padding: 8px;
  border: 0;
  border-radius: 12px;
  background: #fff;
}

.app-main[data-screen="settings"] .settings-tab {
  block-size: 38px;
  display: flex;
  align-items: center;
  padding-inline: 12px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  color: #475569;
}

.app-main[data-screen="settings"] .settings-tab[aria-current="page"] {
  background: #e3f3fb;
  color: #0c6d9e;
  font-weight: 700;
}

/* ══ D.5 — the standalone expenses modal ════════════════════════════════════
   `#expenseModal` is `<section class="modal">` at top level — it is BOTH scrim
   and dialog, with no inner panel element. The generic overlay treatment
   therefore painted the form directly onto the dark scrim: it measured 560 wide
   at centre 1400 against the reference's 778 centred on 839.

   It is the only standalone `.modal` in the codebase (every other one is nested
   inside an overlay), so it is targeted by id rather than by a fragile
   "not inside an overlay" selector. The element becomes the centred panel and
   carries its own scrim behind it. */

.modal.expense-modal {
  position: fixed;
  inset: auto;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  box-sizing: border-box;
  inline-size: 778px;
  max-inline-size: 94vw;
  max-block-size: 86vh;
  overflow: auto;
  padding: 22px;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 20px 45px rgba(15, 23, 42, 0.25);
}

.modal.expense-modal::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: rgba(44, 52, 82, 0.59);
}

/* ══ E.1 — FULL-BLEED REPORTS AND SETTINGS ══════════════════════════════════
   Investigated before changing anything, including the hypothesis that the
   reference simply had the sidebar COLLAPSED — this build supports that
   (`gsbToggle`, `localStorage.sidebar_collapsed`), and a collapsed rail still
   paints its coloured chips. Counting saturated pixels in the chip column
   x1628..1663 settles it:

     050 Reports Hub   9646   full sidebar
     010 Customers     9646   full sidebar
     054 Report Period   19   nothing — not collapsed, ABSENT
     053 Report Daily   742   full-bleed report content, no sidebar
     041 Settings Tab   561   the settings tab column's own icons, no sidebar

   So the individual reports and every settings screen are full-bleed in the
   audited product, while the Reports HUB keeps the shell. Each of those screens
   carries its own `العودة` control — the reports a #f23c5c pill, settings a
   white one — which is the navigation affordance the product substitutes.

   ⚠ SCOPED TO EXCLUDE THE HUB. Hub and reports share `data-screen="reports"`,
   so the hub is excluded by the `.card-grid` it alone renders — evidence from
   the DOM, not a guess. Nothing is removed from the markup; the shell simply
   stops reserving the sidebar track on these screens. */

.app-shell:has(> .app-main[data-screen="reports"]):not(:has(.card-grid)),
.app-shell:has(> .app-main[data-screen="settings"]) {
  grid-template-columns: 1fr;
}

.app-shell:has(> .app-main[data-screen="reports"]):not(:has(.card-grid)) > .app-sidebar,
.app-shell:has(> .app-main[data-screen="settings"]) > .app-sidebar {
  display: none;
}

/* ══ E.2 — REPORTS HUB CARD GRID ════════════════════════════════════════════
   `050_Reports_Hub.png` measured: cards 260x222 on a 280px column pitch and a
   244px row pitch, five columns from x36, first row y88. Inside a card the
   illustration occupies 84x92 at top+18, then the title, then a three-line
   description.

   RTL FLOW VERIFIED, NOT ASSUMED. Reading the reference's labels right to left
   gives daily, period, expenses, credit-invoices, all-invoices — which is the
   DOM order, so this grid genuinely flows RTL. (The dashboard tile grid and the
   POS product grid flow LTR; that difference is why each is checked.)

   ⚠ ONE ORDER DIFFERENCE. The reference places `تقرير إقفالات الوردیات` LAST
   (row 3), while the markup emits it sixth. Corrected with a single `order` on
   its own href — no markup change, and every other card keeps DOM order.

   The twelve illustrations are recovered from this same capture, and the
   mapping was checked semantically before use: calendar-31 for daily, pie for
   types, scales for zakat, clock for shifts, building for hotels-companies. */

.app-main[data-screen="reports"] .card-grid {
  display: grid;
  grid-template-columns: repeat(5, 260px);
  column-gap: 20px;
  row-gap: 22px;
  justify-content: start;
  align-content: start;
  margin-inline: 36px;
}

.app-main[data-screen="reports"] .report-card {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  inline-size: 260px;
  block-size: 222px;
  padding: 18px 16px 14px;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.08);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
}

.app-main[data-screen="reports"] .report-card::before {
  content: "";
  display: block;
  inline-size: 84px;
  block-size: 92px;
  margin-block-end: 12px;
  background: center / contain no-repeat;
}

.app-main[data-screen="reports"] .report-card h2,
.app-main[data-screen="reports"] .report-card h3,
.app-main[data-screen="reports"] .report-card strong {
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="reports"] .report-card p,
.app-main[data-screen="reports"] .report-card span {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.55;
  color: #64748b;
}

/* The reference puts the shift-closing report last; the markup emits it sixth. */
.app-main[data-screen="reports"] .report-card[href$="/reports/shifts"] { order: 1; }

.app-main[data-screen="reports"] .report-card[href$="/reports/daily"]::before { background-image: url("/img/reports/daily.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/period"]::before { background-image: url("/img/reports/period.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/expenses"]::before { background-image: url("/img/reports/expenses.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/credit-invoices"]::before { background-image: url("/img/reports/credit-invoices.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/all-invoices"]::before { background-image: url("/img/reports/all-invoices.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/subscriptions"]::before { background-image: url("/img/reports/subscriptions.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/types"]::before { background-image: url("/img/reports/types.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/worker"]::before { background-image: url("/img/reports/worker.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/customer-account"]::before { background-image: url("/img/reports/customer-account.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/hotels-companies"]::before { background-image: url("/img/reports/hotels-companies.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/zakat"]::before { background-image: url("/img/reports/zakat.png"); }
.app-main[data-screen="reports"] .report-card[href$="/reports/shifts"]::before { background-image: url("/img/reports/shifts.png"); }

/* ══ E.3 — ZATCA SETTINGS ═══════════════════════════════════════════════════
   Measured off `038_ZATCA_Settings.png`: white header bar with a #0f766e mark
   at x1370..1411, page ground #f7faf9, cards tinted #edf5f2. The screen renders
   as an unstyled label/field list today; the vocabulary it already emits
   (`.form-grid`, `.field`, `.field-input`, `.integration-overview`,
   `.connection-config`, `.onboarding-header`, `.metric-item`) is styled here.

   Its accent is teal and belongs to this screen alone — Settings is #e3f3fb,
   Reports #8958f4, Offers #6b64f2. Nothing is shared between them. */

.app-main[data-screen="zatca-settings"] {
  position: relative;
  padding: 0;
  padding-block-start: 57px;
  background: #f7faf9;
}

.app-main[data-screen="zatca-settings"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 51px;
  background: #fff;
  border-block-end: 1px solid #dbe8e4;
}

.app-main[data-screen="zatca-settings"] > .back-btn {
  position: absolute;
  z-index: 1;
  top: 9px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 33px;
  padding-inline: 16px;
  border: 1px solid #dbe8e4;
  border-radius: 999px;
  background: #fff;
  color: #0f766e;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.app-main[data-screen="zatca-settings"] > h1,
.app-main[data-screen="zatca-settings"] > h2 {
  position: absolute;
  z-index: 1;
  inset-block-start: 0;
  inset-inline-start: 16px;
  display: flex;
  align-items: center;
  inline-size: auto;
  block-size: 51px;
  margin: 0;
  padding: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  color: #0f766e;
  font-size: 16px;
  font-weight: 800;
}

.app-main[data-screen="zatca-settings"] .integration-overview,
.app-main[data-screen="zatca-settings"] .connection-config,
.app-main[data-screen="zatca-settings"] .onboarding-header {
  margin: 0 16px 14px;
  padding: 16px 18px;
  border: 1px solid #dbe8e4;
  border-radius: 14px;
  background: #fff;
}

.app-main[data-screen="zatca-settings"] .metric-item {
  display: inline-flex;
  flex-direction: column;
  gap: 4px;
  min-inline-size: 140px;
  padding: 10px 14px;
  border-radius: 10px;
  background: #edf5f2;
  font-size: 12px;
  color: #64748b;
}

.app-main[data-screen="zatca-settings"] .numeric-value {
  font-size: 15px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="zatca-settings"] .form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 18px;
  margin: 0 16px 14px;
  padding: 16px 18px;
  border: 1px solid #dbe8e4;
  border-radius: 14px;
  background: #fff;
}

.app-main[data-screen="zatca-settings"] .field-wide { grid-column: 1 / -1; }

.app-main[data-screen="zatca-settings"] .field label,
.app-main[data-screen="zatca-settings"] .field > span {
  display: block;
  margin-block-end: 6px;
  font-size: 12px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="zatca-settings"] .field-input {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 38px;
  padding-inline: 12px;
  border: 1px solid #dbe8e4;
  border-radius: 8px;
  background: #f7faf9;
  font-size: 13px;
  color: #0f172a;
}

.app-main[data-screen="zatca-settings"] .field-hint {
  margin: 6px 0 0;
  font-size: 11px;
  color: #94a3b8;
}

/* ══ E.4 — MODAL INTERIORS ══════════════════════════════════════════════════
   The modal LAYER (scrim, centring, per-screen widths) was verified earlier.
   This is the interior chrome, measured across six references:

     head height  68px on products/services/expenses/offers; 90px on customers
     head rule    1px #e2e8f0 beneath, body white below it
     head tint    PER SCREEN, and it carries that screen's accent —
                  customers #ffffff · products #f1fbfa · services #f4f3fe
                  expenses  #fff5f7 · hangers #ffffff · offers #ffffff
     close mark   at the inline end of the head

   The tints were checked before being applied: assuming one shared header tint
   would have painted four screens wrong, which is the same mistake the list
   chrome made in Phase 2. */

.cat-modal-head,
.modal-head,
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  box-sizing: border-box;
  block-size: 68px;
  margin: 0;
  padding-inline: 20px;
  border-block-end: 1px solid #e2e8f0;
  background: #fff;
}

.cat-modal-head h2,
.modal-head h2,
.modal-header h2,
.cat-modal-head .modal-title,
.modal-title {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  color: #0f172a;
}

.modal-close-btn,
.cat-modal-head button[type="button"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 30px;
  block-size: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #64748b;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.app-main[data-screen="customers"] .cat-modal-head { block-size: 90px; }
.app-main[data-screen="products"] .cat-modal-head { background: #f1fbfa; }
.app-main[data-screen="services"] .cat-modal-head { background: #f4f3fe; }
.app-main[data-screen="expenses"] .cat-modal-head,
.modal.expense-modal > h2 { background: #fff5f7; }

/* ── modal body ──────────────────────────────────────────────────────────── */

.cat-modal-panel > form,
.cat-modal-panel > div:not(.cat-modal-head):not(.cat-modal-foot),
.modal-body { padding: 18px 20px; }

.cat-modal-panel .field,
.modal-box .field { margin-block-end: 16px; }

.cat-modal-panel .field label,
.modal-box .field label {
  display: block;
  margin-block-end: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.cat-modal-panel .field input[type="text"],
.cat-modal-panel .field input[type="number"],
.cat-modal-panel .field input[type="tel"],
.cat-modal-panel .field input[type="email"],
.cat-modal-panel .field input[type="date"],
.cat-modal-panel .field select,
.cat-modal-panel .field textarea,
.modal-box .field input[type="text"],
.modal-box .field select {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 40px;
  padding-inline: 12px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  font-size: 13px;
  color: #0f172a;
}

.cat-modal-panel .field textarea { block-size: auto; min-block-size: 76px; padding-block: 10px; }

/* ── modal footer ────────────────────────────────────────────────────────── */

.cat-modal-foot,
.modal-footer,
.cat-modal-panel .modal-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 20px;
  border-block-start: 1px solid #e2e8f0;
}

.cat-modal-foot .btn-save,
.modal-footer .btn-save,
.cat-modal-panel .modal-actions .btn-save {
  block-size: 40px;
  padding-inline: 22px;
  border: 0;
  border-radius: 10px;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

.cat-modal-foot .btn-outline,
.modal-footer .btn-outline,
.cat-modal-panel .modal-actions .btn-outline,
.btn-modal-cancel {
  block-size: 40px;
  padding-inline: 22px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #64748b;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

/* ── E.4a — modal field treatment ────────────────────────────────────────────
   Underline inputs, not boxes. Confirmed on THREE references before applying —
   `081` Customers, `085` Products and `086` Services all show a bottom rule
   only, on a white field with no border box:

     label      #7c3aed
     rule       #e8edf5, and #7c3aed on the focused field
     field bg   #ffffff

   The earlier boxed treatment came from the generic form styling, not from any
   modal reference. */

.cat-modal-panel .field input[type="text"],
.cat-modal-panel .field input[type="number"],
.cat-modal-panel .field input[type="tel"],
.cat-modal-panel .field input[type="email"],
.cat-modal-panel .field input[type="date"],
.cat-modal-panel .field select,
.modal-box .field input[type="text"],
.modal-box .field select {
  block-size: 38px;
  padding-inline: 2px;
  border: 0;
  border-block-end: 1px solid #e8edf5;
  border-radius: 0;
  background: #fff;
}

.cat-modal-panel .field input:focus,
.cat-modal-panel .field select:focus,
.modal-box .field input:focus {
  outline: none;
  border-block-end-color: #7c3aed;
}

.cat-modal-panel .field label,
.modal-box .field label {
  color: #7c3aed;
  font-size: 12px;
}

.cat-modal-panel .field .required-mark,
.modal-box .field .required-mark { color: #ef4444; }

/* ══ E.5 — SHIFT KPI TILES ══════════════════════════════════════════════════
   `025_Shifts.png` measured with a tolerance detector (the tiles are near-white
   over a #e1f1fa ground, so exact-white matching found nothing): five columns,
   tiles 265 wide on a 283 pitch from x18. The markup already emits
   `.shift-tiles`, `.shift-tile`, `.shift-tile-label` and `.shift-tile-value`,
   with a `data-tile` key per tile, so no markup was needed. */

.app-main[data-screen="shifts"] { background: #e1f1fa; }

.app-main[data-screen="shifts"] .shift-group-title {
  margin: 0 0 12px;
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="shifts"] .shift-tiles {
  display: grid;
  grid-template-columns: repeat(5, 265px);
  gap: 18px;
  justify-content: start;
}

.app-main[data-screen="shifts"] .shift-tile {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  inline-size: 265px;
  block-size: 150px;
  padding: 18px 20px;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.08);
}

.app-main[data-screen="shifts"] .shift-tile-label {
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="shifts"] .shift-tile-value {
  font-size: 20px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="shifts"] .shift-group { margin: 0 18px 20px; }
.app-main[data-screen="shifts"] .shift-header { margin: 0 18px 18px; }

/* ── E.5a — the shift-close modal input ──────────────────────────────────────
   `092` does NOT use the underline field the customers/products/services
   modals use. Its counted-cash field is a BOX: x649..1030 (382 wide), 78 tall,
   rule #dbeef7, with the figure set large and centred. Its primary action is
   #0b7ba4, not the generic save colour.

   This is the third time in this project that a treatment verified on several
   screens turned out not to be universal — so it is scoped to the screen the
   evidence covers rather than widened. */

.app-main[data-screen="shifts"] .cat-modal-panel .field input[type="number"],
.app-main[data-screen="shifts"] .cat-modal-panel .field input[type="text"] {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 78px;
  padding-inline: 16px;
  border: 1px solid #dbeef7;
  border-radius: 12px;
  background: #fff;
  font-size: 26px;
  font-weight: 700;
  text-align: center;
  color: #0f172a;
}

.app-main[data-screen="shifts"] .cat-modal-panel .field label { color: #0b7ba4; }

.app-main[data-screen="shifts"] .cat-modal-foot .btn-save { background: #0b7ba4; color: #fff; }

/* ── E.6 — the POS suspended-sales modal ─────────────────────────────────────
   `076` measured: the search field spans the panel, the empty message and its
   hint are centred, and the close mark sits in a rounded light box. Scoped to
   this modal — `086` Services shows a plain close mark, so the boxed treatment
   is not shared. */

#suspendedSalesModal .modal-box input[type="search"],
#suspendedSalesModal .modal-box input[type="text"] {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 40px;
  padding-inline: 14px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
}

#suspendedSalesModal .modal-body,
#suspendedSalesModal .modal-box > div:not(.modal-header):not(.modal-footer) {
  text-align: center;
}

#suspendedSalesModal .modal-close-btn {
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #f8fafc;
}

/* The Offers screen has TWO modals at DIFFERENT widths — `090` the general
   offer at 580, `030` the product offer at 680 (measured 680x770, centre 839).
   The per-screen width therefore cannot cover both; the second is set by id. */
#product-offer-create .cat-modal-panel { inline-size: 680px; }

/* ── E.7 — hangers modals ────────────────────────────────────────────────────
   `088`/`089` measured: the primary action is AMBER #f1990a (73 wide), and the
   fields are full-width BOXES (423 inside a 460 dialog), not the underline the
   customers/products/services modals use. `.hanger-grid` is the batch modal's
   two-column container, already in the markup.

   Fourth screen whose modal declines the "shared" underline treatment — the
   pattern is that each screen's modal follows its own accent, so it is scoped. */

.app-main[data-screen="hangers"] .cat-modal-panel .field input[type="text"],
.app-main[data-screen="hangers"] .cat-modal-panel .field input[type="number"],
.app-main[data-screen="hangers"] .cat-modal-panel .field textarea {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 44px;
  padding-inline: 14px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
}

.app-main[data-screen="hangers"] .cat-modal-panel .field textarea {
  block-size: auto;
  min-block-size: 76px;
  padding-block: 10px;
}

.app-main[data-screen="hangers"] .cat-modal-panel .field label { color: #64748b; }

.app-main[data-screen="hangers"] .cat-modal-foot .btn-save {
  background: #f1990a;
  color: #fff;
}

.hanger-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 16px;
}

/* ── E.7a — hangers modals, corrected selectors ──────────────────────────────
   The E.7 rules matched nothing. Probing the open dialog shows why: the panel
   is a FORM using `.settings-grid` with BARE `<label>`/`<input>` — no `.field`
   wrapper — and its footer is `.modal-actions`, not `.cat-modal-foot`.
   `.hanger-grid` appears in the Blade but not in the rendered modal at all.

   The batch dialog uses the same `.settings-grid`, so its two-column layout is
   keyed on the modal id rather than on a class the two share. */

.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid label {
  display: block;
  margin-block-end: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid input,
.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid textarea {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 44px;
  margin-block-end: 14px;
  padding-inline: 14px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
}

.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid textarea {
  block-size: auto;
  min-block-size: 76px;
  padding-block: 10px;
}

.app-main[data-screen="hangers"] .modal-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 20px;
  border-block-start: 1px solid #e2e8f0;
}

.app-main[data-screen="hangers"] .modal-actions .btn-save {
  block-size: 40px;
  padding-inline: 22px;
  border: 0;
  border-radius: 10px;
  background: #f1990a;
  color: #fff;
  font-size: 13.5px;
  font-weight: 700;
}

#hanger-batch .settings-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px;
}

/* ── E.7b — the hangers batch modal ──────────────────────────────────────────
   `089` uses `.batch-range` — not `.settings-grid` — holding two unclassed
   field cells, followed by `.batch-preview`. The E.7a rules missed it for the
   same reason E.7 missed the add modal: the class was assumed from the Blade
   rather than read from the rendered dialog. Probed and corrected.

   Reference: two columns, boxed inputs filling their cell, and a grey rounded
   summary strip beneath. */

.app-main[data-screen="hangers"] .batch-range {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px;
}

.app-main[data-screen="hangers"] .batch-range > div { display: block; }

.app-main[data-screen="hangers"] .batch-range label {
  display: block;
  inline-size: auto;
  margin-block-end: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="hangers"] .batch-range input {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 44px;
  padding-inline: 14px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
}

.app-main[data-screen="hangers"] .batch-preview {
  margin: 14px 0 0;
  padding: 14px;
  border-radius: 10px;
  background: #f1f5f9;
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  color: #475569;
}

/* `088` measured 561 tall against the reference's 436 — the field rhythm was
   too loose once the boxes were added. */
.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid input,
.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid textarea {
  margin-block-end: 10px;
}

.app-main[data-screen="hangers"] .cat-modal-panel .settings-grid textarea {
  min-block-size: 64px;
}

/* ── E.8 — the expenses modal is a PANEL, so it must not be a flex row ───────
   `#expenseModal` is the only standalone `.modal`, and D.5 already made it the
   centred panel. But `.modal.is-open { display: flex }` — the rule that centres
   a real overlay's dialog — still applied to it, so its `<h2>` and `<form>`
   became flex SIBLINGS: the title collapsed to 85px and wrapped vertically
   while the form took the remaining 649, and the scrim showed through the gaps.

   Probed rather than guessed: computed style reported `display: flex` on a
   778x225 white panel with an 85-wide heading. It needs block flow, with the
   heading as a full-width header bar carrying the screen's own #fff5f7 tint. */

.modal.expense-modal.is-open { display: block; }

.modal.expense-modal > h2 {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  inline-size: auto;
  block-size: 68px;
  margin: -22px -22px 0;
  padding-inline: 20px;
  border-start-start-radius: 14px;
  border-start-end-radius: 14px;
  border-block-end: 1px solid #e2e8f0;
  background: #fff5f7;
  font-size: 16px;
  font-weight: 800;
  color: #0f172a;
}

.modal.expense-modal > form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 18px;
  padding-block-start: 18px;
}

.modal.expense-modal > form label {
  display: block;
  margin-block-end: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: #be123c;
}

.modal.expense-modal > form input[type="text"],
.modal.expense-modal > form input[type="number"],
.modal.expense-modal > form input[type="datetime-local"],
.modal.expense-modal > form input[type="date"],
.modal.expense-modal > form select,
.modal.expense-modal > form textarea {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 40px;
  padding-inline: 2px;
  border: 0;
  border-block-end: 1px solid #e8edf5;
  border-radius: 0;
  background: #fff;
  font-size: 13px;
}

.modal.expense-modal > form textarea { block-size: auto; min-block-size: 56px; }

/* ── E.8a — the expenses scrim was painting over its own panel ───────────────
   `#expenseModal` is `position: fixed` with a `transform`, so it establishes a
   stacking context. A `::before` at `z-index: -1` inside it paints ABOVE the
   element's own background and below its content — which is exactly what the
   capture showed: a white 778px panel with the scrim laid over it and the
   labels floating on grey.

   Replaced with a spread box-shadow, which paints the scrim OUTSIDE the panel
   and needs no child element at all.

   The form's label/input pairs have no wrappers, so a two-column grid alternates
   them into label-column / input-column rather than pairing them. Block flow
   keeps each label above its own field; the reference's two-column arrangement
   is recorded as a remaining difference rather than faked. */

.modal.expense-modal::before { content: none; }

.modal.expense-modal {
  box-shadow:
    0 0 0 100vmax rgba(44, 52, 82, 0.59),
    0 20px 45px rgba(15, 23, 42, 0.25);
}

.modal.expense-modal > form {
  display: block;
  padding-block-start: 18px;
}

.modal.expense-modal > form label { margin-block-start: 14px; }

/* ── E.9 — modal toggle switches ─────────────────────────────────────────────
   `085` renders `المنتج نشط` and `إضافة مزرام` as switches, ~50x24, teal when
   on. The DOM emits real `<input type="checkbox">` — probed and confirmed — so
   this is a styling reconstruction of an existing control, not a new one: the
   element, its name, its value and its checked semantics are untouched.

   ⚠ THE PRICING REPEATER AND `ترجمة` BUTTON ARE NOT BUILT. Probing the open
   dialog returns no table and no price markup at all. `085`/`032` are therefore
   a STRUCTURAL DIFFERENCE, not a styling gap, and nothing was invented. */

.cat-modal-panel input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  inline-size: 50px;
  block-size: 24px;
  margin: 0;
  border: 0;
  border-radius: 999px;
  background: #cbd5e1;
  cursor: pointer;
  transition: background 120ms ease;
  vertical-align: middle;
}

.cat-modal-panel input[type="checkbox"]::after {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: #fff;
  transition: inset-inline-start 120ms ease;
}

.cat-modal-panel input[type="checkbox"]:checked { background: #10b981; }
.cat-modal-panel input[type="checkbox"]:checked::after { inset-inline-start: 29px; }

.cat-modal-panel label:has(> input[type="checkbox"]),
.cat-modal-panel .settings-grid label:has(+ input[type="checkbox"]) {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

/* ══════════════════════════════════════════════════════════════════════════
   PHASE 49 — F.1 · compact and narrow layouts
   ══════════════════════════════════════════════════════════════════════════

   SCOPE DISCIPLINE. Every rule below lives inside a `max-width` query, so at
   the 1680x1050 and 1366x768 widths the reference captures were taken at,
   nothing here applies and desktop rendering is byte-identical. Phase 50's
   screen-by-screen comparison against all 102 screenshots is therefore not
   affected by any of it.

   The breakpoints are the ones this build already established, not new ones —
   the corpus specifies no pixel value anywhere. TESTING_STRATEGY.md:114 asks
   only for ">= 1366x768 plus one narrow profile", and TS-UA-02 (:1161) for
   "the page body NEVER scrolls horizontally" at both.

       60rem  the sidebar rail folds        (Phase 03)
       55rem  the settings tab column folds (Phase 12)
       40rem  the dashboard header wraps    (Phase 11)

   Logical properties throughout, per the Phase 03 ratchet.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── F.1a — the body never scrolls horizontally ───────────────────────────
   TS-UA-02 is about the BODY, not about content being unreachable. Nineteen
   views already wrap their table in `.table-scroll`; twenty-two do not, and a
   wide table in an unwrapped view is what pushes the body sideways.

   `overflow-x: hidden` on the body would satisfy the letter of the rule by
   making columns unreachable, which is worse than the bug. So the TABLE is
   made its own scroll container instead, and only at narrow widths.

   Print documents are excluded: `documents/*` renders through its own layout
   at a fixed paper width and must not become scrollable. */
@media (max-width: 60rem) {
  .app-main table {
    display: block;
    max-inline-size: 100%;
    overflow-x: auto;
  }
}

/* ── F.1b — the POS products/cart toggle ──────────────────────────────────
   Phase 49's UI clause names this control explicitly. The markup already
   declares the contract: `.pos-mobile-toggle[role="tablist"]` holds two
   `role="tab"` buttons carrying `data-pos-view="products"` and `"cart"`, and
   the panes are `.pos-grid` and `.pos-sale-panel`. Above 1024px the toggle is
   hidden and BOTH panes show, which is what the desktop reference captures.

   Below it, one pane shows at a time. The selected pane is driven by a
   `data-pos-view` attribute on the POS root, so the CSS has a single source of
   truth and the script only ever sets one value. */
@media (max-width: 1023.98px) {
  .pos-layout[data-pos-view="products"] .pos-sale-panel,
  .pos-layout[data-pos-view="cart"] .pos-grid {
    display: none;
  }

  .pos-mobile-toggle {
    display: flex;
    gap: 0.25rem;
    position: sticky;
    inset-block-end: 0;
  }

  .pos-mobile-toggle button {
    flex: 1 1 50%;
  }
}

/* ── F.1c — the three genuine horizontal overflows, found with Dusk ────────
   TS-UA-02 says "the page body NEVER scrolls horizontally". Measured in a real
   browser at five widths, three elements broke it. None was visible in a
   side-by-side screenshot comparison, because in RTL the overflow escapes to
   the LEFT — the side a left-to-right reader is not watching.

   1. THE DASHBOARD TILE GRID. `repeat(5, var(--ui-tile-w))` pins five 256px
      columns that cannot shrink, so the dashboard overflowed by 137px at
      1366x768 and by 500px at 640. `auto-fit` with the SAME fixed track keeps
      the reference geometry exactly — at the 1680 capture width the content box
      is 1409px and 5*256 + 4*20 = 1360 fits while a sixth would need 1636, so
      the browser still lays out exactly five — and reflows to 4/3/2 below it.
      The tile ORDER is untouched; only how many share a line changes.

   2. SETTINGS FORM CONTROLS. `.field input/select/textarea` set `width: 100%`
      with 0.75rem of inline padding and a 1px border but no `box-sizing`, so
      every control computed 26px WIDER than the column that contains it. This
      one overflowed at every width, 1680 included.

   3. THE CATALOGUE SEARCH FIELD. A fixed 386px `inline-size` inside a toolbar
      whose content box is 333px at the narrow profile. */

.menu-grid {
  grid-template-columns: repeat(auto-fit, var(--ui-tile-w));
}

.field input,
.field select,
.field textarea {
  box-sizing: border-box;
}

.cat-search input[type="search"] {
  max-inline-size: 100%;
}

/* ── F.1d — the catalogue toolbar at narrow widths ────────────────────────
   `.cat-toolbar` is a fixed 76px flex ROW that does not wrap, holding a 386px
   search field and an action button. Below roughly 700px those no longer fit
   the 333px content box, and because a flex item defaults to `min-width: auto`
   the search refuses to shrink and escapes the inline-end edge instead.

   `max-inline-size: 100%` on the input alone does nothing here: the FORM is
   itself content-sized as a non-shrinking flex item, so 100% resolves to the
   386px that is the problem. The constraint has to be released at the flex
   level — hence `min-inline-size: 0` — and only then does the field follow.

   Scoped to <= 60rem and written with the same selector shape as the desktop
   rule, so at every reference width the cascade lands exactly where it did. */
@media (max-width: 60rem) {
  .app-main[data-screen="customers"] > .cat-toolbar,
  .app-main[data-screen="hotels-companies"] > .cat-toolbar {
    flex-wrap: wrap;
    block-size: auto;
    padding-block: 12px;
  }

  .cat-search {
    min-inline-size: 0;
    flex: 1 1 100%;
  }

  .cat-search input[type="search"] {
    inline-size: 100%;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PRINT — keep the interface off the paper.

   `documents/layout.blade.php` has carried print rules since Phase 25, but the
   APP SHELL never did, and the shell is what `shifts/receipt.blade.php` and the
   report screens extend. So the already-shipped receipt print button put the
   sidebar, the top nav, every button and the toast region onto paper. These
   rules repair that; they are not new behaviour for a new control.

   ⚠ HERE AND NOT INLINE IN THE SHELL. An inline <style> was tried first and is
   wrong twice over: it puts stylesheet text into every rendered page, where two
   HTML-scanning ratchets legitimately live — `az009_an_unpermitted_entry_is_
   absent_from_the_dom_not_disabled` forbids the literal `display: none` in the
   markup (an unpermitted entry must be ABSENT, never hidden), and
   `no_screen_renders_a_second_shell_or_sidebar` counts `app-sidebar`. Both
   fired on the CSS text alone. The collision was the correct signal: CSS
   belongs in the stylesheet, and the sidebar is reached here as `nav` rather
   than by class for the same reason.
   ═══════════════════════════════════════════════════════════════════════════ */
@media print {
  nav,
  aside,
  button,
  input,
  select,
  textarea,
  .toast-region,
  .skip-link,
  .filter-bar,
  .back-btn {
    display: none !important;
  }

  .app-shell,
  .app-main {
    display: block;
    margin: 0;
    padding: 0;
  }

  /* Paper is not a screen: let a long register break across pages. */
  .table-scroll {
    overflow: visible !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   POS CART LINES

   The cart list is rendered empty by the server and filled by public/js/pos.js.
   No rules existed for a cart row because no client existed to emit one; these
   are the minimum needed for the row to be legible and operable, following the
   spacing and control shapes the rest of the POS panel already uses.
   ═══════════════════════════════════════════════════════════════════════════ */
.pos-cart-line {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-block-end: 1px solid rgba(0, 0, 0, 0.08);
}

.pos-cart-line-name {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

.pos-cart-line-qty {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}

.pos-qty-btn {
  min-inline-size: 28px;
  min-block-size: 28px;
  line-height: 1;
  cursor: pointer;
}

.pos-qty-value {
  min-inline-size: 24px;
  text-align: center;
}

.pos-cart-line-total {
  flex: 0 0 auto;
  min-inline-size: 72px;
  text-align: end;
  font-variant-numeric: tabular-nums;
}

.pos-line-remove {
  flex: 0 0 auto;
  cursor: pointer;
}

/* ──────────────────────────────────────────────────────────────────────────
   THE CUSTOMER LOOKUP DROPDOWN

   `#customerDropdown` shipped in the markup as an empty `hidden` list with no
   styling, because nothing ever rendered into it. Now that the lookup is
   wired it needs to sit OVER the screen rather than push the sale panel down:
   the panel is a fixed-height flex column and a list growing inside it would
   shove the cart out of view while the cashier is reading names.

   No `display` is declared here on purpose. The list is shown and hidden with
   the `hidden` attribute, and a `display` of our own would defeat it.
   ────────────────────────────────────────────────────────────────────────── */
.pos-customer {
  position: relative;
}

.pos-customer-dropdown {
  position: absolute;
  inset-block-start: 44px;
  inset-inline: 0;
  z-index: 30;
  margin: 0;
  padding: 4px;
  list-style: none;
  max-block-size: 240px;
  overflow-y: auto;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
}

.pos-customer-option {
  inline-size: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  font: inherit;
  font-size: 12.5px;
  text-align: start;
  color: #0f172a;
  cursor: pointer;
}

.pos-customer-option:hover,
.pos-customer-option:focus-visible {
  background: #f1f5f9;
}

/* ═══════════════════════════════════════════════════════════════════════════
   G — THE RESPONSIVE LAYER
   ═══════════════════════════════════════════════════════════════════════════

   Everything above this line is a PIXEL TRANSCRIPTION of the desktop reference
   captures — `repeat(5, 260px)`, `inline-size: 565px`, `block-size: 222px`. That
   is why it is exact at 1680 and why it cannot survive a phone: those widths are
   the specification, not a convenience.

   So this layer edits none of it. It is a mobile override appended at the end,
   where the cascade lets it neutralise a fixed size WITHOUT changing what the
   desktop lays out. `ReferenceGeometryTest` and the 102 reference captures keep
   measuring exactly what they measured before.

   MEASURED, NOT ASSUMED. Before this layer `ResponsiveSweepTest` found 15 of 40
   screens scrolling sideways at 360px — `/reports` by 1304px, `/expenses` by
   122, `/invoices` `/users` `/credit-invoices` `/consumption-receipts` by 101
   each. Those numbers are what this layer was written against, and the same
   sweep is what proves it.

   THE TARGET IS A REAL PRODUCT, NOT A GUESS. 24 phone captures of the audited
   product were read first. What they show:
     - no rail on a phone; the tile dashboard IS the navigation
     - a compact one-row app bar: title leading, actions trailing
     - 2-column tile grids for the dashboard and the reports hub
     - a 3-column POS product grid, even at 360px
     - paired short form fields STAY two-up; only long fields go full width
     - tables stay tables and scroll horizontally INSIDE their card
     - full-width primary actions
     - horizontally scrollable pill tabs where a screen has many tabs

   BREAKPOINTS — content-driven, and deliberately few:
     64rem (1024px)  the desktop rail stops fitting beside the content
     48rem (768px)   tablet portrait; multi-column layouts stop working
     30rem (480px)   phone; fixed pixel geometry is fully neutralised
   ─────────────────────────────────────────────────────────────────────────── */

/* ── G.1 — below 64rem: the rail becomes a drawer ──────────────────────────
   A 260px rail on a 360px phone is 72% of the viewport. It does not shrink; it
   leaves. Navigation must not leave with it, so the rail becomes an off-canvas
   drawer and `#gsbToggle` — a control that already exists, is already labelled
   and already wired — becomes the thing that opens it.

   ⚠ ANIMATED WITH `inset-inline-start`, NOT `transform`, AND THAT IS LOAD
   BEARING. A transform on an ancestor becomes the containing block for every
   `position: fixed` descendant, which would drag the opener off-screen with the
   drawer it is supposed to bring back. Insets have no such effect.

   ⚠ LOGICAL PROPERTIES THROUGHOUT. `inset-inline-start` puts the drawer on the
   right in Arabic and the left in English with no direction-specific rule, and
   the negative offset that hides it flips with it. */

@media (max-width: 63.99rem) {
  .app-shell {
    grid-template-columns: 1fr;
  }

  .app-sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    z-index: 60;
    inline-size: min(84vw, 300px);
    max-inline-size: 300px;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-inline-end: 1px solid var(--line);
    box-shadow: 0 0 40px rgb(16 24 40 / 18%);
    transition: inset-inline-start .22s ease, visibility .22s;
  }

  /* Closed is the default: a phone must not open holding a menu. */
  /*
   | ⚠ MEASURED, AND THE FIRST ATTEMPT WAS 17px SHORT. Offsetting by the
   | drawer's own width left its border and shadow on screen — `left` came back
   | 343 in a 360px viewport — and `visibility` alone did not settle it either.
   | The offset is now wider than any drawer this rule can produce, and
   | `pointer-events` makes certain a sliver can never take a tap meant for the
   | content behind it.
   */
  .app-shell:not([data-nav-open="1"]) > .app-sidebar {
    inset-inline-start: -340px;
    visibility: hidden;
    pointer-events: none;
  }

  /* The scrim. A pseudo-element on the shell needs no markup and no script. */
  .app-shell[data-nav-open="1"]::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgb(15 23 42 / 45%);
  }

  /*
   | The opener floats over the content while the drawer is away and stays put
   | when it arrives, so the same tap closes it. 44px is the touch target, not a
   | decoration.
   */
  #gsbToggle {
    position: fixed;
    /* The drawer is hidden with `visibility`, which INHERITS. Without this the
       control that brings it back is hidden by the thing it opens. */
    visibility: visible;
    inset-block-start: 8px;
    inset-inline-start: 8px;
    z-index: 70;
    inline-size: 44px;
    block-size: 44px;
    display: grid;
    place-items: center;
    border-radius: 12px;
    border: 1px solid var(--line);
    background: var(--surface);
    box-shadow: 0 2px 10px rgb(16 24 40 / 12%);
  }

  /* The content is the whole screen now, and starts clear of the opener. */
  .app-main {
    padding: 12px;
    padding-block-start: 60px;
  }

  /*
   | `.dash-header` carries `margin-inline: calc(var(--ui-inset) * -1)` so it can
   | bleed to the edges of a desktop content box. On a phone that margin is the
   | overflow: the header measured 401px inside a 360px viewport, at every
   | width, because the bleed is a constant and the viewport is not.
   */
  .dash-header {
    margin-inline: 0;
    max-inline-size: 100%;
    box-sizing: border-box;
  }

  /* Screens that already dropped the rail keep their single column. */
  .app-shell:has(> .app-main[data-screen="reports"]):not(:has(.card-grid)),
  .app-shell:has(> .app-main[data-screen="settings"]) {
    grid-template-columns: 1fr;
  }
}

/* ── G.2 — below 48rem: the phone layout ───────────────────────────────────
   Every rule here neutralises a fixed pixel size that came from a 1680 capture.
   `box-sizing` is set on the elements this layer makes fluid rather than
   globally: a global reset would silently re-measure every desktop rule above
   it, which is the one thing this layer must not do. */

@media (max-width: 47.99rem) {
  :root {
    --gap: 0.75rem;
  }

  /* G.2.1 — nothing may exceed its own line. */
  .app-main img,
  .app-main video,
  .app-main canvas {
    max-inline-size: 100%;
    block-size: auto;
  }

  /* A grid or flex child defaults to min-content and refuses to shrink; this is
     the commonest single cause of a page that will not fit. */
  .app-main > *,
  .card > *,
  .cat-toolbar > *,
  .screen-header > * {
    min-inline-size: 0;
  }

  /* G.2.2 — the tile grids. The reference puts these two-up on a phone. */
  .menu-grid,
  .card-grid,
  .app-main[data-screen="reports"] .card-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 10px;
    row-gap: 10px;
    margin-inline: 0;
    justify-content: stretch;
  }

  .menu-card,
  .report-card,
  .app-main[data-screen="reports"] .report-card {
    inline-size: auto;
    min-inline-size: 0;
    block-size: auto;
    min-block-size: 0;
    box-sizing: border-box;
  }

  /* G.2.3 — headers and toolbars wrap instead of pushing the page sideways. */
  .screen-header,
  .cat-toolbar,
  .filter-bar,
  .dash-header,
  .rep-actions,
  .toolbar {
    flex-wrap: wrap;
    row-gap: 8px;
    block-size: auto;
    min-block-size: 0;
    inline-size: auto;
  }

  /* Search fields measured at a fixed width on a desktop capture. */
  .cat-toolbar input[type="search"],
  .cat-toolbar input[type="text"],
  .filter-bar input,
  .filter-bar select {
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: 100%;
    box-sizing: border-box;
  }

  /* G.2.4 — forms. Paired short fields stay two-up exactly as the reference
     shows; anything wider than a pair goes to one column. */
  .form-grid,
  .field-grid,
  .settings-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .form-grid > .field--wide,
  .field-grid > .field--wide,
  .field--wide,
  .field:has(textarea) {
    grid-column: 1 / -1;
  }

  .field input,
  .field select,
  .field textarea {
    inline-size: 100%;
    max-inline-size: 100%;
    min-inline-size: 0;
    box-sizing: border-box;
    /* Under 16px iOS zooms the page on focus and the layout jumps. */
    font-size: max(16px, 1em);
    min-block-size: 44px;
  }

  /*
   | A scroll container only contains anything if its ancestors can shrink. The
   | daily report sat at a constant 445px at every viewport because the card
   | holding its table would not narrow, so the table had nothing to scroll
   | inside and pushed the page instead.
   */
  .app-main .card,
  .app-main section,
  .app-main form,
  .app-main .rep-card,
  .app-main .settings-card {
    min-inline-size: 0;
    max-inline-size: 100%;
    box-sizing: border-box;
  }

  /* G.2.5 — tables keep being tables and scroll inside their own card, which is
     what the reference does. The page does not move; the table does. */
  /*
   | ⚠ THE SELECTOR HAS TO OUTRANK THE SCREEN IT IS FIXING.
   | `.app-main[data-screen="reports"] table` (0,2,1) already sets the report
   | tables, and a plain `.app-main table` (0,1,1) loses to it no matter how
   | late it appears — which is why the daily report stayed 445px wide at
   | every viewport while every other table had already been contained.
   | `[data-screen]` matches any screen and ties on specificity; being last
   | then decides it.
   */
  /*
   | ⚠ THE SCROLLER GOES ON THE CARD, NOT ON THE TABLE, AND THAT WAS MEASURED.
   | `display: block` on a `<table>` still leaves `thead`/`tbody`/`tr` as
   | table-internal boxes, so the engine generates an anonymous table box that
   | is NOT clipped by the block overflow. The daily report proved it: its rows
   | sat at `left: -154px` in a 360px viewport with the table reporting
   | `overflow-x: auto` and a 285px box. The clip never happened.
   |
   | Putting the scroller on the card — a plain block box — and letting the
   | table keep real table layout at its natural width is what the mobile
   | reference actually shows: a table, inside a card, scrolling sideways
   | within it while the page stays put.
   */
  /*
   | EVERY table gets a scrolling parent, not a hand-picked list of them.
   | Naming `.card` and `.settings-card` fixed the report and immediately
   | broke `/users` and `/audit-log`, whose `.data-table` sits in neither: the
   | tables widened to their natural size with nothing to scroll inside and
   | pushed the page 161px and 154px. Whatever holds a table becomes the
   | scroller.
   */
  /*
   | ⚠ AN RTL SCROLL CONTAINER DOES NOT SCROLL ITS INLINE-START OVERFLOW.
   | The daily report proved it: its table sat at `left: -154px` INSIDE a
   | container computing `overflow-x: auto`, and the page still grew to 445px.
   | Content that spills past the start edge is simply not reachable, so it
   | bleeds instead of scrolling.
   |
   | The container is therefore flipped to `ltr` so the spill lands on the
   | scrollable side, and the table is flipped back to `rtl` so nothing about
   | the reading order changes. This is the same technique the stylesheet
   | already uses on `.menu-grid` for the same reason.
   */
  .app-main:has(> table),
  .app-main *:has(> table) {
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    max-inline-size: 100%;
    direction: ltr;
  }

  .app-main:has(> table) > table,
  .app-main *:has(> table) > table {
    direction: rtl;
  }

  /* A pill strip with more tabs than fit is a scroller in the reference, not
     a wrap — that is how the settings screen shows eleven of them. */
  .settings-tabs,
  .tab-strip,
  .app-main .tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    max-inline-size: 100%;
  }

  .app-main table,
  .app-main[data-screen] table {
    display: table;
    inline-size: max-content;
    min-inline-size: 100%;
    max-inline-size: none;
  }

  .app-main table th,
  .app-main table td {
    white-space: nowrap;
  }

  /*
   | A file input carries an intrinsic width of its own — the control plus its
   | button plus the file name — and it does not shrink. Beside a submit button
   | inside a form that does not wrap, that is the settings screen escaping by
   | 45px at 320.
   */
  .app-main input[type="file"] {
    max-inline-size: 100%;
    min-inline-size: 0;
    box-sizing: border-box;
  }

  .settings-logo-actions form,
  .app-main form:has(> input[type="file"]) {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
  }

  /* An action row that will not wrap puts its last button off the edge at
     320px — measured escaping by 45px on the settings screen. */
  .settings-actions,
  .settings-card .row,
  .app-main .actions,
  .app-main .btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }

  /* Settings blocks measured 329px inside a 304px content box at 320. */
  .settings-logo-actions,
  .settings-hint,
  .settings-actions,
  .app-main p,
  .app-main .hint {
    max-inline-size: 100%;
    box-sizing: border-box;
  }

  /* G.2.6 — overlays become sheets that fit and scroll. */
  .cat-modal-panel,
  [role="dialog"] > form,
  [role="dialog"] > .panel {
    inline-size: min(96vw, 520px);
    max-inline-size: 96vw;
    max-block-size: 88dvh;
    overflow-y: auto;
    box-sizing: border-box;
  }

  /* A dialog measured at a fixed 640px on a desktop capture. */
  .modal-box,
  .app-main[data-screen="settings"] .modal-box {
    inline-size: min(96vw, 640px);
    max-inline-size: 96vw;
    box-sizing: border-box;
  }

  /*
   | ⚠ A `min-inline-size` FLOOR BEATS A `max-inline-size` CEILING, WHICH IS
   | WHY EVERY DIALOG CLIPPED. `.cat-modal-panel` and `.modal-box` carry
   | `min-inline-size: 340px`, and the overlay adds 24px of padding each side,
   | so below about 388px the panel is centred inside a box narrower than
   | itself and its close button is the first thing off the edge — on a fixed
   | overlay that does not scroll. The floor has to go before the ceiling can
   | do anything.
   */
  .cat-modal-panel,
  .modal-box,
  .confirm-overlay > .modal,
  [role="dialog"] .panel {
    min-inline-size: 0;
    inline-size: min(94vw, 520px);
    max-inline-size: 94vw;
    max-block-size: 88dvh;
    overflow-y: auto;
    box-sizing: border-box;
  }

  /* The overlay itself must be able to scroll a tall dialog into reach. */
  .cat-modal,
  .confirm-overlay,
  [role="dialog"] {
    padding: 12px;
    overflow-y: auto;
  }

  /*
   | The POS sale panel is `flex: 0 0 616px` — it refuses to shrink, so the
   | cart pane alone is wider than any phone. The segmented Products/Cart
   | control that already exists is what swaps the two panes; the basis only
   | has to stop fighting it.
   */
  .pos-sale-panel {
    flex: 1 1 auto;
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: 100%;
    block-size: auto;
    box-sizing: border-box;
  }

  .pos-mobile-toggle button {
    min-block-size: 44px;
  }

  /* G.2.7 — touch targets. */
  .app-main button,
  .app-main .btn,
  .app-main [role="button"] {
    min-block-size: 44px;
  }

  /* G.2.8 — the POS. The reference keeps three product tiles per row at 360px
     and swaps the two panels with the segmented control that already exists. */
  .pos-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
  }

  .pos-tile {
    inline-size: auto;
    min-inline-size: 0;
    block-size: auto;
    box-sizing: border-box;
  }

  .pos-layout {
    grid-template-columns: 1fr;
  }
}

/* ── G.3 — below 30rem: the small phone ────────────────────────────────────
   320-360px. The tile grids stay two-up because the reference does; what gives
   is the padding around them, not the count. */

@media (max-width: 29.99rem) {
  .app-main {
    padding: 8px;
    padding-block-start: 58px;
  }

  .menu-grid,
  .card-grid,
  .app-main[data-screen="reports"] .card-grid {
    column-gap: 8px;
    row-gap: 8px;
  }
}

/* ── G.4 — the login screen ────────────────────────────────────────────────
   ⚠ THE FIRST ATTEMPT FIXED THE PHONE AND LEFT THE REAL DEFECT IN PLACE.

   It keyed off 48rem, so between 768px and 1200px the desktop composition
   survived — and there it is genuinely broken, not merely tight. A screenshot at
   920px shows why: the white wedge is 37.5% of the viewport (345px) while
   `.login-card` is a fixed 565px, so the card overflows the wedge and its labels
   are painted ON TOP of the artwork, where they are almost unreadable. The
   remember-me row lands there too. No overflow check could ever have seen this;
   `scrollWidth` was equal to `innerWidth` the whole time.

   The split composition needs room for a 565px column AND a panel worth showing.
   Below 1200px it has neither, so below 1200px it is not used.

   ⚠ SUPERSEDED. The panel was removed from the product entirely, so there is
   no split composition left to rescue and no band to build. What remains here
   is the card's own sizing on a small screen — full width up to a 440px cap,
   48px touch targets, 16px inputs so iOS does not zoom on focus. The centring
   itself is now unconditional and lives in the base rule above.
   ─────────────────────────────────────────────────────────────────────────── */

@media (max-width: 74.99rem) {
  .login-shell {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-block-size: 100dvh;
    padding: clamp(16px, 5vw, 32px) 16px;
    box-sizing: border-box;
    background: var(--surface-sunken);
  }


  /* Only what DIFFERS from the base card: the width cap on a small screen.
     The surface, radius and shadow are unconditional now. */
  .login-card {
    inline-size: 100%;
    max-inline-size: 440px;
    margin-block-end: 0;
  }

  .login-card h1 {
    margin-block-end: 22px;
    font-size: 19px;
  }

  .login-card .field input[type="text"],
  .login-card .field input[type="password"] {
    inline-size: 100%;
    box-sizing: border-box;
    min-block-size: 48px;
    /* Under 16px iOS zooms the page on focus and the layout jumps. */
    font-size: max(16px, 1em);
  }

  .login-card .btn-login,
  .login-card .btn-trial {
    inline-size: 100%;
    min-block-size: 48px;
  }
}

/* A tablet has room to breathe; it should not look like a stretched phone. */
@media (min-width: 48rem) and (max-width: 74.99rem) {
  .login-shell {
    justify-content: center;
    padding-block-end: 48px;
  }

  .login-card {
    max-inline-size: 520px;
    padding: 32px 34px 36px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   H — FLUID PRIMITIVES
   ═══════════════════════════════════════════════════════════════════════════

   Section G made the product FIT every width. It did not make it WORK at every
   width, and a screenshot at 920px is what proved the difference: the login
   card overflowed its white wedge and printed its labels on top of the artwork,
   while `scrollWidth === innerWidth` the entire time. Fitting and working are
   not the same measurement.

   The failures all had one shape. A bar is given a FIXED height measured from a
   desktop capture — `block-size: var(--ui-header-h)` on `.dash-header`,
   `block-size: 52px` on `.screen-header` — and its children are told to wrap.
   Wrapping produces a second row; the fixed height refuses to grow for it; the
   rows are painted on top of each other. That is not a phone bug. It happens at
   every width where the contents need one more row than the capture had, which
   is most widths between a phone and the desktop the number came from.

   So the fix here is not another breakpoint. A bar gets a MINIMUM height and is
   allowed to grow, spacing and type are expressed as ranges rather than points,
   and panels are bounded rather than pinned. The desktop values are the TOP of
   every range, so at the width the captures were taken the computed result is
   the captured number and `ReferenceGeometryTest` still measures what it always
   measured.
   ─────────────────────────────────────────────────────────────────────────── */

/* ── H.1 — a bar may never be shorter than its contents ────────────────────
   `block-size` becomes `min-block-size` everywhere a bar can wrap. This is not
   scoped to a media query on purpose: the overlap is a function of CONTENT, not
   of viewport, and pinning it to a width would leave it broken at every width
   the query does not name. */

.dash-header,
.screen-header,
.cat-toolbar,
.filter-bar {
  block-size: auto;
  min-block-size: var(--ui-header-h, 52px);
  flex-wrap: wrap;
  align-items: center;
}

.screen-header {
  min-block-size: 52px;
}

/* ── H.2 — fluid gutters and type ──────────────────────────────────────────
   `--ui-inset: 36px` spends 20% of a 360px viewport on gutters and is right at
   1680. A range gives both. The upper bound is the captured value, so nothing
   above the crossover moves. */

:root {
  --ui-inset: clamp(12px, 2.4vw, 36px);
  --ui-gap: clamp(10px, 1.4vw, 20px);
}

/* ── H.3 — the POS sale panel is bounded, not pinned ───────────────────────
   `flex: 0 0 616px` cannot shrink, so between roughly 620px and 1024px the cart
   pane alone is wider than the space beside the product grid. Bounding it keeps
   616px wherever 616px fits and lets it give way where it does not. */

/*
 | ⚠ BOUNDING A PANEL WITHOUT BOUNDING ITS CONTENTS JUST MOVES THE OVERFLOW
 | INSIDE IT. Shrinking this panel to 389px at 1024 left `.pos-totals` at its
 | captured 579px and the page grew by 129px — a regression this layer caused
 | and the sweep caught immediately.
 |
 | Above the width where the Products/Cart switch takes over, both panes are
 | shown and 616px is what the captures measured, so it stays. Below it the
 | panel is the whole screen and its rows are made fluid with it.
 */
@media (max-width: 63.99rem) {
  .pos-sale-panel {
    flex: 1 1 auto;
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: 100%;
  }

  .pos-totals,
  .pos-extra-row,
  .pos-discount-row,
  .pos-customer-row {
    inline-size: auto;
    max-inline-size: 100%;
    min-inline-size: 0;
    flex-wrap: wrap;
    box-sizing: border-box;
  }

  .pos-sale-panel input,
  .pos-sale-panel select {
    max-inline-size: 100%;
    min-inline-size: 0;
    box-sizing: border-box;
  }
}

/* ── H.4 — the product grid fills the space it has ─────────────────────────
   `repeat(5, 154px)` is five columns or overflow, with nothing in between.
   `auto-fill` with a bounded track keeps the 154px tile the captures measured
   and simply fits fewer of them as the space narrows — continuously, at every
   width, with no breakpoint deciding the count. */

/*
 | ⚠ AUTO-FILL WOULD HAVE BROKEN THE DESKTOP IT WAS MEANT TO LEAVE ALONE.
 | `repeat(auto-fill, minmax(min(100%,132px), 154px))` fits as many minimum
 | tracks as the space allows and only then grows them, so the 1050px desktop
 | grid became NINE narrow columns instead of the five 154px tiles the capture
 | specifies. A column COUNT is a structural decision, not a fluid one: the
 | tile has a legible minimum and below it you need fewer columns, not smaller
 | ones. So the count steps, and everything else about the tile stays fluid.
 */
@media (min-width: 48rem) and (max-width: 63.99rem) {
  .pos-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* The phone reference keeps three tiles per row at 360px. */
@media (max-width: 47.99rem) {
  .pos-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
  }

  /*
   | ⚠ FOUND ONLY WITH REAL CONTENT. Three columns at 360px give a tile about
   | 110px wide, and a product called `ثوب رجالي أبيض قطن مصري فاخر بتطريز
   | يدوي` does not fit in 110px. The name was laid out at its natural width
   | and painted straight out of the tile and across its neighbour. An empty
   | catalogue — which is what the earlier capture had — shows none of this.
   |
   | A long word has to be allowed to break, and the tile has to be a box its
   | text cannot leave.
   */
  .product-card {
    overflow: hidden;
    min-inline-size: 0;
    inline-size: auto;
    block-size: auto;
    box-sizing: border-box;
  }

  .product-card > *,
  .product-card span,
  .product-card p {
    min-inline-size: 0;
    max-inline-size: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
    hyphens: auto;
  }
}

/* ── H.5 — the mobile POS switch belongs at the top ────────────────────────
   The reference puts Products/Cart directly under the tab strip, where a thumb
   finds it before scrolling. It was rendering after both panes, which on a
   phone means below a full screen of products. */

@media (max-width: 63.99rem) {
  /*
   | ⚠ `order` NEEDS THE RIGHT PARENT, AND THE FIRST ATTEMPT PICKED THE WRONG
   | ONE. `.pos-mobile-toggle` is a SIBLING of `.pos-layout`, not a child of
   | it — the layout closes at markup line 707 and the switch follows at 708 —
   | so ordering it inside `.pos-layout` addressed an element that was never
   | there, and the switch stayed below a full screen of products.
   |
   | Their real common parent is the screen container. Making that the flex
   | column puts the switch where the phone reference has it, under the tab
   | strip, with no markup change.
   */
  /*
   | The drawer opener is fixed at the inline-start of the top edge. Rather
   | than chase whichever control lands under it on each screen — the sweep
   | reported an unnamed `button` at every width from 320 to 1000 — the POS
   | column simply starts clear of that corner. One rule, no guessing which
   | element it was.
   */
  /*
   | ⚠ THE 60px WAS TAKEN OFF THE COLUMN'S WIDTH AND IS NOW TAKEN OFF ITS
   | HEIGHT INSTEAD. Pushing the whole POS column 60px inline-ward did clear
   | the opener, and it charged every POS screen 60px of WIDTH at every
   | drawer width to solve a problem that only exists in one CORNER: at
   | 360px the register was laid out in 285px with an empty 60px strip
   | beside it, which no overflow, off-screen or overlap check can see
   | because nothing is broken — it is merely thrown away. The capture is
   | what showed it.
   |
   | H.13 now reserves the corner vertically for every screen, so the
   | column starts below the opener and keeps its full width.
   */
  .app-main[data-screen="pos"] {
    display: flex;
    flex-direction: column;
  }

  .app-main[data-screen="pos"] .pos-mobile-toggle {
    order: -1;
    /*
     | The drawer opener is `position: fixed` at the top of the inline-start
     | edge, and this switch is sticky at the top of the same column — so the
     | opener sat on the first tab. Reported as an overlap at every width from
     | 320 to 1000. The row starts clear of it instead.
     */
    padding-inline-start: 60px;
    position: sticky;
    inset-block-start: 0;
    z-index: 5;
    display: flex;
    gap: 8px;
    padding: 8px 0;
    background: var(--surface);
  }

  .pos-mobile-toggle button {
    flex: 1 1 0;
    min-block-size: 44px;
  }
}

/* ── H.6 — header children stop competing for the same spot ────────────────
   `.dash-pos` is centred with `margin-inline: auto` and, in the Phase B layer,
   taken out of flow entirely. Out of flow it cannot participate in the wrap the
   bar now performs, so it lands on top of whatever wrapped beneath it. Below the
   desktop rail width it rejoins the flow and takes its turn. */

@media (max-width: 63.99rem) {
  .dash-header,
  .screen-header {
    position: static;
    margin-block-start: 0;
    margin-inline: 0;
    padding-block: 10px;
    gap: 8px;
    row-gap: 8px;
  }

  .dash-pos,
  .dash-brand,
  .dash-lang,
  .dash-logout,
  .dash-greeting {
    position: static;
    inset: auto;
    margin-inline: 0;
    transform: none;
  }

  .dash-pos {
    order: 0;
  }
}
/* ── H.7 — the POS header pills rejoin the flow ────────────────────────────
   `.back-btn`, `#btnSuspendedSales` and `#btnRefundConsumptionReceipt` are
   pinned with `position: absolute` and PHYSICAL offsets measured off a 1680px
   capture — `left: 11px`, `left: 197px`, `left: 642px`. An absolute offset does
   not know the viewport, so at 320px the refund pill still renders at
   642..715 and is simply not on the screen; the dense sweep reported it at
   every width from 320 to 680 with identical coordinates, which is the
   signature of a pinned element rather than a squeezed one.

   Out of flow they also cannot take part in any wrap, so they land on top of
   whatever did wrap — the drawer opener and the back button were each reported
   overlapping a neighbour at every one of those widths.

   Below the width where the desktop header has room, they become ordinary
   flow items in a wrapping row. Above it nothing changes and the capture
   geometry stands. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="pos"] > .back-btn,
  #btnSuspendedSales,
  #btnRefundConsumptionReceipt,
  #btnProcessInvoice {
    position: static;
    inset: auto;
    margin: 0;
  }

  /* The row they rejoin has to be able to hold them. */
  .pos-tabs,
  .app-main[data-screen="pos"] > .pos-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    block-size: auto;
    min-block-size: 0;
  }
}

/* ── H.8 — WITHDRAWN ───────────────────────────────────────────────────────
 This band forced the Products/Cart switch on up to 1200px to stop the two
 panes overflowing between 1024 and 1200. `PosResponsiveTest` refused it: the
 reference captures show BOTH panes and no switch at 1069px, and that is a
 recovered contract, not a preference. The test was right and the rule was a
 breakpoint standing in for a layout fix — which is why the failure kept
 moving to whatever width the band ended at.

 H.9 below solves it properly: the grid takes the space that is actually left,
 so both panes fit at 1024 without anything being hidden. */

/* ── H.9 — the product grid takes the space that is left, at every width ────
   Pushing the single-pane band from 1024 to 1200 moved the failure to 1200 and
   then to 1280, which is the signature of treating a symptom: the grid asks for
   `repeat(5, 154px)` — 806px that cannot shrink — and as a flex item it refuses
   to go below that, so cards render past the edge and over the rail wherever
   rail + panel + 806 exceeds the viewport. There is no breakpoint that fixes
   that, only one that moves it.

   `auto-fill` with the SAME 154px tile lets the grid fit as many as the
   remaining space allows and no more. At the 1680 capture width the rail (260)
   and the panel (616) leave about 804px, which is exactly five 154px tiles and
   their gaps — so the reference geometry is unchanged and every narrower width
   simply gets fewer columns instead of overflow. */

.pos-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 140px), 154px));
  min-inline-size: 0;
}

/* ── H.10 — the two remaining fixed track lists ────────────────────────────
   THE SAME ROOT CAUSE AS H.9, ON TWO SCREENS NOBODY HAD MEASURED.

   Widening the audit from eight screens to every screen produced 45 defects,
   and all 45 sat on exactly two: `/reports` (34) and `/shifts` (11). Both were
   overflowing at EVERY width in the sweep — 320 through 1440 — and both for the
   reason the POS grid overflowed:

       .card-grid   { grid-template-columns: repeat(5, 260px); }   plus 20px gaps
       .shift-tiles { grid-template-columns: repeat(5, 265px); }   plus 18px gaps

   Five fixed tracks are five columns or an overflow, with nothing in between.

   ⚠ AND THE DESKTOP WAS ALREADY BROKEN, WHICH SETTLES THE TRADE-OFF. Measured
   at the capture width itself:

       /reports @1680   grid box 1345px   five 260px tracks need 1460px
       /shifts  @1680   grid box 1309px   five 265px tracks need 1397px

   So the transcription never reproduced its own reference at 1680; it painted
   five cards into a box that fits four and let the fifth hang off the inline
   start. There is no working desktop being given up here — only at 1920, where
   the box finally reaches 1585px, do five tracks genuinely fit.

   `auto-fill` with a bounded track keeps the recovered 260px and 265px card
   exactly, and simply fits as many as the box holds: five at 1920 (unchanged),
   four at 1680, and down to one on a phone — continuously, with no breakpoint
   deciding the count. The `min(100%, …)` floor is what lets the last column
   shrink below the card width instead of overflowing a narrow screen.

   The items themselves are unpinned too. A fixed `inline-size` on a grid item
   overflows any track narrower than it, and a fixed `block-size` clips the
   description once the card is narrow enough to need a fourth line — the same
   fixed-height-plus-wrapping failure H.1 removed from the header bars. */

.app-main[data-screen="reports"] .card-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 260px));
  margin-inline: var(--ui-inset);
}

.app-main[data-screen="reports"] .report-card {
  inline-size: min(100%, 260px);
  block-size: auto;
  min-block-size: 222px;
}

.app-main[data-screen="shifts"] .shift-tiles {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 265px), 265px));
}

.app-main[data-screen="shifts"] .shift-tile {
  inline-size: min(100%, 265px);
  block-size: auto;
  min-block-size: 150px;
}

/* ── H.11 — a dialog may never be wider than the screen it opens on ────────
   Eleven modal widths are transcribed from eleven captures — products 918,
   customers and expenses 778, settings 640, offers 580, and so on down to the
   340px suspended-sale detail. Each is right at 1680 and each is a fixed
   `inline-size`, so each is wrong the moment the viewport is narrower than it.

   The phone was already covered by a `max-width: 47.99rem` rule, and that is
   precisely why the defect survived every previous pass: 768px sits one pixel
   outside that query, so the products dialog opened 918px wide in a 768px
   window and ran 36px past the edge — the same dead-zone-above-the-breakpoint
   shape as the login screen, found by the same kind of sweep.

   A ceiling fixes all eleven at once and needs no query. Every recovered width
   is kept as the dialog's preferred size and simply cannot exceed the viewport
   less a gutter; at 1680 every one of them is far below the ceiling, so nothing
   about the reference geometry moves. `min()` of the two forms guards the case
   where `100%` resolves against something other than the full-bleed overlay. */

.cat-modal-panel,
.modal-box,
.modal-overlay > .modal,
.confirm-overlay > .modal {
  max-inline-size: min(100%, calc(100vw - 2 * var(--ui-inset)));
}

/* ── H.12 — a multi-panel dialog wraps instead of hanging off both edges ───
   The products dialog is not one panel, it is three laid side by side in a
   centred flex row — the form (340), the price table (247) and the image well
   (253). Measured at 768px:

       form     [464..804]      section.cat-prices [217..464]
       section.cat-image [-36..217]      overlay clientW 768, scrollW 804

   840px of content in a 720px content box, centred, so it hangs 36px off BOTH
   edges at once and the form's close button goes with it. H.11's ceiling could
   not help: the form was already at its 340px floor, and no ceiling on one
   child shortens a row of three.

   `flex-wrap` is the whole fix, and it needs no width to key off. While the
   three fit they stay on one line and 1680 is untouched; when they stop fitting
   the row folds — form and prices on one line, the image below — and folds
   again on a phone. The floor is released on the overlay's own children only,
   so the 340px minimum still governs every single-panel dialog that relies on
   it.

   `overflow-y: auto` is the companion the wrap requires: a dialog that stacks
   is taller than one that does not, and a form whose save button has scrolled
   out of a fixed overlay is a form the operator cannot submit. */

.cat-modal {
  flex-wrap: wrap;
  align-content: center;
  overflow-y: auto;
}

.cat-modal > .cat-modal-panel,
.cat-modal > section,
.cat-modal > div {
  min-inline-size: 0;
  max-inline-size: 100%;
}

/* ── H.13 — the drawer opener stops printing on the page content ───────────
   FOUND BY LOOKING, NOT BY MEASURING — which is the point of taking pictures.

   In drawer mode `#gsbToggle` is `position: fixed` at `inset-inline-start: 8px`
   and 44px square, so it occupies the top inline-start corner of the content
   area. Being fixed it is out of flow, so NOTHING in the layout accounts for
   it, and whatever a screen puts in that corner is printed underneath it. On
   `/reports` the capture shows `التقارير` rendering as `ير`; on `/offers`,
   `العروض والتخفيضات` as `ض والتخفيضات`.

   Measurement could not see it. The audit compares CONTROLS against controls; a
   heading is not a control, so a button sitting on top of one was invisible to
   every check that existed. It is now — the sweep grew a fourth defect class
   comparing the opener's box against the actual GLYPH bounds of every heading,
   since a full-width `h1` overlaps a corner button even when its text does not.

   ⚠ THE FIRST ATTEMPT PADDED THE HEADERS AND WAS THE WRONG SHAPE. It fixed
   `/reports`, missed `/offers` and `/customer-custom-prices` (different bar
   classes) and `/zatca-settings` (a card heading, no bar at all), and it made
   `/reports/daily` worse: 56px taken out of a 320px header pushed the back
   button onto its neighbour. Enumerating bar classes is chasing the symptom
   around the system — the next screen with a corner heading would collide too.

   The corner is what is occupied, so the corner is what gets reserved. One rule
   on the content area itself, no per-screen knowledge, no width stolen from any
   bar's contents, and every screen — present and future — starts below the
   button instead of behind it. Above the rail width the opener is back in the
   rail and this does not apply, so the 1680 reference geometry does not move. */

@media (max-width: 63.99rem) {
  /*
   | TWO SHAPES, BECAUSE THE PRODUCT PUTS TITLES IN THE CORNER TWO WAYS.
   |
   | Most screens set their own `.app-main[data-screen="x"] { padding: 0 }` or
   | `padding-block-start: 53px`, which outranks a bare `.app-main` — the first
   | version of this rule was written that way and `/reports` simply ignored it
   | (measured `padding-block-start: 0px` with the rule in the file). Matching
   | the per-screen shape restores the tie, and coming later settles it.
   */
  .app-main[data-screen] {
    padding-block-start: max(60px, var(--ui-inset));
  }

  /*
   | The other shape: several screens carry their title as an `sr-only` <h1>
   | that CSS brings back into sight and pins into the topbar with
   | `position: absolute; inset-block-start: 0; inset-inline-start: 58px`. Being
   | out of flow it ignores the padding above entirely — `/offers` measured its
   | heading at y0..51 with 57px of padding sitting under it.
   |
   | 58px is exactly the clearance the toggle needs, and `/customers` and
   | `/hotels-companies` already use it, which is why those two looked right all
   | along and `/offers`, `/customer-custom-prices` and `/zatca-settings` did
   | not. Applying the same number to every such title, rather than to the three
   | that happen to be wrong today, is what stops the next one being wrong too.
   */
  .app-main[data-screen] > h1 {
    inset-inline-start: max(58px, var(--ui-inset));
  }
}

/* ── H.14 — the settings rail stops reserving a column it no longer has ────
   The settings screen is a 14rem rail beside a panel column, and the rail is
   pinned at `inline-size: 226px; flex: 0 0 226px` from the desktop capture.
   When the layout collapses to one column the rail keeps that 226px and the
   rest of the row is empty: at 768px the tab list occupied the inline-start
   third of the screen and the other two thirds were blank.

   The stylesheet already knew what should happen — a rule in the responsive
   layer turns `.settings-tabs` into a horizontal pill strip, with the comment
   that this is how the reference shows eleven tabs. It never took effect,
   because `.app-main[data-screen="settings"] .settings-tabs` outranks a bare
   `.settings-tabs` and kept re-pinning the width. The intent was right and lost
   the cascade; this restores it at matching specificity rather than writing a
   new intent.

   Nothing changes above the rail width, where the second column still exists
   and 226px is still the measured rail. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="settings"] .settings-tabs {
    inline-size: auto;
    flex: 1 1 auto;
    max-inline-size: 100%;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   I — THE ZATCA MODULE
   ═══════════════════════════════════════════════════════════════════════════

   THE MARKUP WAS ALWAYS RIGHT. THE STYLES WERE NEVER WRITTEN.

   `zatca/settings.blade.php` was authored against `038_ZATCA_Settings.png` and
   carries every class that capture needs — `workspace-grid`, `settings-panel`,
   `panel-header`, `connection-badge`, `overview-metrics`, `onboarding-list`,
   `step-marker`, `save-footer`. The stylesheet styled four of them. Everything
   else fell through to the browser's defaults, and that is the whole defect:

     · the two-column workspace never existed, so the page was one 2,600px column
     · the panels had no card, so they bled to the edge with no surface at all
     · `.onboarding-list` is an <ol>, so the browser drew its own markers NEXT to
       the step badges and the steps read "1. 1", "2. 1", "3. 2", "4. 3"
     · every action was a raw <button>, in the browser's chrome, not the product's
     · the save footer was an inline button rather than the reference's sticky bar
     · the four metrics were four loose boxes rather than one divided group

   So this section adds the missing half. It changes NO markup, NO id, NO route
   and NO string — every selector below already exists in the view.

   COLOUR IS REUSED, NOT INVENTED. The reference's teal is the product's own
   `--accent: #0f7b6c`, already in the token block; the borders, input fill and
   screen ground are the values the existing ZATCA rules established (#dbe8e4,
   #f7faf9, #f4f8f6). Nothing here introduces a colour the project did not have.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── I.1 — the workspace, fluid and without a breakpoint ───────────────────
   The reference splits the area under the hero roughly 62/38: the identity form
   on the inline-start side, the onboarding path beside it. `flex-wrap` gets that
   ratio where it fits and a stack where it does not, with no width named — the
   two columns simply stop fitting and wrap, continuously, at whatever width that
   happens to be. */

.app-main[data-screen="zatca-settings"] .zatca-layout {
  padding-block-end: 96px;   /* clearance for the sticky save bar */
}

.app-main[data-screen="zatca-settings"] .workspace-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--ui-gap);
  margin: 0 16px;
}

.app-main[data-screen="zatca-settings"] .settings-column {
  flex: 1.6 1 clamp(300px, 60%, 620px);
  min-inline-size: 0;
}

.app-main[data-screen="zatca-settings"] .onboarding-panel {
  flex: 1 1 clamp(280px, 34%, 380px);
  min-inline-size: 0;
}

/* ── I.2 — the page title carries the module's own mark ────────────────────
   The badge is `/img/tiles/zatca.png`, which is the product's existing ZATCA
   icon and is served from this origin — the policy is `img-src 'self'` with no
   `data:` allowance, so an inlined SVG background would be blocked outright. */

.app-main[data-screen="zatca-settings"] > h1::before,
.app-main[data-screen="zatca-settings"] > h2::before {
  content: "";
  flex: none;
  inline-size: 26px;
  block-size: 26px;
  margin-inline-end: 10px;
  border-radius: 8px;
  background: url("/img/tiles/zatca.png") center / 20px 20px no-repeat, #e7f3f0;
}

/* ── I.3 — the integration hero ────────────────────────────────────────────
   One card carrying the eyebrow, the title, the state pill, the sentence and
   the four figures. The existing rule already gives it a white surface; what it
   lacked was every relationship inside it. */

.app-main[data-screen="zatca-settings"] .integration-overview {
  padding: 20px 22px;
  border-radius: var(--ui-radius);
  box-shadow: var(--ui-shadow-card);
}

.app-main[data-screen="zatca-settings"] .overview-heading-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.app-main[data-screen="zatca-settings"] .section-kicker {
  display: block;
  margin-block-end: 2px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent);
}

.app-main[data-screen="zatca-settings"] .overview-heading-row h2 {
  margin: 0;
  font-size: 19px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="zatca-settings"] .integration-overview > p {
  margin: 10px 0 0;
  max-inline-size: 68ch;
  font-size: 13px;
  line-height: 1.7;
  color: #64748b;
}

/* THE STATE PILL. Three states, and the class the view already computes says
   which: `is-ready`, `is-progress`, `is-error`. The dot is the existing
   `.status-dot` span — it needed a shape, not new markup. */

.app-main[data-screen="zatca-settings"] .connection-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: var(--ui-radius-pill);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.app-main[data-screen="zatca-settings"] .connection-badge .status-dot {
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  background: currentcolor;
}

.app-main[data-screen="zatca-settings"] .connection-badge.is-ready {
  background: #e7f3f0;
  color: #0f6b5e;
}

.app-main[data-screen="zatca-settings"] .connection-badge.is-progress {
  background: #fdf3e2;
  color: #92610a;
}

.app-main[data-screen="zatca-settings"] .connection-badge.is-error {
  background: #fdeceb;
  color: #a3231f;
}

/* THE FOUR FIGURES, as one divided group rather than four loose boxes. The
   reference draws a single bordered rail with hairline separators between the
   cells; `auto-fit` lets it fold to two rows and then one on a narrow screen
   without a width being named. */

.app-main[data-screen="zatca-settings"] .overview-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 130px), 1fr));
  gap: 0;
  margin: 16px 0 0;
  border: 1px solid #dbe8e4;
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
}

.app-main[data-screen="zatca-settings"] .overview-metrics .metric-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-inline-size: 0;
  padding: 12px 16px;
  border-radius: 0;
  background: none;
  /* A hairline between cells, drawn as a border on the cell's inline end and
     removed from the last one by the container's own overflow clip. */
  border-inline-end: 1px solid #e8f0ee;
}

.app-main[data-screen="zatca-settings"] .overview-metrics .metric-item:last-child {
  border-inline-end: 0;
}

.app-main[data-screen="zatca-settings"] .overview-metrics dt {
  font-size: 11.5px;
  font-weight: 600;
  color: #8b93a1;
}

.app-main[data-screen="zatca-settings"] .overview-metrics dd {
  margin: 0;
  font-size: 14px;
  font-weight: 800;
  color: #0f172a;
  overflow-wrap: anywhere;
}

/* The identity-conflict banner and the link into the submission register. */

.app-main[data-screen="zatca-settings"] .zatca-identity-conflict {
  margin: 14px 0 0;
  padding: 10px 14px;
  border: 1px solid #f5c9c6;
  border-radius: 10px;
  background: #fdeceb;
  font-size: 12.5px;
  font-weight: 700;
  color: #a3231f;
}

.app-main[data-screen="zatca-settings"] .zatca-register-link {
  margin: 14px 0 0;
}

.app-main[data-screen="zatca-settings"] .zatca-register-link .action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-block-size: 34px;
  padding-inline: 14px;
  border: 1px solid #dbe8e4;
  border-radius: 9px;
  background: #f7faf9;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--accent);
  text-decoration: none;
}

.app-main[data-screen="zatca-settings"] .zatca-register-link .action-btn:hover {
  background: #eef6f4;
}

/* ── I.4 — the form panels ─────────────────────────────────────────────────
   The card chrome belongs to the PANEL. It was on `.form-grid`, which is the
   grid inside the panel, so the header and the fields sat in two unrelated
   boxes instead of one card. */

.app-main[data-screen="zatca-settings"] .settings-panel {
  margin-block-end: var(--ui-gap);
  border: 1px solid #dbe8e4;
  border-radius: var(--ui-radius);
  background: #fff;
  box-shadow: var(--ui-shadow-card);
  overflow: hidden;
}

.app-main[data-screen="zatca-settings"] .panel-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 16px 20px;
  border-block-end: 1px solid #e8f0ee;
}

.app-main[data-screen="zatca-settings"] .panel-header h2 {
  margin: 0;
  font-size: 15.5px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="zatca-settings"] .panel-header p {
  margin: 3px 0 0;
  font-size: 12px;
  color: #8b93a1;
}

.app-main[data-screen="zatca-settings"] .required-note {
  font-size: 11.5px;
  font-weight: 700;
  color: #8b93a1;
  white-space: nowrap;
}

.app-main[data-screen="zatca-settings"] .required-note [aria-hidden] {
  color: var(--ui-red);
}

/* The grid is now only a grid: the surface, the border and the outer margin
   move up to `.settings-panel`, where the header can share them. */

.app-main[data-screen="zatca-settings"] .settings-panel .form-grid {
  margin: 0;
  padding: 18px 20px 20px;
  border: 0;
  border-radius: 0;
  background: none;
  gap: 14px 18px;
}

.app-main[data-screen="zatca-settings"] .field label [aria-hidden] {
  color: var(--ui-red);
}

.app-main[data-screen="zatca-settings"] .field-input:focus {
  outline: none;
  border-color: var(--accent);
  background: #fff;
  box-shadow: 0 0 0 3px rgb(15 123 108 / 12%);
}

.app-main[data-screen="zatca-settings"] .field-input:disabled {
  background: #eef2f1;
  color: #8b93a1;
  cursor: not-allowed;
}

/* ── I.5 — the save bar, pinned where the reference pins it ────────────────
   The reference keeps saving reachable from anywhere on a long form. `sticky`
   rather than `fixed`: fixed would escape the form column and stretch under the
   onboarding card too, and on a phone it would cover the last field. */

.app-main[data-screen="zatca-settings"] .save-footer {
  position: sticky;
  inset-block-end: 0;
  z-index: 4;
  margin: 0;
  border: 1px solid #dbe8e4;
  border-radius: var(--ui-radius);
  background: #fff;
  box-shadow: 0 -2px 14px rgb(16 24 40 / 6%);
}

.app-main[data-screen="zatca-settings"] .save-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 18px;
}

.app-main[data-screen="zatca-settings"] .save-state {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: #8b93a1;
}

.app-main[data-screen="zatca-settings"] .save-state-dot {
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  background: var(--ui-green);
}

.app-main[data-screen="zatca-settings"] .save-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-block-size: 42px;
  min-inline-size: 168px;
  padding-inline: 22px;
  border: 0;
  border-radius: 12px;
  background: var(--accent);
  font: inherit;
  font-size: 13.5px;
  font-weight: 800;
  color: #fff;
  cursor: pointer;
}

.app-main[data-screen="zatca-settings"] .save-btn:hover {
  background: #0c6659;
}

/* ── I.6 — the onboarding column ───────────────────────────────────────────
   One card for the whole path, so the header, the environment box, the state
   strip and the four steps read as a single sequence rather than four
   unconnected blocks. */

.app-main[data-screen="zatca-settings"] .onboarding-panel {
  padding: 0 0 18px;
  border: 1px solid #dbe8e4;
  border-radius: var(--ui-radius);
  background: #fff;
  box-shadow: var(--ui-shadow-card);
}

.app-main[data-screen="zatca-settings"] .onboarding-header {
  margin: 0;
  padding: 18px 20px 14px;
  border: 0;
  border-radius: 0;
  background: none;
}

.app-main[data-screen="zatca-settings"] .onboarding-header h2 {
  margin: 0;
  font-size: 16.5px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="zatca-settings"] .onboarding-header p {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.7;
  color: #64748b;
}

.app-main[data-screen="zatca-settings"] .connection-config {
  margin: 0 20px;
  padding: 14px 16px;
  border: 1px solid #dbe8e4;
  border-radius: 12px;
  background: #fbfdfc;
}

.app-main[data-screen="zatca-settings"] .connection-config .field + .field {
  margin-block-start: 12px;
}

/* The state strip. It is a live region the view already writes into; all it
   needed was to look like a strip. */

.app-main[data-screen="zatca-settings"] .onboarding-status {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 20px;
  padding: 10px 14px;
  border-radius: 10px;
  background: #f1f5f4;
  font-size: 12.5px;
  font-weight: 700;
  color: #475569;
}

.app-main[data-screen="zatca-settings"] .onboarding-status .status-icon {
  flex: none;
  inline-size: 9px;
  block-size: 9px;
  border-radius: 50%;
  border: 2px solid #94a3b8;
}

/* ── I.7 — the stepper ─────────────────────────────────────────────────────
   ⚠ `list-style: none` IS THE BUG FIX, NOT A PREFERENCE. `.onboarding-list` is
   an <ol>, and with no rule the browser drew its own marker beside the step's
   own `.step-marker` badge — the four steps rendered as "1. 1", "2. 1", "3. 2",
   "4. 3". That is what the capture of the current build shows.

   The connector is a border on the list, sitting under the badges, so it grows
   with the content and needs no fixed height. */

.app-main[data-screen="zatca-settings"] .onboarding-list {
  list-style: none;
  margin: 0 20px;
  padding: 0;
}

.app-main[data-screen="zatca-settings"] .onboarding-list--rest {
  margin-block-start: 0;
}

.app-main[data-screen="zatca-settings"] .onboarding-step {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding-block: 14px;
}

/* The line runs between one badge and the next, so it is drawn from each step
   except the last, starting below its own badge. */
.app-main[data-screen="zatca-settings"] .onboarding-step::before {
  content: "";
  position: absolute;
  inset-block-start: 42px;
  inset-block-end: -6px;
  inset-inline-start: 13px;
  inline-size: 2px;
  background: #e2ece9;
}

.app-main[data-screen="zatca-settings"] .onboarding-list--rest .onboarding-step:last-child::before {
  display: none;
}

.app-main[data-screen="zatca-settings"] .step-marker {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 28px;
  block-size: 28px;
  border-radius: 50%;
  background: #e8eeed;
  font-size: 12.5px;
  font-weight: 800;
  color: #8b93a1;
}

/* The view already computes `is-current` and `is-complete`; they only needed a
   colour. A completed step reads as done, the current one as active, and the
   ones still out of reach stay grey — which is the same information the
   disabled buttons carry, said a second way. */

.app-main[data-screen="zatca-settings"] .onboarding-step.is-current .step-marker {
  background: var(--accent);
  color: #fff;
  box-shadow: 0 0 0 4px rgb(15 123 108 / 12%);
}

.app-main[data-screen="zatca-settings"] .onboarding-step.is-complete .step-marker {
  background: #d8ebe6;
  color: #0f6b5e;
}

.app-main[data-screen="zatca-settings"] .step-content {
  min-inline-size: 0;
  flex: 1 1 auto;
}

.app-main[data-screen="zatca-settings"] .step-content h3 {
  margin: 0;
  font-size: 13.5px;
  font-weight: 800;
  color: #0f172a;
}

.app-main[data-screen="zatca-settings"] .step-content p {
  margin: 4px 0 0;
  font-size: 12px;
  line-height: 1.65;
  color: #8b93a1;
}

.app-main[data-screen="zatca-settings"] .step-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 34px;
  margin-block-start: 10px;
  padding-inline: 16px;
  border: 1px solid #cfe2dd;
  border-radius: 9px;
  background: #f2f8f6;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--accent);
  cursor: pointer;
}

.app-main[data-screen="zatca-settings"] .step-action:hover:not(:disabled) {
  background: #e7f3f0;
}

.app-main[data-screen="zatca-settings"] .step-action:disabled {
  border-color: #e6eaf1;
  background: #f4f5f7;
  color: #b3bac6;
  cursor: not-allowed;
}

/* Step 4 is the irreversible one; the reference gives it the filled treatment. */
.app-main[data-screen="zatca-settings"] .step-action-primary:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

/* The confirm component renders step 4's real trigger once it is reachable. */
.app-main[data-screen="zatca-settings"] .step-content .confirm-trigger,
.app-main[data-screen="zatca-settings"] .step-content details > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 34px;
  margin-block-start: 10px;
  padding-inline: 16px;
  border-radius: 9px;
  background: var(--accent);
  font-size: 12.5px;
  font-weight: 700;
  color: #fff;
  cursor: pointer;
  list-style: none;
}

/* The OTP row: a six-digit box beside its button, wrapping rather than
   overflowing when the column is narrow. */

.app-main[data-screen="zatca-settings"] .otp-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-block-start: 10px;
}

.app-main[data-screen="zatca-settings"] .otp-input {
  inline-size: clamp(96px, 40%, 132px);
  text-align: center;
  letter-spacing: 0.28em;
  font-weight: 700;
}

.app-main[data-screen="zatca-settings"] .otp-row .step-action {
  margin-block-start: 0;
}

/* ── I.8 — the security note ───────────────────────────────────────────────
   BR-373's promise that keys are held encrypted. It is reassurance, so it is
   quiet: a tinted strip, not an alert. */

.app-main[data-screen="zatca-settings"] .security-note {
  margin: 16px 20px 0;
  padding: 12px 14px;
  border: 1px solid #e2ece9;
  border-radius: 12px;
  background: #f7faf9;
}

.app-main[data-screen="zatca-settings"] .security-note p {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.7;
  color: #64748b;
}

.app-main[data-screen="zatca-settings"] .security-note strong {
  color: #0f172a;
}

/* ── I.9 — narrow screens ──────────────────────────────────────────────────
   The columns already wrap on their own. What is left is the padding, which is
   generous at 1680 and would spend a fifth of a 360px screen. */

@media (max-width: 47.99rem) {
  .app-main[data-screen="zatca-settings"] .workspace-grid,
  .app-main[data-screen="zatca-settings"] .integration-overview {
    margin-inline: 10px;
  }

  .app-main[data-screen="zatca-settings"] .integration-overview,
  .app-main[data-screen="zatca-settings"] .panel-header,
  .app-main[data-screen="zatca-settings"] .settings-panel .form-grid {
    padding-inline: 14px;
  }

  .app-main[data-screen="zatca-settings"] .connection-config,
  .app-main[data-screen="zatca-settings"] .onboarding-status,
  .app-main[data-screen="zatca-settings"] .onboarding-list,
  .app-main[data-screen="zatca-settings"] .security-note {
    margin-inline: 14px;
  }

  .app-main[data-screen="zatca-settings"] .onboarding-header {
    padding-inline: 14px;
  }

  /* One field per row: two 13px inputs side by side on a phone are unusable. */
  .app-main[data-screen="zatca-settings"] .settings-panel .form-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .app-main[data-screen="zatca-settings"] .save-btn {
    flex: 1 1 100%;
  }
}

/* ── I.10 — the hero puts its figures BESIDE the title, not beneath it ─────
   Comparing the rebuilt page against `038_ZATCA_Settings.png` at the capture
   width, one compositional difference was left: the reference runs the four
   figures down the inline-END half of the hero, level with the title block, and
   the first pass stacked them underneath. On a 1,400px content area that is the
   difference between a hero that reads as one statement and one that reads as
   two.

   The placement spans every row of the first column, so the optional
   identity-conflict banner and the register link can appear or not without
   moving the figures. A real layout-mode transition is what the narrow case
   needs — side by side genuinely cannot work at 400px — so the query is doing
   layout work here rather than standing in for it. */

@media (min-width: 64rem) {
  .app-main[data-screen="zatca-settings"] .integration-overview {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    column-gap: 28px;
    align-items: start;
  }

  .app-main[data-screen="zatca-settings"] .overview-heading-row,
  .app-main[data-screen="zatca-settings"] .integration-overview > p,
  .app-main[data-screen="zatca-settings"] .zatca-identity-conflict,
  .app-main[data-screen="zatca-settings"] .zatca-register-link {
    grid-column: 1;
  }

  .app-main[data-screen="zatca-settings"] .overview-metrics {
    grid-column: 2;
    grid-row: 1 / -1;
    align-self: center;
    margin: 0;
  }
}

/* ── I.11 — the panel marks ────────────────────────────────────────────────
   A tinted rounded square holding the section's line icon, as the reference
   draws it. The header stops using `space-between` — with three children that
   would have pushed the icon, the title and the required-note to three corners
   — and the note is sent to the inline end on its own instead. */

.app-main[data-screen="zatca-settings"] .panel-header {
  justify-content: flex-start;
  align-items: center;
}

.app-main[data-screen="zatca-settings"] .panel-header > div {
  min-inline-size: 0;
}

.app-main[data-screen="zatca-settings"] .required-note {
  margin-inline-start: auto;
  align-self: flex-start;
}

.app-main[data-screen="zatca-settings"] .panel-icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 38px;
  block-size: 38px;
  margin-inline-end: 12px;
  border-radius: 11px;
  background: #e7f3f0;
  color: var(--accent);
}

/* ── I.12 — the save bar stops floating once the form is a single column ───
   ⚠ FOUND BY MEASUREMENT, NOT BY EYE. The sweep reported the save button
   overlapping `#commercial_registration` at 360px, and it was right: a bar
   pinned to the bottom of the viewport sits on top of whatever field happens to
   be there, and on a phone the identity form is one tall column so that is
   almost always a field the operator is trying to fill.

   On a wide screen the bar is worth its cost — the form is one of two columns,
   there is little under it, and the reference pins it. Once the columns stack
   the cost changes and the bar goes back into the flow at the end of the form,
   which is where a phone expects it anyway. The clearance the layout reserved
   for a floating bar goes with it. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="zatca-settings"] .save-footer {
    position: static;
    box-shadow: var(--ui-shadow-card);
  }

  .app-main[data-screen="zatca-settings"] .zatca-layout {
    padding-block-end: 24px;
  }
}

/* ── I.13 — the title mark gives way before the title does ─────────────────
   ⚠ I.2 CAUSED A PAGE OVERFLOW AND THE SWEEP CAUGHT IT. The badge adds 36px to
   a title that is `position: absolute`, `nowrap`, and already pushed 58px clear
   of the drawer opener. Measured at 320px: the heading ran [-26..247] in a
   305px viewport — 26px off the inline end, which is the whole 26px the page
   was scrolling.

   The mark is decoration and the title is not, so the mark is what gives way.
   Below 30rem it is simply not drawn; there is no width at which a decorative
   square is worth pushing the page's own name off the screen.

   The cap is the guarantee behind that judgement. It is expressed against the
   content box rather than the viewport, so it holds whatever the title says —
   a longer string in another locale cannot reintroduce the same defect. */

@media (max-width: 30rem) {
  .app-main[data-screen="zatca-settings"] > h1::before,
  .app-main[data-screen="zatca-settings"] > h2::before {
    display: none;
  }
}

.app-main[data-screen="zatca-settings"] > h1,
.app-main[data-screen="zatca-settings"] > h2 {
  max-inline-size: calc(100% - 74px);
  overflow: hidden;
}

/* ── I.14 — the columns wrap rather than squeeze ───────────────────────────
   The capture at 768px showed both columns still side by side, with the
   onboarding card crushed to about 230px — narrow enough that the environment
   select truncated its own option text mid-word. Two columns that both fit
   badly are worse than one column that fits.

   A floor on each column is what turns a squeeze into a wrap: while both
   minimums fit on one line they stay side by side, and the moment they do not,
   the flex line breaks and each takes the full width. `min()` keeps the floor
   from becoming an overflow on a phone, where 100% of the line is less than the
   floor itself. No width is named — the wrap happens wherever the content stops
   fitting, which at the current type sizes is a little under 1100px. */

.app-main[data-screen="zatca-settings"] .settings-column {
  min-inline-size: min(100%, 430px);
}

.app-main[data-screen="zatca-settings"] .onboarding-panel {
  min-inline-size: min(100%, 320px);
}


/* ── I.15 — the submission register ────────────────────────────────────────
   THE MODULE'S SECOND SCREEN, AND IT HAD THE SAME DEFECT AS THE FIRST. The
   capture of `/zatca/submissions` at the reference width showed loose text on
   the page ground: a bare `العودة` link, the environment and count as four
   unstyled lines, and the empty state as one sentence in a plain box. No
   reference capture exists for this screen — 038 is the settings page — so the
   authority here is the product's own vocabulary, which is what the view was
   already written against: `.doc-table`, `.empty-state`, `.pill`,
   `.settings-card`, `.table-scroll` and `.overview-metrics` are all classes
   other screens define. Three of them simply had nothing behind them.

   `.doc-table` now joins `.data-table`'s own rules above rather than getting a
   second table style of its own, and `.empty-state`, `.pill` and
   `.settings-card` were already defined and needed nothing. What is left is the
   screen's own frame and the two classes unique to it. */

/* ⚠ SCOPED ON THE PAGE'S OWN CLASS, NOT ON `data-screen`. Measured: the
   submission register renders with `data-screen=""` — the attribute is fed
   from the sidebar registry and this route is not a sidebar entry, so every
   rule written against `[data-screen="zatca-submissions"]` matched nothing
   at all. `.zatca-submissions` is the wrapper the view actually carries. */
.app-main:has(> .zatca-submissions) {
  padding: var(--ui-inset);
}

.zatca-submissions {
  display: flex;
  flex-direction: column;
  gap: var(--ui-gap);
}

/* The register carries the same four-figure rail as the settings screen, so it
   gets the same treatment — the rules were scoped to one screen and the shared
   composition needs both. */
.zatca-submissions .overview-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: 0;
  margin: 0;
  border: 1px solid var(--ui-line);
  border-radius: 12px;
  overflow: hidden;
  background: var(--ui-surface);
  box-shadow: var(--ui-shadow-card);
}

.zatca-submissions .metric-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-inline-size: 0;
  padding: 12px 16px;
  border-inline-end: 1px solid #eef1f6;
}

.zatca-submissions .metric-item:last-child {
  border-inline-end: 0;
}

.zatca-submissions .metric-item dt,
.zatca-submissions .metric-item > *:first-child {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ui-ink-muted);
}

.zatca-submissions .metric-item dd,
.zatca-submissions .metric-item > *:last-child {
  margin: 0;
  font-size: 14px;
  font-weight: 800;
  color: var(--ui-ink);
}

/* `غير مفعّل / غير متاح` — a state, not an error. The gateway being
   unconfigured is the ordinary condition before onboarding finishes, so this
   reads as information rather than as a failure. */
.zatca-submissions .zatca-not-configured {
  margin: 0;
  padding: 12px 16px;
  border: 1px solid #e4e9f2;
  border-radius: 12px;
  background: var(--ui-surface);
  box-shadow: var(--ui-shadow-card);
  font-size: 13px;
  font-weight: 700;
  color: var(--ui-ink-soft);
}

.zatca-submissions .zatca-failed-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.zatca-submissions .zatca-failed-actions .action-btn,
.zatca-submissions .zatca-failed-actions button {
  display: inline-flex;
  align-items: center;
  min-block-size: 30px;
  padding-inline: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 8px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: var(--ui-blue);
  text-decoration: none;
  cursor: pointer;
}

/* The register is wide; it scrolls inside its own container so the page never
   does — the invariant Section F established for every table in the build. */
.zatca-submissions .table-scroll {
  overflow-x: auto;
  max-inline-size: 100%;
}

.zatca-submissions .doc-table {
  inline-size: 100%;
  border-collapse: collapse;
}

/* ── I.16 — the register's exit ────────────────────────────────────────────
   The settings screen styles its back control through `[data-screen=...]`, and
   the register has no such attribute, so `العودة` rendered as a bare underlined
   link. The same values, keyed on the page's own wrapper — the pill is the
   module's, not a new one. It stays in the flow here rather than being pinned
   into a topbar strip, because this screen has no topbar to pin it to. */

.app-main:has(> .zatca-submissions) > .back-btn,
.zatca-submissions > .back-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  block-size: 33px;
  margin-block-end: 4px;
  padding-inline: 16px;
  border: 1px solid #dbe8e4;
  border-radius: 999px;
  background: #fff;
  color: #0f766e;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

/* ── I.17 — the register keeps the corner the opener needs ─────────────────
   ⚠ I.15's SHORTHAND SILENTLY UNDID H.13. `padding: var(--ui-inset)` on the
   register wipes `padding-block-start`, and that longhand is exactly what
   reserves the top inline-start corner for the fixed drawer opener. The sweep
   read the consequence back immediately: `#gsbToggle` overlapping `#btnBack` at
   every width from 320 to 850 — eight defects from one shorthand.

   Re-asserted here rather than by weakening I.15, because the register does
   want an even inset everywhere else; it is only the corner that is spoken for. */

@media (max-width: 63.99rem) {
  .app-main:has(> .zatca-submissions) {
    padding-block-start: max(60px, var(--ui-inset));
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   J — POS: THE SALE PANEL STOPS EATING THE REGISTER
   ═══════════════════════════════════════════════════════════════════════════

   `.pos-sale-panel { flex: 0 0 616px }` — grow 0, SHRINK 0. 616px is the width
   `003_POS.png` measures at 1680, and as a fixed basis that cannot give way it
   is correct at exactly one viewport and wrong at every smaller one.

   Measured, with the reference beside it:

     1680 (reference)   grid 805   cart 620      the register is the larger pane
     1024 (measured)    grid 145   cart 616      one tile, and the cart has the rest
     1100 (measured)    grid 221   cart 616

   At 1024 the cashier gets a 145px register — a single column of tiles beside a
   cart that is four times its width. Nothing overflows, nothing overlaps and no
   control leaves the screen, which is why every geometric check on this build
   passes on that screen: the grid is not broken, it is starved. Only the
   picture shows it.

   Two changes and no width named. The cart may now SHRINK from 616 but never
   grow past it, so wherever 616 fits the reference is reproduced exactly; and
   the register carries a floor, so the shrinking stops while the grid can still
   hold more than one tile. Above about 1400 neither rule engages at all. */

/* ⚠ A SHRINKABLE BASIS WAS NOT ENOUGH, AND THE MEASUREMENT SAID SO. With
   `flex: 0 1 616px` the cart still took every spare pixel in the band — at
   1100 it held 525 against the register-'s 312 — because flex distributes a
   shortfall in PROPORTION to each basis, and 616 is the larger basis. The
   reference has the register larger, so the cart needs a ceiling expressed
   against the CONTAINER, not against itself.

   46% is the reference-'s own proportion: 620 of the 1345 the two panes share
   at 1680. So above roughly 1340px of layout the ceiling is 616 and nothing
   moves; below it the cart yields exactly in step with the container and the
   register stays the larger pane at every width, which is what the capture
   shows and what a cashier needs. */
.pos-sale-panel {
  flex: 0 1 616px;
  max-inline-size: min(616px, 46%);
  min-inline-size: min(100%, 320px);
}

.pos-grid {
  /* The floor that makes the cart yield. `min()` so a single-pane phone, where
     the line is narrower than the floor, is not handed an overflow instead. */
  min-inline-size: min(100%, 300px);
}

/* ── J.2 — the panel's contents shrink with it ─────────────────────────────
   ⚠ BOUNDING THE CONTAINER WITHOUT BOUNDING ITS CONTENTS MOVES THE OVERFLOW,
   IT DOES NOT REMOVE IT. J.1 let the cart yield and the sweep answered
   immediately: 222px of page overflow at 1024, with `#customerCodeSearch` at
   [-34..148] and `#extraInput` at [-237..-63] — the panel had narrowed and its
   rows had not.

   The cause is the flexbox default nobody remembers: a flex item's `min-width`
   is `auto`, which means "never smaller than my content". Every row inside the
   cart is a flex item, so every row refused to narrow and pushed its overflow
   out through the panel. Releasing that floor is the whole fix; it engages only
   while shrinking is actually needed, so at 1680 none of this applies and the
   reference geometry is untouched. */

.pos-sale-panel,
.pos-sale-panel > *,
.pos-customer,
.pos-customer > *,
.pos-money,
.pos-totals,
.pos-discount-row,
.pos-discount-row > *,
.pos-extra-row,
.pos-extra-row > *,
.pos-payment,
.pos-payment > *,
.pos-unify,
.pos-finishing,
.pos-hanger,
.pos-cart {
  min-inline-size: 0;
}

/* An input carries its own intrinsic `size`, which is a width the panel cannot
   argue with unless it is capped. */
.pos-sale-panel input,
.pos-sale-panel select,
.pos-sale-panel textarea {
  min-inline-size: 0;
  max-inline-size: 100%;
  box-sizing: border-box;
}

/* ── J.3 — the cart's rows wrap instead of clipping ────────────────────────
   With the panel now able to narrow, three of its rows are wider than it below
   about 1200px and were losing their labels to the clip rather than overflowing
   — `الخصم` rendered as `الخ`, `نشا` cut mid-word. That is invisible to an
   overflow check, because nothing leaves the page: the label is simply gone.

   A row that cannot fit on one line takes two. At the reference width there is
   room for one, so nothing wraps and nothing moves. */

.pos-discount-row,
.pos-extra-row,
.pos-unify,
.pos-finishing,
.pos-hanger,
.pos-customer {
  flex-wrap: wrap;
  row-gap: 8px;
}

/* A label that has wrapped onto its own line should not then be truncated. */
.pos-discount-row > label,
.pos-extra-row > label,
.pos-finishing > label,
.pos-hanger > label,
.pos-unify > label {
  flex: none;
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   K — THE TWO POS SURFACES THAT WERE NEVER STYLED
   ═══════════════════════════════════════════════════════════════════════════

   Counted against the stylesheet, class by class:

     pos/suspended.blade.php          43 classes, 29 with no rule at all
     pos/partials/deferred.blade.php  19 classes, 17 with no rule at all

   These are not decorative corners. `suspended.blade.php` is the parked-sales
   drawer a cashier opens to resume a held ticket, and `deferred.blade.php` is
   the الفواتير الآجلة tab — one of the two tabs on the till itself. Both
   rendered as unstyled markup on the page ground: a bare list, a bare table, a
   sentence where an empty state should be.

   It is the same finding as the ZATCA screen and the same cause: the views were
   written against a design and the styles were never written. So the same
   remedy — no markup changes, no new colours, and the shared vocabulary reused
   rather than reinvented. `.deferred-table` joins `.data-table`'s own rules
   above rather than getting a second table style; `.empty-state`, `.pill` and
   the `--ui-*` tokens were already defined and needed nothing.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── K.1 — the parked-sales drawer ─────────────────────────────────────────
   A modal that lists held tickets. Each row is a card: who it is for and when
   it was parked on the inline start, the amount and the two actions on the
   inline end. */

.suspended-sales-modal-box {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-block-size: min(80dvh, 620px);
  padding: 18px 20px 20px;
}

.suspended-sales-search-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.suspended-sales-search {
  flex: 1 1 200px;
  min-inline-size: 0;
  box-sizing: border-box;
  block-size: 38px;
  padding-inline: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 9px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 13px;
}

.suspended-sales-hint {
  margin: 0;
  font-size: 12px;
  color: var(--ui-ink-muted);
}

.suspended-sales-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.suspended-sale-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--ui-line);
  border-radius: 12px;
  background: var(--ui-surface);
}

.suspended-sale-info {
  min-inline-size: 0;
  flex: 1 1 190px;
}

.suspended-sale-name {
  display: block;
  font-size: 13.5px;
  font-weight: 800;
  color: var(--ui-ink);
  overflow-wrap: anywhere;
}

.suspended-sale-meta {
  display: block;
  margin-block-start: 3px;
  font-size: 11.5px;
  color: var(--ui-ink-muted);
}

.suspended-sale-side {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.suspended-sale-total {
  font-size: 14px;
  font-weight: 800;
  color: var(--ui-ink);
  font-variant-numeric: tabular-nums;
}

/*
 | A ticket parked long enough to be stale is flagged, not hidden — the cashier
 | decides. Amber rather than red: it is a warning about age, not an error.
 */
.suspended-sale-card.is-stale {
  border-color: #f0d6a4;
  background: #fffaf0;
}

.is-stale-tag {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: var(--ui-radius-pill);
  background: #fdf3e2;
  font-size: 11px;
  font-weight: 700;
  color: #92610a;
}

.btn-suspended-sale-view,
.btn-suspended-sale-delete,
.suspended-sale-restore {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 32px;
  padding-inline: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 8px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  color: var(--ui-blue);
  text-decoration: none;
  cursor: pointer;
}

.suspended-sale-restore {
  border-color: var(--ui-blue);
  background: var(--ui-blue);
  color: #fff;
}

.btn-suspended-sale-delete {
  color: var(--ui-red);
  border-color: #f3d3d4;
  background: #fdf2f2;
}

/* The expanded detail box under a card. */
.suspended-sale-details-box {
  flex: 1 1 100%;
  margin-block-start: 10px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--ui-canvas-2);
  font-size: 12.5px;
  color: var(--ui-ink-soft);
}

.ss-details-customer,
.ss-details-items,
.ss-details-totals,
.ss-details-note {
  margin: 0 0 6px;
}

.ss-details-totals {
  font-weight: 800;
  color: var(--ui-ink);
  font-variant-numeric: tabular-nums;
}

.ss-details-note:last-child {
  margin-block-end: 0;
}

.suspended-sale-delete-confirm {
  flex: 1 1 100%;
  margin-block-start: 10px;
  padding: 12px 14px;
  border: 1px solid #f3d3d4;
  border-radius: 10px;
  background: #fdf2f2;
}

.confirm-modal-title {
  margin: 0 0 4px;
  font-size: 13px;
  font-weight: 800;
  color: #a3231f;
}

.btn-modal-save {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 36px;
  padding-inline: 16px;
  border: 0;
  border-radius: 9px;
  background: var(--ui-blue);
  font: inherit;
  font-size: 13px;
  font-weight: 800;
  color: #fff;
  cursor: pointer;
}

.suspended-sales-empty {
  padding: 36px 20px;
  text-align: center;
  font-size: 13px;
  color: var(--ui-ink-muted);
}

.suspended-sales-badge,
.suspended-sales-count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 20px;
  padding: 2px 7px;
  border-radius: var(--ui-radius-pill);
  background: var(--ui-blue-soft);
  font-size: 11px;
  font-weight: 800;
  color: var(--ui-blue);
  font-variant-numeric: tabular-nums;
}

.header-icon-btn,
.header-text-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-block-size: 34px;
  padding-inline: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 9px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ui-ink-soft);
  text-decoration: none;
  cursor: pointer;
}

/* ── K.2 — the deferred-invoices tab ───────────────────────────────────────
   The second tab on the till. It searches unpaid invoices and offers payment
   against them, and it was rendering as a raw table on the page ground. */

.pos-deferred {
  display: flex;
  flex-direction: column;
  gap: var(--ui-gap);
  padding: var(--ui-inset);
}

.deferred-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 14px 16px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
  box-shadow: var(--ui-shadow-card);
}

.deferred-filters input,
.deferred-filters select,
.deferred-filters .sel {
  min-inline-size: 0;
  box-sizing: border-box;
  block-size: 38px;
  padding-inline: 12px;
  border: 1px solid var(--ui-line);
  border-radius: 9px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 13px;
}

.deferred-filters input[type="search"],
.deferred-filters input[type="text"] {
  flex: 1 1 220px;
}

.deferred-results {
  border-radius: var(--ui-radius);
  overflow: hidden;
}

/* Wide on a phone; it scrolls inside its own box so the page never does. */
.deferred-results,
.deferred-table {
  max-inline-size: 100%;
}

.deferred-table {
  inline-size: 100%;
  border-collapse: collapse;
}

.deferred-table .num,
.deferred-table td.num {
  text-align: end;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.deferred-pay-link,
.deferred-pay-all,
.payment-method-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 32px;
  padding-inline: 14px;
  border: 1px solid var(--ui-line);
  border-radius: 9px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ui-blue);
  text-decoration: none;
  cursor: pointer;
}

.deferred-pay-all {
  border-color: var(--ui-blue);
  background: var(--ui-blue);
  color: #fff;
}

.payment-method-btn[aria-pressed="true"],
.payment-method-btn.active {
  border-color: var(--ui-blue);
  background: var(--ui-blue-soft);
}

/* `.pill` already carries the chip shape; only the paid variant's colour is
   this file's business. */
.pill--paid {
  background: #e7f6ec;
  color: #1b6e3c;
}

/*
 | THE EMPTY STATE IS THE DEFAULT VIEW HERE, NOT AN EDGE CASE — the tab lists
 | nothing until a query is typed, which the reference capture also shows. It
 | therefore gets the product's own empty-state treatment rather than a
 | sentence on the ground.
 */
.deferred-empty,
.deferred-none {
  padding: 48px 24px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius);
  background: var(--ui-surface);
  box-shadow: var(--ui-shadow-card);
  text-align: center;
  color: var(--ui-ink-muted);
}

.deferred-empty-title {
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 800;
  color: var(--ui-ink);
}

.deferred-empty-hint {
  margin: 0;
  font-size: 12.5px;
  color: var(--ui-ink-muted);
}

.deferred-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.def-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 34px;
  min-block-size: 34px;
  padding-inline: 10px;
  border: 1px solid var(--ui-line);
  border-radius: 9px;
  background: var(--ui-surface);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ui-ink-soft);
  text-decoration: none;
}

.def-page-info {
  font-size: 12px;
  color: var(--ui-ink-muted);
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════════════════
   L — THE SHIFTS SCREEN, MEASURED OFF 025_Shifts.png
   ═══════════════════════════════════════════════════════════════════════════

   Every number below was read out of the capture with GD rather than estimated
   by eye, because "match it exactly" is a pixel claim and only pixels can
   settle it. The capture is 1680x1050; the content column runs x 16..1415 and
   the nav rail starts at 1433.

     top bar          y   0..54   #FDFEFE, 1px #D5E3EC beneath
     page ground                  #E9F1F6
     header card      y  73..164  #FFFFFF
     primary button   y 100..136  #0C7EA4
     drawer heading   y 176..189  #48677B
     drawer tiles     y 196..576  5 x 269 wide, 14px gutters   (h 381)
     info heading     y 580..596
     info tiles       y 604..989  4 x 340 wide, 14px gutters   (h 386)
     footer hint      y 999..1025 #E3F3FB on #075D87

     tile label       #587A8F   ink band 12px
     tile value       #0C2D40 neutral · #C71E2B out · #047857 in   band 18px
     tile fill        #FFFFFF grading to #F6FAFC

   THE COLUMN COUNTS COME OUT OF ONE RULE, NOT TWO. `auto-fit` with a 200px
   floor and `1fr` tracks collapses the empty tracks, so the five-item row
   resolves to 5 x 268.8 and the four-item row to 4 x 339.5 — which is the
   capture, to within half a pixel, from a single declaration that also survives
   a narrower screen.

   THE NINE MARKS ARE EXTRACTED, NOT REDRAWN. `public/img/shifts/*.png` were cut
   from this same capture with a border flood fill, which is how the asset
   record says every other icon in this build was obtained. A brightness
   threshold would have punched holes through their white highlights.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── L.1 — the screen ──────────────────────────────────────────────────────
   The ground was #e1f1fa, a guess from an earlier pass. Measured it is
   #E9F1F6, and the difference is visible against the white cards. */

.app-main[data-screen="shifts"] {
  background: #e9f1f6;
}

/* ── L.2 — the header card ────────────────────────────────────────────────
   الكاشير · فُتحت في · المدة on the inline start, the two actions pushed to the
   inline end. */

.app-main[data-screen="shifts"] .shift-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 26px;
  margin: 0 16px 16px;
  padding: 14px 20px;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 1px 2px rgb(16 24 40 / 5%), 0 6px 18px rgb(16 24 40 / 5%);
}

.app-main[data-screen="shifts"] .shift-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-inline-size: 0;
}

.app-main[data-screen="shifts"] .shift-meta-label {
  font-size: 11.5px;
  font-weight: 600;
  color: #587a90;
}

.app-main[data-screen="shifts"] .shift-meta strong,
.app-main[data-screen="shifts"] .shift-meta span[dir="ltr"] {
  font-size: 13.5px;
  font-weight: 800;
  color: #0c2d40;
}

/* المدة — the label sits beside three amber boxes, each a figure over its unit. */
.app-main[data-screen="shifts"] .shift-duration {
  display: flex;
  align-items: center;
  gap: 8px;
}

.app-main[data-screen="shifts"] .shift-dur-box {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-inline-size: 42px;
  padding: 5px 8px;
  border-radius: 9px;
  background: #fdf3e3;
  font-size: 13px;
  font-weight: 800;
  color: #92610a;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.app-main[data-screen="shifts"] .shift-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-inline-start: auto;
}

.app-main[data-screen="shifts"] .shift-actions .btn-outline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  block-size: 37px;
  padding-inline: 16px;
  border: 1px solid #dbe6ec;
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
  font-weight: 700;
  color: #48677b;
  text-decoration: none;
}

.app-main[data-screen="shifts"] .shift-actions .btn-save,
.app-main[data-screen="shifts"] .shift-open-form .btn-save {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 37px;
  padding-inline: 20px;
  border: 0;
  border-radius: 10px;
  background: #0c7ea4;
  font: inherit;
  font-size: 13px;
  font-weight: 800;
  color: #fff;
  cursor: pointer;
}

.app-main[data-screen="shifts"] .shift-actions .btn-save:hover {
  background: #0a6c8d;
}

/* ── L.3 — the two section headings ───────────────────────────────────────── */

.app-main[data-screen="shifts"] .shift-group {
  margin: 0 16px 16px;
}

.app-main[data-screen="shifts"] .shift-group-title {
  margin: 0 0 7px;
  font-size: 13px;
  font-weight: 700;
  color: #48677b;
}

/* ── L.4 — the tile grids ─────────────────────────────────────────────────
   One declaration, both rows. See the section header for why. */

.app-main[data-screen="shifts"] .shift-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: 14px;
  justify-content: initial;
}

/* ── L.5 — the tile ───────────────────────────────────────────────────────
   Measured 381px tall for a single figure. That is what the capture shows and
   it is reproduced rather than corrected: the brief was to match it, and the
   height is the most distinctive thing about this screen. The mark sits at the
   inline start with 28px of padding, the label and figure beside it. */

.app-main[data-screen="shifts"] .shift-tile {
  box-sizing: border-box;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  inline-size: auto;
  block-size: auto;
  min-block-size: 382px;
  padding: 18px 28px;
  border-radius: 14px;
  background: linear-gradient(180deg, #ffffff 0%, #ffffff 42%, #f6fafc 100%);
  box-shadow: 0 1px 2px rgb(16 24 40 / 4%), 0 8px 20px rgb(16 24 40 / 5%);
}

/* The mark. `flex: none` so a long label never squeezes it. */
.app-main[data-screen="shifts"] .shift-tile::before {
  content: "";
  flex: none;
  order: -1;
  inline-size: 64px;
  block-size: 64px;
  background: center / contain no-repeat;
}

.app-main[data-screen="shifts"] .shift-tile-label,
.app-main[data-screen="shifts"] .shift-tile-value {
  display: block;
}

.app-main[data-screen="shifts"] .shift-tile-label {
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  color: #587a8f;
  overflow-wrap: anywhere;
}

.app-main[data-screen="shifts"] .shift-tile-value {
  margin-block-start: 4px;
  font-size: 24px;
  font-weight: 800;
  line-height: 1.25;
  color: #0c2d40;
  font-variant-numeric: tabular-nums;
}

/*
 | The label and figure are one block beside the mark, so they need a wrapper
 | the markup does not have. `flex-wrap` on the tile plus a shared basis gives
 | the same result without one: the two spans stack, and the mark keeps its row.
 */
.app-main[data-screen="shifts"] .shift-tile {
  flex-wrap: wrap;
  align-content: center;
}

.app-main[data-screen="shifts"] .shift-tile-label,
.app-main[data-screen="shifts"] .shift-tile-value {
  flex: 1 1 100%;
  min-inline-size: 0;
}

.app-main[data-screen="shifts"] .shift-tile::before {
  flex: none;
}

/* ── L.6 — the marks, and the figure's tint ───────────────────────────────
   Money that LEAVES the drawer is red, money that ENTERS is green, and a
   reference figure is neutral. That is the capture's own scheme, sampled tile
   by tile rather than assumed from the label. */

.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-opening"]::before { background-image: url("/img/shifts/opening.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-cash-sales"]::before { background-image: url("/img/shifts/cash-sales.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-cash-settled"]::before { background-image: url("/img/shifts/cash-settled.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-refunds"]::before { background-image: url("/img/shifts/refunds.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-expenses"]::before { background-image: url("/img/shifts/expenses.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-card"]::before { background-image: url("/img/shifts/card.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-deferred"]::before { background-image: url("/img/shifts/deferred.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-subs"]::before { background-image: url("/img/shifts/subs.png"); }
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-sales"]::before { background-image: url("/img/shifts/sales.png"); }

.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-cash-sales"] .shift-tile-value,
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-cash-settled"] .shift-tile-value {
  color: #047857;
}

.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-refunds"] .shift-tile-value,
.app-main[data-screen="shifts"] [data-tile="ui.shifts-card-expenses"] .shift-tile-value {
  color: #c71e2b;
}

/* ── L.7 — the footer note ────────────────────────────────────────────────
   A tinted strip, not a paragraph on the ground. */

.app-main[data-screen="shifts"] .shift-expected-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 16px 16px;
  padding: 8px 14px;
  border-radius: 10px;
  background: #e3f3fb;
  font-size: 12.5px;
  font-weight: 600;
  color: #075d87;
}

/* ── L.8 — the close-shift dialog ─────────────────────────────────────────
   `026_Shifts_Modal_closeModal.png`: a 440px panel, the counted-cash box drawn
   large with a teal focus ring, the primary action filled in the same #0C7EA4
   as the header's. The panel chrome is the product's shared modal and is left
   alone; only what this screen adds is set here. */

.app-main[data-screen="shifts"] .shift-count-hint,
.app-main[data-screen="shifts"] .shift-confirm-hint {
  margin: 0 0 14px;
  font-size: 12.5px;
  line-height: 1.7;
  color: #64748b;
}

.app-main[data-screen="shifts"] .cat-modal-panel .field input[type="number"] {
  block-size: 56px;
  font-size: 22px;
  font-weight: 800;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="shifts"] .cat-modal-panel .field input:focus {
  outline: none;
  border-color: #0c7ea4;
  box-shadow: 0 0 0 3px rgb(12 126 164 / 15%);
}

/* The review step's figures. */
.app-main[data-screen="shifts"] .shift-review-figures {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 14px;
  margin: 0 0 14px;
  padding: 12px 14px;
  border-radius: 10px;
  background: #f4f8fa;
  font-size: 13px;
}

.app-main[data-screen="shifts"] .shift-review-figures dt {
  color: #587a90;
}

.app-main[data-screen="shifts"] .shift-review-figures dd {
  margin: 0;
  font-weight: 800;
  color: #0c2d40;
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="shifts"] .shift-variance-label {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  margin-inline-start: 6px;
  border-radius: 999px;
  background: #fdf3e3;
  font-size: 11px;
  font-weight: 700;
  color: #92610a;
}

.app-main[data-screen="shifts"] .shift-warning {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid #f5c9c6;
  border-radius: 10px;
  background: #fdeceb;
  font-size: 12.5px;
  font-weight: 700;
  color: #a3231f;
}

/* ── L.9 — no open shift ──────────────────────────────────────────────────
   The screen's other whole state, and it had no rules at all. */

.app-main[data-screen="shifts"] .shift-empty {
  max-inline-size: 520px;
  margin: 0 16px;
  padding: 32px 28px;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 1px 2px rgb(16 24 40 / 5%), 0 8px 20px rgb(16 24 40 / 5%);
}

.app-main[data-screen="shifts"] .shift-empty-title {
  margin: 0 0 6px;
  font-size: 16px;
  font-weight: 800;
  color: #0c2d40;
}

.app-main[data-screen="shifts"] .shift-empty-sub {
  margin: 0 0 20px;
  font-size: 13px;
  line-height: 1.7;
  color: #587a90;
}

.app-main[data-screen="shifts"] .shift-open-form .field label {
  display: block;
  margin-block-end: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: #48677b;
}

.app-main[data-screen="shifts"] .shift-open-form .field input {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 46px;
  padding-inline: 14px;
  border: 1px solid #dbe6ec;
  border-radius: 10px;
  background: #fff;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  color: #0c2d40;
}

.app-main[data-screen="shifts"] .shift-open-form .btn-save {
  margin-block-start: 16px;
}

/* ── L.10 — narrower screens ──────────────────────────────────────────────
   The grid folds on its own — that is what `auto-fit` is for. What cannot fold
   on its own is a 382px tile, which on a phone is most of the viewport for one
   figure, so below the rail width the tile is content-sized instead. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="shifts"] .shift-tile {
    min-block-size: 120px;
    padding: 16px 18px;
  }

  .app-main[data-screen="shifts"] .shift-tile::before {
    inline-size: 52px;
    block-size: 52px;
  }

  .app-main[data-screen="shifts"] .shift-header,
  .app-main[data-screen="shifts"] .shift-group,
  .app-main[data-screen="shifts"] .shift-expected-hint,
  .app-main[data-screen="shifts"] .shift-empty {
    margin-inline: 12px;
  }

  .app-main[data-screen="shifts"] .shift-actions {
    margin-inline-start: 0;
    flex: 1 1 100%;
  }
}

/* ── L.11 — the tile's interior is a row, not a stack ──────────────────────
   The first pass wrapped the three parts onto their own lines and produced a
   centred column: mark, then label, then figure. The capture is a ROW — the
   mark at the inline start with the label and the figure stacked beside it,
   the whole group vertically centred in the tall card and packed to the start.

   Measured in tile 1 of the capture (tile x 16..284):

     mark  x 198..257      inline start, 27px from the tile edge
     text  x  85..190      ends 8px from the mark, both lines flush to it
     band  y 351..403      centred in a tile spanning 196..576

   A two-column grid says that exactly, and needs no wrapper element the markup
   does not have: the mark spans both rows of column one, the label and figure
   take column two. In RTL column one is the inline start, so the mark lands on
   the right without a single physical property. */

.app-main[data-screen="shifts"] .shift-tile {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-rows: auto auto;
  align-content: center;
  justify-content: start;
  column-gap: 12px;
  row-gap: 2px;
  flex-wrap: nowrap;
}

.app-main[data-screen="shifts"] .shift-tile::before {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: center;
}

.app-main[data-screen="shifts"] .shift-tile-label {
  grid-column: 2;
  grid-row: 1;
  flex: none;
  align-self: end;
}

.app-main[data-screen="shifts"] .shift-tile-value {
  grid-column: 2;
  grid-row: 2;
  flex: none;
  margin-block-start: 0;
  align-self: start;
}

/* ── L.12 — the top bar ───────────────────────────────────────────────────
   `025_Shifts.png` opens with a 54px white strip: the screen's name and a round
   teal mark at the inline start, the `العودة` pill at the inline end. The build
   rendered the name as an `sr-only` heading and the exit as a bare underlined
   link, so the strip was not there at all.

   This is the same treatment `/customers` and `/zatca-settings` already use —
   the heading is brought back into sight rather than a second title being
   invented beside it — and the mark is `/img/tiles/shifts.png`, the product's
   own shifts icon, served from this origin because the policy is
   `img-src 'self'` with no `data:` allowance. */

.app-main[data-screen="shifts"] {
  position: relative;
  padding-block-start: 70px;
}

.app-main[data-screen="shifts"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 54px;
  background: #fdfefe;
  border-block-end: 1px solid #d5e3ec;
}

.app-main[data-screen="shifts"] > h1 {
  position: absolute;
  z-index: 1;
  inset-block-start: 0;
  inset-inline-start: 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  block-size: 54px;
  margin: 0;
  padding: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  font-size: 16px;
  font-weight: 800;
  color: #0c2d40;
  inline-size: auto;
}

.app-main[data-screen="shifts"] > h1::before {
  content: "";
  flex: none;
  order: -1;
  inline-size: 34px;
  block-size: 34px;
  border-radius: 50%;
  background: url("/img/tiles/shifts.png") center / 22px 22px no-repeat, #0c7ea4;
}

.app-main[data-screen="shifts"] > .back-btn {
  position: absolute;
  z-index: 1;
  top: 9px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  block-size: 36px;
  padding-inline: 16px;
  border: 1px solid #dbe6ec;
  border-radius: 999px;
  background: #fff;
  font-size: 13px;
  font-weight: 700;
  color: #0c2d40;
  text-decoration: none;
}

/* ── L.13 — the duration boxes stack their figure over its unit ────────────
   As capture 025 draws them: `25` in bold over `ساعة` in small. */

.app-main[data-screen="shifts"] .shift-dur-box b {
  font-size: 13.5px;
  font-weight: 800;
  line-height: 1.1;
}

.app-main[data-screen="shifts"] .shift-dur-box small {
  font-size: 9.5px;
  font-weight: 600;
  line-height: 1.2;
  opacity: 0.85;
}

/* ── L.14 — the content column starts where the capture starts ─────────────
   Measured side by side at 1680:

     reference   tile 1 x  16..284   w=269
     rebuild     tile 1 x  52..302   w=251

   36px of drift, and it is `.app-main`'s own `padding-inline: var(--ui-inset)`
   — 36px at this width — stacking on top of the 16px the tile group already
   carries. The capture's inset is 16 in total, so the shell's share is removed
   here rather than the group's margin being shrunk to compensate: the group's
   16 is the number the capture actually shows. */

.app-main[data-screen="shifts"] {
  padding-inline: 0;
}

/* ── L.15 — the vertical rhythm, trimmed to the capture ────────────────────
   Measured side by side at 1680, and every remaining difference traced to one
   cause: the rebuild was ~25px taller than the viewport, so it grew a scrollbar
   the capture does not have, and the scrollbar took 15px off the content column
   — which is why the tiles measured 266 against the capture's 269. The width
   error was a symptom of the height error.

     gap card -> row 1     capture 53   rebuild 44
     gap row 1 -> row 2    capture 28   rebuild 44
     row 2 ends            capture 978  rebuild 1003

   So the fix is vertical: the shell's top inset comes down to where the capture
   puts the header card, the space either side of a section heading comes down
   to what the capture leaves, and the page's trailing 40px — invisible, and the
   difference between fitting and not — comes down with them. */

.app-main[data-screen="shifts"] {
  padding-block-start: 58px;
  padding-block-end: 16px;
}

.app-main[data-screen="shifts"] .shift-group {
  margin-block-end: 10px;
}

.app-main[data-screen="shifts"] .shift-group-title {
  margin-block-end: 5px;
}

.app-main[data-screen="shifts"] .shift-tile {
  min-block-size: 380px;
}

/* ── L.16 — the last 24 pixels ─────────────────────────────────────────────
   The tile row already measures 381px against the capture's 381, and the second
   row lands within one pixel. What was left was the page's total height: 1074
   against 1050, which is the whole reason the tiles still measured 266 instead
   of 269 — a scrollbar the capture does not have, taking 15px off the column.

   Three gaps, each measured rather than nudged:

     card -> row 1     capture 53   rebuild 42   (+11 wanted)
     row 1 -> row 2    capture 28   rebuild 36   ( -8 wanted)
     row 2 -> bottom   capture 72   rebuild 97   (-25 wanted)

   Which nets to exactly the 24px the page is over. */

.app-main[data-screen="shifts"] {
  padding-block-end: 24px;
}

.app-main[data-screen="shifts"] .shift-header {
  margin-block-end: 25px;
}

.app-main[data-screen="shifts"] .shift-group {
  margin-block-end: 2px;
}

.app-main[data-screen="shifts"] .shift-expected-hint {
  margin: 20px 16px 0;
  padding: 4px 14px;
}

/* ── L.17 — the page stops being taller than the window ────────────────────
   Measured at three viewport heights, the answer was the same at all three:

     1680x1050   clientWidth 1665   scrollHeight 1074
     1680x1100   clientWidth 1665   scrollHeight 1124
     1680x1200   clientWidth 1665   scrollHeight 1224

   Always exactly 24px over, whatever the window — so it was never the content.
   `.app-shell` carries `min-height: 100vh` and stretches its row to the window;
   `.app-main` is a content-box, so that stretch sizes its CONTENT and the
   24px of trailing padding is then added on top. The page is one padding taller
   than the window at every height, the scrollbar is permanent, and the 15px it
   takes is why the tiles measured 266 against the capture's 269.

   A border-box makes the stretch include the padding, which is what every other
   sized element in this stylesheet already assumes. */

.app-main[data-screen="shifts"] {
  box-sizing: border-box;
}

/* ── L.18 — the shell's permanent scrollbar ────────────────────────────────
   ⚠ NOT A SHIFTS DEFECT. Measured on four screens at 1680x1050:

     /customers     scrollHeight 1074   clientWidth 1665   sidebar 1074
     /roles         scrollHeight 1074   clientWidth 1665   sidebar 1074
     /shifts        scrollHeight 1074   clientWidth 1665   sidebar 1074
     /settings/tax  scrollHeight 1050   clientWidth 1680   sidebar    0

   The one screen without a sidebar is the one screen that fits. `.app-sidebar`
   is `max-height: 100vh` with `padding-block: 0 24px` and a CONTENT box, so its
   border box is 100vh + 24 — the page is 24px taller than the window on every
   screen that has a rail, a vertical scrollbar is permanent, and the 15px it
   takes is why this screen's tiles measured 266 against the capture's 269.

   The width error was never in the tiles. One line fixes it, and it is the line
   that was always meant: a max-height that means "as tall as the window" has to
   count the padding it also asks for. */

.app-sidebar {
  box-sizing: border-box;
}

/* ── L.19 — the phone's tile height, re-asserted ───────────────────────────
   ⚠ L.10 SET THIS AND L.15 UNDID IT. A media query adds no specificity, so the
   later unconditional `min-block-size: 380px` — written while matching the
   capture's desktop rhythm — overrode the 120px the narrow branch had already
   chosen. The capture shows a 381px tile because a 1680px window has the room;
   a 360px phone handing 380px of column to one figure does not, and nine of
   them is a page nobody scrolls to the end of.

   Re-stated after the rule that overrode it, which is the only thing that makes
   it win. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="shifts"] .shift-tile {
    min-block-size: 116px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   M — THE SHIFT CLOSING RECEIPT, TRANSCRIBED FROM THE LIVE PRODUCT
   ═══════════════════════════════════════════════════════════════════════════

   `/shifts/{id}/receipt` is where a close lands (`ShiftController:135`), and
   until now it was the only screen in the build with NO stylesheet at all:
   eight class names, zero matching rules, so the operator's proof-of-drawer
   arrived as a column of unstyled text. That is the page in the report.

   The design is not invented to repair it. The live deployment serves the same
   document as static markup — `div.inv-paper#shPaper` inside `#receiptModal` of
   `screens/shifts/shifts.html` — and every number below was read out of the two
   stylesheets that draw it, `screens/pos/pos.css` and
   `screens/shifts/shifts.css`, rather than estimated from a capture:

     .inv-paper        302px wide · radius 6 · padding 14 14 18 · 12px/700
                       black on white · RTL · shadow 0 2 12 rgb(0 0 0 / 15%)
     .receipt-body     the modal's own ground, radial wash over surface-2,
                       padding 24 20, paper centred
     .inv-header-wrap  column · centred · gap 1 · padding 4 0 6
     .inv-shop-name    15px/900 centred · letter-spacing .3px
     .inv-shop-sub     11px/700 centred
     .sh-title         13px/900 centred · 1px dashed rules above and below
     .sh-sec           11.5px/900 centred · margin 4 0 3
     .sh-sep           1px dashed · margin 6 0
     .sh-kv            flex space-between · gap 8 · 11.5px · padding 2 0
     .sh-kv.sh-strong  12.5px/900
     .sh-note-text     11px · line-height 1.6 · centred · breaks anywhere
     .sh-sign          column · gap 8 · margin-top 8
     .sh-sign-line     11px · start-aligned
     .btn-pdf-receipt  linear-gradient(90deg,#dc2626,#b91c1c) · radius 50px
                       13.5px/700 · padding 10 20 · shadow 0 2 8 red/25
     footer            the three buttons share the width, flex 1 each

   ⚠ THE PAPER IS 302px AND STAYS 302px. It is an 80mm thermal slip; widening it
     to fill a desktop window would make it a different document. The GROUND
     around it takes the width instead.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ══ M.1 — the ground the slip sits on ══════════════════════════════════════

   The reference's `.receipt-body` is a modal interior; this is a page, so the
   wash is the same and the box is the page's own content column. */

.shift-receipt-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  inline-size: 100%;
  min-inline-size: 0;
}

.shift-receipt-body {
  display: flex;
  justify-content: center;
  inline-size: 100%;
  min-inline-size: 0;
  box-sizing: border-box;
  padding: 24px 20px;
  border-radius: 12px;
  background:
    radial-gradient(500px 260px at 50% 0%, rgb(14 165 201 / 10%), transparent 70%),
    var(--ui-canvas-2, #eaeff7);
}

/* ══ M.2 — the paper itself ════════════════════════════════════════════════ */

.shift-receipt {
  box-sizing: border-box;
  inline-size: 100%;
  max-inline-size: 302px;
  padding: 14px 14px 18px;
  border-radius: 6px;
  background: #fff;
  color: #000;
  direction: rtl;
  text-align: start;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.5;
  box-shadow: 0 1px 2px rgb(12 45 64 / 10%), 0 10px 28px rgb(12 45 64 / 14%);
}

/* Every descendant is black on white: this is a slip, not a themed surface. */
.shift-receipt :is(h1, h2, p, div, span) {
  color: #000;
}

/* ══ M.3 — the issuer block ════════════════════════════════════════════════

   `.inv-header-wrap`, and the same five lines the product prints: name,
   address, phone, VAT number, commercial register. */

.shift-receipt-issuer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 4px 0 6px;
  text-align: center;
}

.shift-receipt-issuer > div {
  font-size: 11px;
  font-weight: 700;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.shift-receipt-issuer-name {
  font-size: 15px;
  font-weight: 900;
  line-height: 1.4;
  letter-spacing: 0.3px;
}

/* ══ M.4 — the title, and the two rule styles it sits between ══════════════ */

.shift-receipt-title {
  margin: 6px 0;
  padding: 5px 0;
  border-block: 1px dashed #000;
  text-align: center;
  font-size: 13px;
  font-weight: 900;
  line-height: 1.4;
}

.shift-receipt-sec {
  margin: 4px 0 3px;
  text-align: center;
  font-size: 11.5px;
  font-weight: 900;
  line-height: 1.4;
}

.shift-receipt-sep {
  margin: 6px 0;
  border-block-start: 1px dashed #000;
}

/* ══ M.5 — the label/value rows ════════════════════════════════════════════

   `min-inline-size: 0` on the value is the flexbox default that bites: without
   it a long amount refuses to shrink and pushes the row past 302px, which is
   the failure this stylesheet has now met in five separate sections. */

.shift-receipt-kv {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 0;
  font-size: 11.5px;
  line-height: 1.5;
}

.shift-receipt-kv > span:first-child {
  flex: 0 1 auto;
  min-inline-size: 0;
}

.shift-receipt-kv > span:last-child {
  flex: 0 1 auto;
  min-inline-size: 0;
  text-align: end;
  white-space: nowrap;
}

.shift-receipt-kv.is-strong {
  font-size: 12.5px;
  font-weight: 900;
}

/* The variance is also rendered on the close-review panel of SCR-013, which
   had no rule for it either. Both are reached here. */
.shift-variance {
  font-variant-numeric: tabular-nums;
  font-weight: 900;
}

.shift-variance-label {
  font-size: 0.9em;
  font-weight: 700;
}

/* ══ M.6 — notes and the two signature lines ═══════════════════════════════ */

.shift-receipt-notes {
  margin-block-start: 4px;
}

.shift-receipt-note-text {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.6;
  text-align: center;
  overflow-wrap: anywhere;
}

.shift-receipt-sign {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-block-start: 8px;
}

.shift-receipt-sign-line {
  font-size: 11px;
  font-weight: 700;
  text-align: start;
  overflow-wrap: anywhere;
}

/* ══ M.7 — the action bar ══════════════════════════════════════════════════

   The product puts these three BELOW the slip, in the modal's footer, and in
   this order: `طباعة` · `تصدير PDF` · `تم`. Each takes an equal share of the
   width, which is the footer rule `flex: 1; min-width: 0`. */

.shift-receipt-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  inline-size: 100%;
  max-inline-size: 342px;
}

.shift-receipt-actions > * {
  flex: 1 1 0;
  min-inline-size: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 10px 20px;
  border: 0;
  border-radius: 10px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
}

.btn-receipt-print {
  background: linear-gradient(135deg, #0d7fa5, #0369a1);
  color: #fff;
  box-shadow: 0 3px 10px rgb(3 105 161 / 30%);
}

.btn-receipt-pdf {
  background: linear-gradient(90deg, #dc2626, #b91c1c);
  color: #fff;
  box-shadow: 0 2px 8px rgb(220 38 38 / 25%);
}

.btn-receipt-done {
  background: var(--ui-surface, #fff);
  border: 1.5px solid var(--ui-line, #e6eaf1);
  color: var(--ui-ink, #1f2937);
}

/* ══ M.8 — the phone width ═════════════════════════════════════════════════

   A 302px slip plus the ground's 20px of side padding needs 342px; below that
   the ground gives its padding up rather than the paper giving up its width,
   because the paper's width is the document. */

@media (max-width: 22.5rem) {
  .shift-receipt-body {
    padding: 16px 8px;
    border-radius: 8px;
  }

  .shift-receipt-actions > * {
    padding: 10px 12px;
  }
}

/* ══ M.9 — print ═══════════════════════════════════════════════════════════

   The reference's own print block, rule for rule: no shadow, no radius, 76mm
   of paper inside the 80mm roll with 2mm by 3mm of margin, and the ink pushed
   to 900 with a hairline stroke because thermal heads under-print. The ground,
   the page chrome and the three buttons do not reach paper.

   No `@page` size is declared, and that is the reference's choice too — the
   roll's width is the printer's business, and forcing 80mm here would break
   the same slip when an operator saves it as an A4 PDF. */

@media print {
  .shift-receipt-page {
    display: block;
    gap: 0;
  }

  .shift-receipt-body {
    display: block;
    padding: 0;
    border-radius: 0;
    background: #fff;
  }

  .shift-receipt {
    inline-size: 76mm;
    max-inline-size: 76mm;
    margin: 0 auto;
    padding: 2mm 3mm;
    border-radius: 0;
    box-shadow: none;
    background: #fff;
    font-size: 1.06em;
  }

  .shift-receipt * {
    background: transparent;
    color: #000;
    font-weight: 900;
    text-shadow: none;
  }

  .shift-receipt-actions,
  .no-print {
    display: none;
  }
}


/* ═══════════════════════════════════════════════════════════════════════════
   N — THE SERVICES SCREEN (SCR-015), TRANSCRIBED FROM THE LIVE PRODUCT
   ═══════════════════════════════════════════════════════════════════════════

   `/products` is labelled `الخدمات` and holds garment types — AD-020's naming
   inversion, frozen, not repaired here.

   Every value below was read out of the product's own stylesheet, fetched from
   the running deployment at `screens/products/products.css`, and then confirmed
   against `031_Products.png` and `032_Products_Modal_modalOverlay.png` by
   sampling the pixels rather than by eye. Where the two disagree the CAPTURE
   wins, because the capture is this reconstruction's frozen target and the live
   build has moved on since it was taken.

     .table-container   #fff · 1px #e2e8f0 · radius 12 · shadow 0 1 3 /8%
     .products-table    min-width 920 · border-collapse collapse
     thead tr           gradient rgba(20,184,166,.08) → rgba(13,148,136,.08)
                        border-bottom 2px #e2e8f0
     thead th           padding 14 18 · 12px/700 · #0d9488 · tracking .5
     tbody tr           border-bottom 1px #f1f5f9 · hover rgba(20,184,166,.04)
     tbody tr.row-inactive  opacity .72
     tbody td           padding 14 18 · 14px · #475569
     .col-reorder       width 104 · centred          .col-order-num  width 70
     .col-thumb         width 72
     .product-thumb     52×52 · cover · radius 10 · 1px #e2e8f0 · bg #f1f5f9
     .product-thumb-placeholder  52×52 · dashed #cbd5e1 · #f8fafc · 10px #94a3b8
     .badge-status      inline-flex · gap 6 · padding 3 12 · radius 50 · 12px/700
     .badge-active      rgba(16,185,129,.1) · #059669 · 1px rgba(16,185,129,.25)
     .badge-inactive    rgba(100,116,139,.1) · #64748b · 1px rgba(100,116,139,.25)
     .status-dot        6px · currentColor · opacity .85
     .action-btn        32×32 · radius 8 · 15px glyph
     .btn-edit          rgba(20,184,166,.1) · #0d9488 · 1px rgba(20,184,166,.25)
     .btn-deactivate    rgba(231,76,60,.1) · #e74c3c · 1px rgba(231,76,60,.25)
     .btn-activate      rgba(16,185,129,.1) · #059669 · 1px rgba(16,185,129,.25)
     .btn-delete        rgba(239,68,68,.1) · #ef4444 · 1px rgba(239,68,68,.25)
     .reorder-btn       28×28 · radius 8 · #f0f9ff · 1px #bae6fd · #0369a1
                        disabled: opacity .45 · #f8fafc · #94a3b8 · #e2e8f0
     .search-wrap       #fff · 1.5px #e2e8f0 · radius 10 · max 380 · pad 0 14
                        focus-within #14b8a6 + 3px ring rgba(20,184,166,.1)
     .btn-primary       gradient 90deg #14b8a6 → #0d9488 · radius 50 · pad 10 22
                        14px/700 · shadow 0 2 8 rgba(20,184,166,.3)
     .page-btn/.page-num 34×34 · radius 8 · 1.5px #e2e8f0 · #64748b
     .page-num.active   the gradient · white · shadow 0 2 8 rgba(20,184,166,.3)
     .modal-box         radius 16 · 1px #e2e8f0 · shadow 0 20 60 /30%
                        products modal: max-width min(920px, 100%)
     .modal-header      pad 18 22 · border-bottom 1px #e2e8f0 · the .06 gradient
     .field-label       #0d9488 · 12px/700 · tracking .3
     .field-wrap        border-bottom 2px #e2e8f0 → #14b8a6 on focus-within
     .toggle-switch     44×24 · knob 18 · off #cbd5e1 · on rgba(20,184,166,.35)
                        knob-on #14b8a6, travel 20px
     .modal-footer      pad 16 22 22 · border-top · row-reverse · gap 12
     .price-lines-*     radius 12 · 1px #e2e8f0 · bg #fafafa · head the .06 gradient

   ⚠ EVERYTHING IS SCOPED TO `[data-screen="products"]`, AND THAT IS LOAD-BEARING.
     `.cat-table`, `.cat-toolbar`, `.cat-pill` and `.cat-modal` are SHARED — the
     customers, services, offers and hangers registers draw with the same classes,
     and `ScreenStatesTest` asserts on `.cat-table tbody tr` for four routes. An
     unscoped rule here would restyle four screens nobody asked about.

   ⚠ ONE DECLARED DIVERGENCE, IN THE MARKUP RATHER THAN HERE: the capture's
     move-to-top and move-to-bottom buttons are not built. Neither our catalogue
     nor the live product's own `assets/i18n.js` holds a label for them — the
     product replaced that pair with a cut-and-paste flow — and drawing a control
     with no accessible name, or authoring Arabic for one, are both worse than
     the two arrows that do work. The reason is written out at the cell itself.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ══ N.1 — the toolbar ═════════════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin-block-end: 16px;
}

.app-main[data-screen="products"] .cat-search {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-inline-size: 0;
  max-inline-size: 380px;
  box-sizing: border-box;
  padding-inline: 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
  transition: border-color .25s, box-shadow .25s;
}

.app-main[data-screen="products"] .cat-search:focus-within {
  border-color: #14b8a6;
  box-shadow: 0 0 0 3px rgb(20 184 166 / 10%);
}

.app-main[data-screen="products"] .cat-search-icon {
  inline-size: 17px;
  block-size: 17px;
  flex-shrink: 0;
  color: #94a3b8;
}

.app-main[data-screen="products"] .cat-search input[type="search"] {
  flex: 1;
  min-inline-size: 0;
  padding-block: 10px;
  padding-inline: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
}

.app-main[data-screen="products"] .cat-search input[type="search"]::placeholder {
  color: #94a3b8;
}

/* ══ N.1b — the export pair ════════════════════════════════════════════════

   `.toolbar-actions` and `.btn-export` from the reference's own stylesheet, and
   both exports are TEAL — not the red-and-green convention this kind of pair
   usually gets. The two differ only in their label ink, which the capture
   confirms: PDF `#14b8a6`, Excel `#0d9488`, over one shared
   `rgba(20,184,166,.08)` fill. */

.app-main[data-screen="products"] .toolbar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  flex-shrink: 0;
  min-inline-size: 0;
}

.app-main[data-screen="products"] .btn-export {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  border: 1.5px solid;
  border-radius: 50px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background .2s, border-color .2s, transform .15s;
}

.app-main[data-screen="products"] .btn-export svg {
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
}

.app-main[data-screen="products"] .btn-excel {
  background: rgb(20 184 166 / 8%);
  border-color: rgb(20 184 166 / 35%);
  color: #0d9488;
}

.app-main[data-screen="products"] .btn-excel:hover {
  background: rgb(20 184 166 / 14%);
  border-color: #14b8a6;
  transform: translateY(-1px);
}

.app-main[data-screen="products"] .btn-pdf {
  background: rgb(20 184 166 / 8%);
  border-color: rgb(20 184 166 / 30%);
  color: #14b8a6;
}

.app-main[data-screen="products"] .btn-pdf:hover {
  background: rgb(20 184 166 / 13%);
  border-color: #14b8a6;
  transform: translateY(-1px);
}

.app-main[data-screen="products"] .cat-toolbar .btn-save svg {
  inline-size: 17px;
  block-size: 17px;
  flex-shrink: 0;
}
/* `.btn-primary` — the add button. */
.app-main[data-screen="products"] .cat-toolbar .btn-save {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  align-self: auto;
  padding: 10px 22px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #14b8a6, #0d9488);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 2px 8px rgb(20 184 166 / 30%);
  transition: opacity .2s, transform .15s, box-shadow .2s;
}

.app-main[data-screen="products"] .cat-toolbar .btn-save:hover {
  opacity: .9;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgb(20 184 166 / 40%);
}

.app-main[data-screen="products"] .cat-toolbar .btn-save:active {
  transform: translateY(0);
}

/* ══ N.2 — the table card ══════════════════════════════════════════════════ */

/*
   ⚠ NO `inline-size` HERE, AND `min-inline-size: 0` IS LOAD-BEARING.

   The shared rule already sizes this box with `margin-inline: 16px` and no
   width, so it is parent-minus-32 and correct. Adding `inline-size: 100%`
   made it parent-WIDE plus those margins, which pushed the page out on
   every viewport narrower than the table. And `min-inline-size: 0` is what
   lets `overflow-x: auto` actually contain the 920px floor below: the
   flex/grid default is `min-width: auto`, under which the container refuses
   to be narrower than its content and the floor reaches the document.
*/
.app-main[data-screen="products"] .cat-table-wrap {
  min-inline-size: 0;
  box-sizing: border-box;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
  overflow-x: auto;
  overflow-y: visible;
  overscroll-behavior-x: contain;
}

/*
   `min-width: 920px` is the reference's own floor on `.products-table`, and
   with the `.sr-only` escape fixed above the scroll card holds it: the page
   measures 0px of overflow at every width in the audit's spread, and the
   eight columns keep the widths the capture shows instead of compressing.
*/
.app-main[data-screen="products"] .cat-table {
  inline-size: 100%;
  min-inline-size: 920px;
  border-collapse: collapse;
}

.app-main[data-screen="products"] .cat-table thead tr {
  background: linear-gradient(90deg, rgb(20 184 166 / 8%), rgb(13 148 136 / 8%));
  border-block-end: 2px solid #e2e8f0;
}

.app-main[data-screen="products"] .cat-table thead th {
  padding: 14px 18px;
  border: 0;
  background: transparent;
  text-align: start;
  font-size: 12px;
  font-weight: 700;
  color: #0d9488;
  letter-spacing: .5px;
  white-space: nowrap;
}

.app-main[data-screen="products"] .cat-table tbody tr {
  border-block-end: 1px solid #f1f5f9;
  transition: background .15s;
}

.app-main[data-screen="products"] .cat-table tbody tr:last-child {
  border-block-end: 0;
}

.app-main[data-screen="products"] .cat-table tbody tr:hover {
  background: rgb(20 184 166 / 4%);
}

.app-main[data-screen="products"] .cat-table tbody tr.row-inactive {
  opacity: .72;
}

/*
   NO ZEBRA. The shared register paints every even row #fcfaff; this table does
   not — the capture shows nine identical white rows, and the only row states
   the reference draws are `:hover` and `.row-inactive`.
*/
.app-main[data-screen="products"] .cat-table tbody tr:nth-child(even) {
  background: transparent;
}

/*
   `block-size: auto` undoes the shared 71px row: the reference sizes the row
   from its content — a 52px thumbnail in 14px of padding, 81px in the capture.
*/
.app-main[data-screen="products"] .cat-table tbody td {
  block-size: auto;
  padding: 14px 18px;
  border: 0;
  text-align: start;
  font-size: 14px;
  color: #475569;
  vertical-align: middle;
}

/* the columns that carry a width in the reference */
.app-main[data-screen="products"] .cat-th-reorder,
.app-main[data-screen="products"] .cat-reorder {
  inline-size: 104px;
  text-align: center;
  padding-inline: 4px;
}

.app-main[data-screen="products"] .cat-th-order,
.app-main[data-screen="products"] .cat-order-num {
  inline-size: 70px;
  text-align: center;
  font-size: 13px;
  color: #64748b;
}

.app-main[data-screen="products"] .cat-th-thumb,
.app-main[data-screen="products"] .cat-thumb-cell {
  inline-size: 72px;
  text-align: center;
}

.app-main[data-screen="products"] .cat-count {
  font-weight: 700;
  color: #0d9488;
}

/* the scroll hint is a narrow-viewport affordance, not a permanent caption */
.app-main[data-screen="products"] .cat-scroll-hint {
  display: none;
}

/* ══ N.2b — the sortable headers ═══════════════════════════════════════════

   Five of the eight headers sort, and the capture shows the chevron pair on
   exactly those five: `الترتيب`, `الاسم (عربي)`, `الاسم (إنجليزي)`,
   `أسطر التسعير`, `الحالة`. `⋮⋮`, `الصورة` and `الإجراءات` carry `th-nosort`.

   The control is an `<a>` here where the reference uses a `<button>`, because
   this build sorts on the server; the chrome is the reference's. */

.app-main[data-screen="products"] .cat-table thead th.th-sortable {
  padding: 0;
  vertical-align: middle;
}

/*
   ⚠ NO `inline-size: 100%` WITH A MARGIN. Together they made each header link
     4px wider than its cell, and on a 920px table scrolling under a fixed rail
     `AllScreensAuditTest` measured the rightmost headers overlapping
     `.gsb-item` at every width from 320 to 600. A flex child fills its cell
     without being told to.
*/
.app-main[data-screen="products"] .th-sort-btn {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  min-block-size: 48px;
  box-sizing: border-box;
  margin: 2px;
  padding: 10px 14px;
  border-radius: 8px;
  background: transparent;
  color: #0d9488;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .5px;
  text-align: start;
  text-decoration: none;
  transition: background .15s;
}

.app-main[data-screen="products"] .th-sort-btn:hover {
  background: rgb(20 184 166 / 10%);
}

.app-main[data-screen="products"] .th-sort-btn:focus-visible {
  outline: 2px solid #14b8a6;
  outline-offset: 2px;
}

.app-main[data-screen="products"] .th-sortable.sorted-active .th-sort-btn {
  color: #0f766e;
}

.app-main[data-screen="products"] .th-sort-icons {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  flex-shrink: 0;
  line-height: 1;
}

.app-main[data-screen="products"] .sort-chevron {
  font-size: 7px;
  line-height: 1;
  color: #94a3b8;
  opacity: .3;
  transition: opacity .15s, color .15s;
}

.app-main[data-screen="products"] .sorted-asc .sort-up,
.app-main[data-screen="products"] .sorted-desc .sort-down {
  opacity: 1;
  color: #0d9488;
}

/* ══ N.3 — the thumbnail ═══════════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-thumb {
  inline-size: 52px;
  block-size: 52px;
  border-radius: 10px;
  border: 1px solid #e2e8f0;
  background: #f1f5f9;
  object-fit: cover;
  display: block;
  margin-inline: auto;
}

.app-main[data-screen="products"] .cat-thumb--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 4px;
  border: 1px dashed #cbd5e1;
  background: #f8fafc;
  color: #94a3b8;
  font-size: 10px;
  line-height: 1.2;
  text-align: center;
  object-fit: initial;
}

/* ══ N.4 — the status pill ═════════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 12px;
  border-radius: 50px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.6;
}

/* `.status-dot` — 6px of currentColor, so it tracks the pill's own state. */
.app-main[data-screen="products"] .cat-pill::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: .85;
  flex-shrink: 0;
}

.app-main[data-screen="products"] .cat-pill--on {
  background: rgb(16 185 129 / 10%);
  border: 1px solid rgb(16 185 129 / 25%);
  color: #059669;
}

.app-main[data-screen="products"] .cat-pill--off {
  background: rgb(100 116 139 / 10%);
  border: 1px solid rgb(100 116 139 / 25%);
  color: #64748b;
}

/* ══ N.5 — the row actions ═════════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/*
   ⚠ THE TOGGLE FORM STAYS A FLEX ITEM. `display: contents` was tried and put
     the buttons in the wrong order: section C already sequences this cell with
     `order: 1..4` on the CHILD elements, and dissolving the form takes its
     `order: 2` with it, so its button fell back to `order: 0` and jumped ahead
     of the edit button. The capture reads edit, stop, delete from the right,
     which the existing order values already produce.
*/

.app-main[data-screen="products"] .action-btn,
.app-main[data-screen="products"] .cat-actions .confirm > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  inline-size: 32px;
  block-size: 32px;
  padding: 0;
  border-radius: 8px;
  font: inherit;
  cursor: pointer;
  list-style: none;
  transition: background .2s, border-color .2s;
}

/* `<summary>` draws a disclosure marker in both engines; the trigger is a
   button, so both markers are removed. */
.app-main[data-screen="products"] .cat-actions .confirm > summary::-webkit-details-marker {
  display: none;
}

.app-main[data-screen="products"] .cat-actions .confirm > summary::marker {
  content: "";
}

.app-main[data-screen="products"] .action-btn svg {
  inline-size: 15px;
  block-size: 15px;
  flex-shrink: 0;
}

/*
   ⚠ THE ACCESSIBLE NAME MUST NOT WIDEN THE PAGE.

   Each icon button carries its recovered Arabic in an `.sr-only` span, and
   `.sr-only` is `position: absolute` with no `inset` — so the span sits at its
   STATIC position, which inside a 32px inline-flex button is the slot AFTER
   the icon, i.e. outside the button. With no positioned ancestor between it
   and the document, its containing block is the initial one, so it also
   escapes `.cat-table-wrap`'s clipping and extends the page.

   That is precisely what /products was doing: the spans measured L=-43 and
   L=-83 at 768px against 83px of page overflow, and L=-47/-87 at 1024px
   against 87px. Three earlier instruments missed it — a rect scan called it
   clipped, a scrollWidth scan looked only at boxes whose own content spills,
   and a subtree bisect could only get as far as naming the table wrapper.

   `position: relative` on the button makes it the containing block and
   `inset: 0` parks the span in its corner, where the base rule's 1px box and
   `clip` keep it invisible and harmless. The name is unchanged.
*/
.app-main[data-screen="products"] .action-btn {
  position: relative;
}

.app-main[data-screen="products"] .action-btn .sr-only {
  inset: 0;
  margin: 0;
}

.app-main[data-screen="products"] .btn-edit {
  background: rgb(20 184 166 / 10%);
  border: 1px solid rgb(20 184 166 / 25%);
  color: #0d9488;
}

.app-main[data-screen="products"] .btn-edit:hover {
  background: rgb(20 184 166 / 18%);
  border-color: #14b8a6;
}

.app-main[data-screen="products"] .btn-deactivate {
  background: rgb(231 76 60 / 10%);
  border: 1px solid rgb(231 76 60 / 25%);
  color: #e74c3c;
}

.app-main[data-screen="products"] .btn-deactivate:hover {
  background: rgb(231 76 60 / 20%);
  border-color: #e74c3c;
}

.app-main[data-screen="products"] .btn-activate {
  background: rgb(16 185 129 / 10%);
  border: 1px solid rgb(16 185 129 / 25%);
  color: #059669;
}

.app-main[data-screen="products"] .btn-activate:hover {
  background: rgb(16 185 129 / 20%);
  border-color: #059669;
}

.app-main[data-screen="products"] .btn-delete,
.app-main[data-screen="products"] .btn-danger-trigger {
  background: rgb(239 68 68 / 10%);
  border: 1px solid rgb(239 68 68 / 25%);
  color: #ef4444;
}

.app-main[data-screen="products"] .btn-delete:hover,
.app-main[data-screen="products"] .btn-danger-trigger:hover {
  background: rgb(239 68 68 / 22%);
  border-color: #ef4444;
}

/* ══ N.6 — the reorder controls ════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-reorder {
  white-space: nowrap;
}

.app-main[data-screen="products"] .cat-reorder form {
  display: inline-flex;
}

.app-main[data-screen="products"] .reorder-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  inline-size: 28px;
  block-size: 28px;
  margin-inline: 2px;
  padding: 0;
  border: 1px solid #bae6fd;
  border-radius: 8px;
  background: #f0f9ff;
  color: #0369a1;
  font: inherit;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  transition: background .18s, border-color .18s, color .18s;
}

.app-main[data-screen="products"] .reorder-btn:hover:not(:disabled) {
  background: #e0f2fe;
  border-color: #7dd3fc;
  color: #075985;
}

/*
   THE OUTER PAIR IS GREEN, AND THE VALUES ARE SAMPLED NOT GUESSED.

   `031_Products.png`, row 2 at y=306, the four buttons centred at x=1301,
   1333, 1365 and 1397: the outer two are `#f0fdf4` on `#86efac` with a
   `#15803d` glyph, the inner two the blue triple above. The live product's
   own `.paste-btn` carries the identical green triple, which is the same
   control family under the newer cut-and-paste flow.
*/
.app-main[data-screen="products"] .reorder-btn--end {
  border-color: #86efac;
  background: #f0fdf4;
  color: #15803d;
}

.app-main[data-screen="products"] .reorder-btn--end:hover:not(:disabled) {
  background: #dcfce7;
  border-color: #4ade80;
  color: #166534;
}

.app-main[data-screen="products"] .reorder-btn:disabled {
  opacity: .45;
  cursor: not-allowed;
  background: #f8fafc;
  border-color: #e2e8f0;
  color: #94a3b8;
}

/* ══ N.6b — the long move: the banner, the scissors and the targets ════════

   Values from the reference's own `.cut-bar`, `.btn-cancel-cut`, `.paste-btn`
   and `tr.is-cut`. The palette is amber rather than teal on purpose: this is a
   MODE the operator is in, not an action they can take, and it has to read as
   different from everything else on the screen. */

.app-main[data-screen="products"] .cut-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  box-sizing: border-box;
  margin: 0 16px 12px;
  padding: 12px 16px;
  border: 1.5px solid #fcd34d;
  border-radius: 12px;
  background: linear-gradient(90deg, #fffbeb, #fef3c7);
  box-shadow: 0 2px 8px rgb(245 158 11 / 15%);

  /* Sticky, because the promise is that the operator may search and change
     page while holding a row — the banner has to survive the scroll that
     finding the destination takes. */
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
}

.app-main[data-screen="products"] .cut-bar-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 30px;
  block-size: 30px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #f59e0b;
  color: #fff;
  font-size: 14px;
}

.app-main[data-screen="products"] .cut-bar-text {
  flex: 1;
  min-inline-size: 180px;
  font-size: 14px;
  color: #92400e;
}

.app-main[data-screen="products"] .cut-bar-text b {
  color: #78350f;
}

.app-main[data-screen="products"] .cut-bar-hint {
  display: block;
  font-size: 12px;
  font-weight: 400;
  color: #b45309;
}

.app-main[data-screen="products"] .btn-cancel-cut {
  padding: 7px 16px;
  border: 1.5px solid #fcd34d;
  border-radius: 50px;
  background: #fff;
  color: #b45309;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: background .18s;
}

.app-main[data-screen="products"] .btn-cancel-cut:hover {
  background: #fef3c7;
}

/* The scissors sits with the arrows and takes the banner's colour, so the
   control and the mode it starts are recognisably the same thing. */
.app-main[data-screen="products"] .reorder-btn--cut {
  border-color: #fcd34d;
  background: #fffbeb;
  color: #b45309;
}

.app-main[data-screen="products"] .reorder-btn--cut:hover:not(:disabled) {
  background: #fef3c7;
  border-color: #f59e0b;
  color: #92400e;
}

/* The target that replaces the arrows on every other row while holding. */
.app-main[data-screen="products"] .paste-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  block-size: 30px;
  padding-inline: 16px;
  border: 1px solid #86efac;
  border-radius: 8px;
  background: #f0fdf4;
  color: #15803d;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background .18s, border-color .18s, transform .18s;
}

.app-main[data-screen="products"] .paste-btn:hover {
  background: #dcfce7;
  border-color: #4ade80;
  transform: translateY(-1px);
}

/* The held row, so the operator can see what they are carrying. */
.app-main[data-screen="products"] .cat-table tbody tr.is-cut {
  background: #fffbeb;
  outline: 2px dashed #f59e0b;
  outline-offset: -2px;
}

/* ══ N.9c — the `ترجمة` control ════════════════════════════════════════════

   `.btn-auto-translate` sits at the end of the English-name field, and the
   capture draws it as a small teal outline pill rather than as a field button.
   `#0d9488` on `rgba(20,184,166,.08)`, the toolbar exports' own treatment at
   one size down. */

/* At the field's reading end, as the capture places it. */
.app-main[data-screen="products"] .btn-translate {
  display: flex;
  inline-size: fit-content;

  /* The capture puts it at the LEFT of the English field — the inline END in
     an RTL document — so the auto margin goes on the start side. `flex`, not
     `inline-flex`, because an auto margin only centres a block-level box. */
  margin-inline-start: auto;
  align-items: center;
  gap: 6px;
  margin-block-start: 8px;
  padding: 6px 14px;
  border: 1.5px solid rgb(20 184 166 / 35%);
  border-radius: 50px;
  background: rgb(20 184 166 / 8%);
  color: #0d9488;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background .2s, border-color .2s;
}

.app-main[data-screen="products"] .btn-translate:hover:not(:disabled) {
  background: rgb(20 184 166 / 14%);
  border-color: #14b8a6;
}

.app-main[data-screen="products"] .btn-translate:disabled {
  opacity: .5;
  cursor: progress;
}

.app-main[data-screen="products"] .btn-translate svg {
  inline-size: 14px;
  block-size: 14px;
  flex-shrink: 0;
}
/* ══ N.7 — the pagination bar ══════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  padding: 12px 4px;
}

.app-main[data-screen="products"] .cat-pagination > p {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: #64748b;
  white-space: nowrap;
}

.app-main[data-screen="products"] .cat-pagination .pager {
  display: flex;
  align-items: center;
  gap: 4px;
}

.app-main[data-screen="products"] .cat-pagination .pager a,
.app-main[data-screen="products"] .cat-pagination .pager span,
.app-main[data-screen="products"] .cat-pagination .pager button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-inline-size: 34px;
  block-size: 34px;
  padding-inline: 4px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #475569;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  transition: border-color .2s, color .2s, background .2s;
}

.app-main[data-screen="products"] .cat-pagination .pager a:hover {
  border-color: #14b8a6;
  color: #0d9488;
  background: rgb(20 184 166 / 6%);
}

.app-main[data-screen="products"] .cat-pagination .pager [aria-current],
.app-main[data-screen="products"] .cat-pagination .pager .is-current {
  background: linear-gradient(90deg, #14b8a6, #0d9488);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 2px 8px rgb(20 184 166 / 30%);
}

/*
   `.page-ellipsis` — the gap marker between number runs. The reference's
   `css:205` values, with `padding-inline` for the logical-property ratchet.
*/
.app-main[data-screen="products"] .cat-pagination .page-ellipsis {
  display: inline-flex;
  align-items: center;
  block-size: 34px;
  padding-inline: 4px;
  border: 0;
  background: transparent;
  color: #94a3b8;
  font-size: 14px;
}
.app-main[data-screen="products"] .cat-page-size {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 700;
  color: #64748b;
  white-space: nowrap;
}

.app-main[data-screen="products"] .cat-page-size select {
  padding: 6px 10px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  outline: none;
  cursor: pointer;
  transition: border-color .25s;
}

.app-main[data-screen="products"] .cat-page-size select:focus {
  border-color: #14b8a6;
}

/* ══ N.8 — the empty state ═════════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-empty {
  padding: 60px 20px;
  text-align: center;
  color: #94a3b8;
  font-size: 15px;
}

/* ══ N.9 — the product modal ═══════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-modal-panel {
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 20px 60px rgb(0 0 0 / 30%);
  overflow: hidden;
}

.app-main[data-screen="products"] .cat-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 18px 22px;
  border-block-end: 1px solid #e2e8f0;
  background: linear-gradient(90deg, rgb(20 184 166 / 6%), rgb(13 148 136 / 6%));
}

.app-main[data-screen="products"] .cat-modal-head h2 {
  margin: 0;
  flex: 1;
  min-inline-size: 0;
  font-size: 16px;
  font-weight: 700;
  color: #1e293b;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* `.modal-close-btn` — the × the capture puts opposite the title. */
.app-main[data-screen="products"] .cat-modal-close {
  flex-shrink: 0;
  padding: 4px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #64748b;
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: color .2s, background .2s;
}

.app-main[data-screen="products"] .cat-modal-close:hover {
  color: #dc2626;
  background: rgb(239 68 68 / 10%);
}

.app-main[data-screen="products"] .cat-modal-panel .settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 0 20px;
  padding: 22px 22px 0;
}

.app-main[data-screen="products"] .cat-modal-panel .field {
  margin-block-end: 20px;
  min-inline-size: 0;
}

.app-main[data-screen="products"] .cat-modal-panel .field label {
  display: block;
  margin-block-end: 8px;
  color: #0d9488;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .3px;
}

/* `.required-star` — the asterisk the capture prints in red. */
.app-main[data-screen="products"] .cat-modal-panel .field label span[aria-hidden] {
  color: #ef4444;
}

/* `.field-wrap` is a bottom rule, not a box: the input carries it directly. */
.app-main[data-screen="products"] .cat-modal-panel .field input[type="text"] {
  inline-size: 100%;
  box-sizing: border-box;
  padding: 10px 0;
  border: 0;
  border-block-end: 2px solid #e2e8f0;
  border-radius: 0;
  background: transparent;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  outline: none;
  transition: border-color .25s;
}

.app-main[data-screen="products"] .cat-modal-panel .field input[type="text"]:focus {
  border-block-end-color: #14b8a6;
}

.app-main[data-screen="products"] .cat-modal-panel .field input::placeholder {
  color: #94a3b8;
  font-weight: 700;
}

/* ══ N.9b — the category field ═════════════════════════════════════════════

   `.field-group-full` in the reference: the type select spans the modal's
   full width under the two name columns, and its control is the same
   bottom-ruled field the names use rather than a boxed dropdown. */

.app-main[data-screen="products"] .cat-modal-panel .field-full {
  padding: 0 22px;
  margin-block-end: 20px;
  min-inline-size: 0;
}

.app-main[data-screen="products"] .cat-modal-panel .field-full label {
  display: block;
  margin-block-end: 8px;
  color: #0d9488;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .3px;
}

/*
   `block-size: auto` undoes the shared modal rule's fixed 40px: this field is
   a bottom-ruled line, not a boxed control, and 10px of padding around 14px
   of text does not fit a 40px box — the value rendered clipped at the rule.
*/
.app-main[data-screen="products"] .cat-modal-panel .field-full select {
  inline-size: 100%;
  block-size: auto;
  line-height: 1.5;
  box-sizing: border-box;
  padding: 10px 0;
  border: 0;
  border-block-end: 2px solid #e2e8f0;
  border-radius: 0;
  background: transparent;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  outline: none;
  cursor: pointer;
  transition: border-color .25s;
}

.app-main[data-screen="products"] .cat-modal-panel .field-full select:focus {
  border-block-end-color: #14b8a6;
}

.app-main[data-screen="products"] .cat-category {
  color: #475569;
}

/* ══ N.9d — the picture picker on the create modal ═════════════════════════

   `.image-picker-row` in the reference: the 96px preview square beside the
   control, wrapping on a narrow dialog. On create there is never an image to
   preview, so the square is always the dashed `بدون صورة` placeholder the
   capture shows. */

.app-main[data-screen="products"] .cat-image-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
  padding: 0 14px 14px;
}

.app-main[data-screen="products"] .cat-image-row .cat-thumb--empty {
  inline-size: 96px;
  block-size: 96px;
  margin-inline: 0;
  flex-shrink: 0;
}

/* ══ N.10 — the two toggles ════════════════════════════════════════════════

   The reference draws a 44×24 switch with an 18px knob that travels 20px. Ours
   is a real `<input type="checkbox">` inside its `<label>`, so the switch is
   built on the checkbox itself rather than on a sibling `<span>`: the input is
   sized as the track and `::before` is the knob. Keyboard focus and the
   accessible name are the browser's, unchanged. */

.app-main[data-screen="products"] .cat-toggles {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 22px;
}

.app-main[data-screen="products"] .cat-toggles label {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  font-size: 14px;
  font-weight: 700;
  color: #475569;
  cursor: pointer;
}

.app-main[data-screen="products"] .cat-toggles input[type="checkbox"] {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  inline-size: 44px;
  block-size: 24px;
  flex-shrink: 0;
  margin: 0;
  border: 0;
  border-radius: 24px;
  background: #cbd5e1;
  cursor: pointer;
  transition: background .25s;
}

.app-main[data-screen="products"] .cat-toggles input[type="checkbox"]::before {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgb(0 0 0 / 20%);

  /* NOT `transform`: `translateX` is physical, so under RTL a positive value
     pushes the knob off the track. The reference pairs `right: 3px` with
     `translateX(-20px)`, which is correct only in an RTL-only document; this
     build ships both directions. */
  transition: inset-inline-start .25s, background .25s;
}

.app-main[data-screen="products"] .cat-toggles input[type="checkbox"]:checked {
  background: rgb(20 184 166 / 35%);
}

.app-main[data-screen="products"] .cat-toggles input[type="checkbox"]:checked::before {
  inset-inline-start: 23px;
  background: #14b8a6;
}

.app-main[data-screen="products"] .cat-toggles input[type="checkbox"]:focus-visible {
  outline: 2px solid #14b8a6;
  outline-offset: 2px;
}

/* ══ N.11 — the modal footer ═══════════════════════════════════════════════ */

.app-main[data-screen="products"] .cat-modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-direction: row-reverse;
  gap: 12px;
  padding: 16px 22px 22px;
  border-block-start: 1px solid #e2e8f0;
  background: #fff;
}

.app-main[data-screen="products"] .cat-modal-foot .btn-save {
  padding: 10px 22px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #14b8a6, #0d9488);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 2px 8px rgb(20 184 166 / 30%);
  transition: opacity .2s, transform .15s;
}

.app-main[data-screen="products"] .cat-modal-foot .btn-save:hover {
  opacity: .9;
  transform: translateY(-1px);
}

.app-main[data-screen="products"] .cat-modal-foot .btn-outline {
  padding: 10px 22px;
  border: 1.5px solid #e2e8f0;
  border-radius: 50px;
  background: transparent;
  color: #64748b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color .2s, color .2s;
}

.app-main[data-screen="products"] .cat-modal-foot .btn-outline:hover {
  border-color: #94a3b8;
  color: #1e293b;
}

/* ══ N.12 — the price grid and the image picker inside the modal ═══════════ */

.app-main[data-screen="products"] .cat-prices,
.app-main[data-screen="products"] .cat-image {
  box-sizing: border-box;
  margin: 8px 22px 22px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #fafafa;
  overflow: hidden;
}

.app-main[data-screen="products"] .cat-prices h3,
.app-main[data-screen="products"] .cat-image h3 {
  margin: 0;
  padding: 12px 14px;
  border-block-end: 1px solid #e2e8f0;
  background: linear-gradient(90deg, rgb(20 184 166 / 6%), rgb(13 148 136 / 6%));
  font-size: 13px;
  font-weight: 700;
  color: #0d9488;
  line-height: 1.35;
}

.app-main[data-screen="products"] .cat-prices form,
.app-main[data-screen="products"] .cat-image form,
.app-main[data-screen="products"] .cat-prices > p,
.app-main[data-screen="products"] .cat-image > p {
  padding: 12px 14px;
  margin: 0;
}

.app-main[data-screen="products"] .cat-prices .cat-table {
  min-inline-size: 0;
  font-size: 13px;
}

.app-main[data-screen="products"] .cat-prices .cat-table thead tr {
  background: #f1f5f9;
  border-block-end: 0;
}

.app-main[data-screen="products"] .cat-prices .cat-table thead th {
  padding: 10px 12px;
  color: #64748b;
  font-size: 11px;
  letter-spacing: 0;
}

.app-main[data-screen="products"] .cat-prices .cat-table tbody td {
  padding: 8px 10px;
  border-block-start: 1px solid #e2e8f0;
  border-block-end: 0;
}

.app-main[data-screen="products"] .cat-prices .cat-table tbody tr {
  border-block-end: 0;
}

.app-main[data-screen="products"] .cat-prices input[type="text"] {
  inline-size: 100px;
  min-inline-size: 88px;
  max-inline-size: 120px;
  box-sizing: border-box;
  padding: 6px 10px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-align: end;
  direction: ltr;
}

.app-main[data-screen="products"] .cat-prices input[type="text"]:focus {
  border-color: #14b8a6;
  outline: none;
  box-shadow: 0 0 0 3px rgb(20 184 166 / 12%);
}

/* `.btn-secondary` — the image picker's own control. */
.app-main[data-screen="products"] .cat-image input[type="file"] {
  font: inherit;
  font-size: 13px;
  color: #475569;
}

.app-main[data-screen="products"] .cat-image .cat-thumb {
  inline-size: 96px;
  block-size: 96px;
  margin-inline: 0;
}

/* ══ N.13 — below 768px, the reference's own phone layout ══════════════════

   `@media(max-width:768px)` in the product's stylesheet: the toolbar becomes a
   column, the search takes the full width, and the scroll hint — hidden on the
   desktop — appears above the table because eight columns cannot fit a phone. */

@media (max-width: 48rem) {
  .app-main[data-screen="products"] .cat-toolbar {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .app-main[data-screen="products"] .cat-search {
    max-inline-size: none;
    flex: 0 0 auto;
    inline-size: 100%;
  }

  /* The reference's own phone rule: the two exports share a row, the add takes
     the full width beneath them. */
  .app-main[data-screen="products"] .toolbar-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    inline-size: 100%;
  }

  .app-main[data-screen="products"] .btn-export {
    justify-content: center;
    padding: 10px 12px;
    min-inline-size: 0;
  }

  .app-main[data-screen="products"] .cat-toolbar .btn-save {
    grid-column: 1 / -1;
    inline-size: 100%;
    justify-content: center;
  }

  /*
     THE 920px FLOOR IS RELEASED ON A PHONE, AND THE MEASUREMENT SAYS WHY.

     The reference keeps its floor at every width and lets the card scroll,
     because its rail is a fixed column that never covers the table. Ours is
     an off-canvas drawer, and `AllScreensAuditTest` measured the consequence
     at 320, 360, 414, 480 and 600: the header links in the right-hand columns
     came to rest under `.gsb-item`, reported as `a.gsb-item × a.th-sort-btn`.
     A sort control a thumb cannot reach is worse than a compressed column, so
     below the breakpoint the columns compress instead — and the scroll hint
     below, which is the reference's own, still appears for what is left.
  */
  .app-main[data-screen="products"] .cat-table {
    min-inline-size: 0;
  }

  .app-main[data-screen="products"] .cat-scroll-hint {
    display: block;
    margin: 0;
    padding: 6px 8px;
    border-block-end: 1px solid #e2e8f0;
    background: linear-gradient(180deg, rgb(20 184 166 / 8%), transparent);
    color: #64748b;
    font-size: 11px;
    text-align: center;
  }

  .app-main[data-screen="products"] .cat-pagination {
    justify-content: center;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE SHARED PAGER'S NUMBERS
   ═══════════════════════════════════════════════════════════════════════════

   `components/pager.blade.php` now emits a page-number window between its
   arrows, on all eight screens that use it. `.page-btn` already dressed the
   arrows; these two give the numbers the same chrome everywhere, so a screen
   with no rules of its own does not render them bare. SCR-015 overrides both
   in section N with the capture's teal.
   ═══════════════════════════════════════════════════════════════════════════ */

.page-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-inline-size: 32px;
  block-size: 32px;
  padding-inline: 4px;
  border: 1px solid var(--ui-line, #e6eaf1);
  border-radius: var(--ui-radius-sm, 8px);
  background: var(--ui-surface, #fff);
  color: var(--ui-ink, #1f2937);
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.page-num.is-current {
  background: var(--ui-blue, #2563eb);
  border-color: transparent;
  color: #fff;
}

.page-ellipsis {
  display: inline-flex;
  align-items: center;
  block-size: 32px;
  padding-inline: 4px;
  color: var(--ui-ink-muted, #8b93a1);
}

/* ═══════════════════════════════════════════════════════════════════════════
   O — THE EXPENSES SCREEN (SCR-012), TRANSCRIBED FROM THE LIVE PRODUCT
   ═══════════════════════════════════════════════════════════════════════════

   Every value below was read out of the product's own `screens/expenses/
   expenses.css`, fetched from the running deployment, and confirmed against
   `023_Expenses.png` and `087_Expenses_Modal_Add_Expense.png`.

   ⚠ THE ACCENT IS ROSE, NOT TEAL. Each screen in this product carries its own,
     and SCR-012's is `#f43f5e → #e11d48`. Section N's teal belongs to SCR-015
     and nothing here may reach it — which is why every rule below is scoped to
     `[data-screen="expenses"]` without exception.

     .back-btn / .btn-primary  gradient 90deg #f43f5e → #e11d48 · radius 50
     .summary-card             #fff · 1px #e2e8f0 · radius 12 · padding 20
                               flex row · gap 16 · shadow 0 1 3 /8%
                               border-right 4px, ONE COLOUR EACH:
                                 total-before #6366f1 · total-tax #f59e0b
                                 grand-total  #10b981
     .summary-icon             48x48 · radius 12, tinted to match its edge
     .summary-label            12px/700 #64748b · margin-bottom 6
     .summary-value            22px/700 #1e293b
     thead tr                  gradient rgba(244,63,94,.06) → rgba(225,29,72,.06)
                               border-bottom 2px #e2e8f0
     thead th                  padding 14 18 · 12px/700 · #f43f5e · tracking .5
     tbody tr                  border-bottom 1px #f1f5f9 · hover rgba(244,63,94,.04)
     tbody td                  padding 14 18 · 14px · #475569
     .badge-taxable            padding 3 12 · radius 50 · 12px/700
                               taxable  rgba(245,158,11,.1) · #d97706 · border .25
                               exempt   rgba(100,116,139,.1) · #64748b · border .25
     .action-btn               32x32 · radius 8 · 15px glyph
     .btn-edit                 rgba(244,63,94,.1) · #f43f5e · border .25
     .btn-delete               rgba(239,68,68,.1) · #ef4444 · border .25
     .btn-excel                rgba(16,185,129,.07) · #059669 · border .35
     .btn-pdf                  rgba(244,63,94,.07) · #f43f5e · border .3
     .date-filter-input        1.5px #e2e8f0 · radius 8 · padding 8 12 · 13px/700
                               focus #f43f5e + 3px ring rgba(244,63,94,.1)
     .btn-clear-dates          30x30 · radius 8 · 1.5px #e2e8f0 · #94a3b8
     .page-num.active          the gradient · white · shadow 0 2 8 rgba(244,63,94,.3)
     .modal-header             gradient rgba(244,63,94,.05) → rgba(225,29,72,.05)
     .field-label              #f43f5e · 12px/700 · tracking .3
     .field-wrap               border-bottom 2px #e2e8f0 → #f43f5e on focus-within
     .toggle-switch            44x24 · knob 18 · on rgba(244,63,94,.3), knob #f43f5e

   ⚠ THE MARKUP KEEPS ITS OWN CLASS NAMES. The reference calls these
     `.summary-card` and `.expenses-table`; ours are `.kpi-tile` and
     `#expensesTable`. Renaming them would touch `ScreenStatesTest` and four
     expense probes that address these ids, for no gain: the design lives in the
     values, not in the words.

   ⚠ `border-inline-start` WHERE THE REFERENCE WRITES `border-right`. In its
     RTL-only document those are the same edge. This build ships English too,
     and the logical property is the one that stays the reading-side edge in
     both — the Phase 03 ratchet's whole point.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ══ O.1 — the screen header ═══════════════════════════════════════════════ */

.app-main[data-screen="expenses"] .screen-header h1 {
  font-size: 16px;
  font-weight: 800;
  color: #1e293b;
}

/* `.back-btn` — the capture's rose pill, where the shell ships a plain outline
   link. Restyled, not replaced: it stays the same `<a>` to the dashboard. */
.app-main[data-screen="expenses"] .screen-header .btn-outline {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 18px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #f43f5e, #e11d48);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(244, 63, 94, 0.25);
  transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}

.app-main[data-screen="expenses"] .screen-header .btn-outline:hover {
  opacity: 0.9;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(244, 63, 94, 0.35);
}

/* ══ O.2 — the three summary cards ═════════════════════════════════════════ */

/*
   ⚠ EXACTLY THREE COLUMNS AT EVERY WIDTH.

   `REF.css:24` is `repeat(3, minmax(0, 1fr))` and `023` shows the three cards
   side by side. `auto-fit` with a 220px minimum would lay FIVE tracks across a
   wide main area and leave two of them empty — a rule that reads like it is
   being careful and is in fact drawing a different screen.

   The phone case does not need a different count either: O.9 compresses the
   cards rather than stacking them, which is what the reference's own narrow
   block does.
*/
.app-main[data-screen="expenses"] .kpi-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin-block-end: 16px;
}

.app-main[data-screen="expenses"] .kpi-tile {
  display: flex;
  align-items: center;
  gap: 16px;
  box-sizing: border-box;
  min-inline-size: 0;
  padding: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.app-main[data-screen="expenses"] .kpi-tile:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

/* Indigo, amber, emerald — and deliberately NOT the screen's rose. The edge is
   what tells the three figures apart; painting all three rose would turn it
   from information into decoration. */
.app-main[data-screen="expenses"] .kpi-tile--before { border-inline-start: 4px solid #6366f1; }
.app-main[data-screen="expenses"] .kpi-tile--tax    { border-inline-start: 4px solid #f59e0b; }
.app-main[data-screen="expenses"] .kpi-tile--total  { border-inline-start: 4px solid #10b981; }

.app-main[data-screen="expenses"] .kpi-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 48px;
  block-size: 48px;
  flex-shrink: 0;
  border-radius: 12px;
}

.app-main[data-screen="expenses"] .kpi-icon svg {
  inline-size: 24px;
  block-size: 24px;
}

.app-main[data-screen="expenses"] .kpi-icon--before { background: rgba(99, 102, 241, 0.1); color: #6366f1; }
.app-main[data-screen="expenses"] .kpi-icon--tax    { background: rgba(245, 158, 11, 0.1); color: #f59e0b; }
.app-main[data-screen="expenses"] .kpi-icon--total  { background: rgba(16, 185, 129, 0.1); color: #10b981; }

.app-main[data-screen="expenses"] .kpi-body {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}

.app-main[data-screen="expenses"] .kpi-label {
  margin-block-end: 6px;
  font-size: 12px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="expenses"] .kpi-value {
  font-size: 22px;
  font-weight: 800;
  color: #1e293b;
  overflow-wrap: anywhere;
}

/* ══ O.3 — the filter bar, the exports and the date-clear ══════════════════ */

.app-main[data-screen="expenses"] .filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-inline-size: 0;
  margin-block-end: 10px;
}

.app-main[data-screen="expenses"] .filter-bar input[type="search"],
.app-main[data-screen="expenses"] .filter-bar input[type="text"] {
  flex: 1 1 auto;
  min-inline-size: 0;
  max-inline-size: 380px;
  box-sizing: border-box;
  padding: 10px 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  outline: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="expenses"] .filter-bar label {
  font-size: 13px;
  font-weight: 700;
  color: #64748b;
  white-space: nowrap;
}

.app-main[data-screen="expenses"] .filter-bar input[type="date"],
.app-main[data-screen="expenses"] .filter-bar input[type="datetime-local"],
.app-main[data-screen="expenses"] .filter-bar select {
  box-sizing: border-box;
  padding: 8px 12px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  outline: none;
  cursor: pointer;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="expenses"] .filter-bar input:focus,
.app-main[data-screen="expenses"] .filter-bar select:focus {
  border-color: #f43f5e;
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.1);
}

.app-main[data-screen="expenses"] .btn-clear-dates {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 30px;
  block-size: 30px;
  flex-shrink: 0;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #94a3b8;
  font: inherit;
  font-size: 15px;
  line-height: 1;
  text-decoration: none;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.app-main[data-screen="expenses"] .btn-clear-dates:hover {
  border-color: #f43f5e;
  color: #f43f5e;
  background: rgba(244, 63, 94, 0.06);
}

.app-main[data-screen="expenses"] .btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 22px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #f43f5e, #e11d48);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(244, 63, 94, 0.3);
  transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}

.app-main[data-screen="expenses"] .btn-primary:hover {
  opacity: 0.9;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(244, 63, 94, 0.4);
}

.app-main[data-screen="expenses"] .toolbar-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-block-end: 12px;
}

.app-main[data-screen="expenses"] .btn-export {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  border: 1.5px solid;
  border-radius: 50px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.app-main[data-screen="expenses"] .btn-export svg {
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
}

/* ⚠ EXCEL IS `#059669` HERE AND `#0d9488` ON SCR-015. Not a drift of ours —
     `.btn-excel` carries a different green in each screen's own stylesheet.
     Each screen dresses the shared control in its own terms. */
.app-main[data-screen="expenses"] .btn-excel {
  background: rgba(16, 185, 129, 0.07);
  border-color: rgba(16, 185, 129, 0.35);
  color: #059669;
}

.app-main[data-screen="expenses"] .btn-excel:hover {
  background: rgba(16, 185, 129, 0.14);
  border-color: #10b981;
  transform: translateY(-1px);
}

.app-main[data-screen="expenses"] .btn-pdf {
  background: rgba(244, 63, 94, 0.07);
  border-color: rgba(244, 63, 94, 0.3);
  color: #f43f5e;
}

.app-main[data-screen="expenses"] .btn-pdf:hover {
  background: rgba(244, 63, 94, 0.13);
  border-color: #f43f5e;
  transform: translateY(-1px);
}

/* ══ O.4 — the register and the category master ════════════════════════════

   Both panels are `.settings-card`, whose base rule pads 18/20. The capture
   runs the heading band and the table head to the card's own edges, so the
   padding moves OFF the card and ONTO the two things that need it. */

.app-main[data-screen="expenses"] .settings-card {
  min-inline-size: 0;
  box-sizing: border-box;
  padding: 0;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.app-main[data-screen="expenses"] .settings-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-block-end: 0;
  padding: 14px 18px;
  border-block-end: 1px solid #e2e8f0;
}

.app-main[data-screen="expenses"] .settings-card-head h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 800;
  color: #1e293b;
}

.app-main[data-screen="expenses"] #categoryForm {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px 18px;
  border-block-end: 1px solid #f1f5f9;
}

.app-main[data-screen="expenses"] #categoryForm label {
  font-size: 12px;
  font-weight: 700;
  color: #f43f5e;
  letter-spacing: 0.3px;
}

.app-main[data-screen="expenses"] #categoryForm input[type="text"] {
  flex: 1 1 220px;
  min-inline-size: 0;
  max-inline-size: 340px;
  box-sizing: border-box;
  padding: 9px 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="expenses"] #categoryForm input[type="text"]:focus {
  border-color: #f43f5e;
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.1);
}

.app-main[data-screen="expenses"] #expensesTable,
.app-main[data-screen="expenses"] #categoriesTable {
  inline-size: 100%;
  border-collapse: collapse;
  background: transparent;
  box-shadow: none;
}

.app-main[data-screen="expenses"] #expensesTable thead tr,
.app-main[data-screen="expenses"] #categoriesTable thead tr {
  background: linear-gradient(90deg, rgba(244, 63, 94, 0.06), rgba(225, 29, 72, 0.06));
  border-block-end: 2px solid #e2e8f0;
}

.app-main[data-screen="expenses"] #expensesTable thead th,
.app-main[data-screen="expenses"] #categoriesTable thead th {
  padding: 14px 18px;
  border: 0;
  background: transparent;
  text-align: start;
  font-size: 12px;
  font-weight: 700;
  color: #f43f5e;
  letter-spacing: 0.5px;
  white-space: nowrap;
}

.app-main[data-screen="expenses"] #expensesTable tbody tr,
.app-main[data-screen="expenses"] #categoriesTable tbody tr {
  border-block-end: 1px solid #f1f5f9;
  transition: background 0.15s ease;
}

.app-main[data-screen="expenses"] #expensesTable tbody tr:last-child,
.app-main[data-screen="expenses"] #categoriesTable tbody tr:last-child {
  border-block-end: 0;
}

.app-main[data-screen="expenses"] #expensesTable tbody tr:hover,
.app-main[data-screen="expenses"] #categoriesTable tbody tr:hover {
  background: rgba(244, 63, 94, 0.04);
}

/* The register has NO zebra: the capture shows six identical white rows, and
   the base striping would put a grey band through them. */
.app-main[data-screen="expenses"] #expensesTable tbody tr:nth-child(even),
.app-main[data-screen="expenses"] #categoriesTable tbody tr:nth-child(even) {
  background: transparent;
}

.app-main[data-screen="expenses"] #expensesTable tbody td,
.app-main[data-screen="expenses"] #categoriesTable tbody td {
  block-size: auto;
  padding: 14px 18px;
  border: 0;
  text-align: start;
  font-size: 14px;
  color: #475569;
  vertical-align: middle;
}

/* The four money columns read as figures, not prose: one weight heavier, and
   `tabular-nums` so the decimal points line up down the column. */
.app-main[data-screen="expenses"] #expensesTable tbody td.num {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #1e293b;
}

/* ══ O.5 — the taxable pill ════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] .badge-taxable {
  display: inline-flex;
  align-items: center;
  padding: 3px 12px;
  border-radius: 50px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.6;
  white-space: nowrap;
}

/* Amber when the expense carries VAT — the SAME amber as the tax card's edge,
   so the column and the figure it feeds read as one thing. */
.app-main[data-screen="expenses"] .badge-taxable.is-taxable {
  background: rgba(245, 158, 11, 0.1);
  border: 1px solid rgba(245, 158, 11, 0.25);
  color: #d97706;
}

.app-main[data-screen="expenses"] .badge-taxable.is-exempt {
  background: rgba(100, 116, 139, 0.1);
  border: 1px solid rgba(100, 116, 139, 0.25);
  color: #64748b;
}

/* ══ O.6 — the row actions ═════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] .row-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.app-main[data-screen="expenses"] .row-actions .inline-form {
  display: inline-flex;
  margin: 0;
  padding: 0;
  border: 0;
}

.app-main[data-screen="expenses"] .row-actions .action-btn,
.app-main[data-screen="expenses"] .row-actions .confirm > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  box-sizing: border-box;
  inline-size: 32px;
  block-size: 32px;
  padding: 0;
  border-radius: 8px;
  font: inherit;
  cursor: pointer;
  list-style: none;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.app-main[data-screen="expenses"] .row-actions .confirm > summary::-webkit-details-marker { display: none; }
.app-main[data-screen="expenses"] .row-actions .confirm > summary::marker { content: ""; }

.app-main[data-screen="expenses"] .row-actions svg {
  inline-size: 15px;
  block-size: 15px;
  flex-shrink: 0;
}

/*
   ⚠ THE ACCESSIBLE NAME MUST NOT WIDEN THE PAGE — the same defect Section N
     documents and the same repair. `.sr-only` is `position: absolute` with no
     `inset`, so inside a 32px inline-flex button it parks at the slot AFTER the
     icon, which is outside the button; with no positioned ancestor it also
     escapes every clipping container between here and the viewport. Anchoring
     it to a `position: relative` button confines it to the 32px square.
*/
.app-main[data-screen="expenses"] .row-actions .sr-only {
  inset: 0;
  margin: 0;
}

.app-main[data-screen="expenses"] .row-actions .btn-edit {
  background: rgba(244, 63, 94, 0.1);
  border: 1px solid rgba(244, 63, 94, 0.25);
  color: #f43f5e;
}

.app-main[data-screen="expenses"] .row-actions .btn-edit:hover {
  background: rgba(244, 63, 94, 0.18);
  border-color: #f43f5e;
}

.app-main[data-screen="expenses"] .row-actions .btn-danger-trigger,
.app-main[data-screen="expenses"] .row-actions .btn-delete {
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid rgba(239, 68, 68, 0.25);
  color: #ef4444;
}

.app-main[data-screen="expenses"] .row-actions .btn-danger-trigger:hover,
.app-main[data-screen="expenses"] .row-actions .btn-delete:hover {
  background: rgba(239, 68, 68, 0.22);
  border-color: #ef4444;
}

/* The category master's two controls stay WORDED. The panel is `O-34-02`, not a
   capture, so it gets the vocabulary the services screen already ships rather
   than glyphs invented for a screen nobody photographed. */
.app-main[data-screen="expenses"] .row-actions .btn-icon:not(.action-btn) {
  padding: 6px 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 50px;
  background: #fff;
  color: #64748b;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.app-main[data-screen="expenses"] .row-actions .btn-icon:not(.action-btn):hover {
  border-color: #f43f5e;
  color: #f43f5e;
  background: rgba(244, 63, 94, 0.06);
}

.app-main[data-screen="expenses"] .row-actions .btn-icon.danger {
  border-color: rgba(239, 68, 68, 0.25);
  color: #ef4444;
}

.app-main[data-screen="expenses"] .row-actions .btn-icon.danger:hover {
  border-color: #ef4444;
  color: #ef4444;
  background: rgba(239, 68, 68, 0.1);
}

/* ══ O.7 — the footer, the empty state and the scroll hint ═════════════════ */

.app-main[data-screen="expenses"] .table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  padding: 12px 18px;
  border-block-start: 1px solid #f1f5f9;
  font-size: 13px;
  font-weight: 700;
  color: #64748b;
}

.app-main[data-screen="expenses"] .table-footer .page-num:hover,
.app-main[data-screen="expenses"] .table-footer .page-btn:not(.is-disabled):hover {
  border-color: #f43f5e;
  color: #f43f5e;
  background: rgba(244, 63, 94, 0.06);
}

.app-main[data-screen="expenses"] .table-footer .page-num.is-current {
  background: linear-gradient(90deg, #f43f5e, #e11d48);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 2px 8px rgba(244, 63, 94, 0.3);
}

.app-main[data-screen="expenses"] .empty-state {
  display: block;
  padding: 40px 20px;
  color: #94a3b8;
  font-size: 15px;
  text-align: center;
}

/* The heading band already carries this notice when no shift is open, and there
   it is one line beside the title, not a 40px block. */
.app-main[data-screen="expenses"] .settings-card-head .empty-state {
  padding: 0;
  font-size: 13px;
  font-weight: 700;
  text-align: start;
}

/* A narrow-viewport affordance, not a permanent caption: at the widths the
   references were captured at, the nine columns fit and nothing scrolls. */
.app-main[data-screen="expenses"] .scroll-hint { display: none; }

/* ══ O.8 — the add/edit dialog ═════════════════════════════════════════════

   `#expenseModal` was already made the centred panel by D.5/E.8a. What is left
   is `087`'s rose treatment: a tinted header band, rose field labels, underline
   fields that light up on focus, and the tax toggle.

   ⚠ SCOPED TO `.expense-modal`, NOT `.modal`. The delete confirmation on every
     row is also a `.modal`, nested in `.confirm-overlay`; a bare `.modal` rule
     here would repaint it too. */

/*
   ⚠ 14px, NOT 16px, AND THE CAPTURE SETTLES IT.

   Section O shipped 16px here while `ExpenseWorkflowTest` had pinned 14px since
   before that work landed. The full browser suite had not been run since the
   expenses pass, so the disagreement sat unnoticed until the settings pass ran
   it — which is the argument for running the browser suite at the END of a
   module, not only its own screen's tests.

   MEASURED off `087_Expenses_Modal_Add_Expense.png` rather than argued. The
   panel border (#e2e8f0) runs 752px wide at its topmost row, y=278, and reaches
   its full 780px at y=292. The corner curve therefore spans FOURTEEN rows, and
   the top row is inset fourteen pixels from the straight edge at x=450.

   The dialog's captured width — 778px inside that 1px border — is the same
   measurement's by-product, and is the neighbouring assertion in that test.
*/
.app-main[data-screen="expenses"] .modal.expense-modal {
  border: 1px solid #e2e8f0;
  border-radius: 14px;
}

.app-main[data-screen="expenses"] .modal.expense-modal > h2 {
  border-start-start-radius: 14px;
  border-start-end-radius: 14px;
  background: linear-gradient(90deg, rgba(244, 63, 94, 0.05), rgba(225, 29, 72, 0.05));
  font-size: 16px;
  font-weight: 800;
  color: #1e293b;
}

.app-main[data-screen="expenses"] .modal.expense-modal > form label {
  margin-block-end: 8px;
  color: #f43f5e;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.3px;
}

.app-main[data-screen="expenses"] .modal.expense-modal > form input[type="text"],
.app-main[data-screen="expenses"] .modal.expense-modal > form input[type="number"],
.app-main[data-screen="expenses"] .modal.expense-modal > form input[type="datetime-local"],
.app-main[data-screen="expenses"] .modal.expense-modal > form input[type="date"],
.app-main[data-screen="expenses"] .modal.expense-modal > form select,
.app-main[data-screen="expenses"] .modal.expense-modal > form textarea {
  padding: 10px 2px;
  border-block-end: 2px solid #e2e8f0;
  color: #1e293b;
  font-size: 14px;
  font-weight: 700;
  outline: none;
  transition: border-color 0.25s ease;
}

.app-main[data-screen="expenses"] .modal.expense-modal > form input:focus,
.app-main[data-screen="expenses"] .modal.expense-modal > form select:focus,
.app-main[data-screen="expenses"] .modal.expense-modal > form textarea:focus {
  border-block-end-color: #f43f5e;
}

.app-main[data-screen="expenses"] .modal.expense-modal > form textarea {
  min-block-size: 68px;
  resize: vertical;
}

/*
   THE TAX TOGGLE — 44x24 with an 18px knob, painted on the checkbox itself
   because this dialog's markup has no sibling span to paint. The element, its
   `name`, its `value` and its checked semantics are untouched; only its
   appearance changes.

   The knob travels along the INLINE axis. The reference pairs `right: 3px` with
   a negative `translateX`, which is right only in an RTL-only document — this
   build ships both directions, so the knob moves in logical terms instead.
*/
/*
   ⚠ THE VAT SWITCH IS PAINTED IN O.16, NOT HERE.

   This is where it used to live, and the block that was here painted the
   CHECKBOX itself — correct while the dialog's markup was a bare
   `<input type="checkbox">` inside a `<label>`, with nothing else to paint.

   The restructure gave the dialog the reference's own
   `.toggle-switch` > input + `.toggle-slider`, so the input is now transparent
   and the SLIDER carries the paint. That is the better arrangement: a focus
   ring on an `opacity: 0` input is invisible, and `:checked + .toggle-slider`
   expresses the state without a class anything has to maintain.

   Left as a note rather than silently deleted, because the two answers matched
   the same element and the dead one read like a contradiction.
*/

.app-main[data-screen="expenses"] .modal.expense-modal .modal-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block-start: 22px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .modal-actions .btn-outline {
  padding: 10px 22px;
  border: 1.5px solid #e2e8f0;
  border-radius: 50px;
  background: transparent;
  color: #64748b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}

.app-main[data-screen="expenses"] .modal.expense-modal .modal-actions .btn-outline:hover {
  border-color: #cbd5e1;
  color: #475569;
}

/* ══ O.9 — below the breakpoint ════════════════════════════════════════════

   The reference's own phone rules: the cards COMPRESS rather than stack, the
   filter bar becomes a column, and the scroll hint appears because nine columns
   cannot fit a phone. */

@media (max-width: 48rem) {
  .app-main[data-screen="expenses"] .kpi-tile {
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 8px 6px;
    border-inline-start-width: 0;
    border-block-start: 3px solid transparent;
    border-radius: 10px;
    text-align: center;
  }

  .app-main[data-screen="expenses"] .kpi-tile:hover {
    transform: none;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  }

  .app-main[data-screen="expenses"] .kpi-tile--before { border-block-start-color: #6366f1; }
  .app-main[data-screen="expenses"] .kpi-tile--tax    { border-block-start-color: #f59e0b; }
  .app-main[data-screen="expenses"] .kpi-tile--total  { border-block-start-color: #10b981; }

  .app-main[data-screen="expenses"] .kpi-icon {
    inline-size: 30px;
    block-size: 30px;
    border-radius: 8px;
  }

  .app-main[data-screen="expenses"] .kpi-icon svg {
    inline-size: 15px;
    block-size: 15px;
  }

  .app-main[data-screen="expenses"] .kpi-body {
    align-items: center;
    min-inline-size: 0;
  }

  .app-main[data-screen="expenses"] .kpi-label {
    margin-block-end: 0;
    font-size: 9px;
    line-height: 1.2;
  }

  .app-main[data-screen="expenses"] .kpi-value {
    font-size: clamp(11px, 3.4vw, 15px);
    line-height: 1.2;
  }

  .app-main[data-screen="expenses"] .filter-bar {
    flex-direction: column;
    align-items: stretch;
  }

  .app-main[data-screen="expenses"] .filter-bar input[type="search"],
  .app-main[data-screen="expenses"] .filter-bar input[type="text"] {
    max-inline-size: none;
    inline-size: 100%;
  }

  .app-main[data-screen="expenses"] .toolbar-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .app-main[data-screen="expenses"] .btn-export {
    justify-content: center;
    min-inline-size: 0;
    padding: 10px 12px;
  }

  .app-main[data-screen="expenses"] .scroll-hint {
    display: block;
    margin: 0 0 6px;
    color: #64748b;
    font-size: 11px;
    text-align: center;
  }

  .app-main[data-screen="expenses"] #expensesTable thead th,
  .app-main[data-screen="expenses"] #expensesTable tbody td {
    padding: 10px 12px;
    white-space: nowrap;
  }

  .app-main[data-screen="expenses"] .table-footer {
    justify-content: center;
  }
}

/* ══ O.10 — THE PARTS THE FIRST PASS LEFT UNBUILT ══════════════════════════

   O.1–O.9 dressed the screen the markup already had. This block covers what
   the reference audit found MISSING from that markup and the previous patch
   supplied: the back chevron, the four figure columns' own cell colours, the
   empty-state block, the three-part pagination bar, and the dialog's
   `.form-grid` / `.field-*` / `.toggle-switch` / `.tax-preview-box` structure.

   Values as before: read from `screens/expenses/expenses.css` and checked
   against `023_Expenses.png` and `087_Expenses_Modal_Add_Expense.png`.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] .screen-header .btn-outline svg {
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
}

/* ══ O.11 — the four figure columns ════════════════════════════════════════

   `REF.css:85-87, 193-194`. The tax column carries the tax card's amber and
   the total column the grand total's emerald, so one colour runs from the
   figure at the top of the screen down the column that feeds it. */

.app-main[data-screen="expenses"] #expensesTable .index-cell {
  inline-size: 50px;
  color: #94a3b8;
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="expenses"] #expensesTable .amount-cell {
  color: #1e293b;
  font-weight: 700;
}

.app-main[data-screen="expenses"] #expensesTable .tax-cell {
  color: #d97706;
  font-weight: 700;
}

.app-main[data-screen="expenses"] #expensesTable .total-cell {
  color: #059669;
  font-weight: 800;
}

/* ══ O.12 — the empty state, now a block under a visible header row ════════ */

.app-main[data-screen="expenses"] .empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 40px 20px;
  color: #94a3b8;
  font-size: 15px;
  text-align: center;
}

.app-main[data-screen="expenses"] .empty-state svg {
  inline-size: 52px;
  block-size: 52px;
  color: #cbd5e1;
}

.app-main[data-screen="expenses"] .empty-state p {
  margin: 0;
}

/* The no-open-shift notice in the heading band is a sentence beside a title,
   not a 40px column — it opts out of all of the above. */
.app-main[data-screen="expenses"] .settings-card-head .empty-state {
  display: block;
  padding: 0;
  font-size: 13px;
  font-weight: 700;
  text-align: start;
}

/* ══ O.13 — the pagination bar ═════════════════════════════════════════════

   `.pagination-bar{display:flex;justify-content:space-between;gap:16px;
   padding:12px 4px;flex-wrap:wrap}` — `REF.css:197`. O.7 already gave
   `.table-footer` that shape; what follows is its three parts. */

.app-main[data-screen="expenses"] .pagination-info {
  margin: 0;
  color: #64748b;
  font-size: 13px;
  font-weight: 700;
}

.app-main[data-screen="expenses"] .page-size-selector {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  color: #64748b;
  font-size: 13px;
  font-weight: 700;
}

.app-main[data-screen="expenses"] .page-size-selector label {
  margin: 0;
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
  white-space: nowrap;
}

.app-main[data-screen="expenses"] .page-size-selector select {
  box-sizing: border-box;
  padding: 6px 10px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  outline: none;
  cursor: pointer;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="expenses"] .page-size-selector select:focus {
  border-color: #f43f5e;
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.1);
}

/* The disabled steps a one-page run renders. They are `<span>`s, not dead
   links, so nothing here needs a `:hover`. */
.app-main[data-screen="expenses"] .table-footer .page-btn.is-disabled {
  opacity: 0.45;
  cursor: default;
}

/* ══ O.14 — the dialog's header band and error slot ════════════════════════ */

.app-main[data-screen="expenses"] .modal.expense-modal .modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  box-sizing: border-box;
  margin: -22px -22px 0;
  padding: 18px 22px;
  border-start-start-radius: 16px;
  border-start-end-radius: 16px;
  border-block-end: 1px solid #e2e8f0;
  background: linear-gradient(90deg, rgba(244, 63, 94, 0.05), rgba(225, 29, 72, 0.05));
}

/*
   ⚠ THE `<h2>` LOSES THE BAND IT USED TO BE.

   D.5/E.8a made the bare `<h2>` the header band itself — 68px tall, negative
   margins, its own tint and radius. It is now a child of `.modal-header`, which
   carries all of that, so the old rules would paint a band inside a band and
   pull the title 22px outside its own header. Reset here rather than edited
   there: E.8a's block is the record of what the dialog looked like before the
   markup was restructured, and rewriting history in place loses that.
*/
.app-main[data-screen="expenses"] .modal.expense-modal .modal-header > h2,
.app-main[data-screen="expenses"] .modal.expense-modal .modal-title {
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  block-size: auto;
  inline-size: auto;
  font-size: 16px;
  font-weight: 800;
  color: #1e293b;
}

.app-main[data-screen="expenses"] .modal.expense-modal .modal-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 32px;
  block-size: 32px;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #94a3b8;
  font: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.app-main[data-screen="expenses"] .modal.expense-modal .modal-close-btn:hover {
  background: rgba(244, 63, 94, 0.08);
  color: #f43f5e;
}

.app-main[data-screen="expenses"] .modal.expense-modal .error-msg {
  margin: 16px 0 0;
  padding: 10px 14px;
  border: 1px solid rgba(239, 68, 68, 0.25);
  border-radius: 10px;
  background: rgba(239, 68, 68, 0.08);
  color: #b91c1c;
  font-size: 13px;
  font-weight: 700;
}

/* ══ O.15 — the two-column form ════════════════════════════════════════════

   `.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 24px}` —
   `REF.css:112`. In RTL the odd child takes the right column, which is why
   `087` pairs the title with the amount and the date with the switch. */

/*
   ⚠ EXACTLY TWO COLUMNS, NOT `auto-fit`.

   `REF.css:112` is `grid-template-columns: 1fr 1fr` and the pairing is what
   `087` shows: title beside amount, date beside the switch. `auto-fit` with a
   240px minimum would fit THREE tracks across this 778px panel and repair
   nothing — the phone case is handled by the explicit single column in O.9's
   breakpoint, where it belongs.

   `minmax(0, 1fr)` rather than a bare `1fr`, so a long option in the category
   select cannot push its track past half the panel.
*/
.app-main[data-screen="expenses"] .modal.expense-modal .form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 24px;
  padding-block-start: 18px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .field-group {
  min-inline-size: 0;
  margin-block-start: 14px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .field-group-full {
  grid-column: 1 / -1;
}

.app-main[data-screen="expenses"] .modal.expense-modal .field-label {
  display: block;
  margin-block-end: 8px;
  color: #f43f5e;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.3px;
}

/* `REF.css:116` is the whole of it. It marks a field the server will refuse
   when empty; it does not enforce anything, and neither does ours. */
.app-main[data-screen="expenses"] .modal.expense-modal .required-star {
  color: #ef4444;
}

/*
   `.field-wrap` is the underline, and it lights up when anything inside it has
   focus. Putting the rule on the WRAPPER rather than the input is what lets the
   line span the field while the control itself stays borderless — and it is why
   `:focus-within` is the right selector: the wrapper is never focused itself.
*/
.app-main[data-screen="expenses"] .modal.expense-modal .field-wrap {
  border-block-end: 2px solid #e2e8f0;
  transition: border-color 0.25s ease;
}

.app-main[data-screen="expenses"] .modal.expense-modal .field-wrap:focus-within {
  border-block-end-color: #f43f5e;
}

.app-main[data-screen="expenses"] .modal.expense-modal .field-input {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: auto;
  padding: 10px 2px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  outline: none;
}

.app-main[data-screen="expenses"] .modal.expense-modal textarea.field-input {
  min-block-size: 68px;
  resize: vertical;
}

.app-main[data-screen="expenses"] .modal.expense-modal .field-input::placeholder {
  color: #cbd5e1;
  font-weight: 400;
}

/* ══ O.16 — the VAT switch ═════════════════════════════════════════════════

   44x24 with an 18px knob (`REF.css`), rose when on. The input is the real
   control, laid transparently over the slider it paints — so a keyboard user
   gets a focus ring on something visible, and the checked state is expressed by
   `:checked + .toggle-slider` rather than by a class a script would have to
   maintain. */

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-block: 6px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-switch {
  position: relative;
  display: inline-flex;
  inline-size: 44px;
  block-size: 24px;
  flex-shrink: 0;
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-switch input[type="checkbox"] {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-slider {
  position: absolute;
  inset: 0;
  border-radius: 24px;
  background: #cbd5e1;
  pointer-events: none;
  transition: background 0.25s ease;
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-slider::after {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  transition: inset-inline-start 0.25s ease, background 0.25s ease;
}

/*
   The knob travels along the INLINE axis. The reference pairs `right: 3px` with
   a negative `translateX`, which is correct only in an RTL-only document; this
   build ships English too, so the knob moves in logical terms and lands on the
   reading-side end in both directions.
*/
.app-main[data-screen="expenses"] .modal.expense-modal .toggle-switch input:checked + .toggle-slider {
  background: rgba(244, 63, 94, 0.3);
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-switch input:checked + .toggle-slider::after {
  inset-inline-start: 23px;
  background: #f43f5e;
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-switch input:focus-visible + .toggle-slider {
  outline: 2px solid #f43f5e;
  outline-offset: 2px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .toggle-label {
  color: #475569;
  font-size: 13px;
  font-weight: 700;
}

/* ══ O.17 — the live split ═════════════════════════════════════════════════

   Hidden until there is something to split — a taxable expense with a positive
   amount — which is why it is absent from both captures of this dialog. */

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-box {
  padding: 14px 16px;
  border: 1px solid rgba(244, 63, 94, 0.2);
  border-radius: 12px;
  background: rgba(244, 63, 94, 0.04);
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-block: 6px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-row.total-row {
  border-block-start: 1px solid rgba(244, 63, 94, 0.2);
  margin-block-start: 4px;
  padding-block-start: 10px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-label {
  color: #64748b;
  font-size: 12px;
  font-weight: 700;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-value {
  color: #1e293b;
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The second row is the TAX, whatever the reference's variable names say, and
   it takes the amber the taxable column and the tax card already carry. */
.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-value.total-value {
  color: #d97706;
}

/* ══ O.18 — the footer ═════════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] .modal.expense-modal .modal-footer {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block-start: 24px;
  padding-block-start: 18px;
  border-block-start: 1px solid #e2e8f0;
}

@media (max-width: 48rem) {
  .app-main[data-screen="expenses"] .modal.expense-modal .form-grid {
    grid-template-columns: 1fr;
  }

  .app-main[data-screen="expenses"] .table-footer .pagination-info,
  .app-main[data-screen="expenses"] .table-footer .page-size-selector {
    justify-content: center;
    inline-size: 100%;
    text-align: center;
  }
}

/* ══ O.19 — THE FOUR THINGS THE FIDELITY AUDIT CALLED BLOCKING ═════════════

   O.1–O.18 painted controls. This block fixes the PAGE: the gutters, the header
   band, the shell's back control, and a toolbar that was four stacked blocks
   where the capture is one row.

   Each of these was a rule that never fired rather than a rule that was wrong —
   Section O styled `.kpi-tile`, `.filter-bar` and `.screen-header` without ever
   resetting what the BASE rules give them, and the base gives all three the
   generic white card (`localisation.css:1349` and `:1544`). A screen-scoped
   `background` was painted over a card that was still there.
   ═══════════════════════════════════════════════════════════════════════════ */

/*
   The page gutter. `--ui-inset` is `clamp(12px, 2.4vw, 36px)` and resolves to
   36px at the 1680px width the references were captured at; `023` measures 14.
   The band below is full-bleed, so the gutter has to be a real padding on the
   main element rather than a margin on each child.

   `position: relative` is what lets the shell's back control be placed into the
   band — the arrangement `customers` and `hotels-companies` already use, and
   the reason it is used again here rather than a grid: this codebase has one
   answer for "the shell renders the back link outside the screen's header" and
   a second one would be a second thing to maintain.
*/
.app-main[data-screen="expenses"] {
  position: relative;
  padding-block: 0 20px;
  padding-inline: 14px;
}

/*
   The header BAND — 53px, full-bleed, one hairline under it.

   `.screen-header` is the generic card until this rule: `#fff` with a 16px
   radius, a card shadow, 18/20 padding and a 20px bottom margin. The capture
   has no radius, no shadow, no gutter, and its own 8/12 padding. And it never
   had `display: flex` on this screen — the only `.screen-header` flex in the
   stylesheet is scoped to `reports` — so the title and the back pill stacked.
*/
.app-main[data-screen="expenses"] .screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  box-sizing: border-box;
  min-block-size: 53px;
  margin-block: 0 16px;
  margin-inline: -14px;
  padding: 8px 14px;
  border-radius: 0;
  border-block-end: 1px solid #e2e8f0;
  background: #fff;
  box-shadow: none;
}

/* The UA gives an `<h1>` a 0.67em block margin and nothing in this stylesheet
   takes it back, so the title sat 10.7px below its own centre line. */
.app-main[data-screen="expenses"] .screen-header h1 {
  margin: 0;
}

/*
   ⚠ THE BACK CONTROL IS THE SHELL'S, AND THERE IS ONLY ONE OF IT.

   `layout.blade.php:113` renders `shell/back.blade.php` on every screen with
   `$shell['shows_back']`. This screen was ALSO emitting its own
   `<a class="btn-outline">`, so two `العودة` links rendered — one bare, one
   painted — and the reference has exactly one. The screen's copy is deleted and
   the pill is painted onto `#btnBack` here, which is what the other seven
   screens with a scoped back control already do.

   Absolutely positioned into the band, because the shell renders it as a
   SIBLING of `.screen-header` and before it: in flow it would sit above the
   band on its own line.
*/
.app-main[data-screen="expenses"] > .back-btn {
  position: absolute;
  z-index: 1;
  inset-block-start: 8px;
  inset-inline-start: 14px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  box-sizing: border-box;
  block-size: 37px;
  padding-inline: 18px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #f43f5e, #e11d48);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(244, 63, 94, 0.25);
  transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}

.app-main[data-screen="expenses"] > .back-btn:hover {
  opacity: 0.9;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(244, 63, 94, 0.35);
}

/*
   The chevron, drawn rather than added to the markup.

   `REF.html:31-33` puts a `polyline 15 18 9 12 15 6` inside the button, and it
   is the only thing that makes the control read as "back" rather than as a
   generic pill. It is NOT added to `shell/back.blade.php`: that partial is
   shared by every inner screen in the build, and putting a glyph in it would
   change twenty screens to fix one. A border drawn into a `::before` is the
   same shape and costs nothing outside this screen.
*/
.app-main[data-screen="expenses"] > .back-btn::before {
  content: "";
  inline-size: 7px;
  block-size: 7px;
  flex-shrink: 0;
  border-inline-start: 2.5px solid currentColor;
  border-block-end: 2.5px solid currentColor;
  transform: rotate(45deg);
}

/* ══ O.20 — one toolbar row ════════════════════════════════════════════════

   `REF.html:85-132` is a single 41px `space-between` row. Ours was four blocks
   stacked down the page, and the first of them was a white card. */

.app-main[data-screen="expenses"] .toolbar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  min-inline-size: 0;
  margin-block-end: 16px;
}

/*
   `.filter-bar` STOPS BEING A CARD. The base rule at `:1544` gives it
   `background: var(--ui-surface)`, a 16px radius, the card shadow and 12/16
   padding, and `:5798` adds a 52px minimum height. Section O set a `gap` and a
   `margin` on top of all of that and left the card underneath, which is why the
   screen showed a white panel the reference does not have.
*/
.app-main[data-screen="expenses"] .filter-bar {
  flex: 1 1 auto;
  min-block-size: 0;
  margin: 0;
  padding: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.app-main[data-screen="expenses"] .toolbar-actions {
  flex: 0 0 auto;
  margin-block-end: 0;
}

/* The search field, with the magnifier inside the lit outline rather than
   beside it — which is why the border and the ring are on the wrapper. */
.app-main[data-screen="expenses"] .search-wrap {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 240px;
  box-sizing: border-box;
  min-inline-size: 0;
  max-inline-size: 380px;
  padding: 0 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="expenses"] .search-wrap:focus-within {
  border-color: #f43f5e;
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.1);
}

.app-main[data-screen="expenses"] .search-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: #94a3b8;
}

.app-main[data-screen="expenses"] .search-icon svg {
  inline-size: 17px;
  block-size: 17px;
}

/* The input inside the wrapper carries none of the chrome any more — O.3 gave
   it a border and a ring of its own, and two nested outlines is what that would
   have drawn. */
.app-main[data-screen="expenses"] .search-wrap input[type="search"] {
  flex: 1 1 auto;
  min-inline-size: 0;
  max-inline-size: none;
  block-size: 37px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.app-main[data-screen="expenses"] .search-wrap input[type="search"]:focus {
  border: 0;
  box-shadow: none;
}

/* The add control's `+`, and the size the capture measures for all three
   toolbar buttons. */
.app-main[data-screen="expenses"] .toolbar-actions .btn-primary svg {
  inline-size: 17px;
  block-size: 17px;
  flex-shrink: 0;
}

.app-main[data-screen="expenses"] .toolbar-actions .btn-primary,
.app-main[data-screen="expenses"] .toolbar-actions .btn-export {
  block-size: 39px;
}

/* When there is no open till the add button is replaced by a one-line notice,
   and O.12's 40px centred column is the wrong shape for it. */
.app-main[data-screen="expenses"] .toolbar-actions .empty-state {
  display: block;
  padding: 0;
  font-size: 13px;
  font-weight: 700;
  text-align: start;
}

@media (max-width: 48rem) {
  .app-main[data-screen="expenses"] {
    padding-inline: 10px;
  }

  .app-main[data-screen="expenses"] .screen-header {
    margin-inline: -10px;
    padding-inline: 10px;
  }

  .app-main[data-screen="expenses"] > .back-btn {
    inset-inline-start: 10px;
    block-size: 32px;
    padding-inline: 12px;
    font-size: 12px;
  }

  .app-main[data-screen="expenses"] .toolbar-row {
    flex-direction: column;
    align-items: stretch;
  }

  .app-main[data-screen="expenses"] .search-wrap {
    max-inline-size: none;
  }
}

/* ══ O.21 — CORRECTIONS THE FIDELITY AUDIT MEASURED ════════════════════════

   Five things Section O got close but not right, each one measured against
   `screens/expenses/expenses.css` rather than argued about.
   ═══════════════════════════════════════════════════════════════════════════ */

/*
   ⚠ THE COLUMN-TO-CARD COLOUR TIE WAS OFF BY ONE RAMP STEP, AND O.11'S OWN
     COMMENT CLAIMED IT WAS NOT.

   O.11 says the tax column carries "the same amber as the tax card's edge" and
   then paints `#d97706` next to a card edge of `#f59e0b`; the total column
   painted `#059669` beside an edge of `#10b981`. `REF.css:86-87` is `#f59e0b`
   and `#10b981` — the card hexes exactly, which is what makes the colour read
   as one thing running down the screen rather than two similar ones.

   A comment that describes an intention the rules do not carry out is worse
   than no comment, so this is the correction and O.11's text is now true.
*/
.app-main[data-screen="expenses"] #expensesTable .tax-cell {
  color: #f59e0b;
  font-weight: 700;
}

.app-main[data-screen="expenses"] #expensesTable .total-cell {
  color: #10b981;
  font-weight: 700;
}

/*
   THE SCREEN'S BASELINE WEIGHT IS 700.

   `REF.css:6` puts `font-weight: 700` on the universal selector — it is the
   screen's baseline, not an emphasis. Ours left the register's text cells at
   the shell default while bolding only the money columns, so the title, the
   category and the date read lighter than every figure beside them.

   Two things step back down, as they do in the reference: the confirmation
   sentence, which is prose, and the `ر.س` abbreviation, which is a word inside
   a number and should not compete with it.
*/
.app-main[data-screen="expenses"] {
  font-weight: 700;
}

.app-main[data-screen="expenses"] .confirm-title,
.app-main[data-screen="expenses"] .confirm-overlay p,
.app-main[data-screen="expenses"] .sar,
.app-main[data-screen="expenses"] .money .sr-only {
  font-weight: 400;
}

/*
   AND NOTHING ON THIS SCREEN IS 800.

   Five places had drifted there — the three headings, the KPI figure and the
   total column. The reference's heaviest weight anywhere is 700, and against a
   700 baseline an 800 is a second level of emphasis the design does not have.
*/
.app-main[data-screen="expenses"] .screen-header h1,
.app-main[data-screen="expenses"] .settings-card-head h2,
.app-main[data-screen="expenses"] .kpi-value,
.app-main[data-screen="expenses"] .modal.expense-modal .modal-header > h2,
.app-main[data-screen="expenses"] .modal.expense-modal .modal-title {
  font-weight: 700;
}

/*
   The dialog's body padding. `.modal-body` is `18px 20px` from the base rule
   and the panel already pads 22px, so the form sat inside two paddings and the
   header band's negative margin fought both. One padding, on the body.

   The 6px block-end is deliberate: the last `.field-group` carries its own
   14px top margin, and the two together make the gap the capture measures.
*/
.app-main[data-screen="expenses"] .modal.expense-modal .modal-body {
  padding: 22px 22px 6px;
}

/*
   ⚠ THE FOOTER PAIR BELONGS AT THE OTHER END.

   `087` measures cancel at the physical LEFT with save inboard of it. The
   reference gets there with `justify-content: flex-start` plus
   `flex-direction: row-reverse` over a cancel→save DOM; ours is save→cancel, so
   a single `flex-end` in an RTL row lands both buttons in the same places with
   no reversal to keep in step with the markup.

   O.18 set no `justify-content` at all, which left the base `.modal-footer`
   rule's `flex-end`… in the wrong direction, because that rule is written for
   dialogs whose DOM is the other way round.
*/
.app-main[data-screen="expenses"] .modal.expense-modal .modal-footer {
  justify-content: flex-end;
  margin: 24px -22px -6px;
  padding: 16px 22px 22px;
  border-block-start: 1px solid #e2e8f0;
  background: #fff;
}

/* ══ O.22 — the live split is AMBER, and it is the only amber in the dialog ═

   `REF.css:133-140`, transcribed. O.17 painted it rose because rose is the
   screen's accent, which is exactly the reasoning the reference did not use:
   the box is a tax figure, and the tax colour on this screen is amber
   everywhere else — the KPI card's edge, the taxable pill, the tax column.
   Making the preview rose broke a tie that four other elements keep.

   ⚠ AND ITS SECOND ROW IS GREEN, DESPITE HOLDING THE TAX. O.17's comment is
     right that the row is the TAX and that the reference's ids are backwards;
     it does not follow that the row should be repainted. `REF.css:140` puts
     `#059669` at 18px on it and that is a design choice about the row a reader
     lands on last, not an assertion about what the figure means. The label
     above it says `قيمة الضريبة` either way. */

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-box {
  padding: 16px;
  border: 1px solid #fde68a;
  border-radius: 8px;
  background: #fefce8;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-row.total-row {
  margin-block-start: 8px;
  padding-block-start: 8px;
  border-block-start: 1px solid #fde68a;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-label {
  color: #92400e;
  font-size: 13px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-row.total-row .tax-preview-label {
  font-size: 14px;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-value {
  color: #d97706;
  font-size: 16px;
  text-align: end;
}

.app-main[data-screen="expenses"] .modal.expense-modal .tax-preview-value.total-value {
  color: #059669;
  font-size: 18px;
}

/* ══ O.23 — the second reflow the compaction was missing ═══════════════════

   The reference reflows the cards TWICE — at 900px and again at 768px — and
   O.9 carried the 900px values in a single 768px block, so the compaction fired
   132px late and the cards were still full size through the whole tablet
   range. The 900px step is added here; O.9's block keeps its own values, which
   are the 768px ones. */

@media (max-width: 56.25rem) {
  .app-main[data-screen="expenses"] .kpi-tile {
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 8px 6px;
    border-inline-start-width: 0;
    border-block-start: 3px solid transparent;
    border-radius: 10px;
    text-align: center;
  }

  .app-main[data-screen="expenses"] .kpi-tile:hover {
    transform: none;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  }

  .app-main[data-screen="expenses"] .kpi-tile--before { border-block-start-color: #6366f1; }
  .app-main[data-screen="expenses"] .kpi-tile--tax    { border-block-start-color: #f59e0b; }
  .app-main[data-screen="expenses"] .kpi-tile--total  { border-block-start-color: #10b981; }

  .app-main[data-screen="expenses"] .kpi-icon {
    inline-size: 30px;
    block-size: 30px;
    border-radius: 8px;
  }

  .app-main[data-screen="expenses"] .kpi-icon svg {
    inline-size: 15px;
    block-size: 15px;
  }

  .app-main[data-screen="expenses"] .kpi-body {
    align-items: center;
    min-inline-size: 0;
  }

  .app-main[data-screen="expenses"] .kpi-label {
    margin-block-end: 0;
    font-size: 9px;
    line-height: 1.2;
  }

  .app-main[data-screen="expenses"] .kpi-value {
    font-size: clamp(11px, 3.4vw, 15px);
    line-height: 1.2;
  }
}

/* The 768px step, on top of the 900px one above. */
@media (max-width: 48rem) {
  .app-main[data-screen="expenses"] .kpi-tile {
    padding: 7px 5px;
  }

  .app-main[data-screen="expenses"] .kpi-icon {
    inline-size: 28px;
    block-size: 28px;
  }

  .app-main[data-screen="expenses"] .kpi-icon svg {
    inline-size: 14px;
    block-size: 14px;
  }

  .app-main[data-screen="expenses"] .kpi-label {
    font-size: 8px;
  }

  .app-main[data-screen="expenses"] .kpi-value {
    font-size: clamp(11px, 3.6vw, 14px);
  }

  .app-main[data-screen="expenses"] .pagination-info {
    inline-size: 100%;
    text-align: center;
    white-space: normal;
  }
}

/* ══ O.24 — the header badge ═══════════════════════════════════════════════

   `REF.html:19-25` / `REF.css:12-14`. A 34px round badge with an 18px clock,
   the screen's only 135deg gradient and the only place the rose is a fill. */

.app-main[data-screen="expenses"] .header-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-inline-size: 0;
}

.app-main[data-screen="expenses"] .logo-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  background: linear-gradient(135deg, #f43f5e, #e11d48);
  color: #fff;
}

.app-main[data-screen="expenses"] .logo-icon svg {
  inline-size: 18px;
  block-size: 18px;
}

/* ══ O.25 — A CASCADE DEFECT, AND FOUR MEASUREMENTS ════════════════════════

   ⚠ O.21'S COLOUR RULES NEVER APPLIED. THIS IS THE FIX AND THE RECORD OF WHY.

   O.11 declares `… #expensesTable tbody td.num { color: #1e293b }` and O.21
   answered it with `… #expensesTable .tax-cell { color: #f59e0b }`. Count them:

       td.num     →  1 id · 3 class/attr (.app-main, [data-screen], .num) · 2 type
       .tax-cell  →  1 id · 3 class/attr (.app-main, [data-screen], .tax-cell) · 0 type

   (1,3,2) beats (1,3,0), so every one of those cells kept the ink O.11 gave it
   and the correction was dead the moment it was written. Being LATER in the
   file does not help — order only decides ties.

   The reference has the identical defect and it SHIPS with it: its own
   `.tax-cell` / `.total-cell` rules lose to a heavier selector, and the running
   product renders all three money columns `#475569`. So this is not a case of
   matching what the original does on screen; it is a case of the original's
   stylesheet stating an intention its cascade defeats. The intention is the
   design, the two colours tie the columns to the cards that sum them, and this
   build carries them out.

   Matching `td.tax-cell` gives the correction the same weight plus source
   order — the smallest change that makes it real.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] #expensesTable tbody td.amount-cell {
  color: #1e293b;
  font-weight: 700;
}

.app-main[data-screen="expenses"] #expensesTable tbody td.tax-cell {
  color: #f59e0b;
  font-weight: 700;
}

.app-main[data-screen="expenses"] #expensesTable tbody td.total-cell {
  color: #10b981;
  font-weight: 700;
}

/* The `#` column. The width has to be declared on the HEADER cell — a width on
   a body cell does not size a column — and both halves are centred. */
.app-main[data-screen="expenses"] #expensesTable thead th.col-index,
.app-main[data-screen="expenses"] #expensesTable tbody td.index-cell {
  inline-size: 50px;
  text-align: center;
}

.app-main[data-screen="expenses"] #expensesTable tbody td.index-cell {
  color: #94a3b8;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ══ O.26 — the register scrolls sideways at EVERY width ═══════════════════

   `REF.css:54, 73`: the container is `overflow-x: auto` unconditionally and the
   table has a 900px minimum, so nine columns never compress — the card scrolls
   and the page does not. Ours had no minimum at all and took horizontal scroll
   only from the global `@media (max-width: 60rem)` rule, so between 960px and
   the desktop width the columns squeezed instead.

   `min-inline-size: 0` on the card is what lets it be narrower than its own
   content in the page's flex/grid flow; without it the card grows to 900px and
   pushes the body sideways, which is the defect TS-UA-02 forbids. */

.app-main[data-screen="expenses"] #expensesPanel {
  min-inline-size: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.app-main[data-screen="expenses"] #expensesTable {
  min-inline-size: 900px;
}

/* The hint is the first thing in the card and describes what scrolls, so it
   scrolls with it — hence the sticky inline start, which keeps the sentence in
   view while the columns move under it. Revealed only where it is true. */
.app-main[data-screen="expenses"] .scroll-hint {
  display: none;
}

@media (max-width: 48rem) {
  .app-main[data-screen="expenses"] .scroll-hint {
    display: block;
    position: sticky;
    inset-inline-start: 0;
    inline-size: 100%;
    margin: 0;
    padding: 8px 10px;
    border-block-end: 1px solid #e2e8f0;
    background: linear-gradient(180deg, rgba(244, 63, 94, 0.1), transparent);
    color: #64748b;
    font-size: 12px;
    text-align: center;
  }
}

/* ══ O.27 — the pagination bar, now a sibling below the card ═══════════════

   `023` measures the card ending at y=658 and the bar at y675..738: a 16px gap
   and NO rule between them. Ours drew a `border-block-start` inside the card,
   which read as a ninth row. */

.app-main[data-screen="expenses"] .table-footer {
  margin-block-start: 16px;
  padding: 12px 4px;
  border-block-start: 0;
}

/*
   `.pager` had no layout rule reaching this screen at all — the only one in the
   stylesheet is scoped to `products` — so the control set was block flow, and
   the gaps between the arrows were the whitespace between the tags.
*/
.app-main[data-screen="expenses"] .table-footer .pager {
  display: flex;
  align-items: center;
  gap: 4px;
}

.app-main[data-screen="expenses"] .table-footer .page-btn,
.app-main[data-screen="expenses"] .table-footer .page-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  inline-size: 34px;
  block-size: 34px;
  padding: 0;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #64748b;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.app-main[data-screen="expenses"] .table-footer .page-btn.is-disabled {
  opacity: 0.35;
  cursor: default;
}

.app-main[data-screen="expenses"] .table-footer .page-ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  color: #94a3b8;
}

/* ══ O.28 — the dialog's scrim ═════════════════════════════════════════════

   E.8a painted `rgba(44, 52, 82, 0.59)`, which composites to `#838799` over a
   white page; the reference measures a neutral 50% black with a 4px blur behind
   it. The blur is what separates the dialog from a register full of figures —
   without it the page stays legible through the scrim and the eye keeps
   reading it.

   Still a spread `box-shadow` rather than a `::before`: E.8a records that a
   negative-z child inside this transformed, fixed panel paints OVER the panel's
   own background. The shadow paints outside the element and needs no child.

   `backdrop-filter` degrades to nothing where it is unsupported, which leaves
   the scrim doing its job unaided — so it is safe without a fallback. */

.app-main[data-screen="expenses"] .modal.expense-modal {
  box-shadow:
    0 0 0 100vmax rgba(0, 0, 0, 0.5),
    0 20px 60px rgba(0, 0, 0, 0.3);
}

/* ══ O.29 — the category rename field ══════════════════════════════════════

   `O-34-02`'s panel, finishing the half of itself that had no caller. Compact,
   because it sits in a table cell beside two other controls. */

.app-main[data-screen="expenses"] .category-rename {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
}

.app-main[data-screen="expenses"] .category-rename input[type="text"] {
  flex: 1 1 auto;
  min-inline-size: 0;
  max-inline-size: 260px;
  box-sizing: border-box;
  padding: 7px 12px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="expenses"] .category-rename input[type="text"]:focus {
  border-color: #f43f5e;
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.1);
}

.app-main[data-screen="expenses"] .category-rename .btn-icon {
  padding: 6px 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 50px;
  background: #fff;
  color: #64748b;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.app-main[data-screen="expenses"] .category-rename .btn-icon:hover {
  border-color: #f43f5e;
  color: #f43f5e;
  background: rgba(244, 63, 94, 0.06);
}

/* ══ O.30 — the last measurements ══════════════════════════════════════════ */

/* `REF.css:104` — `.modal-wide { width: 780px }`, re-measured on `087` as
   borders at x450 and x1229. D.5 built it 778 from an earlier reading. */
.app-main[data-screen="expenses"] .modal.expense-modal {
  inline-size: 780px;
}

/*
   ⚠ THE SCRIM'S BLUR IS THE ONE MEASUREMENT NOT REPRODUCED, AND HERE IS WHY.

   The reference is `rgba(0,0,0,.5)` plus `backdrop-filter: blur(4px)`. O.28
   carries the colour; the blur it cannot, because this scrim is a 100vmax
   `box-shadow` and a shadow is a painted region, not an element — there is
   nothing for `backdrop-filter` to filter.

   The obvious alternative is a `::before`, and E.8a is the record of that being
   tried and failing: this panel is `position: fixed` with a `transform`, so it
   establishes a stacking context, and a `z-index: -1` child inside it paints
   ABOVE the panel's own background. The capture from that attempt is a white
   dialog with the scrim laid over its own labels.

   A wrapper element would solve it and would mean changing the dialog's markup
   contract for every screen that reuses `.modal`. Recorded instead: the scrim
   is the right colour and the right opacity, and the page behind it is sharp.
*/

/* ══ O.31 — the delete confirmation ════════════════════════════════════════

   `REF.css:163-172`. Scoped to this screen: `<x-confirm>` is shared by eight
   registers and each capture dresses it differently.

   ⚠ ITS DANGER BUTTON IS RED, NOT THE SCREEN'S ROSE. `#ef4444 → #dc2626` —
     close enough to the rose to look like a mistake and far enough to be a
     deliberate one: a destructive confirmation should not wear the same colour
     as the `حفظ` beside every other form on the screen. */

.app-main[data-screen="expenses"] .confirm-overlay > .modal {
  inline-size: 380px;
  max-inline-size: 92vw;
  padding: 32px 28px 24px;
  border-radius: 18px;
  gap: 14px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.3);
}

/* The 60px warning disc. Painted rather than added to the markup: it is chrome,
   and `<x-confirm>` is shared. */
.app-main[data-screen="expenses"] .confirm-overlay > .modal::before {
  content: "!";
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 60px;
  block-size: 60px;
  margin-inline: auto;
  border: 2px solid rgba(239, 68, 68, 0.2);
  border-radius: 50%;
  background: rgba(239, 68, 68, 0.08);
  color: #dc2626;
  font-size: 28px;
  font-weight: 700;
}

/* The only 400-weight body text on the screen — prose, not a figure. */
.app-main[data-screen="expenses"] .confirm-overlay > .modal p:not(.confirm-title) {
  max-inline-size: 280px;
  margin-inline: auto;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.7;
  text-align: center;
}

.app-main[data-screen="expenses"] .confirm-overlay .btn-modal-danger,
.app-main[data-screen="expenses"] .confirm-overlay .btn-danger {
  padding: 10px 26px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #ef4444, #dc2626);
  color: #fff;
  font-weight: 700;
}

/* ══ N.16 — THE DIALOG IS ONE PANEL NOW, NOT THREE ═════════════════════════

   H.12 records the products dialog as "three panels laid side by side in a
   centred flex row — the form (340), the price table (247) and the image well
   (253)", and wraps them so they stop hanging off both edges. That was a
   layout repair for a STRUCTURE that has since been removed: the grid and the
   picker were three separate `<form>`s beside the identity form, and an
   operator pressing `حفظ` saved one of the three.

   They are children of the one form now. H.12's `flex-wrap` still applies and
   is harmless — a row of one wraps to one line — and its `overflow-y: auto` on
   `.cat-modal` is what keeps the taller dialog scrollable. The rules below are
   the consequences of the move.
   ═══════════════════════════════════════════════════════════════════════════ */

/*
   The panel scrolls, not just the overlay. With the grid and the picker inside
   it, the panel is taller than its own 86vh ceiling on a short window, and a
   save button below the fold is a save button the operator cannot reach.
*/
.app-main[data-screen="products"] .cat-modal-panel {
  overflow-y: auto;
}

/*
   N.12 padded `.cat-prices form` and `.cat-image form`, because each section
   used to hold one. The table is a direct child now, so the padding moves to
   the elements that are actually there.
*/
.app-main[data-screen="products"] .cat-prices > .cat-table {
  margin: 12px 14px;
  inline-size: calc(100% - 28px);
}

/* The stored picture, at the size the empty square already uses — the two swap
   places and nothing else on the row may move when they do. */
.app-main[data-screen="products"] .cat-image-row img.cat-thumb {
  inline-size: 96px;
  block-size: 96px;
  margin-inline: 0;
  flex-shrink: 0;
}

/*
   `إزالة الصورة` — a checkbox now, not a DELETE button.

   ⚠ AND IT IS DELIBERATELY NOT A SWITCH. N.10 turns every checkbox in this
     modal into a 44×24 toggle, which is right for `المنتج نشط` and
     `إضافة مزرام`: those are STATES the product is in. This one is an
     INSTRUCTION that applies once, on save, and a switch would read as
     "this product has no picture" — a state — rather than "remove it".
     So it is left as a plain box, and the rule below is what stops N.10's
     toggle styling from reaching it.
*/
.app-main[data-screen="products"] .cat-remove-image {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0 14px 14px;
  color: #64748b;
  font-size: 12px;
  font-weight: 700;
}

.app-main[data-screen="products"] .cat-remove-image input[type="checkbox"] {
  appearance: auto;
  -webkit-appearance: auto;
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
  margin: 0;
  border-radius: 0;
  background: none;
  accent-color: #dc2626;
}

.app-main[data-screen="products"] .cat-remove-image input[type="checkbox"]::before,
.app-main[data-screen="products"] .cat-remove-image input[type="checkbox"]::after {
  content: none;
}

/* ══ O.32 — THE DRAWER OPENER, AND THE TRAP H.13 WROTE DOWN IN ADVANCE ═════

   `AllScreensAuditTest` reported 16 defects at every width from 320 to 920:
   "drawer opener covers heading text: إدارة المصروفات".

   H.13 reserves the corner with `.app-main[data-screen] { padding-block-start:
   max(60px, var(--ui-inset)) }` below the rail width, and its own comment names
   the way that rule gets lost: "Most screens set their own
   `.app-main[data-screen="x"] { padding: 0 }` ... which outranks a bare
   `.app-main`". O.19 did precisely that — `padding-block: 0 20px`, to let the
   header band sit flush against the top of the content area — and an attribute
   WITH a value beats one without, so the reservation stopped applying and
   `#gsbToggle` printed on top of the title.

   The band still starts flush; what moves is where the content area starts. So
   the rule is restored at the same breakpoint H.13 uses, scoped to this screen
   so it can win on specificity rather than on order alone.

   ⚠ IT IS `max(60px, …)`, NOT 60px, AND THAT IS H.13'S NUMBER. Below the rail
     width the opener is 44px square at an 8px inset; above it, the opener is
     back in the rail and neither rule applies, so the 1680px reference geometry
     the captures were taken at does not move. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="expenses"] {
    padding-block-start: max(60px, var(--ui-inset));
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   P — THE INVOICES SCREEN (SCR-005), TRANSCRIBED FROM THE LIVE PRODUCT
   ═══════════════════════════════════════════════════════════════════════════

   Every value below was read out of the product's own `screens/invoices/
   invoices.css` and confirmed against `006_Invoices.png`.

   ⚠ THE SCREEN HAD ONE RULE. Before this section, `grep 'data-screen="invoices"'`
     returned two hits in 12,600 lines: the sidebar swatch at `:1826`, and
     `background: #f8fafc` at `:2376`. `.invoices-table`, `.pay-pill`,
     `.zatca-pill`, `.btn-view`, `.invoices-filters` and `.invoices-pagination`
     had ZERO rules between them, so the register rendered as a bare browser
     table on a grey page. The markup was built and reasoned about at length;
     the design layer was never written.

   ⚠ THE ACCENT IS VIOLET. `#8b5cf6 → #7c3aed`. Section N's teal is SCR-015's
     and Section O's rose is SCR-012's; each screen in this product carries its
     own and none of them may reach another.

     .back-btn            gradient 90deg #8b5cf6 → #7c3aed · radius 50
     .logo-icon           34px circle, gradient 135deg — the only 135deg here
     .search-wrap         1.5px #e2e8f0 · radius 10 · max 380
                          focus #8b5cf6 + 3px ring rgba(139,92,246,.1)
     .table-container     #fff · 1px #e2e8f0 · radius 12 · shadow 0 1 3 /8%
     .invoices-table      min-width 860
     thead tr             gradient rgba(139,92,246,.06) → rgba(124,58,237,.06)
                          border-bottom 2px #e2e8f0
     thead th             padding 14 18 · 12px/700 · #8b5cf6 · tracking .5
     tbody td             padding 14 18 · 14px · #475569
     tbody tr hover       rgba(139,92,246,.04)
     .inv-num-cell        700 · #8b5cf6 · 13px
     .total-cell          700 · #10b981
     .payment-badge       padding 3 12 · radius 50 · 12px/700, FIVE tones:
                            cash   rgba(16,185,129,.1)  #059669
                            card   rgba(59,130,246,.1)  #2563eb
                            credit rgba(245,158,11,.1)  #d97706
                            mixed  rgba(139,92,246,.1)  #7c3aed
                            bank   rgba(20,184,166,.1)  #0d9488
                            other  rgba(100,116,139,.1) #64748b
     .zatca-badge         radius 999 · 12px/800, FOUR states:
                            disabled  #64748b · pending #d97706
                            submitted #059669 · rejected #dc2626
     .btn-view            rgba(139,92,246,.1) · #8b5cf6 · border .25 · radius 8
     .empty-state         padding 60 20 · 52px glyph · gap 14
     .page-btn/.page-num  34×34 · 1.5px #e2e8f0 · radius 8
     .page-num.active     the gradient · white · shadow 0 2 8 rgba(139,92,246,.3)

   ⚠ OUR CLASS NAMES ARE KEPT. The reference calls these `.table-container`,
     `.payment-badge` and `.zatca-badge`; ours are `.table-scroll`,
     `.pill.pay-pill` and `.pill.zatca-pill`, and `InvoiceRegisterProbeTest`
     addresses several of them. The design is in the values, not in the words.

   ⚠ LOGICAL PROPERTIES THROUGHOUT. The reference is an RTL-only document and
     writes `text-align: right`; this build ships English too and the Phase 03
     ratchet forbids the physical form.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ══ P.1 — the screen and its head ═════════════════════════════════════════ */

/*
   The page gutter. `--ui-inset` resolves to 36px at the width the captures were
   taken at; `006` measures 14. The head is full-bleed, so the gutter has to be
   a padding on the main element rather than a margin on each child, and
   `position: relative` is what lets the shell's back control be placed into the
   band — the arrangement `customers`, `expenses` and five others already use.
*/
.app-main[data-screen="invoices"] {
  position: relative;
  padding-block: 0 20px;
  padding-inline: 14px;
}

/*
   ⚠ THE DRAWER OPENER'S CORNER, RESERVED — THE TRAP H.13 DOCUMENTS.

   H.13 reserves the top inline-start corner below the rail width because
   `#gsbToggle` is `position: fixed`, 44px square and out of flow, so anything a
   screen puts there is printed underneath it. Its rule keys off the bare
   `.app-main[data-screen]`, and the padding above — an attribute WITH a value —
   outranks it. The expenses screen shipped that exact defect and the browser
   audit caught it at every width from 320 to 920.
*/
@media (max-width: 63.99rem) {
  .app-main[data-screen="invoices"] {
    padding-block-start: max(60px, var(--ui-inset));
  }
}

.app-main[data-screen="invoices"] .screen-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  box-sizing: border-box;
  min-block-size: 52px;
  margin-block: 0 16px;
  margin-inline: -14px;
  padding: 8px 14px;
  border-radius: 0;
  border-block-end: 1px solid #e2e8f0;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

/* The UA gives an `<h1>` a 0.67em block margin and nothing in this stylesheet
   takes it back. */
.app-main[data-screen="invoices"] .screen-head h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: #1e293b;
}

/*
   The shell's back control, painted violet. It is rendered by
   `shell/back.blade.php` as a SIBLING of the head and before it, so in flow it
   would sit above the band on its own line — hence the absolute placement, the
   same one `customers` and `expenses` use.
*/
.app-main[data-screen="invoices"] > .back-btn {
  position: absolute;
  z-index: 1;
  inset-block-start: 8px;
  inset-inline-start: 14px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  box-sizing: border-box;
  block-size: 37px;
  padding-inline: 18px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #8b5cf6, #7c3aed);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(139, 92, 246, 0.25);
  transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}

.app-main[data-screen="invoices"] > .back-btn:hover {
  opacity: 0.9;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(139, 92, 246, 0.35);
}

/* The chevron, drawn rather than added to the shared partial — putting a glyph
   in `shell/back.blade.php` would change twenty screens to dress one. */
.app-main[data-screen="invoices"] > .back-btn::before {
  content: "";
  inline-size: 7px;
  block-size: 7px;
  flex-shrink: 0;
  border-inline-start: 2.5px solid currentColor;
  border-block-end: 2.5px solid currentColor;
  transform: rotate(45deg);
}

/*
   `إيصالات الاستهلاك` — the second control in the captured head. Teal, because
   it belongs to the screen it navigates TO, not to this one: `006` draws it in
   the consumption-receipts colour and that is what makes it read as a
   destination rather than as an invoice action.
*/
.app-main[data-screen="invoices"] .btn-consumption {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  box-sizing: border-box;
  block-size: 37px;
  padding-inline: 18px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(90deg, #14b8a6, #0d9488);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(20, 184, 166, 0.25);
  transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}

.app-main[data-screen="invoices"] .btn-consumption:hover {
  opacity: 0.9;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(20, 184, 166, 0.35);
}

.app-main[data-screen="invoices"] .btn-consumption svg {
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
}

/* ══ P.2 — the two search boxes ════════════════════════════════════════════

   `006` puts the wide invoice/customer/phone search at the reading start and
   the customer-code box at the end, on one row. */

.app-main[data-screen="invoices"] .invoices-filters {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  min-inline-size: 0;
  margin-block-end: 16px;
}

.app-main[data-screen="invoices"] .invoices-filters input[type="search"],
.app-main[data-screen="invoices"] .invoices-filters input[type="text"] {
  flex: 1 1 260px;
  min-inline-size: 0;
  max-inline-size: 380px;
  box-sizing: border-box;
  block-size: 41px;
  padding: 0 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  outline: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.app-main[data-screen="invoices"] .invoices-filters input::placeholder {
  color: #94a3b8;
  font-weight: 400;
}

.app-main[data-screen="invoices"] .invoices-filters input:focus {
  border-color: #8b5cf6;
  box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
}

/*
   The submit. The reference has none — every field reloads on a 300 ms debounce
   — but a GET form with two blocking fields cannot be submitted implicitly, so
   this build gives it a button (F-26-02 / D-26-03). It is the screen's accent,
   compact, and it sits at the end of the row.
*/
.app-main[data-screen="invoices"] .invoices-filter-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  box-sizing: border-box;
  block-size: 41px;
  padding-inline: 22px;
  border: 0;
  border-radius: 10px;
  background: linear-gradient(90deg, #8b5cf6, #7c3aed);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(139, 92, 246, 0.25);
  transition: opacity 0.2s ease, transform 0.15s ease;
}

.app-main[data-screen="invoices"] .invoices-filter-submit:hover {
  opacity: 0.9;
  transform: translateY(-1px);
}

/* ══ P.3 — the register ════════════════════════════════════════════════════ */

.app-main[data-screen="invoices"] .table-scroll {
  box-sizing: border-box;
  min-inline-size: 0;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  overflow-x: auto;
  overflow-y: visible;
  overscroll-behavior-x: contain;
}

.app-main[data-screen="invoices"] .invoices-table {
  inline-size: 100%;
  min-inline-size: 860px;
  border-collapse: collapse;
  background: transparent;
  box-shadow: none;
}

.app-main[data-screen="invoices"] .invoices-table thead tr {
  background: linear-gradient(90deg, rgba(139, 92, 246, 0.06), rgba(124, 58, 237, 0.06));
  border-block-end: 2px solid #e2e8f0;
}

.app-main[data-screen="invoices"] .invoices-table thead th {
  padding: 14px 18px;
  border: 0;
  background: transparent;
  text-align: start;
  font-size: 12px;
  font-weight: 700;
  color: #8b5cf6;
  letter-spacing: 0.5px;
  white-space: nowrap;
}

.app-main[data-screen="invoices"] .invoices-table tbody tr {
  border-block-end: 1px solid #f1f5f9;
  transition: background 0.15s ease;
}

.app-main[data-screen="invoices"] .invoices-table tbody tr:last-child {
  border-block-end: 0;
}

.app-main[data-screen="invoices"] .invoices-table tbody tr:hover {
  background: rgba(139, 92, 246, 0.04);
}

/* No zebra: `006` shows every row on the same white. */
.app-main[data-screen="invoices"] .invoices-table tbody tr:nth-child(even) {
  background: transparent;
}

.app-main[data-screen="invoices"] .invoices-table tbody td {
  block-size: auto;
  padding: 14px 18px;
  border: 0;
  text-align: start;
  font-size: 14px;
  color: #475569;
  vertical-align: middle;
}

/*
   ⚠ THREE CELLS CARRY `.num`, AND THEY ARE THREE DIFFERENT THINGS.

   The invoice number, the date and the total all take `class="num"` — it is
   this build's "these are digits, align them" marker, not a colour. The first
   version of this section wrote `td:first-child { color: #8b5cf6 }` for the
   number and `td.num { color: #10b981 }` for the total, which have IDENTICAL
   specificity (1 id · 3 class/attr · 2 type), so the later rule won and painted
   the number AND the date emerald. The same cascade trap Section O's own
   correction documents, made twice in one session.

   Fixed by naming the cells rather than counting them — and the names are the
   reference's own (`invoices.css:100-102`), so the selectors say which cell
   they mean instead of where it happens to sit. `.num` keeps only the alignment
   it was always for.
*/
.app-main[data-screen="invoices"] .invoices-table tbody td.num {
  font-variant-numeric: tabular-nums;
}

/* The invoice number is the row's identity and carries the screen's accent. */
.app-main[data-screen="invoices"] .invoices-table tbody td.inv-num-cell {
  font-size: 13px;
  font-weight: 700;
  color: #8b5cf6;
}

/* `الإجمالي` — emerald, and the only money on the row. */
.app-main[data-screen="invoices"] .invoices-table tbody td.total-cell {
  font-weight: 700;
  color: #10b981;
}

/* ══ P.4 — the customer cell, two lines ════════════════════════════════════

   `006` renders the name on top and the phone or code beneath it in a lighter
   grey; an unidentified sale renders the recovered em dash. */

.app-main[data-screen="invoices"] .invoices-customer {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.app-main[data-screen="invoices"] .invoices-customer-name {
  font-size: 14px;
  font-weight: 700;
  color: #1e293b;
  overflow-wrap: anywhere;
}

.app-main[data-screen="invoices"] .invoices-customer-id {
  font-size: 12px;
  font-weight: 400;
  color: #94a3b8;
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="invoices"] .invoices-customer-none {
  color: #cbd5e1;
}

/* ══ P.5 — the two pills ═══════════════════════════════════════════════════ */

.app-main[data-screen="invoices"] .pill {
  display: inline-flex;
  align-items: center;
  padding: 3px 12px;
  border-radius: 50px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.6;
  white-space: nowrap;
}

/*
   FIVE PAYMENT TONES, ONE PER METHOD THE MODEL DECLARES.

   `Order` carries cash, card, credit, mixed and bank; `InvoiceRow::paymentTone()`
   named three of them and rendered the other two grey, so a card sale and a bank
   transfer were shown as the same thing. Both halves are fixed — the map names
   all five, and each has the reference's own colour (`invoices.css:104-110`).
*/
.app-main[data-screen="invoices"] .pay-pill.is-cash {
  background: rgba(16, 185, 129, 0.1);
  border: 1px solid rgba(16, 185, 129, 0.25);
  color: #059669;
}

.app-main[data-screen="invoices"] .pay-pill.is-card {
  background: rgba(59, 130, 246, 0.1);
  border: 1px solid rgba(59, 130, 246, 0.25);
  color: #2563eb;
}

.app-main[data-screen="invoices"] .pay-pill.is-credit {
  background: rgba(245, 158, 11, 0.1);
  border: 1px solid rgba(245, 158, 11, 0.25);
  color: #d97706;
}

.app-main[data-screen="invoices"] .pay-pill.is-mixed {
  background: rgba(139, 92, 246, 0.1);
  border: 1px solid rgba(139, 92, 246, 0.25);
  color: #7c3aed;
}

.app-main[data-screen="invoices"] .pay-pill.is-bank {
  background: rgba(20, 184, 166, 0.1);
  border: 1px solid rgba(20, 184, 166, 0.25);
  color: #0d9488;
}

.app-main[data-screen="invoices"] .pay-pill.is-neutral {
  background: rgba(100, 116, 139, 0.1);
  border: 1px solid rgba(100, 116, 139, 0.25);
  color: #64748b;
}

/*
   THE ZATCA PILL — four states, and only one of them is reachable today.

   BR-099 makes this column "a derived rendering of the global switch,
   overriding the per-document status", so while ZATCA is off every row reads
   `غير مفعل` and wears the grey. The other three are written anyway, because
   BR-INV-11's warning cuts both ways: a rebuild that only styles the state the
   screenshot happens to show will look correct on this instance and be
   unstyled the moment ZATCA is switched on.

   ⚠ THE STATE CLASS NAMES ARE THE PRODUCT'S OWN, NOT INVENTED HERE.
     `ZatcaDocumentBadge::cssClass()` already returned `zatca-disabled` /
     `zatca-submitted` / `zatca-rejected` / `zatca-pending`, recovered beside
     the labels from `invoices.js:696-711`, and NOTHING CALLED IT — the view
     emitted a bare `zatca-pill`, so every state rendered identically. The row
     now asks the badge for both halves, which is why these selectors match the
     recovered names instead of a parallel set of my own.

     `zatca-failed` is this build's, for the terminal state `D-40-02` adds; it
     wears the rejected red because a submission that will not be retried and
     one the authority refused are the same fact to an operator reading a
     register.
*/
.app-main[data-screen="invoices"] .zatca-pill {
  border-radius: 999px;
  font-weight: 800;
  background: rgba(100, 116, 139, 0.08);
  border: 1px solid rgba(100, 116, 139, 0.18);
  color: #64748b;
}

.app-main[data-screen="invoices"] .zatca-pill.zatca-pending {
  background: rgba(245, 158, 11, 0.1);
  border-color: rgba(245, 158, 11, 0.25);
  color: #d97706;
}

.app-main[data-screen="invoices"] .zatca-pill.zatca-submitted {
  background: rgba(16, 185, 129, 0.1);
  border-color: rgba(16, 185, 129, 0.25);
  color: #059669;
}

.app-main[data-screen="invoices"] .zatca-pill.zatca-rejected,
.app-main[data-screen="invoices"] .zatca-pill.zatca-failed {
  background: rgba(239, 68, 68, 0.1);
  border-color: rgba(239, 68, 68, 0.25);
  color: #dc2626;
}

/* ══ P.6 — the row action ══════════════════════════════════════════════════ */

.app-main[data-screen="invoices"] .invoices-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.app-main[data-screen="invoices"] .btn-view {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  box-sizing: border-box;
  padding: 7px 14px;
  border: 1px solid rgba(139, 92, 246, 0.25);
  border-radius: 8px;
  background: rgba(139, 92, 246, 0.1);
  color: #8b5cf6;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.app-main[data-screen="invoices"] .btn-view:hover {
  background: rgba(139, 92, 246, 0.18);
  border-color: #8b5cf6;
  transform: translateY(-1px);
}

.app-main[data-screen="invoices"] .btn-view svg {
  inline-size: 15px;
  block-size: 15px;
  flex-shrink: 0;
}

/* ══ P.7 — the empty state and the scroll hint ═════════════════════════════ */

.app-main[data-screen="invoices"] .empty-state,
.app-main[data-screen="invoices"] .invoices-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 60px 20px;
  color: #94a3b8;
  font-size: 15px;
  text-align: center;
}

.app-main[data-screen="invoices"] .empty-state svg,
.app-main[data-screen="invoices"] .invoices-empty svg {
  inline-size: 52px;
  block-size: 52px;
  color: #cbd5e1;
}

/* `.table-scroll-hint{display:none}` in the reference — revealed only by its
   phone block, because above that width the columns fit. */
.app-main[data-screen="invoices"] .cat-scroll-hint {
  display: none;
}

@media (max-width: 48rem) {
  .app-main[data-screen="invoices"] .cat-scroll-hint {
    display: block;
    margin: 0 0 6px;
    color: #64748b;
    font-size: 12px;
    text-align: center;
  }
}

/* ══ P.8 — the pagination bar ══════════════════════════════════════════════

   `.pagination-bar{display:flex;justify-content:space-between;padding:12px 4px}`
   — reading right to left: the sentence, the control set, the size selector. */

.app-main[data-screen="invoices"] .invoices-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin-block-start: 16px;
  padding: 12px 4px;
}

.app-main[data-screen="invoices"] .invoices-count {
  font-size: 13px;
  font-weight: 700;
  color: #64748b;
  white-space: nowrap;
}

.app-main[data-screen="invoices"] .invoices-pagination {
  display: flex;
  align-items: center;
  gap: 4px;
}

.app-main[data-screen="invoices"] .invoices-pagination a,
.app-main[data-screen="invoices"] .invoices-pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-inline-size: 34px;
  block-size: 34px;
  padding-inline: 4px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #475569;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.app-main[data-screen="invoices"] .invoices-pagination a:hover {
  border-color: #8b5cf6;
  color: #8b5cf6;
  background: rgba(139, 92, 246, 0.06);
}

.app-main[data-screen="invoices"] .invoices-pagination .is-disabled {
  opacity: 0.35;
  cursor: default;
}

.app-main[data-screen="invoices"] .invoices-pagination .page-num.is-current {
  background: linear-gradient(90deg, #8b5cf6, #7c3aed);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 2px 8px rgba(139, 92, 246, 0.3);
}

/* The ellipsis is not a step and must not look like one. */
.app-main[data-screen="invoices"] .invoices-pagination .page-\… {
  border: 0;
  background: none;
  color: #94a3b8;
}

.app-main[data-screen="invoices"] .invoices-page-size {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: #64748b;
  white-space: nowrap;
}

.app-main[data-screen="invoices"] .invoices-page-size select {
  box-sizing: border-box;
  padding: 6px 10px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #1e293b;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  outline: none;
  cursor: pointer;
  transition: border-color 0.25s ease;
}

.app-main[data-screen="invoices"] .invoices-page-size select:focus {
  border-color: #8b5cf6;
}

.app-main[data-screen="invoices"] .invoices-page-size-submit {
  padding: 6px 14px;
  border: 1.5px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  color: #64748b;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.app-main[data-screen="invoices"] .invoices-page-size-submit:hover {
  border-color: #8b5cf6;
  color: #8b5cf6;
}

/* ══ P.9 — below the breakpoint ════════════════════════════════════════════ */

@media (max-width: 48rem) {
  .app-main[data-screen="invoices"] {
    padding-inline: 10px;
  }

  .app-main[data-screen="invoices"] .screen-head {
    margin-inline: -10px;
    padding-inline: 10px;
  }

  .app-main[data-screen="invoices"] > .back-btn {
    inset-inline-start: 10px;
    block-size: 32px;
    padding-inline: 12px;
    font-size: 12px;
  }

  .app-main[data-screen="invoices"] .btn-consumption {
    block-size: 32px;
    padding-inline: 12px;
    font-size: 12px;
  }

  .app-main[data-screen="invoices"] .invoices-filters {
    flex-direction: column;
    align-items: stretch;
  }

  .app-main[data-screen="invoices"] .invoices-filters input[type="search"],
  .app-main[data-screen="invoices"] .invoices-filters input[type="text"] {
    max-inline-size: none;
    inline-size: 100%;
  }

  .app-main[data-screen="invoices"] .invoices-table thead th,
  .app-main[data-screen="invoices"] .invoices-table tbody td {
    padding: 10px 12px;
  }

  .app-main[data-screen="invoices"] .invoices-foot {
    justify-content: center;
  }

  .app-main[data-screen="invoices"] .invoices-count {
    inline-size: 100%;
    text-align: center;
    white-space: normal;
  }
}

/* ══ P.10 — THREE THINGS THE CAPTURE SETTLED ═══════════════════════════════

   Photographed at 1440 and compared against `006_Invoices.png`. Each of these
   was wrong in the first pass of Section P and none of them was visible from
   the markup alone — which is what the camera is for.
   ═══════════════════════════════════════════════════════════════════════════ */

/*
   ⚠ 1. THE BACK PILL WAS ON THE WRONG SIDE.

   `006` puts `العودة` and `إيصالات الاستهلاك` together at the PHYSICAL LEFT of
   the band, with the title `الفواتير` alone at the right. In an RTL document
   that is the inline-END, and P.1 pinned the pill to `inset-inline-start` — the
   right — so it sat under the title and the head read back-to-front.

   The head reserves room for it rather than the pill overlapping whatever is
   there: the reservation is a padding, so the flex row inside genuinely ends
   before the pill starts and `إيصالات الاستهلاك` lands beside it instead of
   underneath it.
*/
.app-main[data-screen="invoices"] > .back-btn {
  inset-inline-start: auto;
  inset-inline-end: 14px;
}

.app-main[data-screen="invoices"] .screen-head {
  padding-inline-end: 132px;
}

/*
   ⚠ 2. THE CHEVRON POINTED DOWN.

   The borders-and-rotate trick has one correct combination per direction and
   the first pass used the wrong one: `border-inline-start + border-block-end`
   is, in RTL, right + bottom, which at 45° reads as a `v`.

   `border-block-start + border-inline-start` is top + right in RTL — the pair
   the standard chevron is built from — and `-135deg` turns it to point at the
   inline end, which is the direction `العودة` travels and the direction
   `REF.html`'s own `polyline 15 18 9 12 15 6` faces.
*/
.app-main[data-screen="invoices"] > .back-btn::before {
  border-inline-start: 2.5px solid currentColor;
  border-block-start: 2.5px solid currentColor;
  border-block-end: 0;
  transform: rotate(-135deg);
}

/*
   ⚠ 3. THE SEARCH SUBMIT DRIFTED AWAY FROM ITS OWN BOX.

   `.invoices-filters` was `space-between` across THREE children — the wide
   search, the customer-code search and the submit this build adds (F-26-02,
   because a two-blocking-field GET form cannot be submitted implicitly). Three
   items spread across a full row put the button at the far edge, a screen away
   from the field it applies to.

   The reference has two controls at the two ends and no submit at all. So the
   row packs from the reading start — search, then its button — and the code box
   is pushed to the far end on its own. Both ends match the capture and the
   button stays with what it submits.
*/
.app-main[data-screen="invoices"] .invoices-filters {
  justify-content: flex-start;
}

.app-main[data-screen="invoices"] .invoices-filters input[type="search"]:last-of-type {
  margin-inline-start: auto;
}

/* Icon-only, so it is square rather than a pill with nothing in it. */
.app-main[data-screen="invoices"] .invoices-filter-submit {
  inline-size: 41px;
  flex-shrink: 0;
  padding-inline: 0;
}

.app-main[data-screen="invoices"] .invoices-filter-submit svg {
  inline-size: 17px;
  block-size: 17px;
}

/* ══ O.33 — THE SAME TWO ERRORS SECTION P FOUND, ON THIS SCREEN TOO ════════

   The invoices capture exposed a back control pinned to the wrong edge with a
   chevron pointing the wrong way. Section O had written both the same way, and
   the expenses capture shows the same result: `العودة` sitting on top of
   `إدارة المصروفات` at the inline START, with a `v` where a `‹` belongs.

   `023_Expenses.png` puts the pill in `.header-left` — the PHYSICAL left, which
   in this RTL document is the inline END — with the title alone at the other
   side. The audit of that screen even recorded the arrow direction: "a
   left-pointing chevron in an RTL document, i.e. it points away from the
   content ... arrow tip toward x≈83".

   ⚠ FIXED HERE RATHER THAN BY EDITING O.1, so the pair reads as one correction
     made once and applied to both screens, and so O.1 still shows what was
     written and what it got wrong. Same reasoning O.25 gives for its own
     cascade repair.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] > .back-btn {
  inset-inline-start: auto;
  inset-inline-end: 14px;
}

/* Room reserved by padding, so the flex row inside genuinely ends before the
   pill begins instead of running underneath it. */
.app-main[data-screen="expenses"] .screen-header {
  padding-inline-end: 132px;
}

/* `border-block-start + border-inline-start` is top + right in RTL — the pair a
   chevron is built from — and `-135deg` turns it to face the inline end. */
.app-main[data-screen="expenses"] > .back-btn::before {
  border-inline-start: 2.5px solid currentColor;
  border-block-start: 2.5px solid currentColor;
  border-block-end: 0;
  transform: rotate(-135deg);
}

@media (max-width: 48rem) {
  .app-main[data-screen="expenses"] > .back-btn {
    inset-inline-start: auto;
    inset-inline-end: 10px;
  }

  .app-main[data-screen="expenses"] .screen-header {
    padding-inline-end: 104px;
  }
}

/* ══ P.11 — the ZATCA stats bar ════════════════════════════════════════════

   `invoices.css:44-56`, transcribed. Three cards above the register, each
   bordered in its own state's colour, the third clickable. */

.app-main[data-screen="invoices"] .zatca-stats-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-block-end: 16px;
}

.app-main[data-screen="invoices"] .zatca-stat-card {
  display: flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  padding: 8px 16px;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  text-decoration: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.app-main[data-screen="invoices"] .zatca-stat-value {
  font-size: 18px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="invoices"] .zatca-stat-label {
  font-size: 12px;
  font-weight: 700;
  color: #64748b;
  white-space: nowrap;
}

.app-main[data-screen="invoices"] .zatca-stat-sent { border-color: #bbf7d0; }
.app-main[data-screen="invoices"] .zatca-stat-sent .zatca-stat-value { color: #16a34a; }

.app-main[data-screen="invoices"] .zatca-stat-pending { border-color: #fde68a; }
.app-main[data-screen="invoices"] .zatca-stat-pending .zatca-stat-value { color: #d97706; }

.app-main[data-screen="invoices"] .zatca-stat-failed { border-color: #fecaca; }
.app-main[data-screen="invoices"] .zatca-stat-failed .zatca-stat-value { color: #dc2626; }

/* Only the third card leads anywhere, so only it lifts. `a.zatca-stat-failed`
   rather than the class alone: when the operator lacks `zatca_settings` the
   same card renders as a `<div>` and must not pretend to be pressable. */
.app-main[data-screen="invoices"] a.zatca-stat-failed {
  cursor: pointer;
  transition: box-shadow 0.2s ease, transform 0.15s ease;
}

.app-main[data-screen="invoices"] a.zatca-stat-failed:hover {
  box-shadow: 0 4px 14px rgba(220, 38, 38, 0.2);
  transform: translateY(-1px);
}

@media (max-width: 48rem) {
  .app-main[data-screen="invoices"] .zatca-stats-bar {
    inline-size: 100%;
    justify-content: space-between;
  }

  .app-main[data-screen="invoices"] .zatca-stat-card {
    flex: 1;
    justify-content: center;
    padding: 8px 6px;
  }
}

/* ══ Q — A CORRECTION TO SECTIONS O AND P, MEASURED FROM THE CAPTURES ══════

   ⚠ I PAINTED FOUR COLUMNS COLOURS THE PRODUCT DOES NOT SHOW.

   O.25 argued the case explicitly: the reference declares `.tax-cell{color:
   #f59e0b}` and `.total-cell{color:#10b981}`, its own cascade defeats them, and
   "the intention is the design ... this build carries them out". P.3 repeated
   the reasoning for `.inv-num-cell` violet and `.total-cell` emerald.

   The argument was wrong, and the way it was wrong is worth writing down: it
   treated the reference's STYLESHEET as the authority when this build's
   standing rule is that the SCREENSHOTS are. Those are not the same source and
   here they disagree.

   MEASURED, not argued:

     006_Invoices.png  invoice number, date and total all measure #475569.
                       Zero violet pixels, zero emerald pixels.
     023_Expenses.png  the only amber on a row is the `نعم` taxable pill
                       (#d97706). Zero emerald anywhere, on any row.

   The cause is the same on both screens and it is the cascade, not a missing
   rule: `.invoices-table tbody td` is (0,1,2) and `.total-cell` is (0,1,0), so
   the generic body colour wins and the cell colours never apply. The product
   ships that way; the operator has never seen a green total.

   So the four colours are withdrawn and the cells inherit the body slate the
   captures show. What stays is everything the captures DO show — the amber and
   slate taxable pills, the five payment tones, the ZATCA states, the grey `#`
   column (whose reference rule carries `!important` and therefore does apply).

   ⚠ THE POINT IS NOT THAT COLOUR IS WRONG. It is that "the reference intended
     this" is not evidence that the reference DOES it, and a rebuild measured
     against screenshots cannot ship ink the screenshots do not contain.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-main[data-screen="expenses"] #expensesTable tbody td.tax-cell,
.app-main[data-screen="expenses"] #expensesTable tbody td.total-cell {
  color: #475569;
  font-weight: 700;
}

.app-main[data-screen="invoices"] .invoices-table tbody td.inv-num-cell,
.app-main[data-screen="invoices"] .invoices-table tbody td.total-cell {
  color: #475569;
}

/* The number keeps its 13px — `invoices.css:100` sets a size as well as a
   colour, and only the colour is defeated by the cascade. */
.app-main[data-screen="invoices"] .invoices-table tbody td.inv-num-cell {
  font-size: 13px;
  font-weight: 700;
}

/* ═══════════════════════════════════════════════════════════════════════════
   R — THE SETTINGS SCREEN (SCR-021), «ماء وضوء»
   ═══════════════════════════════════════════════════════════════════════════

   ⚠ THE SCREEN HAD FIFTEEN RULES AND LOOKED LIKE AN UNSTYLED FORM.

   Nine tabs of working backend behind bare browser inputs on a white page: no
   ground, no card, no rail treatment, no active state, no field chrome. The
   markup and the persistence were built across five phases and the design layer
   never was — the same shape the invoices register was in before Section P.

   ⚠ AND THIS TIME THE STYLESHEET AND THE CAPTURE AGREE, WHICH I CHECKED
     BEFORE TRUSTING EITHER.

   The product's own settings screen carries a named token set — «ماء وضوء»,
   water and light — and I measured `042_Settings_Tab_إعدادات_الطابعة.png`
   against it rather than transcribing it on faith, because this session has
   twice shipped a colour a reference stylesheet declared and its capture did
   not show. Every landmark matched:

       page ground        measured #e9f1f6   declared --pl-bg        #e9f1f6
       active tab fill    measured #e3f1f4   declared --pl-primary-soft #e3f3fb
       card icon badge    measured #e3f3fb   declared --pl-primary-soft #e3f3fb
       body ink           measured #0c2d40   declared --pl-ink        #0c2d40
       secondary ink      measured #48677b   declared --pl-ink-2      #48677b
       toggle ON          measured #0e996c   declared --pl-success    #0e9f6e
       save button        measured #0887b5   declared --pl-grad 135deg #0ea5c9 → #0369a1

   So the values below are the product's, confirmed by its own screenshot.

   ⚠ OUR CLASS NAMES ARE KEPT. The reference calls these `.side-tab`, `.card`,
     `.card-hdr`, `.c-ico`, `.fwrap`, `.finp`, `.flbl`, `.fg2`; ours are
     `.settings-tab`, `.settings-card`, `.settings-card-head`, `.settings-grid`,
     `.field` and so on, and four probe files address them. The design is in the
     values, not in the words.

   ⚠ LOGICAL PROPERTIES THROUGHOUT — the reference is an RTL-only document and
     writes `text-align: right`; the Phase 03 ratchet forbids the physical form.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ══ R.1 — the ground and the two columns ══════════════════════════════════ */

.app-main[data-screen="settings"] {
  background: #e9f1f6;
  padding-block: 0 20px;
  padding-inline: 14px;
}

/*
   H.13's corner reservation, restored. A per-screen `padding` on
   `.app-main[data-screen="x"]` outranks the bare-attribute rule that keeps the
   fixed 44px drawer opener off the page content — the defect the browser audit
   caught on expenses at every width from 320 to 920.
*/
@media (max-width: 63.99rem) {
  .app-main[data-screen="settings"] {
    padding-block-start: max(60px, var(--ui-inset));
  }
}

.app-main[data-screen="settings"] .screen-head,
.app-main[data-screen="settings"] .screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  box-sizing: border-box;
  min-block-size: 52px;
  margin-block: 0 16px;
  margin-inline: -14px;
  padding: 8px 14px;
  padding-inline-end: 132px;
  border-radius: 0;
  border-block-end: 1px solid #d5e3ec;
  background: #fff;
  box-shadow: 0 1px 3px rgba(12, 45, 64, 0.05);
}

.app-main[data-screen="settings"] .screen-head h1,
.app-main[data-screen="settings"] .screen-header h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: #0c2d40;
}

/* The shell's back control, in the screen's own accent. */
.app-main[data-screen="settings"] > .back-btn {
  position: absolute;
  z-index: 1;
  inset-block-start: 8px;
  inset-inline-end: 14px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  box-sizing: border-box;
  block-size: 37px;
  padding-inline: 18px;
  border: 0;
  border-radius: 50px;
  background: linear-gradient(135deg, #0ea5c9, #0369a1);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 3px 10px rgba(3, 105, 161, 0.3);
  transition: box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1), transform 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] > .back-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 5px 16px rgba(3, 105, 161, 0.36);
}

.app-main[data-screen="settings"] > .back-btn::before {
  content: "";
  inline-size: 7px;
  block-size: 7px;
  flex-shrink: 0;
  border-inline-start: 2.5px solid currentColor;
  border-block-start: 2.5px solid currentColor;
  transform: rotate(-135deg);
}

/*
   The screen is a rail and a panel column. The reference gives the rail a fixed
   252px and lets the panel take the rest; ours is the same shape with our own
   element names.
*/
.app-main[data-screen="settings"] .settings-layout,
.app-main[data-screen="settings"] > .settings-shell {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  min-inline-size: 0;
}

/* ══ R.2 — the tab rail ════════════════════════════════════════════════════ */

.app-main[data-screen="settings"] .settings-tabs {
  inline-size: 252px;
  flex-shrink: 0;
  box-sizing: border-box;
  padding: 10px;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(12, 45, 64, 0.06), 0 4px 14px rgba(12, 45, 64, 0.07);
}

/* `.sb-label` — the rail caption above the tabs. */
.app-main[data-screen="settings"] .settings-tabs-heading {
  margin: 0 0 8px;
  padding-inline: 9px;
  font-size: 10px;
  font-weight: 700;
  color: #7d99ab;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.app-main[data-screen="settings"] .settings-tabs ul {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
   `.side-tab` — 7/9 padding, an 11px gap, a 10px radius, and the resting state
   is the SECONDARY ink at weight 400. The active tab is the only bold one.
*/
.app-main[data-screen="settings"] .settings-tab {
  display: flex;
  align-items: center;
  gap: 11px;
  box-sizing: border-box;
  inline-size: 100%;
  padding: 7px 9px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: #48677b;
  font: inherit;
  font-size: 13px;
  font-weight: 400;
  text-align: start;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1), color 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-tab:hover {
  background: #f2f7fa;
  color: #0c2d40;
}

.app-main[data-screen="settings"] .settings-tab.is-active,
.app-main[data-screen="settings"] .settings-tab[aria-current] {
  background: #e3f3fb;
  color: #075e88;
  font-weight: 700;
}

/*
   THE TAB ICON BADGE — a 34px rounded square, and it is what makes the rail
   read as a rail rather than as a list of links.

   ⚠ DRAWN, NOT ADDED TO THE MARKUP. `042` gives every tab its own glyph, and
     this build has no recovered SVG for any of them. A `::before` box in the
     badge's own colours carries the shape and the spacing the capture measures
     without inventing ten icons — and it leaves the markup free to gain the
     real glyphs later without moving anything.
*/
.app-main[data-screen="settings"] .settings-tab::before {
  content: "";
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  box-sizing: border-box;
  border-radius: 10px;
  border: 1.5px solid #e7f0f6;
  background: #f2f7fa;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1), border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-tab:hover::before,
.app-main[data-screen="settings"] .settings-tab.is-active::before,
.app-main[data-screen="settings"] .settings-tab[aria-current]::before {
  background: #e3f3fb;
  border-color: #a8d4ea;
}

/* ══ R.3 — the panel column and its cards ══════════════════════════════════ */

.app-main[data-screen="settings"] .settings-panels {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 1 1 auto;
  min-inline-size: 0;
  max-inline-size: 1280px;
}

.app-main[data-screen="settings"] .settings-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-inline-size: 0;
}

.app-main[data-screen="settings"] .settings-card {
  box-sizing: border-box;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(12, 45, 64, 0.06), 0 4px 14px rgba(12, 45, 64, 0.07);
  overflow: hidden;
  transition: box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-card:hover {
  box-shadow: 0 1px 2px rgba(12, 45, 64, 0.07), 0 8px 22px rgba(12, 45, 64, 0.1);
}

/*
   The card header: an icon badge, then a title and a sub in a column. Our
   partials render `<h2>` plus a `<p>`, so the badge is drawn the same way the
   tab's is — the shape without an invented glyph.
*/
.app-main[data-screen="settings"] .settings-card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0;
  padding: 15px 20px 13px;
  border-block-end: 1px solid #e7f0f6;
  background: transparent;
}

.app-main[data-screen="settings"] .settings-card-head::before {
  content: "";
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  border-radius: 10px;
  background: #e3f3fb;
}

.app-main[data-screen="settings"] .settings-card-head h2 {
  margin: 0;
  font-size: 13.5px;
  font-weight: 700;
  color: #0c2d40;
  line-height: 1.35;
  letter-spacing: -0.005em;
}

.app-main[data-screen="settings"] .settings-card-head p,
.app-main[data-screen="settings"] .settings-card > .settings-hint {
  margin: 2px 0 0;
  font-size: 11px;
  font-weight: 400;
  color: #48677b;
  line-height: 1.45;
}

/* The header wraps title and sub into one column beside the badge. */
.app-main[data-screen="settings"] .settings-card-head > h2 {
  flex: 1 1 auto;
  min-inline-size: 0;
}

.app-main[data-screen="settings"] .settings-card-head > p {
  flex: 1 1 100%;
  margin-inline-start: 46px;
}

/* ══ R.4 — the field grid ══════════════════════════════════════════════════

   `.fg2` is two equal columns with a 16/20 gap. The capture's rows confirm it:
   نوع الفاتورة beside عدد نسخ الطباعة, then the two margins, then the engine
   beside the logo width. */

.app-main[data-screen="settings"] .settings-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px 20px;
  align-items: start;
  padding: 18px 20px 20px;
}

/* Some panels declare a three-up grid; the reference is two everywhere, and a
   third track would put a 232px control in a 150px slot. */
.app-main[data-screen="settings"] .settings-grid--three {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* A card whose body is not a grid still needs the body padding. */
.app-main[data-screen="settings"] .settings-card > .field,
.app-main[data-screen="settings"] .settings-card > p,
.app-main[data-screen="settings"] .settings-card > .settings-hint {
  padding-inline: 20px;
}

.app-main[data-screen="settings"] .settings-card > .field {
  padding-block: 8px;
}

.app-main[data-screen="settings"] .field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-inline-size: 0;
}

.app-main[data-screen="settings"] .field-full,
.app-main[data-screen="settings"] .field--full {
  grid-column: 1 / -1;
}

/* `.flbl` — 11px, weight 700, the secondary ink. */
.app-main[data-screen="settings"] .field label,
.app-main[data-screen="settings"] .settings-card label {
  display: block;
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  color: #48677b;
  line-height: 1.45;
  letter-spacing: 0.01em;
}

/*
   ⚠ THE BORDER BELONGS TO THE WRAPPER IN THE REFERENCE AND TO THE CONTROL
     HERE, AND THAT IS A MARKUP DIFFERENCE, NOT A DESIGN ONE.

   `.fwrap` is a 1.5px bordered box with `overflow: hidden` and a borderless
   `.finp` inside it, so an input group can hold a suffix button flush to the
   edge. Our partials render the control directly, with no wrapper, so the same
   1.5px border, 10px radius, 40px minimum and focus ring go on the control
   itself. The rendered result is the box the capture measures.
*/
.app-main[data-screen="settings"] .field input[type="text"],
.app-main[data-screen="settings"] .field input[type="number"],
.app-main[data-screen="settings"] .field input[type="email"],
.app-main[data-screen="settings"] .field input[type="tel"],
.app-main[data-screen="settings"] .field input[type="url"],
.app-main[data-screen="settings"] .field input[type="password"],
.app-main[data-screen="settings"] .field input[type="time"],
.app-main[data-screen="settings"] .field select,
.app-main[data-screen="settings"] .field textarea {
  box-sizing: border-box;
  inline-size: 100%;
  min-inline-size: 0;
  min-block-size: 40px;
  padding: 9px 12px;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #fff;
  color: #0c2d40;
  font: inherit;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.5;
  outline: none;
  transition: border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .field select {
  cursor: pointer;
  font-weight: 700;
}

.app-main[data-screen="settings"] .field textarea {
  min-block-size: 92px;
  resize: vertical;
}

.app-main[data-screen="settings"] .field input:focus,
.app-main[data-screen="settings"] .field select:focus,
.app-main[data-screen="settings"] .field textarea:focus {
  border-color: #0284c7;
  box-shadow: 0 0 0 3px rgba(2, 132, 199, 0.14);
}

.app-main[data-screen="settings"] .field input::placeholder,
.app-main[data-screen="settings"] .field textarea::placeholder {
  color: #7d99ab;
  font-weight: 400;
}

/* Figures line up: the reference marks every numeric control tabular. */
.app-main[data-screen="settings"] .field input[type="number"],
.app-main[data-screen="settings"] .field input[type="time"],
.app-main[data-screen="settings"] .field input[dir="ltr"] {
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="settings"] .field small,
.app-main[data-screen="settings"] .settings-hint {
  font-size: 11px;
  font-weight: 400;
  color: #48677b;
  line-height: 1.45;
}

.app-main[data-screen="settings"] .field-error {
  margin: 0;
  color: #e02d3c;
  font-size: 11px;
  font-weight: 700;
}

.app-main[data-screen="settings"] .field input.is-invalid,
.app-main[data-screen="settings"] .field select.is-invalid {
  border-color: #e02d3c;
}

/* ══ R.5 — the switches ════════════════════════════════════════════════════

   44×24 with an 18px knob travelling 20px, grey when off and
   `--pl-success` when on. The knob moves along the INLINE axis: the reference
   pairs `left: 3px` with `translateX(20px)`, which is right only in an RTL-only
   document, and this build ships both directions. */

.app-main[data-screen="settings"] .settings-card input[type="checkbox"] {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  inline-size: 44px;
  block-size: 24px;
  flex-shrink: 0;
  margin: 0;
  border: 0;
  border-radius: 24px;
  background: #cddfea;
  cursor: pointer;
  vertical-align: middle;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-card input[type="checkbox"]::after {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(12, 45, 64, 0.22);
  transition: inset-inline-start 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-card input[type="checkbox"]:checked {
  background: #0e9f6e;
}

.app-main[data-screen="settings"] .settings-card input[type="checkbox"]:checked::after {
  inset-inline-start: 23px;
}

.app-main[data-screen="settings"] .settings-card input[type="checkbox"]:focus-visible {
  outline: 2px solid #0284c7;
  outline-offset: 2px;
}

/* A switch and its label sit on one line. */
.app-main[data-screen="settings"] .settings-card label:has(> input[type="checkbox"]) {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: #0c2d40;
}

/* ══ R.6 — the save button ═════════════════════════════════════════════════ */

.app-main[data-screen="settings"] .settings-card-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 0 20px 20px;
}

.app-main[data-screen="settings"] .btn-primary,
.app-main[data-screen="settings"] .btn-save {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  padding: 11px 30px;
  border: 0;
  border-radius: 10px;
  background: linear-gradient(135deg, #0ea5c9, #0369a1);
  color: #fff;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 3px 10px rgba(3, 105, 161, 0.3);
  transition: box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1), transform 160ms cubic-bezier(0.2, 0.8, 0.3, 1), filter 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .btn-primary:hover,
.app-main[data-screen="settings"] .btn-save:hover {
  box-shadow: 0 5px 16px rgba(3, 105, 161, 0.36);
  filter: saturate(1.08);
}

.app-main[data-screen="settings"] .btn-primary:active,
.app-main[data-screen="settings"] .btn-save:active {
  transform: translateY(1px);
  box-shadow: 0 2px 6px rgba(3, 105, 161, 0.28);
}

.app-main[data-screen="settings"] .btn-primary:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
  filter: grayscale(0.25);
}

.app-main[data-screen="settings"] .btn-secondary-sm,
.app-main[data-screen="settings"] .btn-outline {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #fff;
  color: #48677b;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 160ms ease, color 160ms ease, background 160ms ease;
}

.app-main[data-screen="settings"] .btn-secondary-sm:hover,
.app-main[data-screen="settings"] .btn-outline:hover {
  border-color: #a8d4ea;
  color: #075e88;
  background: #f2f7fa;
}

/* ══ R.7 — feedback ════════════════════════════════════════════════════════ */

.app-main[data-screen="settings"] .alert-success {
  margin-block-end: 16px;
  padding: 12px 16px;
  border: 1px solid rgba(14, 159, 110, 0.25);
  border-radius: 10px;
  background: rgba(14, 159, 110, 0.08);
  color: #047857;
  font-size: 13px;
  font-weight: 700;
}

.app-main[data-screen="settings"] .alert-error {
  margin-block-end: 16px;
  padding: 12px 16px;
  border: 1px solid rgba(224, 45, 60, 0.25);
  border-radius: 10px;
  background: rgba(224, 45, 60, 0.08);
  color: #b91c2c;
  font-size: 13px;
  font-weight: 700;
}

.app-main[data-screen="settings"] .alert-error div + div {
  margin-block-start: 4px;
}

/* ══ R.8 — below the breakpoint ════════════════════════════════════════════

   The rail folds above the panel rather than beside it, and the field grid
   becomes one column — the reference's own narrow behaviour. */

@media (max-width: 55rem) {
  .app-main[data-screen="settings"] .settings-layout,
  .app-main[data-screen="settings"] > .settings-shell {
    flex-direction: column;

    /*
       ⚠ `align-items: flex-start` IS RIGHT FOR THE ROW AND WRONG FOR THE
       COLUMN, AND R.1 SETS IT FOR BOTH.

       Beside the rail it stops the tab rail stretching to the height of the
       tallest card. Once the direction turns, the SAME declaration governs
       the cross axis — which is now the inline one — so `.settings-panels`
       stopped filling the content area and was sized to its own content
       instead.

       MEASURED, not reasoned: at a 485px viewport the panel rendered 692px
       wide and the page scrolled sideways by 342px at 360px. Forcing
       `align-self: stretch` on the panel in the browser brought it to 465px
       — the content width — with nothing else changed.

       The widest thing inside is the delivery log, whose table carries
       `min-inline-size: 650px` so five columns stay readable. That table is
       already wrapped in an `overflow-x: auto` box built for exactly this:
       with the panel stretched, the LOG scrolls inside its own border and
       the PAGE does not. Shrinking the table instead would have made the
       columns unreadable to fix a layout bug that is not the table's.
    */
    align-items: stretch;
  }

  .app-main[data-screen="settings"] .settings-tabs {
    inline-size: 100%;
  }

  /* `.sb-label` — the rail caption above the tabs. */
.app-main[data-screen="settings"] .settings-tabs-heading {
  margin: 0 0 8px;
  padding-inline: 9px;
  font-size: 10px;
  font-weight: 700;
  color: #7d99ab;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.app-main[data-screen="settings"] .settings-tabs ul {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .app-main[data-screen="settings"] .settings-tab {
    inline-size: auto;
  }

  .app-main[data-screen="settings"] .settings-grid,
  .app-main[data-screen="settings"] .settings-grid--three {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 48rem) {
  .app-main[data-screen="settings"] {
    padding-inline: 10px;
  }

  .app-main[data-screen="settings"] .screen-head,
  .app-main[data-screen="settings"] .screen-header {
    margin-inline: -10px;
    padding-inline: 10px;
    padding-inline-end: 104px;
  }

  .app-main[data-screen="settings"] > .back-btn {
    inset-inline-end: 10px;
    block-size: 32px;
    padding-inline: 12px;
    font-size: 12px;
  }

  /* The tab badge is dead weight on a phone rail that has folded to chips. */
  .app-main[data-screen="settings"] .settings-tab::before {
    display: none;
  }
}

/* ══ R.9 — THE SHELL THIS SCREEN ACTUALLY HAS ══════════════════════════════

   ⚠ R.1 STYLED A HEADER BAND THAT DOES NOT EXIST ON THIS SCREEN.

   Every other screen in this build opens with a visible `.screen-header` or
   `.screen-head`. SCR-021 does not: `settings/index.blade.php:25` renders its
   title as `<h1 class="sr-only">` and goes straight into `.settings-layout`.
   So R.1's band rules matched nothing, its `padding-block: 0` left the first
   card flush against the top of the content area, and the back control — which
   R.1 pinned absolutely into that missing band — printed on top of the rail.

   The capture agrees there is no in-screen band: `042`'s header is the app
   chrome, above the content area, not part of the panel column.

   So the top spacing comes back and the back control returns to normal flow,
   which is where it belongs on a screen with nothing to overlay it onto.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-main[data-screen="settings"] {
  padding-block: 16px 20px;
}

/*
   In flow, not absolute. It is the first child of `.app-main`, so it simply
   sits above the layout with its own gap — no band to place it into, and
   nothing for it to collide with.
*/
.app-main[data-screen="settings"] > .back-btn {
  position: static;
  margin-block-end: 16px;
  align-self: flex-start;
}

/*
   ⚠ AND THE BADGES ARE WITHDRAWN.
   R.2 and R.3 drew a 34px `::before` box in place of the reference's tab and
   card icons, on the reasoning that it carried "the shape and the spacing
   without inventing ten glyphs". The capture shows what that actually renders:
   a column of empty outlined pills down the rail and a blank square beside
   every card title. An empty box is not a neutral placeholder — it reads as a
   control that failed to load, which is worse than the honest absence of an
   icon this build has not recovered.

   The spacing they were carrying is kept as padding on the elements
   themselves, so the rhythm survives and nothing empty is drawn.
*/
.app-main[data-screen="settings"] .settings-tab::before,
.app-main[data-screen="settings"] .settings-card-head::before {
  content: none;
}

.app-main[data-screen="settings"] .settings-tab {
  padding: 10px 12px;
}

/*
   The card header stacks title over sub in one column now that no badge sits
   beside them — `flex-wrap` with a 46px indent was positioning the sub against
   an icon that is no longer drawn.
*/
.app-main[data-screen="settings"] .settings-card-head {
  display: block;
}

.app-main[data-screen="settings"] .settings-card-head > p {
  margin-inline-start: 0;
}

/* ══ R.10 — THE TOP PADDING IS THE SHELL'S, NOT A NUMBER I CHOSE ═══════════

   ⚠ R.9 CUT THE RESERVATION THAT KEEPS CONTENT OUT FROM UNDER THE TOPBAR.

   `localisation.css:1652` sets `.app-main { padding-block-start: 36px }` for
   every screen, and it is not decoration: the shell paints a topbar across the
   content area and 36px is what clears it. R.1 set `padding-block: 0 20px` and
   R.9 replaced it with `16px 20px` — both shorthands, both overwriting the
   block-start, and 16px does not clear a 48px band. The back control rendered
   underneath it and came out sliced in half in every frame.

   ⚠ AND I BLAMED THE CAMERA FIRST. The clipping looked exactly like a scroll
     offset, so the first fix was `window.scrollTo(0, 0)` in the shot test. It
     changed nothing, which is what proved the defect was in the page. Checking
     the instrument was right; concluding it was the instrument before the
     re-shoot came back was not.

   Only the block-END and the inline padding are set here now. The block-start
   is left to the base rule, so this screen reserves whatever every other screen
   reserves and cannot drift from it.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-main[data-screen="settings"] {
  padding-block-start: 36px;
  padding-block-end: 20px;
}

@media (max-width: 63.99rem) {
  .app-main[data-screen="settings"] {
    padding-block-start: max(60px, var(--ui-inset));
  }
}

/* ══ R.11 — the recovered glyphs, and what they replace ════════════════════
   ⚠ THIS SECTION WITHDRAWS TWO RULES R.2 AND R.3 SHIPPED.

   Both drew a 34px `::before` box where an icon belongs, with this reasoning:
   "`042` gives every tab its own glyph, and this build has no recovered SVG for
   any of them." That was FALSE, and it was false when it was written.

   The reference's own settings markup carries every glyph inline —
   `screens_settings_settings.html:1369-1502` for the rail, `:1517` onward for
   the cards. They were sitting in the captured corpus the entire time and were
   never looked for; the premise "we would have to invent them" was assumed, not
   checked. Twenty-five icons are now in `App\Support\Settings\SettingsIcons`,
   copied path by path.

   So the placeholder boxes go, and the badges below hold real artwork. The
   geometry (34px, 10px radius, the same colours) is unchanged, because that
   part was measured correctly the first time — only the contents were faked. */

.app-main[data-screen="settings"] .settings-tab::before,
.app-main[data-screen="settings"] .settings-card-head::before {
  content: none;
}

/* ── the rail badge ─ `.tab-ico`, ref:247-261 ─────────────────────────────── */

.app-main[data-screen="settings"] .settings-tab-ico {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  box-sizing: border-box;
  border-radius: 10px;
  border: 1.5px solid #e7f0f6;
  background: #f2f7fa;
  color: #48677b;
  transition:
    background 160ms cubic-bezier(0.2, 0.8, 0.3, 1),
    border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1),
    color 160ms cubic-bezier(0.2, 0.8, 0.3, 1),
    box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-tab-ico svg {
  inline-size: 17px;
  block-size: 17px;
  display: block;
}

.app-main[data-screen="settings"] .settings-tab:hover .settings-tab-ico {
  background: #e3f3fb;
  border-color: #a8d4ea;
  color: #075e88;
}

/*
   The ACTIVE badge is the one place on this screen that carries the gradient,
   and it is why the rail reads at a glance. `ref:257-260`.
*/
.app-main[data-screen="settings"] .settings-tab.is-active .settings-tab-ico,
.app-main[data-screen="settings"] .settings-tab[aria-current] .settings-tab-ico {
  background: linear-gradient(135deg, #0ea5c9, #0369a1);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 2px 8px rgba(3, 105, 161, 0.3);
}

/* The label takes the rest and clips rather than wrapping — `ref:263-267`. */
.app-main[data-screen="settings"] .settings-tab-lbl {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
   The 3px gradient marker on the tab's leading edge — `ref:240-245`.

   `position: relative` is added HERE rather than in R.2, so the rule that
   creates the positioning context sits next to the rule that needs it.
*/
.app-main[data-screen="settings"] .settings-tab {
  position: relative;
}

.app-main[data-screen="settings"] .settings-tab.is-active::after,
.app-main[data-screen="settings"] .settings-tab[aria-current]::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block: 7px;
  inline-size: 3px;
  border-radius: 3px;
  background: linear-gradient(135deg, #0ea5c9, #0369a1);
}

/* ── the card badge ─ `.c-ico`, ref:331-341 ───────────────────────────────── */

.app-main[data-screen="settings"] .settings-card-ico {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  box-sizing: border-box;
  border-radius: 10px;
  background: #e3f3fb;
  color: #075e88;
}

.app-main[data-screen="settings"] .settings-card-ico svg {
  inline-size: 16px;
  block-size: 16px;
  display: block;
}

/*
   THE HEAD'S TITLE AND SUBTITLE ARE NOW WRAPPED IN A DIV, so R.3's
   `margin-inline-start: 46px` on a direct-child `<p>` is no longer how the
   subtitle clears the badge — the wrapper does, by being a flex sibling.

   R.3's rules are left in place and are not wrong: partials that still put the
   title and subtitle directly in the header keep working exactly as before.
   These add the wrapped shape beside them rather than replacing it, so the two
   card markups can coexist while the remaining panels are converted.
*/
.app-main[data-screen="settings"] .settings-card-head > div {
  flex: 1 1 auto;
  min-inline-size: 0;
}

.app-main[data-screen="settings"] .settings-card-head > div > h2 {
  margin: 0;
  font-size: 13.5px;
  font-weight: 700;
  color: #0c2d40;
  line-height: 1.35;
  letter-spacing: -0.005em;
}

.app-main[data-screen="settings"] .settings-card-head > div > p {
  margin: 2px 0 0;
  font-size: 11px;
  font-weight: 400;
  color: #48677b;
  line-height: 1.45;
}

/* ══ R.12 — `حقول مخصصة` ═══════════════════════════════════════════════════
   The tab's fourth card, built for the first time. `ref:631-646`. */

.app-main[data-screen="settings"] .settings-cf {
  padding: 18px 20px 20px;
}

/* `.cf-toolbar` — the add button at one end, the cap hint at the other. */
.app-main[data-screen="settings"] .settings-cf-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin-block-end: 14px;
}

.app-main[data-screen="settings"] .settings-cf-toolbar .settings-hint {
  margin: 0;
  padding-inline: 0;
}

.app-main[data-screen="settings"] .btn-add {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  box-sizing: border-box;
  min-block-size: 36px;
  padding-inline: 14px;
  border: 1.5px solid #a8d4ea;
  border-radius: 10px;
  background: #e3f3fb;
  color: #075e88;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition:
    background 160ms cubic-bezier(0.2, 0.8, 0.3, 1),
    border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .btn-add svg {
  inline-size: 14px;
  block-size: 14px;
  display: block;
}

.app-main[data-screen="settings"] .btn-add:hover:not(:disabled) {
  background: #d3ecf8;
  border-color: #7cc0e2;
}

/*
   THE CAPPED STATE IS VISIBLY INERT, NOT INVISIBLE.

   At 20 rows the control stays on the card, greyed, with the recovered
   `الحد الأقصى 20 حقلًا مخصصًا` as its title. Hiding it would leave the
   operator looking for a button that was there a moment ago.
*/
.app-main[data-screen="settings"] .btn-add:disabled {
  border-color: #dbe7ef;
  background: #f2f7fa;
  color: #90a9ba;
  cursor: not-allowed;
}

.app-main[data-screen="settings"] .settings-cf-rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/*
   `.custom-row` — `ref:636-642`. Two equal label columns and the delete button
   sized to its text, aligned to the bottom of the row so it lines up with the
   inputs rather than with their labels.
*/
.app-main[data-screen="settings"] .settings-cf-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 12px;
  align-items: end;
  box-sizing: border-box;
  padding: 14px;
  border: 1.5px solid #e7f0f6;
  border-radius: 10px;
  background: #f2f7fa;
  transition:
    border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1),
    background 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-cf-row:hover {
  border-color: #a8d4ea;
  background: #fbfdfe;
}

/* The row sits on the tinted ground, so its inputs need the white back. */
.app-main[data-screen="settings"] .settings-cf-row input[type="text"] {
  background: #fff;
}

.app-main[data-screen="settings"] .settings-cf-row .cf-remove {
  align-self: end;
  white-space: nowrap;
}

/* One column on a narrow frame, delete button full width — `ref:646`. */
@media (max-width: 43.75rem) {
  .app-main[data-screen="settings"] .settings-cf-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
  }

  .app-main[data-screen="settings"] .settings-cf-row .cf-remove {
    inline-size: 100%;
  }
}

/* ══ R.13 — the save bar ═══════════════════════════════════════════════════
   `ref:649-660`. Sticky to the bottom of the scroll area, fading into the page
   ground rather than sitting on a hard rule, and the button is at the inline
   END — which in RTL is the left, exactly where `040` shows it. */

.app-main[data-screen="settings"] .settings-save-bar {
  position: sticky;
  inset-block-end: 0;
  z-index: 5;
  display: flex;
  justify-content: flex-end;
  margin-block-start: auto;
  padding-block: 14px 2px;
  background: linear-gradient(180deg, rgba(233, 241, 246, 0), #e9f1f6 42%);
}

.app-main[data-screen="settings"] .settings-save-bar .btn-save {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.app-main[data-screen="settings"] .settings-save-bar .btn-save svg {
  inline-size: 15px;
  block-size: 15px;
  display: block;
}

/* ══ R.14 — the per-field error ════════════════════════════════════════════
   The identity tab now renders `@error` beside each control. Without a rule the
   message would inherit the label's colour and read as a hint rather than as a
   rejection. `--pl-danger` is `#e02d3c` (ref:44). */

.app-main[data-screen="settings"] .field-error {
  margin: 2px 0 0;
  font-size: 11px;
  font-weight: 700;
  color: #e02d3c;
  line-height: 1.45;
}

/*
   And the control that produced it. `aria-invalid` is what the browser and a
   screen reader both read, so the border follows it rather than a class.
*/
.app-main[data-screen="settings"] .field input[aria-invalid="true"],
.app-main[data-screen="settings"] .field select[aria-invalid="true"],
.app-main[data-screen="settings"] .field textarea[aria-invalid="true"] {
  border-color: #e02d3c;
}

/* ══ R.15 — R.9'S COMPENSATIONS, REVERSED ══════════════════════════════════

   R.9 made three changes, all of them downstream of one premise: that this
   build had no icons and never would. That premise was wrong — the reference's
   own markup carries all 25 — so the compensations built on it are now the
   defect, and each is undone here rather than edited in place, so the record of
   what was believed and why stays readable.

     R.9 change                        why it existed          why it goes
     ─────────────────────────────────────────────────────────────────────────
     .settings-card-head{display:block} no badge to sit beside  badge exists
     .settings-tab{padding:10px 12px}   no badge to make room   badge exists
     .back-btn{position:static}         no header band to sit in band exists

   A DOM MEASUREMENT, not a reading of the cascade, is what found them: the
   probe reported `.settings-card-head` computing `display: block` with its icon
   at y=120 and its title at y=154 — two rows where `040` has one — and
   `.back-btn` computing `position: static` at x=1322 while its own
   `inset-inline-end: 14px` sat inert. */

.app-main[data-screen="settings"] .settings-card-head {
  display: flex;
}

/*
   Back to `ref:228` — 7px block, 9px inline. R.9 raised it to 10/12 to hold the
   rhythm the withdrawn badge had been holding; the badge is back, so the
   padding goes back too or every tab is 6px taller than the capture.
*/
.app-main[data-screen="settings"] .settings-tab {
  padding: 7px 9px;
}

/* ══ R.16 — the screen header band ═════════════════════════════════════════

   `ref:<header class="hdr">` — a white band across the content, the gear badge
   and `الإعدادات` at the RTL-leading edge, `العودة` at the other. The build
   already draws this shape on expenses and invoices; settings rendered its
   title `sr-only` and therefore had no band at all. */

.app-main[data-screen="settings"] .screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  box-sizing: border-box;
  min-block-size: 52px;
  margin-block: 0 16px;
  margin-inline: -14px;
  padding: 8px 14px;
  /*
     Room at the inline END for the back control, which is positioned into this
     band rather than being a child of it — the shell renders it as a direct
     child of `.app-main`, before the content, and that is not this screen's to
     change.
  */
  padding-inline-end: 132px;
  border-block-end: 1px solid #d5e3ec;
  background: #fff;
  box-shadow: 0 1px 3px rgba(12, 45, 64, 0.05);
}

.app-main[data-screen="settings"] .screen-header .header-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-inline-size: 0;
}

/* `.h-ico` — the one round gradient badge on the screen, as on expenses. */
.app-main[data-screen="settings"] .screen-header .logo-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  background: linear-gradient(135deg, #0ea5c9, #0369a1);
  color: #fff;
  box-shadow: 0 3px 10px rgba(3, 105, 161, 0.28);
}

.app-main[data-screen="settings"] .screen-header .logo-icon svg {
  inline-size: 17px;
  block-size: 17px;
  display: block;
}

.app-main[data-screen="settings"] .screen-header h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: #0c2d40;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
   THE BACK CONTROL, BACK IN THE BAND AND AT THE END OF IT.

   `inset-inline-end` in RTL is the LEFT, which is where `ref:<div class="hdr-l">`
   puts `العودة` and where `040` shows it. R.1 declared exactly this and R.9
   made it inert by resetting `position`; restoring the position is the whole
   fix, and the inset it needs is already declared above at R.1.
*/
.app-main[data-screen="settings"] > .back-btn {
  position: absolute;
  margin-block-end: 0;
  /*
     CENTRED IN THE BAND, NOT PINNED ABOVE IT.

     R.1 set 8px, which was measured against a band that did not exist —
     the title was `sr-only`, so there was nothing to centre against and
     the pill sat in the topbar reservation. The band now runs from 36px
     (the shell reservation) for 52px, so a 37px pill centres at
     36 + (52 - 37) / 2 = 43.5. Rounded up, because half a pixel of
     asymmetry reads as a mistake and half a pixel of extra clearance does
     not.
  */
  inset-block-start: 44px;
}

/*
   ⚠ AND THE BAND MUST NOT EAT THE TOPBAR RESERVATION.

   `localisation.css:1652` gives every `.app-main` a 36px block-start padding
   because the shell paints a fixed topbar over the content area. R.9 lost that
   once already by writing a `padding-block` shorthand. The band is placed
   INSIDE that padding by a negative inline margin only — the block start is
   untouched — so the reservation still holds and the back control, pinned 8px
   below it, lands on the band rather than under the topbar.
*/

/*
 Narrow: the band's end padding is only needed while the pill floats in it.

 ⚠ AND THE INLINE MARGIN MUST BE RESTATED HERE, NOT INHERITED.

 A block at line 14323 already handles this width correctly — it drops
 `.app-main[data-screen="settings"]` to `padding-inline: 10px` and gives the
 band `margin-inline: -10px` to match. R.16's desktop rule carries -14px at
 the SAME specificity (0,2,1) and sits later in the file, so it won the
 cascade INSIDE that media query too: the band ran 4px past each edge of a
 10px gutter and the page scrolled sideways.

 `ViewportMatrixTest` caught it at 768x1024 and 640x900, and
 `RtlAtBreakpointsTest` printed the arithmetic: `div.screen-header w=617
 left=-4` against a 609px client width. A media query adds no specificity —
 only source order broke the tie, and it broke it the wrong way.
*/
@media (max-width: 63.99rem) {
  .app-main[data-screen="settings"] .screen-header {
    margin-inline: -10px;
    padding-inline: 10px;
    padding-inline-end: 14px;
  }

  .app-main[data-screen="settings"] > .back-btn {
    position: static;
    margin-block-end: 12px;
  }
}

/* ══ R.17 — the detached identity form generates no box ═════════════════════

   `settings/partials/laundry.blade.php` declares `<form id="settings-identity">`
   holding nothing but its CSRF and method inputs; every visible control binds
   to it with the HTML5 `form=` attribute so the four cards can sit in the
   captured order while posting to three different endpoints.

   Left alone that empty form is still a flex ITEM of `.settings-panels`, and a
   zero-height item in a `gap: 16px` column still consumes its gap — 16px of
   dead space above the first card that no element accounts for.

   `display: contents` removes the box without removing the element: the form
   still owns its controls (association is by DOM and by id, never by layout),
   the hidden inputs still post, and nothing is hidden from assistive
   technology. `display: none` would also work for submission but would be a
   lie about intent — this form is not hidden, it simply has no geometry. */

.app-main[data-screen="settings"] .settings-panels > #settings-identity {
  display: contents;
}

/* ══ R.18 — the save bar stops floating in the narrow layout ═══════════════

   `AllScreensAuditTest` took 528 measurements across the width spread and found
   ONE overlap: `/settings/tax @320px — input#commercial_register × button.btn-save`.

   It is a true positive, and it is inherent to what a sticky footer is. The bar
   is pinned to the bottom of the viewport, so at any scroll position some
   control is underneath it. On the reference that is masked by the gradient
   fade and by a panel wide enough that the bar sits beside nothing important;
   at 320px the panel is one narrow column and the bar lands squarely on a
   field.

   Reserving bottom padding does NOT fix this — it only moves which control is
   covered while scrolling. So below the width where the rail folds above the
   panel, the bar stops being sticky and simply ends the form, which is what a
   single-column layout wants anyway: the save is the last thing you reach.

   Desktop keeps the captured behaviour. The audit passed at every other width,
   so this narrows the sticky behaviour to where it was measured to work rather
   than removing it because one width failed. */

@media (max-width: 63.99rem) {
  .app-main[data-screen="settings"] .settings-save-bar {
    position: static;
    background: none;
    padding-block: 4px 0;
  }
}

/*
   And on desktop the last card gets clearance, so the bar overlays the page
   ground rather than resting directly on a card edge when the panel is short.
*/
.app-main[data-screen="settings"] .settings-panels {
  padding-block-end: 8px;
}

/* ══ R.19 — GAP 8: the report-email modal's paired fields ══════════════════

   `093_Settings_Modal_Report_Email_Schedule.png`, measured: the modal card is
   640px wide with 16px padding, so its two field tracks are 294px each with a
   20px gutter. Our `.settings-grid` is already `repeat(2, minmax(0,1fr))` with
   `gap: 16px 20px`, so it produces exactly those numbers AT THAT WIDTH — two
   rules are all that is missing.

   The grid's own 18/20/20 padding is a CARD-BODY padding; inside a modal the
   padding belongs to the modal, so it is zeroed here rather than doubled. Same
   precedent as `#hanger-batch` and `.cat-modal-panel`. */

.app-main[data-screen="settings"] .settings-modal .settings-grid {
  padding: 0;
}

/*
   The modal card itself had NO styling at all — `details.settings-modal` was
   unstyled, so the grid's `1fr` tracks were halves of whatever width the page
   gave it rather than halves of 640px. Chrome from `ref html:887-893` plus the
   inline `padding:16px` at `:3817`.
*/
.app-main[data-screen="settings"] .settings-modal[open] > .settings-form {
  box-sizing: border-box;
  max-inline-size: 640px;
  padding: 16px;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 24px 60px rgba(12, 45, 64, 0.3);
}

/* ══ R.20 — GAP 3: `طرق الدفع المفعلة`, the tender dropdown ════════════════

   Ported from `ref:666-714`, with ONE structural difference: the reference
   opens its menu by toggling an `.open` class from JavaScript, and ours is a
   `<details>` / `<summary>`, so the control opens, ticks and SAVES with
   scripting off. Every `.open` selector below therefore becomes
   `details[open]`, and the geometry is unchanged. */

.app-main[data-screen="settings"] .payment-dropdown {
  position: relative;
  inline-size: 100%;
}

/* The summary IS the field-height row, so it has to lose its marker first. */
.app-main[data-screen="settings"] .payment-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  box-sizing: border-box;
  inline-size: 100%;
  min-block-size: 40px;
  padding: 9px 12px;
  border: 1.5px solid #d7e3ec;
  border-radius: 10px;
  background: #fff;
  color: #0c2d40;
  font-size: 13px;
  font-weight: 700;
  text-align: start;
  cursor: pointer;
  list-style: none;
  transition: border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .payment-trigger::-webkit-details-marker {
  display: none;
}

.app-main[data-screen="settings"] .payment-trigger::after {
  /* The reference's chevron, drawn rather than recovered as an SVG: it is a
     disclosure arrow, and `details` has no glyph of its own once the marker is
     removed. Rotates on open, exactly as `ref:.payment-trigger.open svg`. */
  content: "";
  inline-size: 8px;
  block-size: 8px;
  flex-shrink: 0;
  border-inline-end: 2px solid #48677b;
  border-block-end: 2px solid #48677b;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .payment-trigger:hover {
  border-color: #a8d4ea;
}

.app-main[data-screen="settings"] details[open] > .payment-trigger {
  border-color: #0284c7;
  box-shadow: 0 0 0 3px rgba(2, 132, 199, 0.15);
}

.app-main[data-screen="settings"] details[open] > .payment-trigger::after {
  transform: translateY(2px) rotate(-135deg);
}

/*
   ⚠ HIDDEN UNLESS THE DETAILS IS OPEN, AND THIS RULE IS LOAD-BEARING.

   The first version set `display: block` here unconditionally, which DEFEATED
   the closed-`<details>` hiding. A measurement at 320px reported
   `details open? false` beside `.payment-menu … w=245 h=208 disp=block
   vis=visible`, sitting directly on top of `select#default_payment_method`.

   The menu was therefore permanently open AT EVERY WIDTH. `AllScreensAuditTest`
   caught it at 320-480 only because that is where the single-column grid puts
   the select underneath it rather than beside it — at desktop it was covering
   the neighbouring column instead, which the overlap ratio did not flag.

   Gating `display` on `[open]` restores the element's own semantics instead of
   fighting them.
*/
.app-main[data-screen="settings"] .payment-menu {
  display: none;
}

.app-main[data-screen="settings"] details[open] > .payment-menu {
  display: block;
  position: absolute;
  inset-block-start: calc(100% + 6px);
  inset-inline: 0;
  z-index: 1000;
  overflow: hidden;
  border: 1.5px solid #d7e3ec;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 12px 32px rgba(12, 45, 64, 0.16);
}

.app-main[data-screen="settings"] .payment-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-block-size: 44px;
  padding: 10px 12px;
  border-block-end: 1px solid #e7f0f6;
  cursor: pointer;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .payment-option:last-child {
  border-block-end: none;
}

.app-main[data-screen="settings"] .payment-option:hover {
  background: #f2f7fa;
}

/*
   THE BOX IS DRAWN ON THE LABEL, NOT HIDDEN.

   The reference hides the checkbox and paints a sibling `<div>`. Ours keeps the
   real input visible and sized, because a hidden input with a painted proxy is
   one CSS mistake away from a control a keyboard cannot reach — and the box it
   paints is not recovered artwork, it is a tick.
*/
.app-main[data-screen="settings"] .payment-option input[type="checkbox"] {
  inline-size: 18px;
  block-size: 18px;
  flex-shrink: 0;
  accent-color: #0369a1;
  cursor: pointer;
}

.app-main[data-screen="settings"] .payment-label {
  flex: 1 1 auto;
  min-inline-size: 0;
  font-size: 13px;
  font-weight: 400;
  color: #0c2d40;
}

/*
   Narrow: `ref:1039` and `:1053-1054` raise the type to 16px and the rows to a
   46/48px touch target. Restated INSIDE the media block rather than inherited —
   F-21-10's lesson: a media query adds no specificity, so a later rule at the
   same weight wins inside it too.
*/
@media (max-width: 43.75rem) {
  .app-main[data-screen="settings"] .payment-trigger {
    min-block-size: 46px;
    font-size: 16px;
  }

  .app-main[data-screen="settings"] .payment-option {
    min-block-size: 48px;
  }

  .app-main[data-screen="settings"] .payment-label {
    font-size: 16px;
  }
}

/* ══ R.21 — GAP 2: the locked price-mode segmented control ═════════════════

   Two buttons in one pill, the live one filled. Both are `disabled`, because
   `AD-008` freezes this build price-inclusive and a control that looked
   changeable while the backend refused it is exactly what the campaign
   forbids — so the DISABLED styling here is the honest state, not a
   placeholder. `ref:1849-1870`. */

.app-main[data-screen="settings"] .settings-segmented {
  display: inline-flex;
  padding: 3px;
  border: 1.5px solid #e7f0f6;
  border-radius: 10px;
  background: #f2f7fa;
}

.app-main[data-screen="settings"] .settings-seg {
  padding: 7px 16px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #48677b;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  /*
     `not-allowed`, and NOT the greyed-out ink a disabled control usually gets:
     the text has to stay readable because the whole point of the card is to
     tell the operator which mode they are in.
  */
  cursor: not-allowed;
}

.app-main[data-screen="settings"] .settings-seg.is-active {
  background: #fff;
  color: #075e88;
  box-shadow: 0 1px 3px rgba(12, 45, 64, 0.12);
}

/* ══ R.22 — GAP: the closing-hour row, its preview and the card body ═══════

   Every number here is a GD pixel scan of
   `048_Settings_Tab_ساعة_الإقفال.png` (1680×1050, 1×), not a reading of the
   reference stylesheet — this build has been burned three times by a declared
   value the capture contradicts. */

/*
   THE CARD BODY, AND IT IS MANDATORY RATHER THAN TIDY.

   `.settings-card` is `display: block` with `padding: 0`, so a bare child sits
   flush against the card edge — which is exactly what the old closing markup
   did with its select, its button and its preview. `align-self` cannot fix
   that: there is no flex container to align in.

   `18px 20px 20px` reproduces the reference's card-body padding once, and the
   measurement agrees: divider → row 18px, save → card bottom 20px.
*/
.app-main[data-screen="settings"] .settings-card-body {
  padding: 18px 20px 20px;
}

.app-main[data-screen="settings"] .settings-card-body > .settings-closing-row {
  margin: 0 0 24px;
}

.app-main[data-screen="settings"] .settings-card-body > .settings-closing-preview {
  margin: 0 0 20px;
}

.app-main[data-screen="settings"] .settings-card-body > .btn-save {
  margin: 0;
}

/* ── the row: label · time · مسح, on ONE line ────────────────────────────── */

.app-main[data-screen="settings"] .settings-closing-row {
  display: flex;
  align-items: center;
  gap: 16px;
  /* 320px safety: the row folds rather than overflowing. */
  flex-wrap: wrap;
}

/*
   Scoped to the row rather than weakening the global rule: `.settings-card
   label` is 11px/700/`display:block`, which is right for every stacked field on
   this screen and wrong for a label sitting BESIDE its control.
*/
.app-main[data-screen="settings"] .settings-closing-row label {
  display: inline;
  margin: 0;
  font-size: 13px;
  font-weight: 400;
  color: #48677b;
  letter-spacing: normal;
  white-space: nowrap;
}

/*
   MEASURED 130×42, fill #e9f1f6, border #d5e3ec — a tinted control, not the
   white one every other field on this screen uses.

   A FIXED inline-size, not `min-inline-size`: a native time control's intrinsic
   width depends on the font stack and on whether the locale shows a meridiem
   field, so `auto` would render wider than the capture on some machines.
*/
.app-main[data-screen="settings"] .settings-closing-row input[type="time"] {
  box-sizing: border-box;
  inline-size: 130px;
  block-size: 42px;
  padding: 0 14px;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #e9f1f6;
  color: #0c2d40;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  direction: ltr;
  cursor: pointer;
}

.app-main[data-screen="settings"] .settings-closing-row input[type="time"]:focus {
  outline: none;
  border-color: #0284c7;
  box-shadow: 0 0 0 3px rgba(2, 132, 199, 0.14);
}

/*
   `مسح` — MEASURED 57×36. Its interior reads #ffffff in the capture because
   `background: transparent` sits on the white card body; setting an explicit
   white would be copying the symptom.

   Not `.btn-secondary-sm`: that gives ≈68×38 at 13px.
*/
.app-main[data-screen="settings"] .settings-closing-row #btnClearClosing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  block-size: 36px;
  padding: 0 14px;
  border: 1.5px solid #d5e3ec;
  border-radius: 8px;
  background: transparent;
  color: #48677b;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1), border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-closing-row #btnClearClosing:hover {
  border-color: #a8d4ea;
  background: #f2f7fa;
  color: #075e88;
}

/*
   ⚠ THE PREVIEW HAD NO RULES AT ALL.

   The blade shipped `class="settings-preview"`, a name that appears exactly
   once in this repository — in that blade — and in no stylesheet. Five spans
   rendered as one unstyled run of text. The divergence WAS the defect.

   MEASURED: 1240px wide (the full body content width), 98px tall, #f0f9ff on a
   #bae6fd border.
*/
.app-main[data-screen="settings"] .settings-closing-preview {
  box-sizing: border-box;
  padding: 14px 18px;
  border: 1.5px solid #bae6fd;
  border-radius: 12px;
  background: #f0f9ff;
  color: #0369a1;
  font-size: 13px;
  line-height: 1.8;
}

.app-main[data-screen="settings"] .settings-closing-preview strong {
  font-weight: 700;
}

/* `مسح` hides it, and `[hidden]` must beat the block display above. */
.app-main[data-screen="settings"] .settings-closing-preview[hidden] {
  display: none !important;
}

/*
   MEASURED 170×43 with a 16px glyph. The existing 15px svg rule is scoped to
   `.settings-save-bar` and cannot reach a button that is not in one.
*/
.app-main[data-screen="settings"] .settings-card-body > .btn-save {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-inline-size: 140px;
  padding: 11px 28px;
  border-radius: 10px;
  font-size: 14px;
}

.app-main[data-screen="settings"] .settings-card-body > .btn-save svg {
  inline-size: 16px;
  block-size: 16px;
  display: block;
}

/* ══ R.23 — GAP: the report-email read-outs, actions and delivery log ══════

   Measured from `043_Settings_Tab_تقارير_البريد.png`. The owner's screenshot
   showed this panel as loose stacked text with two identical filled buttons and
   an underlined link; the capture shows bordered read-outs, a primary beside a
   tinted secondary, and a real table. */

/*
   THE READ-OUT BOX — and it is `.settings-state`, a class that ALREADY EXISTS
   and had NO rules at all.

   It is used by report-email, customer-display, geidea, loyalty and updates —
   five panels rendering a value with no styling whatsoever. Inventing a new
   class here would have left all five unstyled and added a dead one.
*/
.app-main[data-screen="settings"] .settings-state {
  display: flex;
  align-items: center;
  gap: 10px;
  box-sizing: border-box;
  min-block-size: 44px;
  padding: 9px 12px;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #fff;
  color: #0c2d40;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.5;
}

/*
   The action row sits directly under the read-outs, so the first grid gives up
   its bottom padding and this one carries the gap — otherwise the two paddings
   stack into a visible trough.
*/
.app-main[data-screen="settings"] .settings-card > .settings-grid:first-of-type {
  padding-block-end: 0;
}

.app-main[data-screen="settings"] .settings-actions {
  padding-block: 0 20px;
  margin-block-start: 10px;
}

/*
   `align-items: flex-end` so a `<details>` and a `<form>` of different heights
   still line their buttons up on one baseline, and both children fill their
   track — `ref:2432`/`:2435` set `width:100%` on each.
*/
.app-main[data-screen="settings"] .settings-actions > * {
  display: flex;
  align-items: flex-end;
  min-inline-size: 0;
}

.app-main[data-screen="settings"] .settings-actions .btn-save,
.app-main[data-screen="settings"] .settings-actions .btn-email-send,
.app-main[data-screen="settings"] .settings-actions form {
  inline-size: 100%;
}

/* ── the tinted secondary ────────────────────────────────────────────────── */

.app-main[data-screen="settings"] .btn-email-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  min-block-size: 44px;
  padding: 11px 20px;
  border: 1.5px solid #cfe8f7;
  border-radius: 10px;
  background: #e3f3fb;
  color: #075e88;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1), border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .btn-email-send:hover {
  background: #d3ecf8;
  border-color: #a8d4ea;
}

.app-main[data-screen="settings"] .btn-email-send svg {
  inline-size: 15px;
  block-size: 15px;
  display: block;
}

/*
   ── the ghost, and why `تحديث` was underlined ────────────────────────────

   The whole settings scope carries exactly two `text-decoration: none`
   declarations and both are on the tab rail, so any other `<a>` here inherits
   the UA underline. That is the entire cause.
*/
.app-main[data-screen="settings"] .btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  flex-shrink: 0;
  min-block-size: 34px;
  padding: 0 14px;
  border: 1.5px solid #d5e3ec;
  border-radius: 8px;
  background: transparent;
  color: #48677b;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: background 160ms cubic-bezier(0.2, 0.8, 0.3, 1), border-color 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .btn-ghost:hover {
  border-color: #a8d4ea;
  background: #f2f7fa;
  color: #075e88;
}

/* ── the delivery log ────────────────────────────────────────────────────── */

/*
   A non-`.field` child of a card gets no inline padding, so the wrapper carries
   it. `overflow-x: auto` keeps a wide log scrolling inside its own box instead
   of widening the panel — the rule `AllScreensAuditTest` measures at 320px.
*/
.app-main[data-screen="settings"] .email-log-table-wrap {
  margin: 0 20px 20px;
  overflow-x: auto;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
}

.app-main[data-screen="settings"] .report-email-log {
  inline-size: 100%;
  min-inline-size: 650px;
  border-collapse: collapse;
  font-size: 12px;
}

.app-main[data-screen="settings"] .report-email-log thead th {
  padding: 11px 12px;
  border-block-end: 1px solid #d5e3ec;
  background: #eef6fb;
  color: #48677b;
  font-size: 11.5px;
  font-weight: 700;
  text-align: start;
  white-space: nowrap;
}

.app-main[data-screen="settings"] .report-email-log tbody td {
  padding: 11px 12px;
  border-block-end: 1px solid #eef4f8;
  color: #0c2d40;
  text-align: start;
  vertical-align: top;
}

.app-main[data-screen="settings"] .report-email-log tbody tr:last-child td {
  border-block-end: 0;
}

/* The timestamp column is Western digits in an RTL table. */
.app-main[data-screen="settings"] .report-email-log tbody td:first-child {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── the result pill ─────────────────────────────────────────────────────── */

.app-main[data-screen="settings"] .email-log-status {
  display: inline-flex;
  align-items: center;
  padding: 3px 12px;
  border: 1px solid transparent;
  border-radius: 50px;
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
}

.app-main[data-screen="settings"] .email-log-status.failed {
  border-color: #fecdd3;
  background: #fff1f2;
  color: #be123c;
}

.app-main[data-screen="settings"] .email-log-status.sent {
  border-color: #bbf7d0;
  background: #f0fdf4;
  color: #15803d;
}

/*
   The scrubbed SMTP error. It wraps rather than stretching the table, and
   breaks inside a long URL — the capture's own details column is a wrapped
   Google support link.
*/
.app-main[data-screen="settings"] .email-log-message {
  max-inline-size: 420px;
  color: #48677b;
  line-height: 1.6;
  word-break: break-word;
}

.app-main[data-screen="settings"] .report-email-log-empty {
  margin: 0 20px 20px;
  padding: 18px;
  color: #7d99ab;
  font-size: 12px;
  text-align: center;
}

/* ══ R.24 — GAP: the payment-device panel ═════════════════════════════════

   Measured from `047_Settings_Tab_جهاز_الدفع.png`. */

/*
   THE STATUS PILL — `ref:395-399`. An inline rounded chip with a coloured dot
   at its inline start, not the line of loose text this panel rendered.

   It is a MODIFIER on `.settings-state` rather than a new class: the base box
   (R.23) is right for a read-out field, and this is the same value in a
   different shape.
*/
.app-main[data-screen="settings"] .geidea-status {
  padding: 18px 20px 0;
}

.app-main[data-screen="settings"] .settings-state--pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-block-size: 0;
  max-inline-size: 100%;
  padding: 6px 12px;
  border-radius: 999px;
  /* `rgba(245,158,11,.10)` over white — the measured #fef5e6. */
  background: #fef5e6;
  border-color: #d5e3ec;
  font-size: 12.5px;
  font-weight: 700;
  color: #0c2d40;
}

.app-main[data-screen="settings"] .settings-state-dot {
  inline-size: 9px;
  block-size: 9px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #f59e0b;
}

.app-main[data-screen="settings"] .settings-state--pill small {
  font-size: 11.5px;
  font-weight: 400;
  color: #48677b;
}

/*
   THREE TRACKS, not one column per row.

   `047` measures the content box at 1240px with control edges at 567/968/985/
   1387 and a 17px gutter — 3 × 402 + 2 × 17 = 1240 to the pixel. `F-21-06`
   counted `class="fg2"` per panel and recorded "Geidea 0", concluding this
   panel "was ALREADY correct at one column". The panel uses `fg3`, which that
   census could not see: a false negative, and the screenshot settles it.
*/
.app-main[data-screen="settings"] .settings-grid--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px 16px;
}

/* Two tracks below the reference's own 1560px switch, one when narrow. */
@media (max-width: 97.5rem) {
  .app-main[data-screen="settings"] .settings-grid--three {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 40rem) {
  .app-main[data-screen="settings"] .settings-grid--three {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
   A note that spans the whole row — `ref:388-392`, `.ge-note`. Rendered always
   and toggled by `hidden`, so changing the transport updates it without a round
   trip; `[hidden]` has to beat the block display.
*/
.app-main[data-screen="settings"] .settings-card .settings-hint[hidden] {
  display: none !important;
}

/*
   `إعدادات وأدوات متقدمة` — the collapsed disclosure, `ref:3113`.

   A bordered summary spanning the panel, with the reference's own 12px/14px
   padding. It is `<details>` so it opens with scripting off, and its fields
   post either way.
*/
.app-main[data-screen="settings"] .settings-advanced {
  margin: 0 20px 18px;
  border: 1px solid #d5e3ec;
  border-radius: 10px;
}

.app-main[data-screen="settings"] .settings-advanced > summary {
  padding: 12px 14px;
  color: #0c2d40;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}

.app-main[data-screen="settings"] .settings-advanced > summary::-webkit-details-marker {
  display: none;
}

/* The disclosure triangle, drawn — a `list-style:none` summary has no glyph. */
.app-main[data-screen="settings"] .settings-advanced > summary::before {
  content: "";
  display: inline-block;
  inline-size: 7px;
  block-size: 7px;
  margin-inline-end: 8px;
  border-inline-end: 2px solid #48677b;
  border-block-end: 2px solid #48677b;
  transform: translateY(-2px) rotate(-45deg);
  transition: transform 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.app-main[data-screen="settings"] .settings-advanced[open] > summary::before {
  transform: translateY(1px) rotate(45deg);
}

/* Its fields keep the card's own inline rhythm once opened. */
.app-main[data-screen="settings"] .settings-advanced > .field {
  padding-inline: 14px;
  padding-block: 8px;
}

.app-main[data-screen="settings"] .settings-advanced > .field:last-of-type {
  padding-block-end: 14px;
}

/* ══ R.25 — `مستلمون إضافيون`, the report-email tab's third card ═══════════

   Translated from the reference's own block (`ref:754-835`) rather than
   invented: the add row, the pill-shaped time, the dimmed `is-off` state, the
   dashed empty box and the amber paused banner are all its shapes.

   THREE DELIBERATE DEPARTURES, each because this build's control differs:

     · the meridiem is a RADIO GROUP, not two `aria-pressed` buttons, so the
       "on" rule keys off `:checked` instead. Two buttons that remember a
       choice need a script; the affordance is identical without one.
     · the pause control is a `<button role="switch">` inside a real form, so
       it is styled from `aria-checked` — the same 50x24 teal switch E.9 draws
       for the category modal, which is this build's established switch.
     · the delete control is `<x-confirm>`'s `<summary>`, so the icon-square
       rules land on `.recipient-actions .confirm > summary` rather than on the
       reference's `.recipient-icon-btn`. The overlay it opens is Phase 10's
       and needs nothing here.

   Physical properties are avoided throughout — `VisualVerificationTest` bans
   them, and the card is mirrored wholesale in an RTL document. */

.app-main[data-screen="settings"] .recipient-paused {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 12px;
  padding: 10px 13px;
  border: 1.5px solid #f3ddb4;
  border-radius: 10px;
  background: #fef6e7;
  color: #9a6412;
  font-size: 12px;
  font-weight: 700;
}

.app-main[data-screen="settings"] .recipient-paused-ico {
  display: inline-flex;
  flex-shrink: 0;
}

.app-main[data-screen="settings"] .recipient-paused svg {
  inline-size: 16px;
  block-size: 16px;
}

/* ── the add row ─────────────────────────────────────────────────────────── */

.app-main[data-screen="settings"] .recipient-add {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  flex-wrap: wrap;
  padding: 14px;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #f7fbfd;
}

/* `flex: 1 1 220px` with a floor, so the address field takes the slack and the
   clock keeps its natural width — `ref:760`. `min-inline-size` rather than the
   reference's `min-width`, for the RTL mirror. */
.app-main[data-screen="settings"] .recipient-add > .field {
  flex: 1 1 220px;
  min-inline-size: 200px;
  margin: 0;
  padding: 0;
}

/* One control height across the whole row — `ref:762`. */
.app-main[data-screen="settings"] .recipient-add input[type="email"],
.app-main[data-screen="settings"] .recipient-add select {
  block-size: 42px;
}

.app-main[data-screen="settings"] .recipient-add > .btn-save {
  block-size: 42px;
  flex: 0 0 auto;
}

.app-main[data-screen="settings"] .recipient-time-picker {
  display: flex;
  align-items: center;
  gap: 6px;
}

.app-main[data-screen="settings"] .recipient-time-sep {
  font-weight: 700;
  color: #5b7c91;
}

.app-main[data-screen="settings"] .recipient-ampm {
  display: flex;
  overflow: hidden;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #fff;
}

/* The radio is taken out of the flow; its label IS the button. Not
   `display: none` — a hidden input cannot receive focus, and the group has to
   stay reachable from the keyboard. */
.app-main[data-screen="settings"] .recipient-ampm input[type="radio"] {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.app-main[data-screen="settings"] .recipient-ampm label {
  display: inline-flex;
  align-items: center;
  block-size: 39px;
  margin: 0;
  padding: 0 12px;
  color: #5b7c91;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background 160ms ease, color 160ms ease;
}

.app-main[data-screen="settings"] .recipient-ampm label:has(> input:checked) {
  background: #0e7fa8;
  color: #fff;
}

.app-main[data-screen="settings"] .recipient-ampm label:has(> input:focus-visible) {
  outline: 2px solid #0e7fa8;
  outline-offset: -2px;
}

/* ── the list ────────────────────────────────────────────────────────────── */

.app-main[data-screen="settings"] .recipient-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-block-start: 14px;
}

.app-main[data-screen="settings"] .recipient-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 11px 14px;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #fff;
  transition: border-color 160ms ease, background 160ms ease;
}

.app-main[data-screen="settings"] .recipient-row:hover {
  border-color: #a8d5ea;
  background: #f4fafd;
}

/* A PAUSED row is dimmed, never hidden — it keeps its place and its time. */
.app-main[data-screen="settings"] .recipient-row.is-off {
  background: #f7fbfd;
}

.app-main[data-screen="settings"] .recipient-row.is-off .recipient-mail {
  color: #5b7c91;
}

.app-main[data-screen="settings"] .recipient-mail {
  flex: 1 1 200px;
  min-inline-size: 0;
  overflow: hidden;
  color: #0c2d40;
  font-size: 13px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-main[data-screen="settings"] .recipient-time {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border: 1px solid #c3e5f6;
  border-radius: 999px;
  background: #eaf6fc;
  color: #0b5f7d;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.app-main[data-screen="settings"] .recipient-row.is-off .recipient-time {
  border-color: #d5e3ec;
  background: #f7fbfd;
  color: #5b7c91;
}

.app-main[data-screen="settings"] .recipient-time-ico {
  display: inline-flex;
}

.app-main[data-screen="settings"] .recipient-time svg {
  inline-size: 13px;
  block-size: 13px;
}

/* `margin-inline-start: auto` pushes the actions to the row's trailing edge in
   either direction — `ref:801` reaches for the logical property too. */
.app-main[data-screen="settings"] .recipient-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-inline-start: auto;
}

.app-main[data-screen="settings"] .recipient-actions form {
  display: flex;
  margin: 0;
}

/* ── the pause switch ────────────────────────────────────────────────────── */

.app-main[data-screen="settings"] .recipient-switch {
  position: relative;
  inline-size: 50px;
  block-size: 24px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: #cbd5e1;
  cursor: pointer;
  transition: background 120ms ease;
}

.app-main[data-screen="settings"] .recipient-switch-slider {
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: #fff;
  transition: inset-inline-start 120ms ease;
}

.app-main[data-screen="settings"] .recipient-switch[aria-checked="true"] {
  background: #10b981;
}

.app-main[data-screen="settings"] .recipient-switch[aria-checked="true"] .recipient-switch-slider {
  inset-inline-start: 29px;
}

.app-main[data-screen="settings"] .recipient-switch:focus-visible {
  outline: 2px solid #0e7fa8;
  outline-offset: 2px;
}

/* ── the delete control — `<x-confirm>`'s trigger as a 36px icon square ──── */

.app-main[data-screen="settings"] .recipient-actions .confirm > summary {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 36px;
  block-size: 36px;
  padding: 0;
  border: 1.5px solid #d5e3ec;
  border-radius: 10px;
  background: #fff;
  color: #5b7c91;
  cursor: pointer;
  list-style: none;
  transition: background 160ms ease, color 160ms ease, border-color 160ms ease;
}

.app-main[data-screen="settings"] .recipient-actions .confirm > summary::-webkit-details-marker {
  display: none;
}

.app-main[data-screen="settings"] .recipient-actions .confirm > summary:hover {
  border-color: #f4c7cc;
  background: #fdecee;
  color: #be123c;
}

.app-main[data-screen="settings"] .recipient-actions .confirm > summary svg {
  inline-size: 16px;
  block-size: 16px;
}

/* ── the empty state ─────────────────────────────────────────────────────── */

.app-main[data-screen="settings"] .recipient-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-block-start: 14px;
  padding: 26px 18px;
  border: 1.5px dashed #d5e3ec;
  border-radius: 10px;
  color: #5b7c91;
  font-size: 12px;
  text-align: center;
}

.app-main[data-screen="settings"] .recipient-empty svg {
  inline-size: 26px;
  block-size: 26px;
  opacity: 0.7;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SECTION Q — THE PLATFORM CONSOLE (ADM-001 … ADM-004)

   ─────────────────────────────────────────────────────────────────────────
   EVERY VALUE BELOW IS MEASURED, NOT CHOSEN
   ─────────────────────────────────────────────────────────────────────────

   `screenshots/admin/_source/html/` holds the HTML the nine console mock-ups
   were rendered from, and its `<style>` block is the reference this section
   reproduces. Where a number appears here — 250px, 34px, `#0e5c8f`, `radius
   11` — it was read out of that block, not picked. Section 0.6 of
   `ADMIN_UI_SCREENS_SPECIFICATION.md` restates the same palette in prose and
   the two agree.

   ─────────────────────────────────────────────────────────────────────────
   ⚠ NOTHING HERE CAN REACH A TENANT SCREEN, AND IT IS HELD BY TWO MECHANISMS
   ─────────────────────────────────────────────────────────────────────────

   This stylesheet already dresses 93 captured tenant screens across sixteen
   thousand lines, and Sections N, O and P each record a defect caused by a
   rule that matched more than its author expected. The console is a fourth
   surface with its own palette, its own rail width and its own top band, so an
   unscoped `.card` or `.btn-primary` here would repaint the till.

   Two mechanisms keep that from happening, and they cover different things:

   1. RULES THAT RESTYLE SHARED CHROME — `.app-shell`, `.app-sidebar`,
      `.app-main`, `.gsb-*` — are attribute-scoped under
      `.app-shell[data-surface='console']`. These are the only selectors in
      this section that name a class the rest of the file also styles, and
      every one of them carries the attribute. Removing `data-surface` from
      the layout returns the shell to its tenant appearance exactly.

   2. EVERYTHING ELSE IS A NEW CLASS UNDER A PREFIX THIS FILE DID NOT
      PREVIOUSLY CONTAIN. `console-` appears zero times in the sixteen
      thousand lines above; it is asserted by a probe rather than trusted, so
      a later section that reaches for the name is told rather than colliding
      quietly. A prefixed class cannot match an element no console template
      renders, which is a stronger guarantee than an ancestor selector — it
      holds even for markup moved outside the shell.

   The two print views are outside both mechanisms and need neither: they are
   self-contained documents that never load this file.

   ─────────────────────────────────────────────────────────────────────────
   WHAT IS DELIBERATELY INHERITED RATHER THAN RESTATED
   ─────────────────────────────────────────────────────────────────────────

   The shell grid, the RTL leading edge, the collapse behaviour, the skip link,
   the toast region and the focus ring all come from Sections A and G and are
   not touched. §0.6 asks the console to "deliberately reuse the tenant
   product's visual language so the two feel like one system"; reusing the
   layout primitives is the strongest form of that, and it is why the collapse
   toggle works on the console without a line of new script.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Q.1 · the canvas and the rail ──────────────────────────────────────── */

/*
 * §0.6 — "Canvas | Vertical gradient `#eef3f9 → #e8eef6`".
 *
 * On the SHELL rather than on `body`, because `body` is shared with every
 * tenant screen and Section A already paints it. `min-height: 100vh` is
 * inherited from `.app-shell`, so the gradient covers a short page too.
 */
.app-shell[data-surface='console'] {
  background: linear-gradient(180deg, #eef3f9, #e8eef6);
}

/*
 * §1.1 — "RTL-leading rail, 250 px". The tenant rail is 248px; the console's
 * own reference measures 250, and the two are different surfaces.
 *
 * ⚠ ABOVE THE DRAWER BREAKPOINT ONLY, AND THE FIRST DRAFT FORGOT THE QUERY.
 *
 * Section G turns the rail into an off-canvas drawer below 64rem by collapsing
 * the shell to a single column. An unconditional `grid-template-columns` here
 * is a more specific selector, so it won every time — the drawer stayed a
 * 250px column on a 360px phone and squeezed the top bar into what was left.
 * Found in the 360px capture, not by any assertion: nothing asserts a
 * viewport.
 *
 * Matching Section G's own breakpoint rather than choosing one keeps the two
 * halves of the behaviour from drifting apart.
 */
@media (min-width: 64rem) {
  .app-shell[data-surface='console'] {
    grid-template-columns: 250px 1fr;
  }
}

.app-shell[data-surface='console'] .app-sidebar {
  background: #fff;
  border-inline-start: 1px solid #e7eef6;
  border-inline-end: 0;
  padding: 14px 12px;
}

.app-shell[data-surface='console'] .gsb-head {
  padding: 2px 6px 14px;
}

.app-shell[data-surface='console'] .gsb-title {
  color: #94a3b8;
  font-size: 12.5px;
  font-weight: 600;
}

.app-shell[data-surface='console'] .gsb-toggle {
  background: #fff;
  block-size: 30px;
  border: 1px solid #e7eef6;
  border-radius: 9px;
  color: #64748b;
  inline-size: 30px;
}

.app-shell[data-surface='console'] .gsb-item {
  align-items: center;
  border-radius: 12px;
  display: flex;
  gap: 10px;
  justify-content: space-between;
  margin-block-end: 6px;
  padding: 7px 10px;
  /* The rail's own chip carries the colour; the row stays white until active,
     so the reference's single accent per row is preserved. */
  background: transparent;
}

.app-shell[data-surface='console'] .gsb-label {
  color: #334155;
  flex: 1;
  font-size: 13.5px;
  font-weight: 600;
  text-align: start;
}

/*
 * §1.1 — "one bespoke two-stop gradient chip (34 px, radius 11) per entry".
 *
 * The gradient stops arrive as `--gsb-from` / `--gsb-to` from the registry, so
 * the data lives in PHP and the paint lives here — the same split the tenant
 * rail already uses, which is why both rails share one blade contract.
 */
.app-shell[data-surface='console'] .gsb-swatch {
  align-items: center;
  background: linear-gradient(135deg, var(--gsb-from), var(--gsb-to));
  block-size: 34px;
  border-radius: 11px;
  box-shadow: 0 4px 10px rgb(15 40 80 / 15%);
  color: #fff;
  display: inline-flex;
  flex: 0 0 34px;
  inline-size: 34px;
  justify-content: center;
}

/*
 * The active row: `#f1f5f9` plus a 3px `#0ea5e9` rail on the RTL-LEADING edge.
 *
 * `inset-inline-start: -12px` rather than `right`, so the rail lands on the
 * correct side when the console is read in English. The reference hard-codes
 * `right: -12px` because it is Arabic-only; this build is not.
 */
.app-shell[data-surface='console'] .gsb-item[aria-current='page'] {
  background: #f1f5f9;
  position: relative;
}

.app-shell[data-surface='console'] .gsb-item[aria-current='page']::before {
  background: #0ea5e9;
  border-radius: 3px;
  content: '';
  inline-size: 3px;
  inset-block: 8px;
  inset-inline-start: -12px;
  position: absolute;
}

/* §1.1's separator, above the logout row. */
.app-shell[data-surface='console'] .gsb-logout {
  border-block-start: 1px solid #eef2f7;
  margin-block-start: 8px;
  padding-block-start: 8px;
}

.app-shell[data-surface='console'] .gsb-item--logout {
  inline-size: 100%;
  opacity: 0.72;
}

/* ── Q.2 · the top bar (§1.2) ───────────────────────────────────────────── */

.app-shell[data-surface='console'] .app-main {
  /* The band spans the full content column, so the page's own inset moves to
     `.console-page` and the main column keeps none of its own. */
  padding: 0;
}

.console-topbar {
  align-items: center;
  background: #fff;
  block-size: 60px;
  border-block-end: 1px solid #e9eff6;
  display: flex;
  justify-content: space-between;
  padding: 0 18px;
}

.console-brand {
  align-items: center;
  display: flex;
  gap: 10px;
}

.console-logo {
  align-items: center;
  background: linear-gradient(135deg, #0ea5e9, #075985);
  block-size: 36px;
  border-radius: 11px;
  color: #fff;
  display: inline-flex;
  inline-size: 36px;
  justify-content: center;
}

.console-brand-text b {
  color: #0b3a55;
  font-size: 15.5px;
}

.console-brand-text small {
  color: #94a3b8;
  display: block;
  font-size: 11px;
  font-weight: 600;
}

.console-top-actions {
  align-items: center;
  display: flex;
  gap: 8px;
}

.console-pill {
  align-items: center;
  background: none;
  block-size: 34px;
  border: 0;
  border-radius: 11px;
  display: inline-flex;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  gap: 7px;
  padding: 0 12px;
}

.console-pill--user { background: #eef4ff; color: #3b4f8a; }
.console-pill--lang { background: #eff6ff; color: #2563eb; cursor: pointer; }

.console-pill--out {
  background: #fdeaea;
  color: #dc2626;
  cursor: pointer;
  inline-size: 34px;
  justify-content: center;
  padding: 0;
}

/* ── Q.3 · the page header (§1.3) ───────────────────────────────────────── */

.console-page {
  padding: 16px 20px 24px;
}

.console-pagehead {
  align-items: flex-start;
  display: flex;
  gap: 16px;
  justify-content: space-between;
  margin-block-end: 16px;
}

.console-pagehead h1 {
  color: #0b5570;
  font-size: 19px;
  font-weight: 700;
  margin: 0;
}

.console-pagehead p {
  color: #7c8ea3;
  font-size: 12.5px;
  margin: 4px 0 0;
}

/*
 * §1.3 — the action row, ordered `العودة · PDF · Excel · <primary>`.
 *
 * ⚠ PLAIN `row`, AND THE FIRST DRAFT'S `row-reverse` WAS BACKWARDS.
 *
 * §1.3's order is a READING order, and in RTL reading starts at the right. The
 * templates emit `العودة` first, so normal flow already puts it on the right
 * and the primary action on the far left — which is exactly where `A03` and
 * `A06` photograph `إضافة باقة` and `مشترك جديد`.
 *
 * `row-reverse` inverted all four, putting `العودة` where the primary belongs.
 * Caught by comparing the capture with the reference rather than by any test:
 * both orders are valid HTML and every assertion still passed.
 */
.console-btns {
  align-items: center;
  display: flex;
  gap: 8px;
}

.console-btn {
  align-items: center;
  block-size: 40px;
  border: 1px solid transparent;
  border-radius: 11px;
  cursor: pointer;
  display: inline-flex;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  gap: 8px;
  justify-content: center;
  padding: 0 15px;
  text-decoration: none;
  white-space: nowrap;
}

.console-btn--primary { background: #0e5c8f; box-shadow: 0 6px 14px rgb(14 92 143 / 28%); color: #fff; }
.console-btn--purple { background: #7c3aed; box-shadow: 0 6px 14px rgb(124 58 237 / 25%); color: #fff; }
.console-btn--excel { background: #e9f8f0; color: #15803d; }
.console-btn--pdf { background: #fdeaea; color: #dc2626; }
.console-btn--ghost { background: #fff; border-color: #e4ebf3; color: #526277; }
.console-btn--cancel { background: #fff; border-color: #e4ebf3; color: #526277; }
.console-btn--danger { background: #e5262b; color: #fff; }
.console-btn--warn { background: #c2570b; color: #fff; }

/*
 * §ADM-004 Preconditions — the primary button is DISABLED, with a tooltip,
 * when no active package exists. Rendered as a `<span>` rather than a disabled
 * link, because a disabled anchor is not a thing HTML has; `aria-disabled`
 * carries the state and `pointer-events` carries the behaviour.
 */
.console-btn.is-disabled {
  cursor: not-allowed;
  filter: grayscale(0.55);
  opacity: 0.55;
  pointer-events: none;
}

/* ── Q.4 · cards, tables, pills (§0.6) ──────────────────────────────────── */

.console-card {
  background: #fff;
  border: 1px solid #eaf0f7;
  border-radius: 16px;
  box-shadow: 0 2px 10px rgb(16 48 86 / 4%);
  overflow: hidden;
}

.console-card-head {
  align-items: center;
  border-block-end: 1px solid #f0f4f9;
  display: flex;
  justify-content: space-between;
  padding: 14px 16px;
}

.console-card-head h2 {
  align-items: center;
  color: #0b3a55;
  display: flex;
  font-size: 14.5px;
  font-weight: 700;
  gap: 8px;
  margin: 0;
}

.console-hint {
  color: #94a3b8;
  font-size: 12px;
  font-weight: 600;
}

/*
 * A wide table must scroll INSIDE its card rather than widen the page — the
 * invariant Section F established for every table in this build, and the reason
 * `ContinuousWidthAuditTest` passes at 320px.
 */
.console-table-wrap {
  overflow-x: auto;
}

.console-table {
  border-collapse: collapse;
  inline-size: 100%;
}

.console-table th {
  background: #f7fafd;
  border-block-end: 1px solid #eaf0f7;
  color: #7c8ea3;
  font-size: 12px;
  font-weight: 700;
  padding: 11px 14px;
  text-align: start;
  white-space: nowrap;
}

.console-table td {
  border-block-end: 1px solid #f2f6fa;
  color: #243d57;
  font-size: 12.8px;
  padding: 11px 14px;
  text-align: start;
  white-space: nowrap;
}

/* §0.6 — "no zebra". The last rule is dropped so the card's own edge closes it. */
.console-table tbody tr:last-child td { border-block-end: 0; }

.console-sub { color: #94a3b8; font-size: 11.5px; }

.console-row-link { color: inherit; text-decoration: none; }
.console-row-link:hover strong { text-decoration: underline; }

.console-empty {
  color: #94a3b8;
  padding: 34px 14px;
  text-align: center;
  white-space: normal;
}

/* §0.6's five status pills, verbatim. */
.console-tag {
  border-radius: 999px;
  display: inline-block;
  font-size: 11.5px;
  font-weight: 700;
  padding: 3px 10px;
  white-space: nowrap;
}

.console-tag--on { background: #e8f8ef; color: #16794a; }
.console-tag--trial { background: #eef2ff; color: #4338ca; }
.console-tag--exp { background: #fdeaea; color: #b91c1c; }
.console-tag--off { background: #fef3e2; color: #b45309; }
.console-tag--soon { background: #fff7ed; color: #c2410c; }

/* §0.6 — "Row actions | 30×30 radius-9 icon buttons". */
.console-acts {
  display: flex;
  gap: 6px;
  justify-content: flex-start;
}

.console-act {
  align-items: center;
  block-size: 30px;
  border: 0;
  border-radius: 9px;
  cursor: pointer;
  display: inline-flex;
  flex: 0 0 30px;
  inline-size: 30px;
  justify-content: center;
  padding: 0;
}

.console-act--edit { background: #e8f1fe; color: #2563eb; }
.console-act--pause { background: #eef2f7; color: #475569; }
.console-act--del { background: #fdeaea; color: #dc2626; }
.console-act--key { background: #f5eefe; color: #7c3aed; }

/* ── Q.5 · the filter bar ───────────────────────────────────────────────── */

.console-filters {
  align-items: flex-end;
  border-block-end: 1px solid #f0f4f9;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 12px 14px;
}

.console-fi {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.console-fi label {
  color: #8ea0b5;
  font-size: 11.5px;
  font-weight: 700;
}

.console-inp {
  background: #fff;
  block-size: 38px;
  border: 1px solid #e4ebf3;
  border-radius: 10px;
  color: #24384f;
  font: inherit;
  font-size: 13px;
  min-inline-size: 150px;
  padding: 0 12px;
}

.console-inp--search { min-inline-size: 280px; }
.console-inp--ro { background: #eef2f7; color: #5b6b7f; }

/*
 * ⚠ NO NATIVE SPINNER ON A NUMBER FIELD, AND IT IS A COLLISION FIX RATHER THAN
 *   A TASTE ONE.
 *
 * `A04` renders `30`, `4`, `1`, `0` and `7` as plain right-aligned numbers with
 * no stepper. This build's number inputs carry `dir="ltr"` so the digits order
 * correctly, and Section Q right-aligns them so they sit where the reference
 * puts them — but Chrome draws the spin buttons at the RIGHT of the field too,
 * so the value and the arrows land on the same pixels. Reported as fields
 * overlapping, and it is: two things painted in one place.
 *
 * Removing the stepper resolves it and matches the reference. Nothing is lost —
 * every one of these fields is typed, not nudged, and each keeps its `min`,
 * `max` and `step`, so the keyboard arrows and the server rules still bound the
 * value.
 */
.console-inp[type='number']::-webkit-outer-spin-button,
.console-inp[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}

.console-inp[type='number'] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/*
 * The official Saudi Riyal sign, beside a label rather than inside its string.
 *
 * The catalogue holds `السعر`, not `السعر (ر.س)`, so the currency is a MARK the
 * template places — `SaudiRiyalSign::mark()` — and not three characters baked
 * into a translation. `.sar` (Section A) already sizes it in `em`, so it tracks
 * whatever text it sits next to: an 11.5px field label, a 12px column header or
 * a 12px KPI caption.
 */
.console-cur {
  margin-inline-start: 4px;
  opacity: 0.75;
}

/*
 * A VALUE WRAPPED `dir="ltr"` STILL SITS AT THE RTL EDGE.
 *
 * §0.6 wraps every numeral, date, code and phone in `dir="ltr"` so the DIGITS
 * order correctly. That is a bidi instruction, not an alignment one — and its
 * side effect is that the text aligns to the LTR start, i.e. the left of the
 * box, while the reference photographs `30`, `249.00`, `0551234567` and `naqaa`
 * all sitting at the RIGHT edge like every other field on the form.
 *
 * `text-align: end` inside an LTR element resolves to the right, which restores
 * the reference's alignment without touching the digit order the `dir` is
 * there for.
 */
.console-inp[dir='ltr'] { text-align: end; }

.console-inp--area {
  block-size: auto;
  min-inline-size: 0;
  padding: 9px 12px;
  resize: vertical;
}

.console-inp:focus-visible {
  border-color: #38bdf8;
  box-shadow: 0 0 0 3px rgb(56 189 248 / 18%);
  outline: 0;
}

/*
 * THE UNSCRIPTED SUBMIT, AND WHY IT DISAPPEARS WHEN SCRIPTING WORKS.
 *
 * `A03` and `A06` draw four and five filter dropdowns and NO apply control,
 * because the reference's own client submits on change. `console.js` reproduces
 * that — but the filter bar must still work without it, so the form keeps a
 * real submit button and the capture then showed a `بحث` button the reference
 * does not have.
 *
 * `console.js` marks the document as scripted on load, and this hides the
 * fallback under that mark. So a scripted browser matches the reference exactly
 * and an unscripted one keeps a working filter — rather than choosing one and
 * abandoning the other.
 *
 * Hidden with `display: none` rather than `visibility`, so it also leaves the
 * tab order: a control that cannot be reached should not be a tab stop.
 */
.console-filters-go { block-size: 38px; }

:root[data-console-js='1'] .console-filters-go { display: none; }

/* ── Q.6 · pagination ───────────────────────────────────────────────────── */

.console-pag {
  align-items: center;
  display: flex;
  gap: 12px;
  justify-content: space-between;
  padding: 12px 14px;
}

.console-pag .pager {
  display: flex;
  gap: 6px;
}

.console-pag .page-btn {
  align-items: center;
  background: #f4f7fb;
  block-size: 32px;
  border-radius: 9px;
  color: #64748b;
  display: inline-flex;
  font-size: 12.5px;
  font-weight: 700;
  justify-content: center;
  min-inline-size: 32px;
  text-decoration: none;
}

.console-pag .page-btn[aria-current='page'] { background: #0e5c8f; color: #fff; }
.console-pag .page-btn.is-disabled { opacity: 0.45; }

.console-pag-info {
  color: #94a3b8;
  font-size: 12px;
  font-weight: 600;
}

/* ── Q.7 · the dashboard (ADM-002) ──────────────────────────────────────── */

/*
 * Six tiles across, and `auto-fit` rather than a hard `repeat(6, 1fr)`.
 *
 * The reference is a 1680px mock-up and six 1fr columns at 360px give each tile
 * 44px of content. Section G's rule for this build is that every screen works
 * from 320px up, and `ContinuousWidthAuditTest` enforces it — so the track is
 * declared with a minimum and the row reflows to 3×2 and then 2×3 on the way
 * down. At the reference width it renders as six across, which is the
 * measurement that had to be matched.
 */
.console-kpis {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  margin-block-end: 14px;
}

.console-kpi {
  background: #fff;
  border: 1px solid #eaf0f7;
  border-radius: 16px;
  box-shadow: 0 2px 10px rgb(16 48 86 / 4%);
  color: inherit;
  display: block;
  padding: 14px;
  text-decoration: none;
}

.console-kpi--inert { cursor: default; }

.console-kpi-chip {
  align-items: center;
  block-size: 38px;
  border-radius: 12px;
  color: #fff;
  display: flex;
  inline-size: 38px;
  justify-content: center;
  margin-block-end: 10px;
}

.console-kpi-chip--indigo { background: linear-gradient(135deg, #6366f1, #4338ca); }
.console-kpi-chip--green { background: linear-gradient(135deg, #10b981, #059669); }
.console-kpi-chip--violet { background: linear-gradient(135deg, #a855f7, #9333ea); }
.console-kpi-chip--amber { background: linear-gradient(135deg, #f59e0b, #d97706); }
.console-kpi-chip--red { background: linear-gradient(135deg, #f43f5e, #e11d48); }
.console-kpi-chip--blue { background: linear-gradient(135deg, #38bdf8, #0284c7); }

.console-kpi-val {
  color: #12304c;
  display: block;
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.4px;
}

.console-kpi-lbl {
  color: #7c8ea3;
  display: block;
  font-size: 12px;
  font-weight: 600;
  margin-block-start: 3px;
}

.console-kpi-delta {
  border-radius: 7px;
  display: inline-block;
  font-size: 11.5px;
  font-weight: 700;
  margin-block-start: 7px;
  padding: 2px 8px;
}

.console-kpi-delta--up { background: #e8f8ef; color: #15803d; }
.console-kpi-delta--down { background: #fdeaea; color: #dc2626; }
.console-kpi-delta--flat { background: #eef2f7; color: #64748b; }

/*
 * The two bands. `A02` makes the first column of each the wider one — the
 * expiry table and the chart — and `minmax(0, …)` is what lets the inner table
 * scroll instead of forcing the track wider than its share.
 */
.console-band {
  display: grid;
  gap: 14px;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  margin-block-end: 14px;
}

@media (max-width: 68rem) {
  .console-band { grid-template-columns: minmax(0, 1fr); }
}

/* ── Q.8 · the chart, drawn in `div`s (§0.6 — no charting library) ──────── */

.console-bars {
  align-items: flex-end;
  block-size: 150px;
  display: flex;
  gap: 18px;
  padding: 16px 18px 0;
}

.console-bar {
  align-items: center;
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 8px;
  justify-content: flex-end;
  block-size: 100%;
}

.console-bar b { color: #0b3a55; font-size: 12px; }
.console-bar small { color: #8ea0b5; font-size: 11.5px; font-weight: 700; }

/*
 * `--h` is the only value the template computes, and it is a percentage of the
 * tallest bar. `min-block-size` keeps a zero month visible as a hairline rather
 * than as nothing at all — an absent bar reads as a missing month, which is the
 * one thing the zero-fill in `ConsoleDashboardQuery` exists to prevent.
 */
.console-bar-fill {
  background: linear-gradient(180deg, #38bdf8, #0284c7);
  block-size: var(--h, 0%);
  border-radius: 9px 9px 4px 4px;
  inline-size: 100%;
  max-inline-size: 46px;
  min-block-size: 3px;
}

/* ── Q.9 · modals (§1.5) ────────────────────────────────────────────────── */

/*
 * §1.5 — scrim `rgba(12,32,56,.42)`, card radius 18, header on a mint→azure
 * gradient, body on `#fbfdff`, footer with a full-width primary and a
 * fixed-width `إلغاء`.
 *
 * ⚠ THE OVERLAY IS `position: fixed` AND THE CARD SCROLLS INSIDE IT. A modal
 * taller than the viewport that scrolls the PAGE instead leaves the operator
 * unable to reach the save button on a laptop — which is exactly what `A07`'s
 * three panels do at 900px on a 768px-tall screen.
 */
.console-scrim {
  align-items: center;
  background: rgb(12 32 56 / 42%);
  display: flex;
  inset: 0;
  justify-content: center;
  padding: 16px;
  position: fixed;
  z-index: 60;
}

.console-modal {
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 30px 70px rgb(3 28 60 / 35%);
  display: flex;
  flex-direction: column;
  inline-size: 100%;
  overflow: hidden;

  /*
   * `100%` OF THE SCRIM'S PADDED BOX, NOT `94vh`.
   *
   * The scrim is `inset: 0` with 16px of padding, so `100%` is the viewport
   * less that padding — which is what the gap around the card is for. `94vh`
   * ignored the padding and could ask for 94vh + 32px on a short screen, i.e.
   * more room than exists.
   */
  max-block-size: 100%;
}

/*
 * ⚠ THE HEAD AND FOOT DO NOT SHRINK; THE BODY DOES. THIS IS THE FIX FOR AN
 *   UNREACHABLE SAVE BUTTON.
 *
 * A flex item defaults to `min-height: auto`, which refuses to shrink below its
 * CONTENT height. `.console-m-body` therefore stayed at its full natural height
 * however short the viewport was, pushed the footer past the card's clipped
 * edge, and `overflow: hidden` on the card cut it off. The operator could fill
 * `A04` in full and never reach `حفظ`.
 *
 * Reported from a ~900px browser pane. `ConsoleShotTest` never saw it because
 * it frames every modal at the reference's 1050px, where the tallest modal
 * still fits — a fixed-viewport camera cannot find a defect that only appears
 * at another size. `ConsoleGeometryProbeTest` now asserts the footer is
 * reachable at 1050, 900, 800 and 700.
 *
 * `min-block-size: 0` is the whole repair: it lets the body shrink, so its own
 * `overflow: auto` finally does something and the footer stays pinned inside
 * the card.
 */
.console-m-head,
.console-m-foot {
  flex: 0 0 auto;
}

/*
 * ⚠ THE `<form>` IS THE FLEX ITEM, NOT THE HEAD/BODY/FOOT — AND MISSING THIS
 *   IS WHY THE FIRST ATTEMPT AT THIS FIX CHANGED NOTHING.
 *
 * `A04` and `A07` wrap all three sections in a form, because the modal posts.
 * So `.console-modal`'s only flex child is that form; the head, body and foot
 * are the FORM's children and, without this rule, plain blocks in a box with no
 * bounded height. `min-block-size: 0` on the body therefore governed nothing,
 * the footer was laid out past the card's capped height, and `overflow: hidden`
 * painted it away — the operator saw a modal with no save button.
 *
 * `A09` has no form (it is read-only), which is exactly why it was the one
 * modal the first fix repaired. That asymmetry is what identified the cause.
 *
 * The form must therefore be BOTH: an item that may shrink (`flex: 1 1 auto`
 * with `min-block-size: 0`) and a column container for the three sections it
 * holds. `overflow: hidden` on it keeps the clipping boundary on the form
 * rather than on the card, so the body's own `overflow: auto` is what scrolls.
 */
.console-modal > form {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-block-size: 0;
  overflow: hidden;
}

/* The four specified widths, from each popup's own section. */
.console-modal--package { max-inline-size: 760px; }
.console-modal--subscriber { max-inline-size: 900px; }
.console-modal--license { max-inline-size: 560px; }
.console-modal--suspend { max-inline-size: 470px; }
.console-modal--confirm { max-inline-size: 440px; }

.console-m-head {
  background: linear-gradient(180deg, #eefaf4, #f2fbff);
  border-block-end: 1px solid #e9f2f8;
  padding: 16px;
  position: relative;
  text-align: center;
}

.console-m-head--danger { background: linear-gradient(180deg, #fff1f1, #fff7f7); }
.console-m-head--warn { background: linear-gradient(180deg, #fff7ed, #fffbf5); }

.console-m-head h2 {
  color: #0b3a55;
  font-size: 17px;
  font-weight: 800;
  margin: 0;
}

.console-m-head p {
  color: #7c94a8;
  font-size: 12px;
  margin: 3px 0 0;
}

/*
 * §1.5 — "✕, red square, RTL-TRAILING corner".
 *
 * Trailing, so `inset-inline-END`. The first draft used `inline-start`, which
 * in RTL is the right-hand side — the leading corner, next to the title — and
 * every reference modal photographs it on the left. Logical properties were the
 * right instinct and the wrong end of the axis.
 */
.console-m-close {
  align-items: center;
  background: #fdeaea;
  block-size: 34px;
  border-radius: 10px;
  color: #dc2626;
  display: inline-flex;
  inline-size: 34px;
  inset-block-start: 14px;
  inset-inline-end: 14px;
  justify-content: center;
  position: absolute;
}

.console-m-body {
  background: #fbfdff;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 12px;
  overflow: auto;
  padding: 16px;

  /* The repair. See the note on `.console-m-head, .console-m-foot` above. */
  min-block-size: 0;
}

.console-m-error {
  background: #fdeaea;
  border-radius: 10px;
  color: #b91c1c;
  font-size: 12.5px;
  font-weight: 600;
  margin: 0;
  padding: 10px 12px;
}

.console-panel {
  background: #f6f9fc;
  border: 1px solid #eef3f8;
  border-radius: 14px;
  padding: 13px;
}

.console-p-title {
  align-items: center;
  color: #5b7793;
  display: flex;
  font-size: 12.5px;
  font-weight: 800;
  gap: 8px;
  margin: 0 0 11px;
}

.console-grid2,
.console-grid3 {
  display: grid;
  gap: 10px;
}

/*
 * ⚠ FIXED TRACK COUNTS, NOT `auto-fit`.
 *
 * `repeat(auto-fit, minmax(210px, 1fr))` fits as many columns as the width
 * allows, and inside a 760px modal that is THREE — so `A04`'s two-column
 * `بيانات الباقة` panel rendered as three and every field landed one cell from
 * where the reference puts it. `auto-fit` is the right tool for a card grid
 * whose column count is free; it is the wrong one for a form whose column count
 * is part of the design.
 *
 * The narrow-viewport collapse is a media query instead, which is explicit
 * about where it happens rather than letting the track width decide.
 */
.console-grid2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.console-grid3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

@media (max-width: 40rem) {
  .console-grid2,
  .console-grid3 { grid-template-columns: minmax(0, 1fr); }
}

.console-f {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-inline-size: 0;
}

.console-f label {
  color: #7d92a8;
  font-size: 11.5px;
  font-weight: 700;
}

/* §0.4 — the required marker. Red, and `aria-hidden` in the template, because
   the field's own `required` attribute is what a screen reader announces. */
.console-f label b { color: #dc2626; }

.console-f .console-inp { inline-size: 100%; min-inline-size: 0; }

.console-f-err {
  color: #b91c1c;
  font-size: 11.5px;
  font-weight: 600;
}

.console-note {
  color: #8ea0b5;
  font-size: 12px;
  margin: 10px 2px 0;
}

/* §ADM-004 — the one-time password line. Loud, because it is shown once. */
.console-once {
  background: #fff7ed;
  border: 1px solid #fed7aa;
  border-radius: 12px;
  color: #9a3412;
  font-size: 13px;
  font-weight: 700;
  margin-block-end: 14px;
  padding: 12px 14px;
}

/* §ADM-003 `pkgFeatures[]` — the ten chips. */
.console-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.console-ck {
  align-items: center;
  background: #fff;
  border: 1px solid #e4ebf3;
  border-radius: 10px;
  color: #3c5570;
  cursor: pointer;
  display: inline-flex;
  font-size: 12.5px;
  font-weight: 600;
  gap: 7px;
  padding: 8px 11px;
}

/* The native box is hidden from sight but NOT from the accessibility tree or
   the keyboard: `opacity: 0` over the drawn box keeps it focusable and
   clickable, where `display: none` would make the chip unreachable by tab. */
.console-ck input {
  block-size: 16px;
  inline-size: 16px;
  margin: 0;
  opacity: 0;
  position: absolute;
}

.console-ck-box {
  align-items: center;
  block-size: 16px;
  border: 1.6px solid #cbd5e1;
  border-radius: 5px;
  color: transparent;
  display: inline-flex;
  flex: 0 0 16px;
  inline-size: 16px;
  justify-content: center;
}

/*
 * THE TICKED STATE IS DRIVEN BY `:checked`, NOT BY THE SERVER-RENDERED CLASS.
 *
 * `.is-on` is what the template stamps from the STORED chip set, and on its own
 * it is a photograph: clicking the box would flip the native input and change
 * nothing visible, because the class was decided before the page was sent. A
 * checkbox that does not appear to respond is the "شكلي" control this work is
 * not allowed to ship, and it would have been invisible to every server-side
 * test — the markup is correct either way.
 *
 * So the live state is the sibling selector, which needs no script:
 * `input:checked ~ .console-ck-box` fills the box, and `:has(input:checked)`
 * tints the chip around it. `.is-on` is kept alongside as the first-paint value
 * and as the fallback for a browser without `:has()`, where the chip tint is
 * then merely stale while the BOX — the thing that says ticked — is always live.
 */
.console-ck.is-on,
.console-ck:has(input:checked) {
  background: #f0f9ff;
  border-color: #bae6fd;
  color: #0c4a6e;
}

.console-ck:not(:has(input:checked)).is-on {
  background: #fff;
  border-color: #e4ebf3;
  color: #3c5570;
}

.console-ck.is-on .console-ck-box,
.console-ck input:checked ~ .console-ck-box {
  background: #0ea5e9;
  border-color: #0ea5e9;
  color: #fff;
}

.console-ck input:not(:checked) ~ .console-ck-box {
  background: #fff;
  border-color: #cbd5e1;
  color: transparent;
}

.console-ck input:disabled ~ .console-ck-label { opacity: 0.75; }

.console-ck:focus-within {
  box-shadow: 0 0 0 3px rgb(56 189 248 / 18%);
}

/*
 * §1.5 — "a full-width primary and a fixed-width `إلغاء`".
 *
 * Plain `row`, for the reason `.console-btns` records: the templates emit the
 * primary first, so RTL flow puts it on the right and `إلغاء` on the left,
 * which is what `A04`, `A05`, `A07`, `A08` and `A09` all photograph. The first
 * draft's `row-reverse` swapped them.
 */
.console-m-foot {
  background: #fff;
  border-block-start: 1px solid #eef3f8;
  display: flex;
  gap: 10px;
  padding: 14px 16px;
}

/* §1.5 — "a full-width primary and a fixed-width `إلغاء`". */
.console-m-foot .console-btn { block-size: 44px; flex: 1; }
.console-m-foot .console-btn--cancel { flex: 0 0 120px; }
.console-m-foot form { display: flex; flex: 1; }
.console-m-foot form .console-btn { inline-size: 100%; }

/* ── Q.10 · the confirmation overlays (§1.5 rows 2 and 3) ───────────────── */

/*
 * Built on `<details>`, exactly as the shared `<x-confirm>` is. The summary is
 * the row's own icon button, so the marker has to go: without this the browser
 * draws a disclosure triangle inside a 30px square.
 */
.console-confirm > summary { list-style: none; }
.console-confirm > summary::-webkit-details-marker { display: none; }
.console-confirm > summary::marker { content: ''; }

/*
 * The confirmation overlays get the same treatment as `.console-m-body`, for
 * the same reason. They are short today, but `#suspendOverlay` carries a
 * textarea and a four-line body, and "short today" is how the package modal
 * looked at 1050px too.
 */
.console-c-body {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow: auto;
  padding: 22px 20px;
  text-align: center;
}

.console-c-ic {
  align-items: center;
  background: #fdeaea;
  block-size: 56px;
  border-radius: 18px;
  color: #dc2626;
  display: flex;
  inline-size: 56px;
  justify-content: center;
  margin: 0 auto 14px;
}

.console-c-ic--warn { background: #fef3e2; color: #b45309; }

.console-c-body h3 {
  color: #16304a;
  font-size: 15px;
  font-weight: 800;
  margin: 0 0 8px;
}

.console-c-body p {
  color: #7c8ea3;
  font-size: 13px;
  line-height: 1.7;
  margin: 0;
}

/* The in-use / still-active guard line. Darker than the body copy because it is
   the sentence that explains why the button below it will not work. */
.console-c-guard {
  color: #b45309 !important;
  font-weight: 600;
  margin-block-start: 8px !important;
}

.console-f--reason {
  margin-block-start: 14px;
  text-align: start;
}

.console-c-body .console-inp { inline-size: 100%; }

/* A disabled confirm still has to look refused rather than merely quiet. */
.console-m-foot .console-btn:disabled {
  cursor: not-allowed;
  filter: grayscale(0.5);
  opacity: 0.5;
}

/* ── Q.11 · the licence modal (A09) ─────────────────────────────────────── */

.console-keyrow {
  align-items: center;
  display: flex;
  gap: 8px;
  margin-block-end: 12px;
}

/*
 * §ADM-004 Popup 3 — "the key in a monospace, letter-spaced field".
 *
 * `--font-numeric` is this build's own monospace stack, declared in Section A,
 * so the key renders in the same face every other machine-readable value does
 * rather than in a second stack that would have to be maintained here.
 */
.console-inp--key {
  flex: 1;
  font-family: var(--font-numeric);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 2px;
  text-align: center;
}

.console-confirm--inline { flex: 1; }
.console-confirm--inline > summary { list-style: none; }
.console-confirm--inline > summary.console-btn { inline-size: 100%; }

/* ── Q.12 · narrow viewports ────────────────────────────────────────────── */

/*
 * Section G's rule applies to the console as it does to every other screen:
 * the product works from 320px up, and `ContinuousWidthAuditTest` measures it.
 * The reference is a 1680px mock-up and says nothing about phones, so this is
 * the console honouring an existing invariant rather than inventing a design.
 */
@media (max-width: 48rem) {
  .console-page { padding: 12px 12px 20px; }

  .console-pagehead {
    align-items: stretch;
    flex-direction: column;
  }

  .console-btns { flex-wrap: wrap; }

  /*
   * The brand must SHRINK, not clip. At 360px the 15.5px lockup plus three
   * pills overflows, and a flex item defaults to `min-width: auto`, so the
   * title was cut off mid-word rather than wrapping or ellipsing. `min-inline-
   * size: 0` is what lets it give way; the ellipsis is what makes the giving
   * way legible.
   */
  .console-topbar { gap: 8px; padding: 0 12px; }
  .console-brand { min-inline-size: 0; }
  .console-brand-text { min-inline-size: 0; }
  .console-brand-text small { display: none; }

  .console-brand-text b {
    display: block;
    font-size: 13px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .console-logo { flex: 0 0 30px; block-size: 30px; inline-size: 30px; }

  /* The result line reads as a sentence or it reads as nothing. */
  .console-pag { flex-wrap: wrap; }
  .console-pag-info { flex: 1 0 100%; }

  .console-inp,
  .console-inp--search { min-inline-size: 0; }

  .console-fi { flex: 1 1 160px; }

  .console-m-foot { flex-direction: column-reverse; }
  .console-m-foot .console-btn--cancel { flex: 1; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   S — THE SHARED COMPONENT LAYER THE RECONSTRUCTION LEFT UNFINISHED
   ═══════════════════════════════════════════════════════════════════════════

   WHY THIS SECTION EXISTS, AND WHY IT IS NOT TWENTY MORE SCREEN SECTIONS.

   A coverage census over every `class="…"` token in `resources/views` against
   this stylesheet found three screens rendering essentially unstyled — the
   register the operator sees first on each:

       subscriptions            4 of 21 classes had any rule   (19%)
       customer-custom-prices   5 of 14                        (35%)
       hotels-companies         7 of  7, but every one of them scoped
                                to ANOTHER screen by `[data-screen]`

   The cause was never missing design. It was VOCABULARY. Those screens were
   built with private class names — `subs-table`, `ccp-panel`, `subs-tabs` —
   while the shared layer above styles `data-table`, `panel` and `settings-card`.
   Two names for one component, so the component had no rules.

   The fix is therefore the one the brief asks for in its own words: "أنشئ أو
   أصلح SHARED DESIGN FOUNDATION … ثم اجعل جميع صفحات المشروع تستخدم نفس
   المكونات". The screens move ONTO the shared vocabulary; this section adds
   only what the shared layer genuinely lacked — tabs, badge tones, the search
   field, the pagination bar, the toolbar and the row-action buttons.

   ─────────────────────────────────────────────────────────────────────────
   THE ACCENT IS A PER-SCREEN CUSTOM PROPERTY, BECAUSE THE REFERENCE'S IS
   ─────────────────────────────────────────────────────────────────────────

   The reference gives every screen its own accent: `hotels-companies.css`
   declares `--primary:#7c3aed`, `products.css` paints its add button
   `linear-gradient(90deg,#14b8a6,#0d9488)`, `expenses.css` uses rose.

   ⚠ AND THE ACCENT IS *NOT* THE SIDEBAR CHIP GRADIENT. That was the first
   hypothesis and it is wrong — checked against four screens before any code
   was written: the registry gives `products` orange while its reference button
   is teal, and `services` teal while its reference button is indigo. The
   accents are authored per screen, so they are TRANSCRIBED per screen below,
   each from that screen's own reference stylesheet.

   Every component here reads `var(--screen-accent, …)`, so a screen opts in by
   declaring two properties and inherits the whole system.

   ⚠ SPECIFICITY IS DELIBERATELY THE LOWEST THAT WORKS. Every selector is a
   single class. This section sits at the END of the file, so it supersedes the
   generic Phase-B rules above it — and every `[…data-screen="x"]` rule, at one
   more attribute of specificity, still wins over it. That is what keeps the
   screens already measured against their captures exactly where they are.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── S.1  the screen accents, transcribed ────────────────────────────────── */

/*
 * Each pair is read off the named reference file. The deep stop is the second
 * stop of that screen's own gradient, so a button can paint the gradient the
 * reference paints without a third token.
 */
/*
 * ⚠ THE WASHES ARE SPELLED OUT RATHER THAN DERIVED. `color-mix()` would compute
 * them from the accent in one line, and it is deliberately not used: it would
 * make the whole component layer depend on a colour function this build has
 * never shipped, for a saving of two declarations per screen. The reference
 * writes its own tints out literally too.
 */
.app-main[data-screen="subscriptions"] {
  --screen-accent: #0284c7;
  --screen-accent-deep: #0369a1;
  --screen-accent-grad-from: #0d7fa5;
  --screen-accent-wash: rgb(2 132 199 / 8%);
  --screen-accent-wash-2: rgb(2 132 199 / 12%);
  /* `subscriptions.html`: `.btn-primary{…border-radius:50px}` — a full pill. */
  --screen-btn-radius: 50px;
}

/*
 * ⚠ VIOLET, NOT THE PINK ITS SIDEBAR CHIP PAINTS. The registry gives this
 * screen `#ec4899 → #db2777`, and `customer-custom-prices.css` declares
 * `--primary:#7c3aed` — the same violet `hotels-companies.css` uses. Taken
 * from the reference file, which is the second time the chip-gradient
 * hypothesis would have produced the wrong colour.
 */
.app-main[data-screen="customer-custom-prices"] {
  --screen-accent: #7c3aed;
  --screen-accent-deep: #6d28d9;
  --screen-accent-grad-from: #8b5cf6;
  --screen-accent-wash: rgb(124 58 237 / 8%);
  --screen-accent-wash-2: rgb(124 58 237 / 12%);
}

.app-main[data-screen="hotels-companies"] {
  --screen-accent: #7c3aed;
  --screen-accent-deep: #6d28d9;
  --screen-accent-grad-from: #8b5cf6;
  --screen-accent-wash: rgb(124 58 237 / 8%);
  --screen-accent-wash-2: rgb(124 58 237 / 12%);
}

/*
 * ⚠ VIOLET, AND ITS SIDEBAR CHIP IS ORANGE. `credit-invoices.css` paints
 * `.btn-filter`, `.invoices-table thead th` and `.page-num.active` from
 * `#8b5cf6 → #7c3aed`; the registry gives this screen `#f97316 → #ea580c`.
 * Third screen where the chip-gradient hypothesis would have been wrong, which
 * is why every accent in this block is read off a file rather than derived.
 */
.app-main[data-screen="credit-invoices"] {
  --screen-accent: #8b5cf6;
  --screen-accent-deep: #7c3aed;
  --screen-accent-grad-from: #8b5cf6;
  --screen-accent-wash: rgb(139 92 246 / 6%);
  --screen-accent-wash-2: rgb(139 92 246 / 10%);
  --screen-btn-radius: 8px;
}

/*
 * `consumption-receipts.css` declares its palette as named properties —
 * `--cr-primary:#0d9488`, `--cr-primary-2:#14b8a6`, `--cr-border:#ccfbf1` —
 * and this is the one screen whose reference accent DOES agree with its
 * sidebar chip. Transcribed all the same, for the same reason.
 */
.app-main[data-screen="consumption-receipts"] {
  --screen-accent: #0d9488;
  --screen-accent-deep: #0d9488;
  --screen-accent-grad-from: #14b8a6;
  --screen-accent-wash: rgb(13 148 136 / 8%);
  --screen-accent-wash-2: rgb(13 148 136 / 12%);
  --screen-btn-radius: 9px;
}

/* ── S.2  the tab strip ──────────────────────────────────────────────────── */

/*
 * `subscriptions.html`: `.tabs{display:flex;gap:6px;border-bottom:2px solid
 * #d5e3ec}` with the active tab carrying a 3px accent underline that overlaps
 * the strip's own by `margin-bottom:-2px`.
 *
 * Rendered here as a `<nav>` of links rather than the reference's buttons —
 * the tabs are URL states in this build, which is what lets them work without
 * JavaScript. `aria-current="page"` is the active selector: the attribute the
 * markup already sets is the state, so there is no class to keep in sync.
 */
/*
 * ⚠ NO `overflow-x` HERE, AND THE REASON IS A SCROLLBAR THE CAPTURE FOUND.
 *
 * The reference declares `overflow-x:auto` on this strip so a phone can scroll
 * a long tab row. Copied verbatim it produced a defect the reference does not
 * have: the active tab carries `margin-block-end:-2px` to overlap the strip's
 * own border, which puts 2px of the child OUTSIDE the container's content box —
 * and once one axis is not `visible`, CSS computes the other to `auto` too. So
 * the strip grew a permanent vertical scrollbar, visible at the inline start of
 * the 1440px capture as a pair of arrows beside the tabs.
 *
 * The scroller therefore lives in the narrow-viewport rule below, which is the
 * only place it was ever for.
 */
.tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  border-block-end: 2px solid var(--ui-line);
  margin-block-end: var(--ui-gap);
}

.tabs a {
  padding: 10px 16px;
  border: 0;
  background: transparent;
  color: var(--ui-ink-soft);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  border-start-start-radius: 8px;
  border-start-end-radius: 8px;
  border-block-end: 3px solid transparent;
  margin-block-end: -2px;
  flex-shrink: 0;
  white-space: nowrap;
}

.tabs a:hover {
  color: var(--screen-accent, var(--ui-blue));
}

.tabs a[aria-current="page"] {
  background: linear-gradient(180deg, var(--screen-accent-wash-2, rgb(37 99 235 / 10%)), transparent);
  color: var(--screen-accent, var(--ui-blue));
  border-block-end-color: var(--screen-accent, var(--ui-blue));
}

/* ── S.3  the toolbar and the search field ───────────────────────────────── */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  justify-content: space-between;
  margin-block-end: var(--ui-gap);
}

.toolbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/*
 * `.search-wrap{background:#fff;border:1.5px solid #d5e3ec;border-radius:10px;
 * padding:0 12px;flex:1;min-width:200px;max-width:360px}`.
 *
 * The magnifier is a child, so the input keeps the whole remaining width and
 * the caret never lands under the glyph — the failure a background-image
 * magnifier produces in RTL, where the icon sits where typing starts.
 */
.search-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  background: #fff;
  border: 1.5px solid var(--ui-line);
  border-radius: 10px;
  padding: 0 12px;
  flex: 1 1 auto;
  min-inline-size: 200px;
  max-inline-size: 360px;
  transition: border-color .2s, box-shadow .2s;
}

.search-wrap:focus-within {
  border-color: var(--screen-accent, var(--ui-blue));
  box-shadow: 0 0 0 3px var(--screen-accent-wash-2, rgb(37 99 235 / 10%));
}

.search-wrap svg {
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
  color: var(--ui-ink-muted);
}

.search-wrap input {
  flex: 1 1 auto;
  border: 0;
  outline: none;
  background: transparent;
  padding: 10px 0;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  color: var(--ui-ink);
  min-inline-size: 0;
}

/* ── S.4  badge tones ────────────────────────────────────────────────────── */

/*
 * The five the reference declares, each a tint of its own hue over white:
 *   .badge-act    rgba(16,185,129,.12) / #047857
 *   .badge-near   rgba(217,119,6,.12)  / #b45309
 *   .badge-exp    rgba(224,45,60,.1)   / #e02d3c
 *   .badge-closed rgba(72,103,123,.12) / #48677b
 *   .badge-none   rgba(88,122,144,.15) / #48677b
 *
 * Verified against `016_Subscriptions.png` by sampling the composited pixels —
 * #e7f6f0, #fdf3e3, #fdecee and #e8eff4 — which is what those four tints
 * resolve to over white. The capture and the live stylesheet agree exactly.
 */
.pill--active {
  background: rgb(16 185 129 / 12%);
  color: #047857;
}

.pill--near {
  background: rgb(217 119 6 / 12%);
  color: #b45309;
}

.pill--expired {
  background: rgb(224 45 60 / 10%);
  color: #e02d3c;
}

.pill--closed {
  background: rgb(72 103 123 / 12%);
  color: #48677b;
}

.pill--none {
  background: rgb(88 122 144 / 15%);
  color: #48677b;
}

/* ── S.5  the table card ─────────────────────────────────────────────────── */

/*
 * `.table-container{background:#fff;border:1px solid #d5e3ec;border-radius:12px}`
 * — the card the table sits in. The shared `.data-table` above already carries
 * the reference's 12px/14px cell padding and tinted head; what it never had was
 * a container, so a table on an unstyled screen bled to the page edge.
 *
 * `overflow-x: auto` here rather than on the page is §المرحلة 8's requirement
 * and the reference's own behaviour: a wide register scrolls inside its card.
 */
.table-container {
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: 12px;
  overflow-x: auto;
  margin-block-end: var(--ui-gap);
}

.table-container .data-table {
  box-shadow: none;
  border-radius: 0;
  margin-block-end: 0;
}

/*
 * The head tint is the accent at 8%, so a screen's table announces the same
 * hue as its buttons — the relationship the reference keeps on every screen.
 */
.table-container .data-table thead tr {
  background: var(--screen-accent-wash, rgb(37 99 235 / 6%));
  border-block-end: 2px solid var(--ui-line);
}

.table-container .data-table th {
  background: transparent;
  color: var(--screen-accent, var(--ui-blue));
  font-size: 12px;
  font-weight: 700;
}

.table-container .data-table td {
  color: var(--ui-ink-soft);
  border-block-start: 0;
  border-block-end: 1px solid #eaf1f6;
}

.table-container .data-table tbody tr:last-child td {
  border-block-end: 0;
}

.table-container .data-table tbody tr:hover {
  background: var(--screen-accent-wash, rgb(37 99 235 / 6%));
}

/* ── S.6  the pagination bar ─────────────────────────────────────────────── */

.pagination-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 4px;
  flex-wrap: wrap;
}

.pagination-info {
  font-size: 13px;
  color: var(--ui-ink-soft);
  font-weight: 700;
}

.pagination-controls {
  display: flex;
  align-items: center;
  gap: 4px;
}

/*
 * `.page-btn{width:34px;height:34px;border:1.5px solid #d5e3ec;border-radius:8px}`.
 * The shared rule above sized it by padding; the reference sizes it as a
 * square, which is what keeps a row of pager controls on one baseline whether
 * a label is one glyph or four.
 */
.pagination-controls .page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 34px;
  block-size: 34px;
  padding: 0 8px;
  border: 1.5px solid var(--ui-line);
  border-radius: 8px;
  background: #fff;
  color: var(--ui-ink-soft);
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.pagination-controls .page-btn:hover {
  border-color: var(--screen-accent, var(--ui-blue));
  color: var(--screen-accent, var(--ui-blue));
  background: var(--screen-accent-wash, rgb(37 99 235 / 6%));
}

.pagination-controls .page-btn[aria-current="page"] {
  background: linear-gradient(90deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  border-color: transparent;
  color: #fff;
}

/* ── S.7  buttons the shared layer never had ─────────────────────────────── */

/*
 * The reference's primary is a 90deg gradient on a 50px pill, not the flat
 * rectangle the generic `.btn-primary` above paints. A screen that declares an
 * accent gets the gradient; one that does not keeps the flat blue, so nothing
 * already measured against a capture moves.
 */
.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}

.app-main .btn-primary {
  background: linear-gradient(90deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  border-radius: var(--screen-btn-radius, var(--ui-radius-sm));
}

.btn-secondary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 16px;
  border: 1.5px solid var(--ui-line);
  border-radius: var(--ui-radius-pill);
  background: #fff;
  color: var(--ui-ink-soft);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}

.btn-secondary:hover {
  border-color: var(--screen-accent, var(--ui-blue));
  color: var(--screen-accent, var(--ui-blue));
}

/*
 * The row-action button. `.action-btn-label{padding:8px 14px;min-height:36px;
 * border-radius:10px;font-size:12px;font-weight:700}` — the reference's row
 * controls are labelled pills, not bare glyphs.
 */
.action-btn-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 8px 14px;
  min-block-size: 36px;
  border: 1px solid var(--ui-line);
  border-radius: 10px;
  background: #fff;
  color: var(--ui-ink-soft);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 1px 2px rgb(0 0 0 / 6%);
}

.action-btn-label:hover {
  border-color: var(--screen-accent, var(--ui-blue));
  color: var(--screen-accent, var(--ui-blue));
}

.action-btn-label--accent {
  background: linear-gradient(90deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  border-color: transparent;
  color: #fff;
}

.action-btn-label--accent:hover {
  color: #fff;
  opacity: .93;
}

.actions-cell {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}

/* ── S.8  the screen header's title block and action cluster ─────────────── */

/*
 * `.app-title{font-size:18px;font-weight:800}` over
 * `.app-subtitle{font-size:12px;color:#48677b;font-weight:600}`. The generic
 * `.screen-header` above is already the white card; this is the arrangement
 * INSIDE it, which nothing had defined.
 */
.screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}

.header-titles {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.header-titles h1,
.header-titles h2 {
  font-size: 18px;
  font-weight: 800;
  color: var(--ui-ink);
  margin: 0;
  line-height: 1.3;
}

.header-titles p {
  font-size: 12px;
  font-weight: 600;
  color: var(--ui-ink-soft);
  margin: 0;
}

.screen-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* ── S.9  the filter row ─────────────────────────────────────────────────── */

/*
 * `.filter-field{background:#f2f7fa;border:1.5px solid #d5e3ec;border-radius:
 * 10px;padding:5px 10px}` with its label inside the box beside the control —
 * the arrangement `016_Subscriptions.png` shows for `الحالة`.
 */
.filter-field {
  display: flex;
  align-items: center;
  gap: 6px;
  /* `#f2f7fa` is the reference`s own literal. `--ui-canvas` is the PAGE
     ground and a different role — reusing it here painted the field the same
     grey as the canvas behind it. */
  background: #f2f7fa;
  border: 1.5px solid var(--ui-line);
  border-radius: 10px;
  padding: 5px 10px;
  min-inline-size: 0;
  transition: border-color .18s, box-shadow .18s;
}

.filter-field:focus-within {
  border-color: var(--screen-accent, var(--ui-blue));
  box-shadow: 0 0 0 3px var(--screen-accent-wash-2, rgb(37 99 235 / 10%));
}

.filter-field > label {
  font-size: 12px;
  color: var(--ui-ink-soft);
  font-weight: 700;
  white-space: nowrap;
  flex-shrink: 0;
  margin: 0;
}

.filter-field input,
.filter-field select {
  border: 0;
  background: transparent;
  outline: none;
  color: var(--ui-ink);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  padding: 2px;
  min-inline-size: 0;
}

/* ── S.10  the empty state inside a card ─────────────────────────────────── */

/*
 * `.empty-state{text-align:center;padding:48px;color:#587a90}`. The generic
 * rule above already paints a card; a table's own empty row must NOT, or the
 * screen shows a card inside a card.
 */
.table-container .empty-state,
.data-table .empty-state,
td.empty-state {
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  padding: 48px 16px;
  text-align: center;
  color: var(--ui-ink-muted);
}

/* ── S.11  narrow viewports ──────────────────────────────────────────────── */

/*
 * §المرحلة 8. The reference collapses the toolbar to a column, lets the search
 * field take the full width and keeps the table scrolling inside its own card
 * rather than widening the page — asserted by the width sweep, not eyeballed.
 */
@media (max-width: 48rem) {
  .toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .search-wrap {
    max-inline-size: none;
  }

  .screen-header {
    align-items: flex-start;
  }

  .screen-header-actions,
  .toolbar-actions {
    inline-size: 100%;
  }

  .pagination-bar {
    justify-content: center;
  }

  .filter-field {
    inline-size: 100%;
  }

  .filter-field input,
  .filter-field select {
    inline-size: 100%;
  }

  /*
   * The tab strip becomes a scroller here and only here — see the note on
   * `.tabs`. `overflow-y: hidden` is explicit rather than inherited from the
   * `auto`-computation, so the 2px overlap is clipped instead of growing a
   * second scrollbar.
   */
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
  }
}

/* ── S.12  the header badge ──────────────────────────────────────────────── */

/*
 * `.logo-icon{width:34px;height:34px;border-radius:10px;background:linear-
 * gradient(135deg,…)}` — the reference opens every header bar with a rounded
 * square carrying the screen's glyph, and it is the one place the accent
 * appears as a FILL rather than as a tint.
 *
 * Two screens had already built this privately, each under its own
 * `[data-screen]` scope; both still win over this rule, so neither moves. What
 * changes is that a third screen no longer has to copy them to have a header.
 */
.header-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  min-inline-size: 0;
}

.logo-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  flex-shrink: 0;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  color: #fff;
}

.logo-icon svg {
  inline-size: 18px;
  block-size: 18px;
}

/* ── S.13  the global back control ───────────────────────────────────────── */

/*
 * ⚠ THE SHELL RENDERS THIS ON EVERY SCREEN AND ONLY SIX SCREENS EVER STYLED IT.
 *
 * `shell/back.blade.php` emits `<a class="back-btn">` for all 46 operator
 * screens. A grep for the class found rules under exactly six `[data-screen]`
 * scopes and no unscoped rule at all, so on the other forty it painted as a
 * default browser link — underlined, link-blue, floating above the content.
 * That is what the first capture of the subscriptions register showed, and it
 * was never a subscriptions defect: it was forty screens sharing one gap.
 *
 * The metrics are `subscriptions.html`'s: `.back-btn{padding:8px 18px;
 * border-radius:50px;background:linear-gradient(90deg,#0d7fa5,#0369a1);
 * color:#fff;font-size:14px;font-weight:700;box-shadow:0 2px 8px …}`. That is
 * the variant for a control on a LIGHT ground, which is where the shell puts
 * it; `hotels-companies.css`'s translucent-white variant is for the one that
 * sits on a navy header bar, and the screens that have such a bar already
 * declare it themselves — at three points of specificity, so they still win.
 */
.back-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  align-self: flex-start;
  /*
   * ⚠ `border-box`, AND THE CAPTURE IS WHY. Six screens position this control
   * absolutely inside their own header band and fix its height with
   * `block-size: 35px`; none of them declares a box model. Under the default
   * `content-box` the 8px block padding added here made the pill 51px tall and
   * it hung 7px below the 52px navy band on Hotels & Companies — measured, not
   * guessed: sampling the capture found the pill's `#3957b8` still painting at
   * y=55 where the band had already given way to white.
   */
  box-sizing: border-box;
  padding: 8px 18px;
  border: 0;
  border-radius: var(--ui-radius-pill);
  background: linear-gradient(90deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 2px 8px rgb(15 23 42 / 18%);
  margin-block-end: var(--ui-gap);
}

.back-btn:hover {
  opacity: .95;
}

/* ── S.14  an empty state inside a card is not a second card ─────────────── */

/*
 * The reference's is plain text: `.empty-state{text-align:center;padding:48px;
 * color:#587a90}` — no ground, no border, no shadow. The shared rule above
 * paints a card because on several screens the empty state IS the whole panel.
 *
 * When it is nested inside one, that produces a card inside a card, which the
 * Custom Prices capture showed twice on one screen: a white panel holding a
 * second white panel holding one line of grey text. Two classes of specificity,
 * so a screen's own `[data-screen]` rule still wins over it.
 */
.settings-card .empty-state,
.panel .empty-state,
.table-container .empty-state,
.card .empty-state {
  background: transparent;
  box-shadow: none;
  border: 0;
  border-radius: 0;
  padding: 48px 16px;
}

/* ── S.15  the control base ──────────────────────────────────────────────── */

/*
 * ⚠ THE STYLESHEET HAD NO ELEMENT-LEVEL RULE FOR A FORM CONTROL. NOT ONE.
 *
 * Every input, select and textarea in this build is styled through a class —
 * `.field input`, `.console-inp`, `.search-input` and a few dozen per-screen
 * rules. A control that belongs to none of them fell through to the browser's
 * own widget: a hairline 1px box with 2px of padding and a system font. 39 view
 * files declare a `<select>`, and the Roles capture shows what that looks like
 * beside a finished card — a native dropdown floating on a designed page.
 *
 * That is the brief's "Inputs غير موحدة" precisely, and it is one rule, not
 * forty screens.
 *
 * ⚠ WRITTEN INSIDE `:where()`, WHICH CONTRIBUTES NO SPECIFICITY AT ALL. The
 * whole block therefore weighs (0,0,1) — a bare element selector — so EVERY
 * existing rule in this file outranks it, including the single-class ones
 * beside it in this section. It can only reach a control nothing else styles,
 * which is the entire intent: a floor, not an override.
 *
 * The metrics are the reference's own, and they agree across its screens:
 * `1.5px` border, `10px` radius, `8px 12px` padding, `13px`, weight `700`
 * (`hotels-companies.css` `.date-input`, `subscriptions.html` `.filter-input`).
 *
 * Checkboxes and radios are excluded by the type list — padding on a checkbox
 * does not make it tidier, it makes it a rectangle.
 */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="hidden"]),
       select,
       textarea) {
  box-sizing: border-box;
  border: 1.5px solid var(--ui-line);
  border-radius: 10px;
  padding: 8px 12px;
  background: #fff;
  color: var(--ui-ink);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  max-inline-size: 100%;
}

:where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="hidden"]),
       select,
       textarea):focus {
  outline: none;
  border-color: var(--screen-accent, var(--ui-blue));
  box-shadow: 0 0 0 3px var(--screen-accent-wash-2, rgb(37 99 235 / 10%));
}

:where(input:disabled, select:disabled, textarea:disabled) {
  background: var(--ui-active, #f1f5f9);
  cursor: not-allowed;
}

:where(select) {
  cursor: pointer;
}

/*
 * A control the shell drops inside a `search-wrap` or a `filter-field` must
 * NOT carry the floor's border — the wrapper is the field there, and two
 * borders would draw a box inside a box. Those wrappers already say so in
 * their own rules above; this only restates it for the bare-element floor,
 * which they do not outrank on the properties they never set.
 */
.search-wrap :where(input, select, textarea),
.filter-field :where(input, select, textarea) {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 8px 0;
}

.search-wrap :where(input, select, textarea):focus,
.filter-field :where(input, select, textarea):focus {
  box-shadow: none;
}

/* ── S.16  three components the reference names and this layer lacked ────── */

/*
 * ⚠ THESE ARE THE REFERENCE'S OWN CLASS NAMES, NOT NEW ONES. Before writing a
 * single rule the two remaining raw screens were read at source, and both
 * `credit-invoices.css` and `consumption-receipts.css` declare `.page-num`,
 * `.page-size-selector` and `.table-scroll-hint` beside the `toolbar` /
 * `search-wrap` / `table-container` / `pagination-bar` vocabulary this layer
 * already carries. Adding a fourth synonym for a numbered pager button is
 * exactly the duplicate-vocabulary defect this whole section exists to undo.
 */

/*
 * `.page-num{min-width:34px;height:34px;padding:0 4px;border:1.5px solid
 * #e2e8f0;border-radius:8px}` with `.page-num.active` painting the accent
 * gradient. This build marks the current page with `aria-current`, which the
 * markup already sets, so the attribute is the selector and there is no class
 * to keep in step.
 */
.page-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-inline-size: 34px;
  block-size: 34px;
  padding: 0 6px;
  border: 1.5px solid var(--ui-line);
  border-radius: 8px;
  background: #fff;
  color: var(--ui-ink-soft);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

.page-num:hover {
  border-color: var(--screen-accent, var(--ui-blue));
  color: var(--screen-accent, var(--ui-blue));
  background: var(--screen-accent-wash, rgb(37 99 235 / 6%));
}

.page-num[aria-current="page"],
.page-num.is-current {
  background: linear-gradient(90deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  border-color: transparent;
  color: #fff;
}

/*
 * `.page-size-selector{display:flex;align-items:center;gap:8px;font-size:13px;
 * font-weight:700;color:#64748b}` — the `عرض [50] في الصفحة` cluster, which is
 * a labelled row and not a field.
 */
.page-size-selector {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--ui-ink-soft);
  flex-wrap: wrap;
}

.page-size-selector label,
.page-size-selector span {
  margin: 0;
  white-space: nowrap;
}

.page-size-selector select {
  padding: 6px 10px;
  border-radius: 8px;
}

/*
 * ⚠ `.table-scroll-hint{display:none}` — THE REFERENCE HIDES IT AT WIDTH.
 *
 * Both remaining screens render `اسحب الجدول يميناً ويساراً` unconditionally,
 * and the reference declares it hidden and shows it only where the table
 * actually overflows. A permanent instruction to do something the viewport
 * does not require is noise on the screen it is meant to help.
 */
.table-scroll-hint {
  display: none;
  margin: 0;
  padding-block: 8px;
  font-size: 12px;
  font-weight: 700;
  text-align: center;
  color: var(--ui-ink-muted);
}

@media (max-width: 62rem) {
  .table-scroll-hint {
    display: block;
  }
}

/* ── S.17  the permission matrix ─────────────────────────────────────────── */

/*
 * ⚠ FOUND BY MEASUREMENT, NOT BY LOOKING. The screen census this campaign ran
 * counts CLASS NAMES, and by that measure Roles read 28% — which was easy to
 * dismiss, because the screen has few classes and the part a capture shows
 * first (the picker and the empty state) was already fine. The matrix renders
 * only after a user is chosen, so no capture in `storage/qa` contains it.
 *
 * A second census, counting ELEMENTS reached by any rule rather than names,
 * put the area at 2 of 7 and named exactly what was unreached: `selector-form`,
 * `perm-group`, `btn-group-toggle`, `perm-grid`, `perm-check`. Every control on
 * the working screen.
 *
 * ⚠ THE GROUP ACCENTS ARE THE REFERENCE'S OWN, AND THE MAPPING IS EXACT.
 * `roles.css` declares `.perm-group--sales{border-top-color:#7c3aed}`,
 * `--customers` teal, `--admin` orange, `--reports` blue; `RolesController`
 * numbers its four panels 1..4 as sales, customers, admin, reports. Same four
 * groups, same order, so `data-group` carries the accent with no new markup.
 *
 * ⚠ AND `perm-check` MEANS SOMETHING ELSE IN THE REFERENCE. There it is the
 * 18px checkbox BOX and `.perm-item` is the row; here it has been the row since
 * Phase 22. The values below are the reference's `.perm-item` values, applied to
 * the element that plays that part — copying by name would have styled the row
 * as an 18px square.
 */

/* The four panels sit two-up, as `.permissions-grid` does. */
#permissionForm {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}

.perm-group {
  position: relative;
  display: block;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 1px solid var(--ui-line);
  border-block-start: 3px solid var(--perm-accent, var(--ui-blue));
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
  transition: box-shadow .2s;
}

.perm-group:hover {
  box-shadow: 0 3px 12px rgb(0 0 0 / 8%);
}

.perm-group[data-group="1"] { --perm-accent: #7c3aed; --perm-wash: rgb(124 58 237 / 6%); }
.perm-group[data-group="2"] { --perm-accent: #0d9488; --perm-wash: rgb(13 148 136 / 6%); }
.perm-group[data-group="3"] { --perm-accent: #ea580c; --perm-wash: rgb(234 88 12 / 6%); }
.perm-group[data-group="4"] { --perm-accent: #2563eb; --perm-wash: rgb(37 99 235 / 6%); }

/*
 * `<legend>` STAYS A `<legend>`. The reference builds its header from a `<div>`
 * and the toggle button inside it; reproducing that here would mean moving the
 * legend out of first position, which is invalid HTML and costs the fieldset
 * its accessible name. So the legend IS the header bar — a legend accepts a
 * background and padding — and the button is placed into it.
 */
.perm-group > legend {
  box-sizing: border-box;
  inline-size: 100%;
  margin: 0;
  padding: 12px 16px;
  padding-inline-end: 96px;
  background: #fafafa;
  border-block-end: 1px solid #f1f5f9;
  border-start-start-radius: 11px;
  border-start-end-radius: 11px;
  color: var(--ui-ink);
  font-size: 13px;
  font-weight: 700;
}

.perm-group > .btn-group-toggle {
  position: absolute;
  inset-block-start: 10px;
  inset-inline-end: 12px;
  padding: 4px 10px;
  border: 1px solid var(--ui-line);
  border-radius: var(--ui-radius-pill);
  background: transparent;
  color: var(--ui-ink-soft);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: all .2s;
}

.perm-group > .btn-group-toggle:hover {
  background: var(--perm-wash, rgb(37 99 235 / 6%));
  border-color: var(--perm-accent, var(--ui-blue));
  color: var(--perm-accent, var(--ui-blue));
}

/* `.perm-items{padding:6px 10px 10px;display:flex;flex-direction:column;gap:1px}` */
.perm-grid {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 10px 10px;
}

/* `.perm-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:9px}` */
.perm-check {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 9px;
  cursor: pointer;
  transition: background .15s;
}

.perm-check:hover {
  background: var(--perm-wash, rgb(37 99 235 / 6%));
}

.perm-check input[type="checkbox"] {
  inline-size: 17px;
  block-size: 17px;
  flex-shrink: 0;
  border-radius: 4px;
  cursor: pointer;
  accent-color: var(--perm-accent, var(--ui-blue));
}

.perm-check span {
  flex: 1 1 auto;
  font-size: 13px;
  font-weight: 700;
  color: var(--ui-ink-soft);
  user-select: none;
}

/*
 * `.save-bar{display:flex;justify-content:flex-end}` with the save control a
 * 50px pill at `12px 36px`. The button is a direct child of the form, so it
 * spans the two panel columns rather than sitting inside one.
 */
#permissionForm > .btn-save {
  grid-column: 1 / -1;
  justify-self: end;
  padding: 12px 36px;
  border-radius: var(--ui-radius-pill);
}

/* `.user-selector-card` — the picker above the matrix. */
.selector-form {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 18px 22px;
  border: 1px solid var(--ui-line);
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
  margin-block-end: var(--ui-gap);
}

.selector-form label {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--ui-ink-soft);
  white-space: nowrap;
}

.selector-form select {
  flex: 1 1 200px;
  min-inline-size: 200px;
  padding: 11px 14px;
  border-radius: 12px;
  background: #f8fafc;
}

@media (max-width: 62rem) {
  #permissionForm {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── S.18  the box model, and why this rule has to exist at all ──────────── */

/*
 * ⚠ THIS STYLESHEET HAS NO GLOBAL `box-sizing` RESET. It declares
 * `box-sizing: border-box` 126 times instead, once per component, which means
 * every component author has had to remember it — and the components added in
 * this section did not.
 *
 * The cost was measured, not guessed. `ContinuousWidthAuditTest` swept 528
 * width measurements and returned 20 defects, all of them the same shape:
 *
 *   /customer-custom-prices @360px — page overflows by 4px
 *   /customer-custom-prices @360px — span.search-wrap w=343 escapes 19px
 *   /credit-invoices @320px — input#dateFrom, input#dateTo, the filter button
 *   /credit-invoices @768px — the filter button still off-screen
 *
 * A `search-wrap` stretched to the column width by `align-items: stretch` and
 * then adding 24px of padding and 3px of border OUTSIDE that width is 27px
 * wider than its parent, on every screen that adopted it. The desktop captures
 * never showed it because there is slack at 1440px.
 *
 * ⚠ AND THE `min-inline-size` HAD TO GO WITH IT. `.search-wrap` carries
 * `min-inline-size: 200px` so it holds a usable field on a desktop toolbar; at
 * 320px that is a floor the flex item cannot shrink past, so the row overflows
 * whatever the box model says. Both halves were needed — fixing only the box
 * model left `/credit-invoices` still overflowing at 320.
 */
.search-wrap,
.filter-field,
.filter-bar,
.toolbar,
.table-container,
.btn-primary,
.btn-save,
.btn-outline,
.btn-cancel,
.btn-secondary,
.btn-secondary-sm,
.action-btn-label,
.pagination-bar,
.page-size-selector,
.selector-form,
.perm-group,
.perm-check,
.tabs a {
  box-sizing: border-box;
  max-inline-size: 100%;
}

/*
 * A date input has an intrinsic minimum width that ignores the flex basis, so
 * it needs an explicit floor of zero or it pushes its own field off the edge —
 * which is exactly what `input#dateFrom` did at 320, 360 and 414.
 */
.filter-field input,
.filter-field select,
.search-wrap input,
.search-wrap select {
  min-inline-size: 0;
}

/*
 * ⚠ THE FIRST VERSION OF THE RULE BELOW WAS `\.filter-bar > *, .toolbar > *
 * { inline-size: 100% }` AND IT WAS MUCH WORSE THAN THE DEFECT IT FIXED.
 *
 * `.filter-bar` is not only this section`s: the reports screens have used it
 * since Phase 35, with their own row of fixed-width date fields. Forcing every
 * CHILD of a non-wrapping flex row to the full container width makes the row N
 * times the container, and the sweep said so in numbers — `/reports/types`
 * went to `scrollWidth 2657 > innerWidth 360`, `/reports/all-invoices` to 2076,
 * six report screens in all, plus `/users` and `/users/sessions` where a button
 * became a full-width block that overflowed by its own padding.
 *
 * 20 defects became 146. A blanket child selector inside a media query reaches
 * every screen that ever adopted the parent class, which is the whole point of
 * a shared vocabulary and the whole danger of styling it by position.
 *
 * So the rule names the two components this section owns, and nothing else in
 * the row is touched.
 */
@media (max-width: 48rem) {
  .toolbar > .search-wrap,
  .filter-bar > .search-wrap,
  .toolbar > .filter-field,
  .filter-bar > .filter-field {
    inline-size: 100%;
    min-inline-size: 0;
  }

  .filter-bar {
    flex-wrap: wrap;
  }
}

/* ── S.19  the offers picker and its empty states ────────────────────────── */

/*
 * GAP 1 OF THE FINAL CLOSURE. The element census put this area at 40 of 59 —
 * the lowest of any screen after Roles — and named what was unreached: the
 * whole price-line picker inside the product-offer modal, plus the index's two
 * empty states, its badge and its table wrapper.
 *
 * ⚠ EVERY VALUE BELOW IS TRANSCRIBED FROM `offers.css`, AND THE COMPONENTS ARE
 * ITS OWN. Our names predate this campaign and map one-to-one onto the
 * reference's, so nothing is renamed and no synonym is added:
 *
 *     offers-picker          → po-products-section
 *     offers-picker-head     → po-products-header
 *     offers-picker-count    → po-selected-count
 *     offers-picker-search   → po-search-wrap input
 *     offers-picker-list     → po-products-list
 *     offers-pill            → badge
 *     offers-table-wrap      → table-wrap
 *
 * The accent is `--accent:#6366f1` / `--accent2:#8b5cf6` from that file's own
 * `:root` — indigo, and this time it DOES agree with the sidebar chip, which
 * three other screens did not.
 */
.app-main[data-screen="offers"] {
  --screen-accent: #6366f1;
  --screen-accent-deep: #8b5cf6;
  --screen-accent-grad-from: #6366f1;
  --screen-accent-wash: rgb(99 102 241 / 8%);
  --screen-accent-wash-2: rgb(99 102 241 / 12%);
}

/* `.table-wrap{background:var(--surf);border:1px solid var(--bdr);border-radius:12px}` */
.offers-table-wrap {
  box-sizing: border-box;
  max-inline-size: 100%;
  overflow-x: auto;
  background: var(--ui-surface);
  border: 1px solid var(--ui-line);
  border-radius: 12px;
  box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
  margin-block-end: var(--ui-gap);
}

/* `.badge{padding:3px 10px;border-radius:50px;font-size:11px;font-weight:700}` */
.offers-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: var(--ui-radius-pill);
  background: var(--screen-accent-wash-2, rgb(99 102 241 / 12%));
  color: var(--screen-accent, var(--ui-blue));
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.offers-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 8px;
  margin-block-end: var(--ui-gap);
}

/*
 * `.empty-state` here is the reference's COMPOSITION — a column with a 72px
 * gradient tile above a heading and a line of prose. The shared `.empty-state`
 * above paints a card, which is right where the empty state IS the panel; this
 * is that same card with the reference's interior.
 */
.empty-state .empty-icon,
.empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 72px;
  block-size: 72px;
  margin-inline: auto;
  margin-block-end: 16px;
  border-radius: 20px;
  background: linear-gradient(135deg, var(--screen-accent-wash-2, rgb(99 102 241 / 10%)), var(--screen-accent-wash, rgb(139 92 246 / 8%)));
  color: var(--screen-accent, var(--ui-blue));
}

.empty-icon svg {
  inline-size: 32px;
  block-size: 32px;
}

.empty-state h3 {
  margin: 0 0 6px;
  font-size: 16px;
  font-weight: 700;
  color: var(--ui-ink);
}

.empty-state p {
  margin: 0;
  font-size: 13px;
  font-weight: 400;
  color: var(--ui-ink-muted);
}

/* ── the price-line picker ───────────────────────────────────────────────── */

/* `.po-products-section{border:1.5px solid var(--bdr);border-radius:10px}` */
.offers-picker {
  box-sizing: border-box;
  max-inline-size: 100%;
  border: 1.5px solid var(--ui-line);
  border-radius: 10px;
  overflow: hidden;
  background: #fff;
}

/* `.po-products-header{padding:10px 14px;background:linear-gradient(180deg,#fafbfc,#f8fafc)}` */
.offers-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  background: linear-gradient(180deg, #fafbfc, #f8fafc);
  border-block-end: 1px solid var(--ui-line);
}

.offers-picker-head .field-label {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--ui-ink-soft);
}

/* `.po-selected-count` — an accent pill, so the count reads as a state. */
.offers-picker-count {
  padding: 3px 10px;
  border-radius: var(--ui-radius-pill);
  background: var(--screen-accent-wash-2, rgb(99 102 241 / 10%));
  color: var(--screen-accent, var(--ui-blue));
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

/*
 * `.po-search-wrap{border-bottom:1px solid var(--bdr);padding:8px 12px}` with a
 * borderless input inside. Ours IS the input, so it carries the row: the
 * bottom rule and the padding belong to it, and the zero-specificity control
 * floor is overridden here rather than fought.
 */
.offers-picker-search {
  box-sizing: border-box;
  inline-size: 100%;
  border: 0;
  border-block-end: 1px solid var(--ui-line);
  border-radius: 0;
  padding: 10px 12px;
  background: #fff;
  font-size: 13px;
  font-weight: 700;
  color: var(--ui-ink);
}

.offers-picker-search:focus {
  outline: none;
  border-block-end-color: var(--screen-accent, var(--ui-blue));
  box-shadow: none;
}

/* `.po-products-list{max-height:280px;overflow-y:auto;background:#fafbfc}` */
.offers-picker-list {
  max-block-size: 280px;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  background: #fafbfc;
}

.offers-picker-product {
  list-style: none;
  padding: 10px 12px;
  border-block-end: 1px solid var(--ui-line);
  background: #fff;
}

.offers-picker-product:last-child {
  border-block-end: 0;
}

.offers-picker-product > label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 13px;
  color: var(--ui-ink);
}

.offers-picker-product > ul {
  margin: 6px 0 0;
  padding: 0;
  padding-inline-start: 26px;
  list-style: none;
}

.offers-picker-line {
  list-style: none;
  border-radius: 8px;
}

.offers-picker-line > label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ui-ink-soft);
  transition: background .15s;
}

.offers-picker-line > label:hover {
  background: var(--screen-accent-wash, rgb(99 102 241 / 6%));
}

.offers-picker-line input[type="checkbox"],
.offers-picker-product input[type="checkbox"] {
  inline-size: 15px;
  block-size: 15px;
  flex-shrink: 0;
  cursor: pointer;
  accent-color: var(--screen-accent, var(--ui-blue));
}

.offers-picker-price {
  margin-inline-start: auto;
  font-size: 12px;
  font-weight: 700;
  color: var(--ui-ink-soft);
}

/*
 * The picker's own empty — `.empty-state.small{padding:24px}` in the reference.
 * It sits INSIDE the bordered section, so it must not paint a card of its own;
 * the nesting rule in S.14 already says that for a card, and this says it for
 * the picker.
 */
.offers-picker .offers-empty {
  margin: 0;
  padding: 24px 16px;
  background: transparent;
  box-shadow: none;
  text-align: center;
  font-size: 13px;
  color: var(--ui-ink-muted);
}

/* ── S.20  SCR-023, the deferred settlement screen ───────────────────────── */

/*
 * GAP 2 OF THE FINAL CLOSURE, AND THE EVIDENCE PROBLEM IT WAS BLOCKED ON IS
 * SOLVED RATHER THAN WORKED AROUND.
 *
 * ⚠ THE PREVIOUS PHASE'S PREMISE WAS TRUE OF THE SCREENSHOT AND FALSE OF THE
 * REFERENCE. `payments/show.blade.php` opens by recording that "THE WORKING
 * SCREEN WAS NEVER CAPTURED" — `051_Payment.png` shows it stuck in its error
 * state, two of nine controls visible — and concludes that its layout is
 * reconstructed from strings rather than from a picture. That was correct
 * about the 95 frozen captures, and this campaign repeated it as a blocked gap.
 *
 * But the screen's own docblock cites `payment.html:32`, `:61`, `:107`, `:114`
 * and `:248-251`. Those line numbers come from a FILE, and the file is still
 * served: `learntech.alrabtplus.com/screens/payment/payment.html`, with
 * `payment.css` and `payment.js` beside it. Fetched, they carry the complete
 * working state — the summary card, the amount field with its fill button, the
 * method group, the mixed section, the after-payment figure, the confirm
 * button, the history table and the fully-paid panel.
 *
 * So nothing below is composed from other screens and nothing is invented.
 * Every value is transcribed from `payment.css`, and the class names map onto
 * it one-to-one — one of them, `payment-method-btn`, is already the
 * reference's own name in this markup.
 *
 * ⚠ THE LOADING AND ERROR STATES ARE DELIBERATELY NOT REPRODUCED, AND THE
 * REFERENCE'S OWN SCRIPT IS THE REASON. `payment.js` toggles `#loadingState`
 * and `#errorState` around a `fetch` and `#btnRetry` re-fires it. This build
 * renders the screen on the server: there is no request to fail and none to
 * retry, so a retry button here would be a control that cannot do anything —
 * the broken affordance this build refuses on principle. Validation failures
 * surface through the shared toast the layout already renders, which is this
 * architecture's error channel and is styled already.
 */
.pay-shell {
  --screen-accent: #8b5cf6;
  --screen-accent-deep: #7c3aed;
  --screen-accent-grad-from: #8b5cf6;
  --screen-accent-wash: rgb(139 92 246 / 6%);
  --screen-accent-wash-2: rgb(139 92 246 / 10%);

  display: flex;
  flex-direction: column;
  min-block-size: 100vh;
  background: var(--ui-canvas, #f1f5f9);
}

/* `.header-bar{min-height:52px;background:#fff;border-bottom:1px solid #e2e8f0}` */
.pay-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  box-sizing: border-box;
  min-block-size: 52px;
  padding: 8px 14px;
  background: #fff;
  border-block-end: 1px solid var(--ui-line);
  box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
  flex-shrink: 0;
}

/* `.header-title{font-size:16px;font-weight:700;color:#1e293b}` */
.pay-title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--ui-ink);
}

/*
 * The back control keeps the shared pill from S.13 — it is the same control the
 * shell draws on every other screen — but this screen renders it INSIDE the
 * bar, so it must not carry the block margin the in-flow version needs.
 */
.pay-topbar .pay-back {
  margin-block-end: 0;
}

/* `.main-content{padding:16px 14px 20px;display:flex;flex-direction:column;gap:16px}` */
.pay-shell > .pay-panel {
  margin-inline: auto;
  inline-size: calc(100% - 28px);
  max-inline-size: 980px;
}

/* `.card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:20px}` */
.pay-panel {
  box-sizing: border-box;
  background: #fff;
  border: 1px solid var(--ui-line);
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
  margin-block: 16px 0;
}

.pay-shell > .pay-panel:last-of-type {
  margin-block-end: 20px;
}

/* `.card-title{font-size:16px;font-weight:700;margin-bottom:16px;padding-bottom:12px;border-bottom:2px solid #f1f5f9}` */
.pay-panel > h2 {
  margin: 0 0 16px;
  padding-block-end: 12px;
  border-block-end: 2px solid #f1f5f9;
  font-size: 16px;
  font-weight: 700;
  color: var(--ui-ink);
}

/*
 * `.info-grid` over `.info-item` / `.info-label` / `.info-value`. Ours is a
 * `<dl>`, which is the right element for label-and-value pairs and is what the
 * recovered markup already used; the grid is laid on the list itself, so each
 * `dt`+`dd` reads as one item without a wrapper element per pair.
 */
.pay-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 6px 16px;
  margin: 0;
}

.pay-figures dt {
  align-self: end;
  font-size: 12px;
  font-weight: 700;
  color: var(--ui-ink-soft);
}

.pay-figures dd {
  margin: 0 0 10px;
  font-size: 14px;
  font-weight: 700;
  color: var(--ui-ink);
}

/*
 * `.amount-value{font-size:18px}` with `.total{color:#8b5cf6}`,
 * `.paid{color:#10b981}` and `.remaining{color:#f59e0b}`. The three money rows
 * are the reference's `amounts-grid`; here they are the last pairs of the same
 * list, so the treatment lands on the values themselves.
 */
.pay-figures dd.num {
  font-size: 18px;
}

.pay-figures dd.pay-remaining {
  color: #f59e0b;
}

/* `.form-label{display:block;font-size:14px;font-weight:700;margin-bottom:8px}` */
.pay-form > label {
  display: block;
  margin-block-end: 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--ui-ink);
}

/* `.form-input{width:100%;padding:12px 14px;border:1.5px solid #e2e8f0;border-radius:10px}` */
.pay-form > input[type="number"] {
  box-sizing: border-box;
  inline-size: 100%;
  margin-block-end: 10px;
  padding: 12px 14px;
  border: 1.5px solid var(--ui-line);
  border-radius: 10px;
  font-size: 14px;
  font-weight: 700;
  color: var(--ui-ink);
}

/* `.btn-pay-full{padding:12px 18px;background:rgba(139,92,246,.1);border:1.5px solid rgba(139,92,246,.3)}` */
.pay-full-btn {
  box-sizing: border-box;
  margin-block-end: 20px;
  padding: 12px 18px;
  border: 1.5px solid rgb(139 92 246 / 30%);
  border-radius: 10px;
  background: var(--screen-accent-wash-2, rgb(139 92 246 / 10%));
  color: var(--screen-accent, var(--ui-blue));
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

.pay-full-btn:hover {
  background: rgb(139 92 246 / 18%);
}

/* `.payment-methods{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:10px}` */
.pay-methods {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 10px;
  margin: 0 0 20px;
  padding: 0;
  border: 0;
}

.pay-methods > legend {
  margin-block-end: 8px;
  padding: 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--ui-ink);
}

/*
 * `.payment-method-btn` — THE REFERENCE'S OWN CLASS NAME, and it was already in
 * this markup: `2px solid #e2e8f0` at rest, violet on hover, violet-tinted when
 * chosen.
 *
 * ⚠ THE CHOSEN STATE IS READ FROM THE DOM, NOT FROM A CLASS. The reference
 * toggles `.selected` from script. Here the control is a `<label>` wrapping a
 * radio, so the state already exists in the markup and `:has()` reads it —
 * nothing to keep in step, and the group still shows its selection with
 * scripting off. `:has()` is already used elsewhere in this stylesheet.
 */
.payment-method-btn {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 10px;
  border: 2px solid var(--ui-line);
  border-radius: 10px;
  background: #fff;
  color: var(--ui-ink-soft);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: all .2s;
}

.payment-method-btn:hover {
  border-color: var(--screen-accent, var(--ui-blue));
  background: var(--screen-accent-wash, rgb(139 92 246 / 5%));
}

.payment-method-btn:has(input:checked) {
  border-color: var(--screen-accent, var(--ui-blue));
  background: var(--screen-accent-wash-2, rgb(139 92 246 / 10%));
  color: var(--screen-accent, var(--ui-blue));
}

.payment-method-btn:focus-within {
  outline: 2px solid var(--screen-accent, var(--ui-blue));
  outline-offset: 2px;
}

/* `.mixed-payment-section{margin-top:14px;padding:12px;background:#f8fafc;border:1.5px dashed #e2e8f0;border-radius:12px}` */
.pay-mixed {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 6px 10px;
  align-items: end;
  box-sizing: border-box;
  margin-block: 14px 20px;
  padding: 12px;
  background: #f8fafc;
  border: 1.5px dashed var(--ui-line);
  border-radius: 12px;
}

.pay-mixed label {
  font-size: 12px;
  font-weight: 700;
  color: var(--ui-ink-soft);
}

.pay-mixed input {
  box-sizing: border-box;
  inline-size: 100%;
}

/* `.remaining-after-payment` with `.remaining-label` and `.remaining-value`. */
.pay-after {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  margin: 0 0 20px;
  padding: 16px;
  border: 2px solid rgb(245 158 11 / 25%);
  border-radius: 12px;
  background: linear-gradient(135deg, rgb(245 158 11 / 8%), rgb(251 191 36 / 8%));
  color: #92400e;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
}

.pay-after .num {
  font-size: 22px;
  font-weight: 900;
  color: #f59e0b;
}

/* `.btn-primary.btn-large{width:100%;padding:14px 24px;font-size:15px}` */
.pay-confirm {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  inline-size: 100%;
  padding: 14px 24px;
  border: 0;
  border-radius: 10px;
  background: linear-gradient(90deg, var(--screen-accent-grad-from, var(--ui-blue)), var(--screen-accent-deep, var(--ui-blue)));
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 2px 8px rgb(139 92 246 / 25%);
  transition: all .2s;
}

.pay-confirm:hover {
  opacity: .9;
}

.pay-confirm:disabled {
  opacity: .5;
  cursor: not-allowed;
}

/*
 * `.fully-paid-message` — the settled state is a panel of its own in the
 * reference, not a line of text under the summary.
 */
.pay-done {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 40px 20px;
  border: 2px solid rgb(16 185 129 / 25%);
  background: linear-gradient(135deg, rgb(16 185 129 / 5%), rgb(5 150 105 / 5%));
  color: #059669;
  font-size: 18px;
  font-weight: 700;
  text-align: center;
}

/* `.payments-table` — the history register, in this screen's own accent. */
.pay-history-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.pay-history-table thead tr {
  background: var(--screen-accent-wash, rgb(139 92 246 / 6%));
  border-block-end: 2px solid var(--ui-line);
}

.pay-history-table th {
  padding: 12px 14px;
  text-align: start;
  font-size: 12px;
  font-weight: 700;
  color: var(--screen-accent, var(--ui-blue));
  white-space: nowrap;
}

.pay-history-table td {
  padding: 12px 14px;
  text-align: start;
  color: var(--ui-ink-soft);
  border-block-end: 1px solid #f1f5f9;
}

.pay-history-table tbody tr:last-child td {
  border-block-end: 0;
}

.pay-history-table tbody tr:hover {
  background: var(--screen-accent-wash, rgb(139 92 246 / 4%));
}

/* `.empty-history{padding:40px 20px;color:#94a3b8;text-align:center}` */
.pay-empty {
  margin: 0;
  padding: 40px 20px;
  color: var(--ui-ink-muted);
  font-size: 13px;
  text-align: center;
}

@media (max-width: 48rem) {
  .pay-panel {
    padding: 16px;
  }

  .pay-figures {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SECTION T — THE BRANCH MODULE
   ═══════════════════════════════════════════════════════════════════════════

   AUTHORED, not transcribed, and it is the only section here that is. Every
   other screen in this stylesheet reproduces a captured reference; the audited
   product had no branch model at all (`DATABASE_BLUEPRINT.md` §2.3, "There is
   no branch key"), so there is nothing to reproduce.

   So this section INVENTS NO COMPONENTS. It does exactly two things: it gives
   the branches screen an accent so the shared component layer paints it like
   every other screen, and it lays out the one genuinely new element — the
   branch bar in the shell.

   ⚠ LOGICAL PROPERTIES ONLY. `margin-left/right`, `padding-left/right`,
   `border-left:/right:` and `text-align: left/right` are banned build-wide and
   `VisualVerificationTest` enforces it: this application is Arabic-first RTL,
   and a physical direction is a bug that only shows up in one language.
   ────────────────────────────────────────────────────────────────────────── */

/* T.1 — the screen accent. Teal, matching the sidebar entry's own gradient
   (`#0d9488` → `#0f766e`) so the rail swatch and the screen agree. */
.app-main[data-screen="branches"] {
  --screen-accent: #0d9488;
  --screen-accent-deep: #0f766e;
  --screen-accent-grad-from: #14b8a6;
  --screen-accent-wash: rgb(13 148 136 / 8%);
  --screen-accent-wash-2: rgb(13 148 136 / 12%);
  --screen-btn-radius: 10px;
}

/* T.2 — the branch chip.

   ⚠ THIS WAS A BANNER AND IT WAS WRONG. The first version was a bordered,
   filled, full-width band with a visible label and two text buttons. It read
   as a notice rather than as navigation, and on the dashboard it was worse
   than that: `.dash-header` carries `margin-block-start: -36px`, so the header
   rode up over the band and the two overlapped on the screen every operator
   lands on.

   What replaced it is a CHIP — one line, header-height, no fill of its own on
   the container, and small enough to sit beside the greeting. The container is
   now only a flex row: every pixel of the visual belongs to `.branch-bar-chip`
   or to the `pill--none` inside it, so the same partial reads correctly both
   inside `.dash-header` and standing alone above an ordinary screen.

   ⚠ `justify-content: flex-start`, AND IT WAS `flex-end` FOR ONE RUN OF THE
   SUITE. THAT RUN FOUND 96 DEFECTS.

   Ending the bar at the inline edge looked tidier and put the chip opposite the
   screen's own `.screen-header`. What it actually did was park the chip on top
   of the global back control: `AllScreensAuditTest` reported
   "controls overlap: a#btnBack.back-btn × select#branchSelector.branch-bar-select"
   on twenty-four screens — every `/reports/*`, every `/settings/*`, `/invoices`
   and `/expenses` — at 1024, 1200, 1440 and 1920.

   The reason is that those screens pin the back control out of flow at the
   PHYSICAL left: `.app-main[data-screen="reports"] > .back-btn` is
   `position: absolute; top: 9px; left: 12px`, and settings, offers and
   zatca-settings say the same with `left: 16px`. In Arabic the inline END is
   that same left edge, so `flex-end` aimed the chip straight at it. Being out
   of flow, the button could not be pushed — only covered.

   Starting the bar at the inline edge keeps it at the opposite side from all of
   them, which is where the old band's controls sat and why this never came up
   before. Inside `.dash-header` the flex parent places the bar and this has no
   effect. */
.branch-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  margin-block-end: 14px;
  box-sizing: border-box;
  max-inline-size: 100%;
  min-inline-size: 0;
}

.branch-bar-form {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  min-inline-size: 0;
}

/* The chip proper: glyph + select + confirm, inside one rounded outline so the
   three read as a single control rather than as a row of loose widgets. 36px
   matches `.dash-pos`'s 38px closely enough that the header keeps its height
   — the pill is the tallest item and nothing here exceeds it. */
.branch-bar-chip {
  block-size: 36px;
  padding-inline: 10px 4px;
  border: 1px solid #dbe3ec;
  border-radius: 999px;
  background: #fff;
  box-sizing: border-box;
  /* So the chip can give ground when its row is short. Without this the
     select's own intrinsic width becomes a floor and the whole chip refuses to
     shrink, which is what pushed it onto a row of its own on a phone. */
  min-inline-size: 0;
}

.branch-bar-chip:focus-within {
  border-color: var(--screen-accent, #0d9488);
}

.branch-bar-glyph {
  display: inline-flex;
  flex: 0 0 auto;
  inline-size: 16px;
  block-size: 16px;
  color: var(--screen-accent, #0d9488);
}

.branch-bar-glyph svg {
  inline-size: 100%;
  block-size: 100%;
  display: block;
}

/* ⚠ CAPPED AND ELLIPSISED, WHICH IS THE WHOLE POINT OF THE CAP. A select sizes
   itself to its widest option, so one long site name — a street plus a section
   qualifier is ordinary — would widen the header until the centred POS pill
   collided with the cluster. The full name stays reachable through the `title`
   the partial sets. */
.branch-bar-select {
  flex: 1 1 auto;
  min-inline-size: 0;
  max-inline-size: 150px;
  border: 0;
  background: transparent;
  padding-block: 0;
  padding-inline: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: #0f172a;
  text-overflow: ellipsis;
  cursor: pointer;
}

.branch-bar-select:focus-visible {
  outline: 2px solid var(--screen-accent, #0d9488);
  outline-offset: 2px;
  border-radius: 6px;
}

/* The two icon buttons. `sr-only` text and a `title` carry the names the
   captions used to; only the pixels are gone. 28px keeps both inside the
   chip's 36px without stretching it. */
.branch-bar-go,
.branch-bar-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  inline-size: 28px;
  block-size: 28px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
}

.branch-bar-go svg,
.branch-bar-clear svg {
  inline-size: 15px;
  block-size: 15px;
  display: block;
}

.branch-bar-go {
  background: var(--screen-accent, #0d9488);
  color: #fff;
}

.branch-bar-go:hover {
  background: var(--screen-accent-deep, #0f766e);
}

.branch-bar-clear {
  background: #eef1f6;
  color: #48677b;
}

.branch-bar-clear:hover {
  background: #e2e8f0;
}

.branch-bar-go:focus-visible,
.branch-bar-clear:focus-visible {
  outline: 2px solid var(--screen-accent, #0d9488);
  outline-offset: 2px;
}

/* The no-roster state. A `pill--none` — the tone the rest of the product
   already uses for "nothing here" — sized to the chip beside it rather than
   filling the row. */
.branch-bar-empty {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-inline-size: 100%;
  min-inline-size: 0;
}

.branch-bar-empty .branch-bar-glyph {
  color: inherit;
}

.branch-bar-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-inline-size: 0;
}

/* T.2b — the chip inside the dashboard header.

   `.dash-header` is a flex row: lang 2, greeting 3, brand 5 with
   `margin-inline-start: auto`, and `.dash-pos` absolutely centred and out of
   flow. Order 4 seats the chip between the greeting and the brand, so the
   three user controls stay one cluster at the inline start and the brand keeps
   the far end to itself.

   ⚠ `flex-shrink` IS DELIBERATE. The centred POS pill is out of flow, so it
   cannot push anything — it can only be overlapped. Letting the chip give
   ground before the header runs out of room is what keeps the two apart at the
   widths where the cluster and the pill approach each other. */
.dash-header .branch-bar {
  order: 4;
  inline-size: auto;
  flex: 0 1 auto;
  margin-block-end: 0;
  flex-wrap: nowrap;
}

.dash-header .branch-bar-select {
  max-inline-size: 120px;
}

/* On a wide desktop the cap can afford to be a little more generous: 120px
   truncates a site name that would have fitted whole.

   ⚠ 140px, AND THE NUMBER WAS SET BY THE SWEEP RATHER THAN BY ESTIMATE. The
   first attempt used 170px on an arithmetic argument that the header had
   hundreds of pixels to spare — and the measurement answered
   "english @1440px — the branch bar covers the POS pill by 3x36px". At 1440 the
   pill is still pinned to the exact centre, so the cluster does not have the
   whole header to grow into, only the half of it up to that pill. */
@media (min-width: 85rem) {
  .dash-header .branch-bar-select {
    max-inline-size: 140px;
  }
}

/* T.2c — the POS pill rejoins the flow while the header carries five items.
   ════════════════════════════════════════════════════════════════════════════
   ⚠ THIS IS THE ONE RULE HERE THAT TOUCHES SOMETHING OTHER THAN THE BRANCH
   CONTROL, AND IT IS NOT A PREFERENCE. IT IS ARITHMETIC.

   `.dash-pos` is `position: absolute` and centred on the header box, so it is
   out of flow: the user cluster growing towards the middle does not push it,
   it COVERS it. Chrome then refuses the click outright — the first run of
   `BranchSelectorHeaderTest` failed with `element click intercepted` on
   `.branch-bar-go`, with `.dash-pos-glyph` named as the element that would
   have received it. The control was not merely ugly, it was unusable.

   Measured, in English at 1024px: the header box is 728px, the pill occupies
   its centre at 568..694, and the cluster — language 72, greeting 168, chip
   228, two 8px gaps — runs from 267 to 751. For the cluster to clear the pill
   it would have to end by 568, which leaves 53px for the chip. There is no
   branch control in 53px. The same sum in Arabic, and at 1100, 1200 and 1280,
   gives the same answer: a five-item header and a pill pinned to the exact
   centre cannot both hold below 1440.

   ⚠ SO THE MECHANISM IS BORROWED, NOT INVENTED. H.6 already returns this pill
   to the flow below the rail breakpoint, for this exact reason in its own
   words: "Out of flow it cannot participate in the wrap the bar now performs,
   so it lands on top of whatever wrapped beneath it." This widens that band to
   where the fifth item made it necessary, and nothing else about the pill —
   its gradient, its size, its glyph, its label, its route — changes.

   ⚠ AND THE REFERENCE WIDTH IS UNTOUCHED. `ReferenceGeometryTest` asserts the
   dashboard against a true 1680px viewport, which is above this query; at 1440
   the sweep measures no collision with the pill still pinned. The captured
   geometry is therefore reproduced exactly where it is asserted. */
@media (max-width: 89.99rem) {
  .dash-header .dash-pos {
    position: static;
    inset: auto;
    transform: none;
    margin-inline: auto;
  }
}

/* T.2d — clearance under a back control that is pinned out of flow.
   ════════════════════════════════════════════════════════════════════════════
   ⚠ THE FIX IS VERTICAL, AND THAT IS THE WHOLE POINT.

   Seven screens take the global back control out of the flow and pin it to the
   top of `.app-main`. Five of them use a PHYSICAL offset (`left: 12px` on
   reports, `left: 16px` on settings, offers and zatca-settings) and two use a
   logical one (`inset-inline-end: 12px` on hotels-companies and
   customer-custom-prices). So the edge the button occupies depends on BOTH the
   screen and the language, and there is no horizontal placement for the chip
   that is clear of all of them in both directions — `flex-end` collided in
   Arabic on twenty-four screens, and `flex-start` then collided in English on
   `/reports`. Each horizontal answer just moved the collision.

   What every one of those buttons does have in common is that it sits in the
   first ~46px of the screen: `top: 8..11px` plus a 33..35px pill. Clearing that
   band vertically is therefore an answer that does not depend on direction, on
   which edge the screen chose, or on the language — and it leaves the chip
   where the reader's eye starts.

   ⚠ SCOPED TO THE SCREENS THAT ACTUALLY PIN ONE. A blanket margin would put
   fifty pixels of nothing above the chip on the thirty screens that let their
   back control sit in the flow, where it wraps normally and never overlaps. */
.app-main[data-screen="reports"] > .branch-bar,
.app-main[data-screen="offers"] > .branch-bar,
.app-main[data-screen="zatca-settings"] > .branch-bar,
.app-main[data-screen="hotels-companies"] > .branch-bar,
.app-main[data-screen="customer-custom-prices"] > .branch-bar {
  margin-block-start: 52px;
}

/* T.3 — the roster panel, below the register on the branches screen. */
.branch-roster {
  margin-block-start: 28px;
  padding-block-start: 20px;
  border-block-start: 1px solid #e2e8f0;
}

/* ⚠ THE CHIP MUST NOT PUSH A NARROW SCREEN SIDEWAYS. The old rule turned the
   band into a COLUMN below 480px, because its select carried a 180px minimum
   and the row could not otherwise fit — the `scrollWidth` failure a shared
   `filter-bar` rule once caused on six report screens.

   The chip has no minimum, so it no longer needs to stack. What it needs
   instead is a smaller cap: on a phone the header's flow items already wrap
   onto a second line, and a 120px select there leaves the greeting and the
   language toggle their own room on the first. */
@media (max-width: 480px) {
  .branch-bar-select,
  .dash-header .branch-bar-select {
    max-inline-size: 96px;
  }

  .branch-bar-chip {
    padding-inline: 8px 3px;
  }

  .branch-bar-empty {
    font-size: 11.5px;
  }
}

/* ⚠ ON A PHONE THE CHIP RIDES WITH THE GREETING RATHER THAN TAKING A ROW.
   ════════════════════════════════════════════════════════════════════════════
   `.dash-header` already wrapped below 40rem, into three rows: the language
   toggle with the POS pill, the greeting, then the brand. Measured at 360px the
   chooser made that FOUR — it is 165px wide against a 306px header, and flex
   breaks a line on an item's HYPOTHETICAL size, so a chip sized by its own
   content refuses the greeting's row even when the shrunk result would have
   fitted on it.

   A small `flex-basis` is what settles that: the line is measured against
   110px, the chip joins the greeting, and only then does it grow into whatever
   room is left — which is why the select carries `flex: 1 1 auto` and both it
   and the chip carry `min-inline-size: 0`. The header returns to the same three
   rows it had before the branch module existed.

   ⚠ GROWTH IS SCOPED TO THIS BREAKPOINT. `flex-grow: 1` on a wide header would
   stretch the chip across the free space between the greeting and the brand,
   which is the opposite of compact. */
@media (max-width: 40rem) {
  .dash-header .branch-bar {
    flex: 1 1 110px;
  }
}

/* Package consumption is one shared status component on the three quota-bound
   registers. Logical properties keep it correct in both Arabic and English. */
.quota-usage {
  display: grid;
  grid-template-columns: minmax(12rem, 1fr) auto;
  align-items: center;
  gap: .5rem 1rem;
  margin-block: 1rem;
  padding: .85rem 1rem;
  border: 1px solid #dbe4ee;
  border-inline-start: 4px solid #2563eb;
  border-radius: .75rem;
  background: #f8fbff;
  color: #1f2937;
}

.quota-usage--warning {
  border-inline-start-color: #d97706;
  background: #fffbeb;
}

.quota-usage--reached {
  border-inline-start-color: #dc2626;
  background: #fef2f2;
}

.quota-usage__title { font-weight: 700; }
.quota-usage__value { font-variant-numeric: tabular-nums; }

.quota-usage__track {
  grid-column: 1 / -1;
  overflow: hidden;
  block-size: .45rem;
  border-radius: 999px;
  background: #dbe4ee;
}

.quota-usage__bar {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: #2563eb;
}

.quota-usage--warning .quota-usage__bar { background: #d97706; }
.quota-usage--reached .quota-usage__bar { background: #dc2626; }

.quota-usage__message {
  grid-column: 1 / -1;
  font-size: .875rem;
  color: #92400e;
}

.quota-usage--reached .quota-usage__message { color: #991b1b; }

.quota-usage__upgrade {
  justify-self: start;
  color: #1d4ed8;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: .2em;
}

@media (max-width: 640px) {
  .quota-usage { grid-template-columns: 1fr; }
  .quota-usage__track,
  .quota-usage__message { grid-column: 1; }
}
