/* app.css */
/* =========================================================
   أثر — Base
   Reset, bilingual typography, layout, utilities.
   Reads only layer-2 semantic tokens. Zero hardcoded colour.
   ========================================================= */
/* =========================================================
   أثر — Design Tokens
   ---------------------------------------------------------
   Three layers, in order. Components may ONLY read layer 2
   and 3. Never reference a layer-1 primitive outside this file.

     1. Primitives  --athar-surface-container   raw values
     2. Semantic    --color-surface             role, swaps per theme
     3. Component   defined in components/*.css

   Dark is the default: :root holds the dark values because the
   Athar logo is built for a dark ground, and because the design
   system is specified in dark. Light is an explicit opt-in via
   [data-theme="light"] — theme-init sets the attribute before
   first paint, so there is no flash.
   ========================================================= */

/* ---------------------------------------------------------
   LAYER 1a — The dark palette (Athar Commerce OS)

   The design system's own tokens, transcribed from the project's
   design file at the repository root.
   They are the authority for dark mode and nothing here is
   derived or eyeballed: if a value needs to change, it changes
   in the design file first and is copied across.

   The set is tonal rather than flat. Six ground steps carry the
   whole hierarchy — canvas, band, card, menu, and the two
   extremes — so a dashboard separates its panels by tone and not
   by drawing a border around everything.

   Every fill has an on-* partner: the ink that was measured
   against it. A fill used without its partner is how a bright
   teal button ends up with white text nobody can read.
   --------------------------------------------------------- */
:root {
  /* Grounds — container levels, deepest to brightest */
  --athar-surface-container-lowest: #0b1015;
  --athar-background: #0f1419;
  --athar-surface: #0f1419;
  --athar-surface-dim: #0f1419;
  --athar-surface-container-low: #171c21;
  --athar-surface-container: #1b2025;
  --athar-surface-container-high: #252a30;
  --athar-surface-container-highest: #30353b;
  --athar-surface-bright: #353a3f;
  --athar-surface-variant: #30353b;

  /* Ink on those grounds */
  --athar-on-background: #dee3ea;
  --athar-on-surface: #dee3ea;
  --athar-on-surface-variant: #bacac6;
  --athar-inverse-surface: #dee3ea;
  --athar-inverse-on-surface: #2c3136;

  /* Lines */
  --athar-outline: #859490;
  --athar-outline-variant: #3b4a47;

  /* Primary — the teal cyan of the mark. The operational core:
     conversion triggers, positive metrics, active navigation. */
  --athar-primary: #45e5d3;
  --athar-on-primary: #003732;
  --athar-primary-container: #00c9b7;
  --athar-on-primary-container: #004f47;
  --athar-inverse-primary: #006a60;
  --athar-surface-tint: #37ddca;
  --athar-primary-fixed: #5ffae6;
  --athar-primary-fixed-dim: #37ddca;
  --athar-on-primary-fixed: #00201c;
  --athar-on-primary-fixed-variant: #005048;

  /* Secondary — copper bronze. Tier badges, premium locks,
     revenue totals. Never a call to action. */
  --athar-secondary: #f8ba87;
  --athar-on-secondary: #4c2701;
  --athar-secondary-container: #6a3f17;
  --athar-on-secondary-container: #e8ac7a;
  --athar-secondary-fixed: #ffdcc2;
  --athar-secondary-fixed-dim: #f8ba87;
  --athar-on-secondary-fixed: #2e1500;
  --athar-on-secondary-fixed-variant: #673d15;

  /* Tertiary — deep cyan / petrol. Structural support: chart
     fills, category flags, hover gradients, secondary clusters. */
  --athar-tertiary: #84dbf6;
  --athar-on-tertiary: #003642;
  --athar-tertiary-container: #67bfda;
  --athar-on-tertiary-container: #004c5d;
  --athar-tertiary-fixed: #b3ebff;
  --athar-tertiary-fixed-dim: #7cd2ee;
  --athar-on-tertiary-fixed: #001f27;
  --athar-on-tertiary-fixed-variant: #004e5f;

  /* Error */
  --athar-error: #ffb4ab;
  --athar-on-error: #690005;
  --athar-error-container: #93000a;
  --athar-on-error-container: #ffdad6;
}

/* ---------------------------------------------------------
   LAYER 1b — Brand ramps

   The palette above is specified for dark only. Light mode is
   still the same brand, so the three brand hues are also written
   as ramps: the same colours mixed toward white and toward the
   ground, with the deep steps chosen so ink on paper clears AA
   rather than merely looking right.

     teal   the primary, anchored on #00c9b7
     cyan   the tertiary petrol, anchored on #1b829b
     brass  the secondary copper, anchored on #d49a6a

   -500 is the brand value in each. Steps above it are for dark
   grounds, steps below for paper.
   --------------------------------------------------------- */
:root {
  --athar-teal-200: #a8f2e9;
  --athar-teal-300: #5ce6d7;
  --athar-teal-400: #22d8c6;
  --athar-teal-500: #00c9b7;
  --athar-teal-600: #00a898;
  --athar-teal-700: #0a7d73;
  --athar-teal-800: #0a5c55;

  --athar-cyan-200: #b7e0ea;
  --athar-cyan-300: #7ec8da;
  --athar-cyan-400: #2ba3be;
  --athar-cyan-500: #1b829b;
  --athar-cyan-600: #136a80;
  --athar-cyan-700: #0e5265;
  --athar-cyan-800: #0a3a49;

  --athar-brass-200: #f0d5bc;
  --athar-brass-300: #e6bd97;
  --athar-brass-500: #d49a6a;
  --athar-brass-600: #ac7a4f;
  /* The reading step. A diamond mark and the word beside it do not
     need the same value: one is a shape, the other has to clear 4.5
     on all three paper grounds, the sunken one included. */
  --athar-brass-700: #7d5535;

  /* Paper — the light ground. A clean cool slate, a shade off white.
     50 is the container and sidebar surface, 100 the page canvas. */
  --athar-paper-50:  #ffffff;
  --athar-paper-100: #f8fafc;
  --athar-paper-200: #eaecef;
  --athar-paper-300: #dde0e4;
  --athar-paper-400: #c2c7ce;

  --athar-neutral-100: #e8eaec;
  --athar-neutral-300: #aab1b8;
  --athar-neutral-500: #79818a;
  --athar-neutral-700: #4a525b;
  --athar-neutral-900: #171a1d;

  /* Status outside the palette.

     Success is the primary teal by design — a finished task and a
     rising trendline are the same signal. Error is the palette's
     own. Warning is the one hue the system does not ship: amber
     sits a step off the copper secondary on purpose, because a
     warning that looks like a premium badge is a warning nobody
     reads. */
  --athar-amber-300: #f5c86b;
  --athar-amber-600: #a2681c;
  --athar-red-600: #ba1a1a;
}

/* ---------------------------------------------------------
   LAYER 1c — Type, space, shape, motion
   --------------------------------------------------------- */
:root {
  /* ---- Typography primitives ----
     Almarai everywhere — Arabic and Latin out of the same file,
     both drawn by Boutros, so a bilingual line does not change
     voice halfway across.

     It replaced IBM Plex Sans Arabic on the storefront and Cairo
     on the dashboard (owner's instruction, 2026-09-09). Plex and
     Cairo are the two faces every generated Arabic template
     reaches for first, and the platform had one of each.

     Almarai ships four cuts and no more: 300, 400, 700, 800. The
     weight tokens below name those and nothing else. A browser
     asked for 600 in a family that has none picks 700 by itself;
     writing 700 here only says out loud what was going to happen,
     and leaves 800 free for the one step above bold.

     --font-figures is the one thing Almarai cannot do, and it was
     measured rather than assumed. Its digits are proportional and
     it carries no tnum feature at all: at 40px the 1 is 14.20px
     wide against the 8 at 21.24px, and setting either
     font-variant-numeric: tabular-nums or "tnum" 1 moves the
     string not one hundredth of a pixel (measured in Chrome on
     the running app, 2026-09-09). A right-aligned column of
     riyals in that face does not line up — the decimal wanders by
     a digit-width per row, which is exactly the thing a merchant
     scanning revenue needs it not to do.

     So figures are set in the platform's monospace instead: every
     rule that already paired --font-latin with tabular-nums now
     reads this token, and that pairing was already the codebase's
     marker for "this is a number to be compared".

     Neither this nor --font-mono downloads anything. A second
     webfont for the sake of the digits is a network round trip
     and a second voice on the page; the system monospace is on
     every machine and its digits are equal-width by definition. */
  --font-display-ar: "Almarai", "Segoe UI", Tahoma, sans-serif;
  --font-body-ar: "Almarai", "Segoe UI", Tahoma, sans-serif;
  --font-latin: "Almarai", "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-figures: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;


  --fw-regular: 400;
  --fw-medium: 400;
  --fw-semibold: 700;
  --fw-bold: 700;
  --fw-black: 800;

  /* Fluid scale, matched to the design system's ramp. Each clamp
     runs from the mobile size to the desktop one:

       display-lg   34 -> 48    --fs-h1
       headline-xl  26 -> 32    --fs-h2
       headline-md  22          --fs-h3 / --fs-xl
       title-lg     18          --fs-h4 / --fs-lg
       body-lg      16          --fs-base
       body-md      14          --fs-sm
       label-md     13          --fs-xs
       label-sm     11          --fs-eyebrow */
  --fs-eyebrow: 0.6875rem;
  --fs-xs: 0.8125rem;
  --fs-sm: 0.875rem;
  --fs-base: 1rem;
  --fs-lg: 1.125rem;
  --fs-xl: 1.375rem;
  --fs-h4: 1.125rem;
  --fs-h3: clamp(1.25rem, 1.05rem + 0.7vw, 1.375rem);
  --fs-h2: clamp(1.625rem, 1.28rem + 1.5vw, 2rem);
  --fs-h1: clamp(2.125rem, 1.55rem + 2.5vw, 3rem);


  /* Generous by RTL necessity, not by taste: Arabic diacritics and
     ligatures clip against a tight baseline, so body sits at 1.6
     rather than the 1.45 a Latin-only system would take. */
  --lh-tight: 1.25;
  --lh-heading: 1.32;
  --lh-snug: 1.45;
  --lh-normal: 1.6;
  --lh-relaxed: 1.9;

  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-wide: 0.06em;
  --tracking-eyebrow: 0.04em;

  /* ---- Space — 8px architecture, 4px sub-rhythm ---- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --space-32: 8rem;
  --space-48: 12rem; /* 192px — the largest step. Nothing on the
                        marketing pages spends it since the section
                        padding came back down to --space-24. */

  /* The same scale under the design system's own names, so a spec
     that says space-lg and a stylesheet that says --space-6 are
     provably the same measurement. */
  --space-xxs: var(--space-1);
  --space-xs: var(--space-2);
  --space-sm: var(--space-3);
  --space-md: var(--space-4);
  --space-lg: var(--space-6);
  --space-xl: var(--space-8);
  --space-2xl: var(--space-12);
  --space-3xl: var(--space-16);

  --grid-margin-desktop: 2rem;
  --grid-margin-mobile: 1rem;
  --grid-gutter: 1.25rem;

  /* ---- Density ----
     The space scale answers "how far apart", and every panel was
     reaching into it for the same answer: --space-6 inside a card,
     --space-4 when the card was in a table. Two numbers doing one
     job, chosen per file, is how a dashboard ends up with cards
     that breathe differently on adjacent screens.

     These three name the job instead. A panel is the unit a
     high-end shop is built out of — a card, a form well, a summary
     box — and it has one resting inside measure, one for a phone or
     a dense table, and one for the panel a page is built around. */
  --space-panel: 1.75rem;
  --space-panel-tight: 1.125rem;
  --space-panel-loose: 2.5rem;

  /* ---- Radius ----
     Rounded, not pill. Buttons, inputs and dropdowns hold 8px —
     corporate gravitas rather than a chat app. Cards and modules
     scale to 16px. Full radii are spent only on live status pills,
     which is what tells a status apart from a click target. */
  --radius-xs: 0.25rem;
  --radius-sm: 0.5rem;
  --radius-md: 0.75rem;
  --radius-lg: 1rem;
  --radius-xl: 1.5rem;
  --radius-pill: 9999px;
  --radius-premium: 1rem;

  /* ---- Motion ---- */
  --dur-fast: 160ms;
  --dur-base: 240ms;
  --dur-slow: 320ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  /* Indeterminate motion — a spinner and a progress bar that do not
     know how long they have. Linear, because an eased spinner reads
     as stuttering, and slow enough that it is not a distraction next
     to a form somebody is still reading. */
  --dur-spin: 760ms;
  --dur-crawl: 1400ms;
  --ease-linear: linear;
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  /* ---- Layout ---- */
  --container-max: 1180px;
  --container-wide: 1400px;
  --header-height: 72px;
  --rail-width: 56px;

  /* ---- Grain ----
     A single tile of film grain, laid over the whole page at very
     low opacity. Flat digital colour is the tell of a generated
     interface; a little noise gives the surface a texture the eye
     reads as printed. */
  --texture-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------
   LAYER 2 — Semantic · DARK (default)

   Where the palette becomes roles. Two rules hold the whole
   thing together.

   **Hierarchy is carried by the container levels, not by
   borders.** canvas -> band -> surface -> raised -> highest is
   five steps of one ground, and a panel is told apart from the
   page by the step it sits on. Lines are a refinement on top of
   that, never the thing doing the work.

   **Every fill names its own ink.** --color-primary is a bright
   teal and white on it is 1.6:1, unreadable. So --color-on-primary
   exists and is the only ink allowed on a primary fill. Same for
   secondary, tertiary, success and error. --color-text-on-brand
   stays white and now means one thing only: ink for a scrim, a
   photograph, or a fill dark enough to take it.
   --------------------------------------------------------- */
:root,
:root.dark,
:root[data-theme="dark"] {
  /* ---- Grounds ---- */
  --color-canvas: var(--athar-background);
  --color-canvas-deep: var(--athar-surface-container-lowest);
  --color-surface: var(--athar-surface-container);
  --color-surface-raised: var(--athar-surface-container-high);
  --color-surface-sunken: var(--athar-surface-container-lowest);

  /* One step above raised: a hovered row, a tooltip, the thing that
     has to sit on top of something already lifted. */
  --color-surface-highest: var(--athar-surface-container-highest);

  --color-line: var(--athar-outline-variant);
  --color-line-strong: var(--athar-outline);
  --color-line-gold: rgba(248, 186, 135, 0.34);

  /* ---- Ink ---- */
  --color-text: var(--athar-on-surface);
  --color-text-secondary: var(--athar-on-surface-variant);
  --color-text-muted: var(--athar-outline);
  --color-text-on-brand: #ffffff;

  /* ---- The on-* family ----
     One per fill, each measured against the fill it names:
     on-primary clears 8.4:1 on --color-primary, on-secondary 11.5:1
     on --color-gold, on-error 7.9:1 on --color-danger. */
  --color-on-primary: var(--athar-on-primary);
  --color-on-secondary: var(--athar-on-secondary);
  --color-on-tertiary: var(--athar-on-tertiary);
  --color-on-success: var(--athar-on-primary);
  --color-on-warning: var(--athar-on-secondary);
  --color-on-error: var(--athar-on-error);

  /* ---- Primary — the operational core ---- */
  --color-primary: var(--athar-primary);
  --color-primary-hover: var(--athar-primary-fixed);
  --color-primary-soft: rgba(0, 201, 183, 0.14);
  --color-primary-text: var(--athar-primary);

  /* The deep end of the brand, for a surface that carries brand
     colour without asking to be clicked: an avatar, a chat bubble
     the visitor wrote. Dark enough to take white. */
  --color-primary-deep: var(--athar-inverse-primary);
  --color-text-on-deep: #ffffff;

  /* ---- Growth — a rising number, a positive delta ---- */
  --color-growth: var(--athar-primary-container);
  --color-growth-soft: rgba(0, 201, 183, 0.14);
  --color-growth-text: var(--athar-primary);

  /* ---- Premium — the copper. Tier badges and nothing else. ---- */
  --color-gold: var(--athar-secondary);
  --color-gold-soft: rgba(248, 186, 135, 0.14);
  --color-gold-text: var(--athar-secondary);

  /* ---- Entry tier — the petrol tertiary ----
     The plan row wears the artwork: petrol for the tier you start
     on, teal for the one most stores end on, copper for the top. */
  --color-entry: var(--athar-tertiary-container);
  --color-entry-soft: rgba(132, 219, 246, 0.14);
  --color-entry-text: var(--athar-tertiary);
  --color-line-entry: rgba(132, 219, 246, 0.34);

  /* ---- Status ---- */
  --color-success: var(--athar-primary-container);
  --color-success-soft: rgba(0, 201, 183, 0.14);
  --color-warning: var(--athar-amber-300);
  --color-warning-soft: rgba(245, 200, 107, 0.16);
  --color-danger: var(--athar-error);
  --color-danger-soft: rgba(255, 180, 171, 0.16);

  /* ---- Delta — the two directions a KPI can move ----
     Status colours answer "did something break". A delta answers
     "is this up or down", and the top of the dashboard reads faster
     when the two questions do not share a palette. Emerald sits far
     enough off the brand teal that a rising figure is not read as a
     link; coral is warmer than --color-danger, which stays reserved
     for something that actually went wrong. */
  --color-delta-up: #10b981;
  --color-delta-up-soft: rgba(16, 185, 129, 0.1);
  --color-delta-down: #f43f5e;
  --color-delta-down-soft: rgba(244, 63, 94, 0.1);

  /* ---- Chart ----
     The lead series is electric violet #8b5cf6 by the owner's
     specification, and it is written here as well as in chart.js
     because the CSS bars the panel falls back to have to be the same
     colour as the line they stand in for. The petrol second series
     is still read from here at draw time. */
  --color-chart-primary: #8b5cf6;
  --color-chart-petrol: var(--athar-tertiary);
  --color-chart-glow: rgba(139, 92, 246, 0.55);

  --color-focus: var(--athar-primary-container);

  /* Scrim behind the mobile drawer. Not black: the canvas dimmed,
     so the page reads as pushed back rather than switched off. */
  --color-scrim: rgba(6, 10, 13, 0.66);

  /* ---- Hero wash ----
     The three stops a page's opening section is lit with. Kept here
     rather than in a page file because every marketing page opens
     the same way and they must not drift apart. Deliberately weaker
     than it looks written down: on a near-black ground even 10% of
     a saturated teal reads as a coloured panel. */
  --hero-wash-brand: rgba(0, 201, 183, 0.10);
  --hero-wash-deep: rgba(0, 106, 96, 0.30);
  --hero-wash-top: rgba(255, 255, 255, 0.02);

  /* ---- The alternating band ----
     A long page is a dozen sections of the same shape, and on one
     flat canvas they read as a single column with headings in it.
     This is the ground that breaks the run — one container step, no
     border. A band that announces itself competes with the content
     it is separating, which is the opposite of the job. */
  --color-canvas-band: var(--athar-surface-container-low);

  /* The anchor — the third ground, and the one that gives the page a
     pulse. The band above is a change of light; this is a change of
     room. It is the palette's deep petrol rather than more graphite,
     so a section wearing it reads as brand rather than as a darker
     rectangle, and it holds the same value in both themes. Spend it
     on two or three sections. A fourth turns the rhythm back into a
     texture. */
  --color-canvas-anchor: var(--athar-on-tertiary);
  --color-anchor-line: rgba(132, 219, 246, 0.18);

  --grain-opacity: 0.05;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.42);
  --shadow-lg: 0 24px 64px rgba(0, 0, 0, 0.5);
  --shadow-glow: 0 0 0 1px rgba(248, 186, 135, 0.16), 0 20px 56px rgba(0, 0, 0, 0.45);

  /* The ink a shadow is cast in, and how much of it this ground can
     take. Dark casts in black, because the canvas is already close
     to it; paper casts in the neutral the text is set in, because a
     black shadow on warm paper reads as a hole.

     --shadow-soft and --shadow-deep below are written once, off
     these two, so the light theme restates two values instead of
     eight — and so a shadow can never drift between themes. */
  --shadow-ink: 0 0 0;
  --shadow-strength: 1;

  /* Depth here is chromatic, not opaque: the ground does the lifting
     and these two put light on the edges. The rim is the inner
     highlight a lit card catches along its top edge; the bloom is
     the diffused teal a primary control throws under the cursor.
     Both are off by default and spent deliberately. */
  --rim-light: inset 0 1px 0 0 rgba(0, 201, 183, 0.25);
  --shadow-bloom: 0 0 20px rgba(0, 201, 183, 0.35);

  /* A focus ring, as a shadow rather than an outline: it follows the
     border-radius, and it can sit next to a shadow in the same
     property without either one clipping the other. */
  --color-focus-soft: rgba(0, 201, 183, 0.2);

  color-scheme: dark;
}

/* ---------------------------------------------------------
   LAYER 2 — Depth

   Two elevations, written once and tinted by the theme.

   Real depth is two things at once: a tight contact shadow that
   says the surface is touching the page, and a wide ambient one
   that says there is air under it. Stacked, they read as a lifted
   object; either alone reads as a filter.

   --shadow-soft  a panel at rest
   --shadow-deep  the same panel picked up: a hover, a menu, a dialog
   --------------------------------------------------------- */
:root {
  --shadow-soft:
    0 1px 2px rgb(var(--shadow-ink) / calc(0.22 * var(--shadow-strength))),
    0 4px 10px rgb(var(--shadow-ink) / calc(0.16 * var(--shadow-strength))),
    0 12px 28px rgb(var(--shadow-ink) / calc(0.14 * var(--shadow-strength)));

  --shadow-deep:
    0 2px 4px rgb(var(--shadow-ink) / calc(0.26 * var(--shadow-strength))),
    0 10px 24px rgb(var(--shadow-ink) / calc(0.26 * var(--shadow-strength))),
    0 28px 64px rgb(var(--shadow-ink) / calc(0.3 * var(--shadow-strength)));

  --ring-focus: 0 0 0 3px var(--color-focus-soft);

  /* ---- Gradients ----

     --gradient-sheen is light, not colour: a highlight along the top
     edge and a shade along the bottom, as if the surface had a
     thickness. White and black alpha only, so it lays over any fill
     — primary, copper, a merchant's own shop colour — without
     touching that fill's contrast against its label. It is an
     overlay, always the first layer of a two-layer background.

     --gradient-primary is the filled CTA. It runs primary into the
     brand teal and stops there: the design file's petrol end lands
     at 4.39:1 against --color-on-primary, a hair under AA at the
     14px bold a button is set in, and a gradient whose far corner
     fails is a gradient that fails. The petrol still gets its run in
     --gradient-brand below, which carries no small text. */
  --gradient-sheen: linear-gradient(
    180deg,
    rgb(255 255 255 / 0.16) 0%,
    rgb(255 255 255 / 0.03) 46%,
    rgb(0 0 0 / 0.07) 100%
  );

  --gradient-primary: linear-gradient(
    165deg,
    var(--color-primary-hover) 0%,
    var(--color-primary) 46%,
    var(--color-growth) 100%
  );

  --gradient-gold: linear-gradient(
    165deg,
    var(--athar-secondary-fixed) 0%,
    var(--athar-secondary) 100%
  );

  /* The emblem's own ramp — teal down to petrol — spent on the trace
     rail, the mark, and one word per page. Put it on a button as
     well and it stops being a signature. */
  --gradient-brand: linear-gradient(
    180deg,
    var(--athar-primary) 0%,
    var(--athar-primary-container) 46%,
    var(--athar-cyan-500) 100%
  );
  --gradient-trace: var(--gradient-brand);
  --gradient-word: linear-gradient(
    100deg,
    var(--athar-primary-fixed) 0%,
    var(--athar-primary-container) 50%,
    var(--athar-tertiary) 100%
  );

  /* A panel lit from above: the raised surface at the top edge
     falling to the flat one. Two steps of the same neutral, so it
     reads as light rather than as colour. */
  --gradient-surface: linear-gradient(
    180deg,
    var(--color-surface-raised) 0%,
    var(--color-surface) 100%
  );
}

/* ---------------------------------------------------------
   LAYER 2 — Semantic · LIGHT (explicit opt-in)

   The design system is specified for dark. Light is the same
   brand rebuilt on paper: the brand ramps from layer 1b taken at
   their deep steps, because a colour that reads as luminous on a
   #0f1419 ground reads as a highlighter on white.

   Every on-* token is restated. On paper the brand fills are dark
   and the ink is white — the exact inverse of the rule above —
   and a token left unset here would silently put dark ink on a
   dark fill.
   --------------------------------------------------------- */
:root[data-theme="light"] {
  --color-canvas: var(--athar-paper-100);
  --color-canvas-deep: var(--athar-paper-200);
  --color-surface: var(--athar-paper-50);
  --color-surface-raised: var(--athar-paper-50);
  --color-surface-sunken: var(--athar-paper-200);
  --color-surface-highest: var(--athar-paper-50);

  --color-line: var(--athar-paper-300);
  --color-line-strong: var(--athar-paper-400);
  --color-line-gold: rgba(172, 122, 79, 0.42);

  --color-text: var(--athar-neutral-900);
  --color-text-secondary: var(--athar-neutral-700);
  --color-text-muted: var(--athar-neutral-500);
  --color-text-on-brand: #ffffff;

  /* Inverted from dark: the fills are deep here, so the ink is white. */
  --color-on-primary: #ffffff;
  --color-on-secondary: #ffffff;
  --color-on-tertiary: #ffffff;
  --color-on-success: #ffffff;
  --color-on-warning: #ffffff;
  --color-on-error: #ffffff;

  --color-primary: var(--athar-teal-700);
  --color-primary-hover: var(--athar-teal-800);
  --color-primary-soft: rgba(0, 201, 183, 0.12);
  --color-primary-text: var(--athar-teal-800);

  --color-primary-deep: var(--athar-cyan-700);
  --color-text-on-deep: #ffffff;

  --color-growth: var(--athar-teal-700);
  --color-growth-soft: rgba(0, 201, 183, 0.12);
  --color-growth-text: var(--athar-teal-800);

  --color-gold: var(--athar-brass-600);
  --color-gold-soft: rgba(212, 154, 106, 0.18);
  /* Darker than the fill above it, the same way growth-text is darker
     than growth. A diamond mark and the word beside it do not need
     the same value: one is a shape and the other has to be read. */
  --color-gold-text: var(--athar-brass-700);

  --color-entry: var(--athar-cyan-600);
  --color-entry-soft: rgba(27, 130, 155, 0.10);
  --color-entry-text: var(--athar-cyan-600);
  --color-line-entry: rgba(27, 130, 155, 0.34);

  --color-success: var(--athar-teal-700);
  --color-success-soft: rgba(0, 201, 183, 0.12);
  --color-warning: var(--athar-amber-600);
  --color-warning-soft: rgba(162, 104, 28, 0.12);
  --color-danger: var(--athar-red-600);
  --color-danger-soft: rgba(186, 26, 26, 0.10);

  /* Delta on paper. The specified pair is tuned for a #111827 card and
     neither colour survives the trip to white — #10b981 reads 2.5:1
     there and #f43f5e 3.7:1, both under the 4.5:1 that eyebrow-sized
     text needs. Same two hues, taken to the depth paper wants, with
     the vibrance left in the tint behind them. */
  --color-delta-up: #047857;
  --color-delta-up-soft: rgba(4, 120, 87, 0.1);
  --color-delta-down: #be123c;
  --color-delta-down-soft: rgba(190, 18, 60, 0.1);

  --color-chart-primary: #6d28d9;
  --color-chart-petrol: var(--athar-cyan-600);
  --color-chart-glow: rgba(109, 40, 217, 0.28);

  --color-focus: var(--athar-teal-700);

  --color-scrim: rgba(23, 26, 29, 0.42);

  /* Paper takes the wash the other way round: the tint has to sit
     above the canvas rather than below it, so these are lighter and
     the top stop is a real colour instead of a white lift. */
  --hero-wash-brand: rgba(0, 201, 183, 0.10);
  --hero-wash-deep: rgba(27, 130, 155, 0.14);
  --hero-wash-top: rgba(255, 255, 255, 0.55);

  /* Paper's band. Cooler than the canvas rather than darker, so the
     step reads as a change of light and not as a grey rectangle. */
  --color-canvas-band: #f1f4f8;

  /* Same anchor, deliberately unchanged. A dark section on paper is
     the strongest move the page has; making it lighter here would
     spend the move and buy nothing. */
  --color-canvas-anchor: var(--athar-on-tertiary);
  --color-anchor-line: rgba(132, 219, 246, 0.20);

  --grain-opacity: 0.03;

  --shadow-sm: 0 1px 2px rgba(23, 26, 29, 0.06);
  --shadow-md: 0 8px 24px rgba(23, 26, 29, 0.07);
  --shadow-lg: 0 24px 64px rgba(23, 26, 29, 0.09);
  --shadow-glow: 0 0 0 1px rgba(172, 122, 79, 0.22), 0 20px 48px rgba(23, 26, 29, 0.08);

  /* Paper takes about a quarter of the ink the dark ground does, and
     takes it in the neutral the text is set in rather than in black.
     --shadow-soft and --shadow-deep are not restated: they read
     these two and re-tint themselves. */
  --shadow-ink: 23 26 29;
  --shadow-strength: 0.3;

  /* The rim and the bloom are dark-mode effects. Neon edge light on
     paper reads as a rendering artefact, so both are off. */
  --rim-light: none;
  --shadow-bloom: none;

  --color-focus-soft: rgba(10, 125, 115, 0.24);

  color-scheme: light;
}


/* ---------------------------------------------------------
   Reset
   --------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  position: relative;
  margin: 0;
  background: var(--color-canvas);
  color: var(--color-text);
  font-family: var(--font-body-ar);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Film grain over the whole page. Perfectly flat digital colour is
   the tell of a generated interface; a little tooth makes the
   surface read as printed. Never interactive, never in print. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  background-image: var(--texture-grain);
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
}
@media print {
  body::after { display: none; }
}

img,
svg,
video { max-width: 100%; display: block; }

button,
input,
select,
textarea { font: inherit; color: inherit; }

ul, ol { margin: 0; padding: 0; list-style: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------
   Typography — bilingual
   Arabic and Latin do not share a rhythm. Latin display text
   gets negative tracking to close its wide sidebearings;
   Arabic gets none, because letter-spacing breaks the joins
   between glyphs. Every rule below is split on that fact.
   --------------------------------------------------------- */
:root[data-lang="en"] body { font-family: var(--font-latin); }

h1, h2, h3, h4, h5 {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display-ar);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  color: var(--color-text);
}

:root[data-lang="en"] h1,
:root[data-lang="en"] h2,
:root[data-lang="en"] h3,
:root[data-lang="en"] h4,
:root[data-lang="en"] h5 {
  font-family: var(--font-latin);
  letter-spacing: var(--tracking-tight);
}

h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); line-height: var(--lh-snug); }
h4 { font-size: var(--fs-h4); font-weight: var(--fw-bold); line-height: var(--lh-snug); }

p {
  margin: 0 0 var(--space-4);
  color: var(--color-text-secondary);
}

a {
  color: var(--color-primary-text);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
a:hover { color: var(--color-growth-text); }

strong { font-weight: var(--fw-bold); color: var(--color-text); }

code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  letter-spacing: 0;
}

/* Numerals stay Latin and tabular in both languages so figures in
   tables and KPI tiles align on the decimal. */
.u-figure {
  font-family: var(--font-figures);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: var(--tracking-tight);
}

/* Emphasis inside a headline. Brass text over a brass rule, the way
   a copy editor would mark a word — not a gradient fill. The full
   logo ramp stays reserved for the trace rail and the wordmark. */
.u-word-mark {
  color: var(--color-gold-text);
  text-decoration: underline;
  text-decoration-color: var(--color-line-gold);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
  text-decoration-skip-ink: none;
}

/* ---------------------------------------------------------
   Eyebrow
   A section label, not decoration: it names the section for
   screen readers and for scanning. Latin gets caps + tracking;
   Arabic has no case, so it earns its rank from the gold node
   instead of from letterforms it does not have.
   --------------------------------------------------------- */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
  font-family: var(--font-body-ar);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold);
  color: var(--color-gold-text);
}
.eyebrow::before {
  content: "";
  width: 5px;
  height: 5px;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--color-gold);
}
:root[data-lang="en"] .eyebrow {
  font-family: var(--font-latin);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
}

/* A gold hairline. The only decorative rule in the system, and
   only where a real boundary exists. */
.hairline {
  height: 1px;
  border: 0;
  margin: var(--space-10) 0;
  background: linear-gradient(
    to left,
    transparent,
    var(--color-line-gold) 20%,
    var(--color-line-gold) 80%,
    transparent
  );
}

/* ---------------------------------------------------------
   Layout
   --------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--space-6);
}

/* One beat, and the ground carries the emphasis.

   Band and anchor used to take --space-32 while the canvas took
   --space-24, on the theory that a tinted section should also breathe
   wider. What that bought was arithmetic: three different silences for
   the same event, and no reader can hear a rhythm in that. So every
   full section is one number — .section, .section--band and
   .section--anchor all resolve to the same padding, and a tinted
   section says "this is a different thing" with its ground alone.

   That number is 96px (owner's spec, 2026-09-08). It was raised to
   192px on 2026-09-06 and measured badly: the padding came to about
   half the height of every section on the page — 384px of seam around
   a 300px card in the maker's note — so the page read as air with
   content in it. 96px is where it was before that, and the number the
   rest of this comment was written about.

   **The seam is twice the number.** Vertical padding does not collapse
   between siblings that carry a background, so the space a reader sees
   between two sections is the sum of the two that meet: 96px each read
   as a 192px seam, and 192px each reads as 384px. 192px of seam is a
   fifth of a 900px viewport, which reads as a break; 384px was a third
   of it, which reads as the page having ended. The phone step below
   exists for the same reason, one size down.

   The strip keeps half the section's value, as it always has. */
.section { padding-block: var(--space-24); }
.section--tight { padding-block: var(--space-16); }

/* The thin band — a marquee, a row of facts. It wears the same tint
   as a full band but is punctuation, not a section, so it takes the
   short step and never the section one. Before this existed the three
   strips on the home page carried 18px, 0px and 32px, all of them
   chosen by hand and none of them the same. */
.section--strip { padding-block: var(--space-12); }

/* Below the tablet break every step drops one rung: 96·64·48 becomes
   64·48·32. Remove this block to run the desktop ladder at every
   width. */
@media (max-width: 900px) {
  .section { padding-block: var(--space-16); }
  .section--tight { padding-block: var(--space-12); }
  .section--strip { padding-block: var(--space-8); }
}

/* Two sections on the same ground read as one long column, and the
   home page had two such seams. The tint separates wherever the
   ground changes; where it does not change, a hairline does — which
   is the complement of the rule below, not a contradiction of it. */
.section + .section:not(.section--band):not(.section--anchor),
.section--band + .section--band {
  border-block-start: 1px solid var(--color-line);
}
.section--anchor + .section:not(.section--band):not(.section--anchor),
.section--band + .section:not(.section--band):not(.section--anchor) {
  border-block-start: 0;
}

/* The alternating ground. No border: the tint is the separation, and
   a hairline on top of it turns one soft step into two hard ones. */
.section--band {
  background: var(--color-canvas-band);
}

/* The anchor — a section that changes the room rather than the light.
   It is dark in both themes, so it restates the local colour tokens
   instead of asking every component inside it to know where it is:
   a card, a heading and a muted caption keep their own rules and
   simply resolve against a different ground.

   Two per page, and far apart. Three was the old budget and the home
   page spent all three inside five sections of each other, with a
   single canvas section between each — which is how the loudest move
   the layout has turned into a flicker. */
.section--anchor {
  background: var(--color-canvas-anchor);

  --color-text: #eef4f6;
  --color-text-secondary: rgba(216, 232, 237, 0.76);
  /* 0.62, not the 0.56 this started at: on this ground that lands at
     4.39:1, which is under AA for anything not set large. 0.62 clears
     it at 5.06:1 and is still visibly the quietest text here. */
  --color-text-muted: rgba(199, 219, 226, 0.62);

  /* Surfaces lift out of the anchor, never off the page palette —
     a graphite card on a teal-navy ground reads as a mistake. The
     anchor holds one value in both themes, so these do too. */
  --color-surface: #00404e;
  --color-surface-raised: #0a4f5e;
  --color-surface-sunken: #002a34;
  --color-surface-highest: #125d6d;

  --color-line: var(--color-anchor-line);
  --color-line-strong: rgba(139, 197, 209, 0.3);

  /* The palette's own primary and secondary, not their container
     steps: a fill that reads as brand on the graphite canvas is a
     shade too deep to carry a link on this petrol ground. */
  --color-primary-text: var(--athar-primary);
  --color-gold-text: var(--athar-secondary);

  /* Casting in black over a dark ground would show as a smudge, so
     shadows here are nearly off and depth comes from the surface step. */
  --shadow-ink: 4 22 30;
  --shadow-strength: 0.85;
}

.stack > * + * { margin-top: var(--space-4); }
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}

/* ---------------------------------------------------------
   Focus — one visible ring everywhere, keyboard only
   --------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

.skip-link {
  position: absolute;
  inset-inline-start: var(--space-4);
  top: var(--space-4);
  z-index: 100;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
  color: var(--color-text);
  transform: translateY(-200%);
  transition: transform var(--dur-base) var(--ease-out);
}
.skip-link:focus-visible { transform: translateY(0); }

/* ---------------------------------------------------------
   Reveal on scroll
   One short fade with a few pixels of travel, once. shell.js adds
   .is-revealed as an element enters; with reduced motion, or with
   JavaScript off, the element is simply visible.
   --------------------------------------------------------- */
/* --reveal-delay lets a row of siblings arrive in order rather than
   all at once. They enter the viewport on the same frame, so without
   it a grid of cards is one block appearing, which is a flash and not
   a movement. Default zero: an element that does not set it behaves
   exactly as it did before. */
:root.js [data-reveal] {
  opacity: 0;
  translate: 0 14px;
  transition:
    opacity var(--dur-slow) var(--ease-out),
    translate var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
:root.js [data-reveal].is-revealed {
  opacity: 1;
  translate: 0 0;
}
@media (prefers-reduced-motion: reduce) {
  :root.js [data-reveal] { opacity: 1; translate: 0 0; }
}

/* ---------------------------------------------------------
   Utilities
   --------------------------------------------------------- */
.u-muted { color: var(--color-text-muted); }
.u-secondary { color: var(--color-text-secondary); }
.u-lead { font-size: var(--fs-lg); line-height: var(--lh-relaxed); }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* components/brand.css */
/* =========================================================
   Component — Brand lockup

   The mark is raster artwork (the owner's logo, keyed to
   transparency), so its colour is fixed and cannot follow the
   theme. That is correct: a logo that changes colour with the
   interface is no longer a logo. Only the wordmark beside it
   takes a token, because it is type, not artwork.
   ========================================================= */
.brand {
  --brand-mark: 38px;
  --brand-word: 1rem;

  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--color-text);
}

.brand__mark {
  height: var(--brand-mark);
  width: auto;
  flex: none;
  /* The artwork was drawn to sit on a dark ground; on paper it keeps
     its own weight, so it gets no filter — only a hair of contrast
     lift so the deepest blues do not muddy against warm white. */
  transition: transform var(--dur-base) var(--ease-out);
}
.brand:hover .brand__mark { transform: translateY(-1px); }

/* Latin wordmark. Wide tracking and a heavy weight, matching the
   lockup the owner supplied. Solid, never gradient. */
.brand__wordmark {
  font-family: var(--font-latin);
  font-size: var(--brand-word);
  font-weight: var(--fw-bold);
  letter-spacing: 0.17em;
  line-height: 1;
  color: var(--color-text);
  /* the tracking adds a trailing gap; pull it back so the lockup
     stays optically centred */
  margin-inline-end: -0.17em;
}

.brand--mark .brand__wordmark { display: none; }

.brand--sm { --brand-mark: 30px; --brand-word: 0.82rem; }
.brand--lg { --brand-mark: 60px; --brand-word: 1.45rem; }

@media (max-width: 420px) {
  .brand { --brand-mark: 32px; --brand-word: 0.86rem; }
}

/* The mark is the way home from every page. On a phone it is also
   a 32px target sitting beside a 48px burger. */
@media (pointer: coarse) {
  a.brand { min-block-size: 44px; }
}

/* components/button.css */
/* =========================================================
   Component — Button
   The brand gradient is deliberately absent here. It belongs to
   the trace, the logo, and one word per page. A solid button
   next to a gradient trace reads as restraint; two gradients
   read as a template.
   ========================================================= */
.btn {
  --btn-bg: transparent;
  --btn-fg: var(--color-text);
  --btn-border: transparent;
  --btn-pad-block: 0.7rem;
  --btn-pad-inline: 1.4rem;

  /* Two layers: an optional sheen over the fill. A flat button that
     is only its fill is the tell of a template; the same fill with
     a highlight on its top edge reads as an object with thickness.
     Off by default — the sheen is spent on filled buttons only,
     where there is a surface for the light to fall on. */
  --btn-sheen: none;
  --btn-shadow: none;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--btn-pad-block) var(--btn-pad-inline);
  border: 1px solid var(--btn-border);
  border-radius: var(--radius-sm);
  background: var(--btn-sheen), var(--btn-bg);
  box-shadow: var(--btn-shadow);
  color: var(--btn-fg);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translateY(1px); }
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}

/* The ring follows the radius, which an outline does not. */
.btn:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus), var(--btn-shadow);
}

.btn__icon {
  width: 1.05em;
  height: 1.05em;
  flex: none;
}

/* Primary — the one action a screen is asking for.

   The fill is the brand ramp rather than a flat colour, which is
   what the design system asks a conversion trigger to look like.
   Gradients are safe here because both ends were measured against
   the same ink: --color-on-primary clears 8.4:1 on the light stop
   and 6.4:1 on the dark one, so there is no corner of the button
   where the label stops being readable.

   The sheen still lies over that fill rather than replacing it —
   white and black alpha only, so it moves the top and bottom
   edges without touching either contrast figure. */
.btn--primary {
  --btn-bg: var(--gradient-primary);
  --btn-fg: var(--color-on-primary);
  --btn-sheen: var(--gradient-sheen);
  --btn-shadow: var(--shadow-soft);
}
.btn--primary:hover {
  --btn-bg: var(--color-primary-hover);
  /* --shadow-bloom is `none` in light mode, and `box-shadow: <list>,
     none` is invalid as a whole — so on paper the primary button was
     losing its hover shadow entirely rather than losing the glow.
     The halo is written here instead, in a form that resolves in
     both themes. */
  --btn-shadow:
    var(--shadow-deep),
    0 0 18px color-mix(in srgb, var(--color-primary) 34%, transparent);
}

/* Secondary — a real action that is not the page's one ask.
   A frame in the brand colour over a faint wash of it: it reads as a
   button from across the screen without competing with the filled
   primary beside it. */
.btn--secondary {
  --btn-bg: color-mix(in srgb, var(--color-primary) 8%, transparent);
  --btn-fg: var(--color-text);
  --btn-border: color-mix(in srgb, var(--color-primary) 50%, transparent);
}
.btn--secondary:hover {
  --btn-bg: var(--color-primary-soft);
  --btn-border: var(--color-primary);
  --btn-fg: var(--color-primary-text);
}

/* Ghost — the quiet register: going back, a side option, a toolbar.
   It used to be bare grey text, which merchants read as a label rather
   than something to press (owner, 2026-09-16). Now a neutral frame on
   a surface one step up: still the quietest button, but a button. */
.btn--ghost {
  --btn-bg: var(--color-surface-raised);
  --btn-fg: var(--color-text);
  --btn-border: var(--color-line-strong);
}
.btn--ghost:hover {
  --btn-bg: var(--color-primary-soft);
  --btn-border: var(--color-growth);
}

/* Danger — what cannot be taken back: delete, remove, cancel a
   renewal. A red frame and wash at rest, solid red under the pointer,
   so it is never mistaken for the row's ordinary verb. */
.btn--danger {
  --btn-bg: var(--color-danger-soft);
  --btn-fg: var(--color-danger);
  --btn-border: color-mix(in srgb, var(--color-danger) 45%, transparent);
}
.btn--danger:hover {
  --btn-bg: var(--color-danger);
  --btn-fg: var(--color-on-error);
  --btn-border: var(--color-danger);
}

/* Gold — reserved for upgrade / premium. If it appears twice on
   one screen, one of them is wrong. */
.btn--gold {
  --btn-bg: var(--color-gold-soft);
  --btn-fg: var(--color-gold-text);
  --btn-border: var(--color-line-gold);
}
.btn--gold:hover {
  --btn-bg: var(--color-gold);
  --btn-fg: var(--color-on-secondary);
  --btn-border: var(--color-gold);
  --btn-sheen: var(--gradient-sheen);
  --btn-shadow: var(--shadow-soft);
}

.btn--lg {
  --btn-pad-block: 0.95rem;
  --btn-pad-inline: 1.9rem;
  font-size: var(--fs-base);
}
.btn--sm {
  --btn-pad-block: 0.45rem;
  --btn-pad-inline: 0.9rem;
  font-size: var(--fs-xs);
}
.btn--block { width: 100%; }

/* Icon-only — square, and it must still carry an aria-label */
.btn--icon {
  --btn-pad-block: 0.6rem;
  --btn-pad-inline: 0.6rem;
  --btn-border: var(--color-line);
}
.btn--icon:hover { --btn-border: var(--color-line-strong); }

/* ---------------------------------------------------------
   Action — the row's own verb

   For the one thing a table row exists to do: open it. It is
   louder than a ghost and quieter than the page's primary
   button, which is the register a list of rows needs — twenty
   filled blue buttons down a column is a list where nothing
   is more important than anything else.

   The soft primary wash rather than a solid fill for the same
   reason: it reads as a control at a glance without competing
   with the one filled button the screen actually has.
   --------------------------------------------------------- */
.btn--action {
  --btn-bg: var(--color-primary-soft);
  --btn-fg: var(--color-primary-text);
  --btn-border: transparent;
  font-weight: var(--fw-bold);
}
.btn--action:hover {
  --btn-bg: var(--color-primary);
  --btn-fg: var(--color-on-primary);
  --btn-shadow: var(--shadow-soft);
  /* 1.02, not more: a button that grows in a dense row pushes
     its neighbours in the eye even when the layout holds. */
  transform: scale(1.02);
}
.btn--action:active { transform: scale(0.99); }

/* A row action under a finger has nothing to hover from, and a
   scale that only fires on tap reads as a stutter. */
@media (hover: none) {
  .btn--action:hover { transform: none; }
}

/* ---------------------------------------------------------
   Touch floor

   A finger does not aim as well as a cursor. On a coarse
   pointer every button clears 48px of height, and the page's
   primary ask goes full width once the row can no longer hold
   two buttons side by side.
   --------------------------------------------------------- */
@media (pointer: coarse) {
  .btn { min-block-size: 48px; }
  .btn--sm { min-block-size: 44px; }
}

/* components/busy.css */
/* =========================================================
   Component — Busy

   What a slow request looks like.

   Until now it looked like nothing. A merchant uploading a 40 MB
   clip, or a shopper on a Syrian mobile connection pressing «أكمل
   الطلب», pressed a button that did not change, waited on a page
   that did not change, and pressed it again — which is how a
   checkout becomes two orders and an upload becomes two files.

   The whole system hangs off one attribute, `aria-busy="true"`,
   which is what the platform is already obliged to set for screen
   readers. js/busy.js sets it on the form and on the button that
   submitted it, and everything below follows from that: no state
   class to keep in sync, no second vocabulary, and a control that
   is announced as busy is also drawn as busy.

   Everything animated here is a transform or an opacity on a
   pseudo-element, so nothing below reflows the page or repaints a
   layer the browser cannot promote. The reduced-motion rule in
   app.css freezes every animation on the platform, so both
   indicators have a still form that still reads as «working» —
   otherwise reduced motion would mean no feedback at all, which is
   the bug this file exists to fix.
   ========================================================= */

/* ---------------------------------------------------------
   The button

   The label is not removed and the button is not resized: the
   label goes transparent in place and a ring is drawn over it.
   Swapping the text for a spinner re-measures the button, which
   moves the row it sits in at the exact moment the person is
   still looking at where they clicked.

   Not [disabled]: a disabled submit button is dropped from the
   payload it was about to send, so a form that reads which button
   was pressed loses that. It is taken out of reach with
   pointer-events instead, and js/busy.js refuses the second
   submit outright.
   --------------------------------------------------------- */
.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  cursor: progress;
}

/* The icon inside a button paints in currentColor, which is now
   transparent — but an <svg> with its own fill would still show
   through the ring, so the whole label is hidden as one. */
.btn[aria-busy="true"] > * {
  opacity: 0;
}

.btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: 1.15em;
  height: 1.15em;
  margin-block-start: -0.575em;
  margin-inline-start: -0.575em;
  border-radius: var(--radius-pill);

  /* Half a ring drawn, half left open — the gap is what makes the
     rotation legible; a closed ring spinning looks still.

     Two borders coloured and two left transparent, rather than one
     translucent ring under one opaque arc: no colour function is
     involved, so there is nothing here a browser can fail to parse
     and leave the merchant with an invisible spinner.

     --btn-fg rather than currentColor, because currentColor is the
     transparent the label was just set to. */
  border: 2px solid transparent;
  border-block-start-color: var(--btn-fg);
  border-inline-end-color: var(--btn-fg);

  animation: athar-spin var(--dur-spin) var(--ease-linear) infinite;
  will-change: transform;
}

@keyframes athar-spin {
  to { transform: rotate(1turn); }
}

/* ---------------------------------------------------------
   The form

   A thread of light along the top edge, crawling. It is drawn on
   the form itself rather than on the page, so the person's eye
   does not have to leave the thing they are waiting on — and it
   costs one composited pseudo-element rather than a fixed overlay
   that would have to sit above the grain layer.

   The fields go quiet but stay readable: a form that greys out
   entirely while it uploads hides the very data the person is
   waiting to see accepted.
   --------------------------------------------------------- */
form[aria-busy="true"] {
  position: relative;
  cursor: progress;
}

form[aria-busy="true"] .field__control,
form[aria-busy="true"] .field__check,
form[aria-busy="true"] .field__file {
  opacity: 0.62;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

form[aria-busy="true"]::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-soft);
  pointer-events: none;
}

form[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 34%;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  pointer-events: none;

  animation: athar-crawl var(--dur-crawl) var(--ease-in-out) infinite;
  will-change: transform;
}

/* Travels the width of the form and back, in transform only. The
   percentages are of the bar's own 34%, so the two ends land flush
   against the form's edges at any width. */
@keyframes athar-crawl {
  0% { transform: translateX(0); }
  50% { transform: translateX(194%); }
  100% { transform: translateX(0); }
}

/* RTL runs the other way: translateX is physical, and a progress
   bar that crawls away from the reading edge reads as rewinding. */
[dir="rtl"] form[aria-busy="true"]::after {
  animation-name: athar-crawl-rtl;
}

@keyframes athar-crawl-rtl {
  0% { transform: translateX(0); }
  50% { transform: translateX(-194%); }
  100% { transform: translateX(0); }
}

/* ---------------------------------------------------------
   A busy region that is neither

   A panel refreshing in place, a table reloading a page of rows.
   Same attribute, same vocabulary, no spinner: the content dims
   and stops taking clicks, which is all a region needs to say.
   --------------------------------------------------------- */
[data-busy-region][aria-busy="true"] {
  opacity: 0.55;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

/* ---------------------------------------------------------
   Reduced motion

   app.css cuts every animation on the platform to 0.01ms, so both
   indicators above stop dead — the spinner freezes at whatever
   angle it started on and the bar parks at the inline start. Left
   alone, that is a loading state that does not load.

   So under reduced motion they become still marks instead: a
   complete ring rather than a stalled three-quarter one, and a
   full-width bar rather than a stalled third of one. Neither
   moves, both say the same thing, and aria-busy is carrying the
   message to anybody not looking at either.
   --------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy="true"]::after {
    border-color: var(--btn-fg);
  }

  form[aria-busy="true"]::after {
    width: 100%;
    opacity: 0.7;
  }
}

/* A link that is navigating.

   Same ring the buttons get, and the same reason it is not a
   width change: the row must not move while the next page is
   still loading. */
a.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  cursor: progress;
}

/* components/field.css */
/* =========================================================
   Component — Field (label + control + help/error)
   ========================================================= */
.field {
  --field-bg: var(--color-surface-sunken);
  --field-border: var(--color-line);
  --field-fg: var(--color-text);

  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field__label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--color-text);
}
.field__required {
  display: inline-block;
  margin-inline-start: var(--space-2);
  padding: 0.05em 0.5em;
  border: 1px solid var(--color-gold);
  border-radius: var(--radius-xs);
  color: var(--color-gold);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: 1.5;
  vertical-align: 0.1em;
}

/*
   The three custom properties are scoped to .field, so a control written on
   its own — a select inside a table row, a filter with no label — resolved
   them to nothing and rendered as bare text on a transparent ground: no
   border, no background, invisible as a control. The admin stores screen
   worked around it by redeclaring all three on .store-status; the next
   screen to do the same would be the second copy of a fix.

   The fallbacks below are read only when nothing above has set them, so a
   control inside .field still takes .field's values — including the ones
   .field.is-invalid overrides.
*/
.field__control {
  width: 100%;
  padding: 0.8rem 1rem;
  border: 1px solid var(--field-border, var(--color-line));
  border-radius: var(--radius-sm);
  background: var(--field-bg, var(--color-surface-sunken));
  color: var(--field-fg, var(--color-text));
  font-size: var(--fs-sm);
  transition:
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}
.field__control::placeholder { color: var(--color-text-muted); }
.field__control:hover { --field-border: var(--color-line-strong); }
.field__control:focus {
  outline: none;
  --field-border: var(--color-focus);
  box-shadow: var(--ring-focus);
}
.field__control:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Below 16px, iOS Safari reads a tap into the field as a request to zoom
   the whole page in — a finger never taps precisely enough to explain the
   jump away, so it reads as the page malfunctioning. Desktop keeps the
   smaller size; a mouse click never triggers it. */
@media (pointer: coarse) {
  .field__control { font-size: 16px; }
}

textarea.field__control {
  min-height: 7rem;
  resize: vertical;
  line-height: var(--lh-normal);
}

/*
   The chevron is two solid half-squares that meet at a point, drawn in
   currentColor so no icon file and no hardcoded hex is involved.

   Order matters and was reversed until 2026-08-27: the first gradient is
   the triangle whose mass sits top-RIGHT, so it has to be the LEFT half of
   the pair, and the second (mass top-LEFT) the right half. Placed the other
   way round the two wedges lean apart and the arrow reads as a notched bar
   rather than a v. The offsets are in px because the two boxes must sit
   exactly one width apart: 0.25rem between them left a pixel of overlap.
*/
select.field__control {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: left 13px top 55%, left 18px top 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-inline-start: 2.4rem;
}
:root[dir="ltr"] select.field__control {
  background-position: right 18px top 55%, right 13px top 55%;
  padding-inline-start: 1rem;
  padding-inline-end: 2.4rem;
}

.field__help {
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
}

/* Errors state what happened and what to do. They do not apologise. */
.field__error {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--color-danger);
}

.field.is-invalid { --field-border: var(--color-danger); }
.field.is-invalid .field__control:focus {
  box-shadow: 0 0 0 3px var(--color-danger-soft);
}

/* Input with a trailing affordance (password reveal, unit, search) */
.field__wrap { position: relative; display: flex; align-items: center; }
.field__wrap .field__control { padding-inline-end: 2.9rem; }
.field__affix {
  position: absolute;
  inset-inline-end: 0.6rem;
  display: inline-flex;
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
}
.field__affix:hover { color: var(--color-text); }
.field__affix:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 1px;
  color: var(--color-text);
}

/* One eye at a time, chosen by the button's own pressed state — the same
   pattern as the sun and moon on the theme toggle. Pressed means the secret
   is on screen, so the crossed-out eye is what you press to hide it again. */
.field__eye { inline-size: 1.15rem; block-size: 1.15rem; }
.field__affix .field__eye--shut,
.field__affix[aria-pressed="true"] .field__eye--open { display: none; }
.field__affix[aria-pressed="true"] .field__eye--shut { display: inline; }
.field__affix[aria-pressed="true"] { color: var(--color-text); }
/* =========================================================
   Component — Field · additions
   ---------------------------------------------------------
   م0 defined the label, the control, the help line and the error.
   Two things the forms in م6–م15 needed and it did not have: a
   checkbox row, and a way to lay fields out side by side.
   ========================================================= */

/* A checkbox and its label on one line. The box keeps the brand
   colour rather than the browser's, and the whole row is the hit
   target — a 13px square is not one. */
.field__check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--fs-sm);
  color: var(--color-text);
  cursor: pointer;
  line-height: var(--lh-snug);
}
.field__check input[type="checkbox"] {
  flex: none;
  inline-size: 1.1rem;
  block-size: 1.1rem;
  margin-block-start: 0.15rem;
  accent-color: var(--color-primary);
  cursor: pointer;
}
.field__check:hover { color: var(--color-text); }
.field__check input:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* Fields in a row. Collapses to one column early: two number inputs
   side by side at 380px are two inputs nobody can read the labels of. */
.field-row {
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 620px) {
  .field-row--2 { grid-template-columns: 1fr 1fr; }
  .field-row--3 { grid-template-columns: repeat(3, 1fr); }
}

/* A group of fields under a heading, inside a card. */
.field-set {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* ---------------------------------------------------------
   The file picker
   ---------------------------------------------------------
   ::file-selector-button could be painted, but the words on
   it belong to the browser: «Choose file / No file chosen»,
   in the browser's language, unmoved by our own switch. So
   the input is moved out of sight and a face is drawn for
   it — and the input stays focusable, because a control you
   cannot reach with Tab is worse than an ugly one.

   The input is 1px at the start corner rather than pushed
   off with a negative offset: in RTL the overflow lands on
   the left, and a -1px margin is a scrollbar across the
   whole page.
   --------------------------------------------------------- */
.field__file { position: relative; }

.field__file-input {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
  pointer-events: none;
}

.field__file-face {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  border: 1px solid var(--field-border, var(--color-line));
  border-radius: var(--radius-sm);
  background: var(--field-bg, var(--color-surface-sunken));
  cursor: pointer;
  transition:
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}
.field__file-face:hover { --field-border: var(--color-line-strong); }

/* The ring goes on the face, because the face is what is on screen. */
.field__file-input:focus-visible + .field__file-face {
  --field-border: var(--color-focus);
  box-shadow: var(--ring-focus);
}
.field.is-invalid .field__file-input:focus-visible + .field__file-face {
  box-shadow: 0 0 0 3px var(--color-danger-soft);
}
.field__file-input:disabled + .field__file-face {
  opacity: 0.5;
  cursor: not-allowed;
}

.field__file-btn {
  flex: none;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xs);
  background: var(--color-surface-raised);
  color: var(--color-text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
}

/* One line however many names: a chosen file must not push the
   button out of the card. */
.field__file-name {
  min-inline-size: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
}
.field__file.has-file .field__file-name {
  color: var(--color-text);
  font-weight: var(--fw-medium);
}

/* A value that cannot be edited, shown where its input would have been. It
   keeps the field's rhythm so a locked row does not fall out of the column. */
.field__static {
  padding: var(--space-2) 0;
  color: var(--color-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}

/* ---------------------------------------------------------
   A line under a field that offers a way out
   ---------------------------------------------------------
   The same size and colour as .field__help, because it is
   the same kind of line: quiet, and read only by somebody
   who has stopped and looked.
   --------------------------------------------------------- */
.field__aside {
  margin: calc(var(--space-2) * -1) 0 0;
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
  line-height: var(--lh-snug);
}

/* A button that has to read as a link: it submits, so it cannot be an <a>,
   and it sits mid-sentence, so it cannot look like a button either. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--color-primary-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.link-button:hover { color: var(--color-text); }
.link-button:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ---------------------------------------------------------
   The shape of a date
   ---------------------------------------------------------
   An empty date input carries the browser's own «يوم/شهر/سنة»,
   and Chrome lays the three words out left to right — day
   first on the left, the order the figures go in. Read right
   to left, as Arabic is, they arrive as سنة/شهر/يوم and the
   field looks like it is asking for the year first. dir on
   the input does not move them; neither does direction on
   ::-webkit-datetime-edit. The order is fixed in the browser.

   So while the box is empty and unfocused the browser's line
   is painted transparent — the calendar button is an image
   and stays where it was — and ours is printed over it as
   ordinary Arabic. A filled field is left alone: ٢٥/٠٣/١٩٩٠
   is a run of numerals and reads correctly on its own.

   .is-empty comes from js/date-field.js, so a page with no
   JavaScript keeps the browser's field untouched rather than
   an empty box with nothing written in it.
   --------------------------------------------------------- */
.field__date { position: relative; display: flex; align-items: center; }

/* The side it sits on is the side the browser puts its own figures
   on, which is the browser locale's and not the page's: an Arabic
   Chrome keeps the date box RTL — calendar button on the left, text
   on the right — even after the page switches to English. So the
   dir here is written by js/date-field.js from what the input
   actually computed to. Anchored to the page instead, the hint slid
   under the calendar button the moment somebody pressed EN. */
.field__date-hint {
  position: absolute;
  inset-inline-start: 1rem;
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  pointer-events: none;
  opacity: 0;
}
.field__date-hint[dir="rtl"] { inset: auto 1rem auto auto; }
.field__date-hint[dir="ltr"] { inset: auto auto auto 1rem; }

.field__date.is-empty:not(:focus-within) .field__control { color: transparent; }
.field__date.is-empty:not(:focus-within) .field__date-hint { opacity: 1; }

/* components/alert.css */
/* =========================================================
   Component — Alert
   ---------------------------------------------------------
   The line that says what just happened. Tinted rather than
   filled: a solid green bar across the top of a page reads as
   an interruption, and most of what it announces is routine.
   ========================================================= */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--alert-line, var(--color-line));
  border-radius: var(--radius-md);
  background: var(--alert-bg, var(--color-surface));
  color: var(--color-text);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.alert + .alert { margin-block-start: var(--space-3); }

.alert svg {
  flex: none;
  inline-size: 1.15rem;
  block-size: 1.15rem;
  margin-block-start: 0.1rem;
  color: var(--alert-icon, var(--color-text-muted));
}

.alert p { margin: 0; }
.alert p + p { margin-block-start: var(--space-1); }

.alert--success {
  --alert-bg: var(--color-success-soft);
  --alert-line: color-mix(in srgb, var(--color-success) 34%, transparent);
  --alert-icon: var(--color-success);
}

.alert--danger {
  --alert-bg: var(--color-danger-soft);
  --alert-line: color-mix(in srgb, var(--color-danger) 34%, transparent);
  --alert-icon: var(--color-danger);
}

.alert--warning {
  --alert-bg: var(--color-warning-soft);
  --alert-line: color-mix(in srgb, var(--color-warning) 34%, transparent);
  --alert-icon: var(--color-warning);
}

.alert--info {
  --alert-bg: var(--color-primary-soft);
  --alert-line: color-mix(in srgb, var(--color-primary) 30%, transparent);
  --alert-icon: var(--color-primary-text);
}

/* A hairline rule of the same tint, for notes that sit inside a card
   and should not repeat the card's own border. */
.alert--flat {
  border: 0;
  border-inline-start: 3px solid var(--alert-icon, var(--color-line));
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
:root[dir="ltr"] .alert--flat {
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}

/* components/platform-toggle.css */
/* =========================================================
   Component — Platform toggles (language + theme)

   Two small square controls that travel together wherever the
   shell goes: header, drawer, footer, auth card. The theme button
   shows the mode it will switch you to, not the one you are in —
   a control should be labelled by its action.
   ========================================================= */
.platform-toggles {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.platform-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}
.platform-toggle:hover {
  border-color: var(--color-line-strong);
  background: var(--color-surface-raised);
  color: var(--color-text);
}

.platform-toggle--lang {
  width: auto;
  padding-inline: var(--space-3);
  font-family: var(--font-latin);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide);
}

.platform-toggle__icon { width: 17px; height: 17px; }

/* Only one of the two icons is ever drawn: sun while dark is on
   (press it and you get light), moon while light is on. */
:root .platform-toggle__icon--moon,
:root[data-theme="light"] .platform-toggle__icon--sun { display: none; }
:root .platform-toggle__icon--sun,
:root[data-theme="light"] .platform-toggle__icon--moon { display: block; }

/* Theme and language are the two controls a visitor reaches for on
   a phone with one hand. 34px was a miss waiting to happen. */
@media (pointer: coarse) {
  .platform-toggle {
    min-inline-size: 44px;
    min-block-size: 44px;
  }
}

/* pages/auth.css */
/* =========================================================
   Page — Auth (login · register · password)
   ---------------------------------------------------------
   One centred card on the graphite ground, with a single brass
   pool of light behind it. No split-screen marketing panel:
   somebody on this page has already decided, and a column of
   selling copy beside the password field only delays them.
   ========================================================= */

.auth-body {
  min-block-size: 100dvh;
}

.auth {
  position: relative;
  display: flex;
  flex-direction: column;
  min-block-size: 100dvh;
  padding: var(--space-5);
  gap: var(--space-8);
  /* The glow is wider than the viewport on a phone by design. clip lets it
     bleed without becoming something to scroll to; clip rather than hidden
     so the vertical axis stays visible. */
  overflow-x: clip;
}

/* The one atmospheric touch: brass, low, behind the card. */
.auth__glow {
  position: absolute;
  inset-block-start: -14vh;
  /*
     Centred by auto margins, not by a translate off a logical edge.
     inset-inline-start: 50% resolves to right: 50% in Arabic, and the
     -50% translate then walks the pool further the same way: it landed
     507px past the left edge, which in RTL is scrollable overflow. Every
     auth page — sign in, register, reset, the store wizard — opened with
     a sideways scrollbar and the whole layout pushed off centre.
  */
  inset-inline: 0;
  margin-inline: auto;
  inline-size: min(1100px, 140vw);
  block-size: min(760px, 92vh);

  /*
    A mesh, not a pool.

    One centred radial is a spotlight, and a spotlight behind a card
    reads as a stage rather than as a room. Four offset radials at
    different sizes overlap into a field with no findable centre —
    which is what a mesh gradient is, and why it reads as light in a
    space rather than as a lamp pointed at something.

    Every stop is a brand token: teal at the top where the mark's
    arrow head is, brass low and wide, blue at the foot. The ramp is
    the logo's own, spent here as atmosphere rather than as a line —
    which is the one use the trace rail does not claim.

    Four layers of radial at 92vh is still one paint, and the whole
    thing is behind a card that blurs it again.
  */
  background:
    radial-gradient(38% 42% at 18% 22%, color-mix(in srgb, var(--color-growth) 22%, transparent) 0%, transparent 72%),
    radial-gradient(44% 38% at 82% 16%, color-mix(in srgb, var(--color-primary) 24%, transparent) 0%, transparent 74%),
    radial-gradient(52% 48% at 50% 88%, color-mix(in srgb, var(--color-gold) 20%, transparent) 0%, transparent 70%),
    radial-gradient(36% 34% at 72% 62%, color-mix(in srgb, var(--color-primary-deep) 26%, transparent) 0%, transparent 68%);

  filter: blur(46px);
  pointer-events: none;
}

/*
  The mesh drifts, very slowly, and only where motion is welcome.

  Twenty-two seconds for one pass and never more than 3% of travel:
  at that rate nobody watching the sign-in form sees it move, and
  anybody who looks up after typing their password finds the light
  somewhere slightly different. Transform only — the gradient itself
  is never recomputed.
*/
@media (prefers-reduced-motion: no-preference) {
  .auth__glow {
    animation: auth-mesh 22s var(--ease-in-out) infinite alternate;
  }
}

@keyframes auth-mesh {
  from { transform: translate3d(-1.5%, -1%, 0) scale(1); }
  to { transform: translate3d(1.5%, 1%, 0) scale(1.06); }
}

.auth__bar,
.auth__foot {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  inline-size: min(100%, var(--container-max));
  margin-inline: auto;
}

.auth__main {
  position: relative;
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
}

.auth__foot {
  flex-wrap: wrap;
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
}
.auth__foot p { margin: 0; }
.auth__foot nav {
  display: flex;
  gap: var(--space-4);
}
.auth__foot a {
  color: var(--color-text-muted);
  text-decoration: none;
}
.auth__foot a:hover {
  color: var(--color-text-secondary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------------------------------------------------------
   The card
   --------------------------------------------------------- */
/* ---------------------------------------------------------
   The card

   It floats: the ground behind it is a wash rather than a
   surface, so the card has to carry its own separation. That
   is --shadow-deep — the platform's «picked up» elevation,
   spent here because this is the one screen with a single
   object on it and nothing else to compete for depth.

   The backdrop filter is the second half of floating. The
   glow behind the card is blurred at the source, but the
   surface is not fully opaque in light mode, so a saturating
   backdrop keeps the pool of brass reading *through* the
   card rather than being covered by it. Browsers without it
   lose the tint and nothing else.
   --------------------------------------------------------- */
.auth-card {
  position: relative;
  inline-size: min(100%, 27rem);
  padding: var(--space-panel-loose) var(--space-panel);
  border: 1px solid var(--color-line);
  border-block-start: 1px solid var(--color-line-gold);
  border-radius: var(--radius-premium);
  background: var(--color-surface);
  box-shadow: var(--shadow-deep);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
}

/* The lit top edge. One hairline of white at 8%, which is what
   a real surface catching light from above looks like — and
   the reason the card reads as raised rather than pasted on. */
.auth-card::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: var(--space-8);
  block-size: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgb(255 255 255 / 0.09) 50%,
    transparent 100%
  );
  pointer-events: none;
}

.auth-card__head {
  margin-block-end: var(--space-6);
}
.auth-card__title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display-ar);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  color: var(--color-text);
}
.auth-card__note {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--color-text-secondary);
  line-height: var(--lh-snug);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.auth-form__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--fs-xs);
}
.auth-form__row a {
  color: var(--color-primary-text);
  text-decoration: none;
}
.auth-form__row a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The small print under a submit button. */
.auth-form__note {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
  line-height: var(--lh-snug);
  text-align: center;
}

.auth-card__foot {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--color-line);
  font-size: var(--fs-sm);
  color: var(--color-text-secondary);
  text-align: center;
}
.auth-card__foot a {
  color: var(--color-primary-text);
  font-weight: var(--fw-bold);
  text-decoration: none;
}
.auth-card__foot a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (max-width: 480px) {
  .auth { padding: var(--space-4); gap: var(--space-6); }
  .auth-card { padding: var(--space-6) var(--space-5); }
  .auth__foot { justify-content: center; text-align: center; }
}

/* ---------------------------------------------------------
   The one action

   Full width, and the only place on an auth screen that
   carries the brand gradient. --gradient-primary was defined
   in §7 and spent nowhere; a single filled control on an
   otherwise quiet card is exactly the large surface it was
   written for, and there is no small text sitting on it whose
   contrast the sheen could disturb.
   --------------------------------------------------------- */
.auth-form .btn--primary {
  --btn-bg: transparent;
  --btn-sheen: var(--gradient-primary);
  --btn-shadow: var(--shadow-soft);
  padding-block: 0.85rem;
  border-radius: var(--radius-md);
  font-size: var(--fs-base);
}
.auth-form .btn--primary:hover {
  --btn-shadow: var(--shadow-deep);
  filter: saturate(112%);
}

/* A required field says so where the eye already is — on the
   label, not in a legend under the form. */
/* Every field on a sign-in form is required, so a word on each would be
   noise: the star stays here, drawn over the word the component prints. */
.auth-form .field__required {
  margin-inline-start: 0.15em;
  padding: 0;
  border: 0;
  font-size: 0;
  color: var(--color-danger);
}
.auth-form .field__required::after {
  content: "*";
  font-size: var(--fs-sm);
}
/* ---------------------------------------------------------
   Glass

   A backdrop-filter only reads as glass if the surface is not
   opaque — blurring something and then covering it produces a
   card with an expensive no-op behind it. The surface drops to
   88% and the mesh comes through at exactly the strength the
   blur left it.

   Guarded on @supports: a browser without backdrop-filter gets
   the solid surface it had before, which is correct rather than
   a translucent card over an unblurred gradient.
   --------------------------------------------------------- */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .auth-card {
    background: color-mix(in srgb, var(--color-surface) 88%, transparent);
  }
}

/* The edge. A glass panel catches light on the side it is lit
   from and loses it on the other, which one flat border cannot
   say — so the inline-start edge is brighter than the rest. */
.auth-card {
  border-inline-start-color: color-mix(in srgb, var(--color-line-strong) 70%, transparent);
}

/* ---------------------------------------------------------
   Continue with Google

   Above the form and quieter than its submit: a surface
   button with Google's own mark, then a rule broken by «or».
   Google's brand rules ask for the four-colour G on a plain
   ground, which is why this is not the gradient button.
   --------------------------------------------------------- */
.auth-google {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin-block-end: var(--space-5);
}

.auth-google__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-block-size: 3rem;
  padding: 0.75rem var(--space-4);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  text-decoration: none;
  transition: border-color var(--dur-fast, 150ms) ease, background-color var(--dur-fast, 150ms) ease;
}
.auth-google__btn:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 6%, var(--color-surface));
}
.auth-google__btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.auth-google__mark {
  flex: none;
}

.auth-google__or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
}
.auth-google__or::before,
.auth-google__or::after {
  content: "";
  flex: 1;
  block-size: 1px;
  background: var(--color-line);
}
