﻿/* ══════════════════════════════════════════════════════════════════════════
   Petromix Field — design tokens
   Derived from design-refs/linear-DESIGN.md (VoltAgent/awesome-design-md).

   WHAT IS BORROWED FROM LINEAR, AND WHAT IS NOT.
   "Linear-style" is doing real work in this file, so a future reader deserves
   to know which decisions came from the reference and which are ours.

   Taken from the reference:
     · hierarchy carried by an elevation ladder, not by heavy shadows
     · 1px hairline borders as the primary separator
     · 4px spacing base, and its exact scale
     · the radius scale, including 8px buttons. The reference says, verbatim,
       "Don't pill-round CTAs"
     · aggressive negative letter-spacing on display sizes
     · display weight 600 against body weight 400; it resists 700+
     · its own named free substitutes: Inter and JetBrains Mono

   Deliberately NOT taken, with reasons:
     · Lavender #5e6ad2 as the accent. Petromix's brand is amber, it is on the
       drums and the logo, and the reference's own rule is one accent only.
       We take Linear's STRUCTURE and keep our COLOUR.
     · "Don't ship a light-mode page." Written for Linear's MARKETING site.
       This is a field app whose users stand in Egyptian daylight, so light
       mode is legibility, not preference.
     · 96px section rhythm. Marketing spacing. This is a dense sales tool at
       VISUAL_DENSITY 7.

   ── THE LIGHT-MODE PROBLEM, AND THE FIX ──────────────────────────────────
   Linear's ladder lifts panels OUT of a near-black canvas by getting lighter.
   On a white page that runs out immediately: once a card is white there is
   nowhere further up to go, and the first attempt at this file had a
   "featured" card at #f6f7f9 on a #fbfbfc canvas, which was invisible.

   So light mode inverts the ground AND switches the mechanism:
     · the canvas drops to a tinted grey, and cards lift to PURE WHITE
     · elevation above that is carried by SHADOW, not by more lightness

   That is not a betrayal of the reference. The reference avoids shadows on
   dark because they do not read on dark. On light they are the natural
   mechanism, and its actual principle, "hierarchy must be legible", is what
   we are keeping.

   Hence the tokens below are ELEVATION TRIPLETS (background / border /
   shadow) rather than bare surface colours. A component asks for a level and
   gets whatever that level means in the current mode.

   THEME MECHANICS. Dark is the default, because that is what the app is
   today. The system preference switches to light, and an explicit
   data-theme on <html> beats both so the in-app toggle always wins.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Self-hosted fonts ─────────────────────────────────────────────────────
   These were loaded from fonts.googleapis.com. The service worker refuses
   cross-origin requests by design, so offline the app fell back to system-ui
   and every figure lost its tabular numerals. Self-hosting fixes a real bug.

   Variable fonts: one file each covers weights 400 to 700, which is why two
   files total 86 KB rather than the six static cuts they replace.

   LATIN ONLY, deliberately. Arabic is NOT served from here. The app's Arabic
   stack stays on system fonts (see rtl.css) so an Arabic rep downloads no
   webfont at all, which was a considered offline decision and must not
   regress. unicode-range keeps the browser from even fetching these for
   Arabic text. */
@font-face {
  font-family: "Inter";
  src: url("./fonts/inter-var.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
                 U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("./fonts/jetbrains-mono-var.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+2212, U+FEFF;
}

:root {
  /* ── Ground ─────────────────────────────────────────────────────────── */
  --canvas: #08080a;          /* not #000 — the reference is explicit */

  /* ── Elevation ladder ───────────────────────────────────────────────────
     Each level is a triplet. Do not skip levels: a level-3 panel sitting
     directly on the canvas reads as a mistake.

       0  flat      body copy, hero type, footers
       1  card      default cards, panels
       2  featured  the one card that matters, hover state
       3  floating  dropdowns, sheets, the notification panel
  */
  /* Separation comes from FILL CONTRAST, not from a border.
     The reference mock has no visible hairlines anywhere: its cards read as
     cards because they are meaningfully lighter than a near-black canvas.
     Ours were #131316 on #0a0a0b, a 2% step, which needed a hairline to be
     legible at all. Widening the step to ~8% lets the border almost vanish,
     which is what makes the surface feel like an object rather than a box. */
  --e1-bg:     rgba(28, 28, 31, .90);
  --e1-border: rgba(255, 255, 255, .04);
  --e1-shadow: inset 0 1px 0 rgba(255, 255, 255, .04);   /* top edge highlight */

  --e2-bg:     rgba(38, 38, 42, .90);
  --e2-border: rgba(255, 255, 255, .07);
  --e2-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);

  --e3-bg:     #303035;
  --e3-border: rgba(255, 255, 255, .09);
  --e3-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 16px 40px rgba(0, 0, 0, .55);

  --hairline:        rgba(255, 255, 255, .08);
  --hairline-strong: rgba(255, 255, 255, .14);

  /* ── Wash ──────────────────────────────────────────────────────────────
     Translucent overlays for hover rows, zebra striping and inset chips,
     where a solid surface token would be too heavy. These exist because the
     codebase was full of rgba(255,255,255,.05) films, which are invisible the
     moment the surface underneath turns white. */
  --wash:        rgba(255, 255, 255, .055);
  --wash-strong: rgba(255, 255, 255, .10);

  /* ── Glass ─────────────────────────────────────────────────────────────
     Translucent CHROME only: the rep app's top bar and bottom rail, which
     content scrolls underneath. Cards do not get this. The reference builds
     depth from flat surfaces and hairlines, and the design skill calls
     glassmorphism inappropriate for dense product UI, so the translucency
     stays where it does real work and comes off everything else.
     It is the canvas colour with alpha, so the bar reads as the page dimmed
     rather than as a separate material. */
  --glass: rgba(10, 10, 11, .78);

  /* Ambient background. Faint on purpose: strong enough to give the canvas
     depth, far too weak to move the contrast of anything sitting on it. */
  --aurora-a:  rgba(247, 161, 26, .34);
  --aurora-b:  rgba(214, 88, 48, .26);
  --grid-line: rgba(255, 255, 255, .085);

  /* Drop-shadow colour for floating chrome (sheets, toasts, popovers).
     Pure black is correct on a dark ground and far too harsh on a light one,
     so it tints toward the canvas in light mode. Scrims are NOT this: a modal
     scrim stays true black in both themes because its job is to dim. */
  --shadow-color: rgba(0, 0, 0, .45);

  /* ── Ink ───────────────────────────────────────────────────────────────
     These four values are the reference's own. */
  --ink:          #f7f8f8;
  --ink-muted:    #d0d6e0;
  /* Lifted from Linear's own #8a8f98. That value measured 5.23:1 on a card,
     which looked safe, but only 3.54:1 on a small chip sitting on a wash over
     a now-translucent card. 5.04:1 there, and 7.44:1 on plain card, so it
     clears AA everywhere it is actually used rather than only on the
     best-case surface. */
  --ink-subtle:   #a6acb5;
  --ink-tertiary: #868b95;

  /* ── Accent ────────────────────────────────────────────────────────────
     Petromix amber. Brand mark, primary action, focus ring, link emphasis.
     Nothing else. */
  --accent:       #f7a11a;
  --accent-hover: #ffb03a;
  --accent-focus: rgba(247, 161, 26, .48);
  --accent-wash:  rgba(247, 161, 26, .12);
  --on-accent:    #1a1206;
  /* Amber text passes on the dark canvas. On white it is 1.9:1, which fails
     WCAG AA at any size, so light mode overrides this one separately. */
  --accent-ink:   #f7a11a;

  /* ── Semantic ──────────────────────────────────────────────────────────
     Two, and no more. The reference ships one on marketing; a sales tool
     genuinely needs win and loss. */
  --success:      #4cc38a;
  --success-wash: rgba(76, 195, 138, .12);
  --danger:       #f2555a;
  --danger-wash:  rgba(242, 85, 90, .12);

  /* ── Spacing ───────────────────────────────────────────────────────────
     4px base, the reference's scale verbatim. Every padding and gap should
     resolve to one of these. */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-6:  24px;
  --space-8:  32px;
  --space-12: 48px;

  /* ── Radius ────────────────────────────────────────────────────────────
     Replaces the fourteen ad-hoc values the audit found. Buttons are 8px.
     --r-pill survives for avatars, toggles and status pills only. */
  /* Scaled up to match the reference, which is noticeably rounder than Linear.
     Cards read as soft objects rather than panels. Buttons stay at 10px: the
     Linear rule against pill CTAs still holds, but 8px looked mean beside
     16px cards. */
  --r-xs:   6px;
  --r-sm:   8px;
  --r-md:   10px;   /* buttons, inputs */
  --r-lg:   16px;   /* cards */
  --r-xl:   20px;   /* hero cards, large panels */
  --r-2xl:  28px;   /* floating chrome */
  --r-pill: 9999px;

  /* ── Type ──────────────────────────────────────────────────────────────
     Named --font-* rather than --ui/--mono ON PURPOSE. app.css and
     manager.css already define --ui and --mono as Archivo; colliding on
     those names would silently reskin the entire app the moment this file
     loads. The swap happens deliberately, in the typography phase. */
  --font-ui:   "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Size / line-height / tracking triplets. Tracking goes negative as size
     grows, which is the most characteristic thing about the reference. */
  --t-display:  32px; --t-display-lh:  1.10; --t-display-ls:  -0.8px;
  --t-headline: 24px; --t-headline-lh: 1.20; --t-headline-ls: -0.5px;
  --t-title:    18px; --t-title-lh:    1.30; --t-title-ls:    -0.3px;
  --t-body:     15px; --t-body-lh:     1.50; --t-body-ls:     -0.05px;
  --t-sm:       13px; --t-sm-lh:       1.50; --t-sm-ls:        0;
  --t-caption:  12px; --t-caption-lh:  1.40; --t-caption-ls:   0;
  --t-button:   14px; --t-button-lh:   1.20; --t-button-ls:    0;
  /* Eyebrows take POSITIVE tracking, against the negative display. The
     reference uses that contrast to mark a label as taxonomy. */
  --t-eyebrow:  11px; --t-eyebrow-lh:  1.30; --t-eyebrow-ls:   0.10em;

  /* ── Premium layer ─────────────────────────────────────────────────────
     DESIGN_VARIANCE 8 · MOTION_INTENSITY 7 · VISUAL_DENSITY 7.

     Sources: design-refs/stripe-DESIGN.md and supabase-DESIGN.md.
     Two mechanisms are lifted from them literally, because they are what make
     those products read as expensive rather than merely tidy:

     · TINTED SHADOWS. Stripe's card lift is rgba(0,55,112,.08), navy, not
       black. A black shadow on a coloured surface reads as dirt; a shadow
       tinted toward the surface's own hue reads as light. Ours tints toward
       the ink.
     · LAYERED SHADOWS. Stripe level 2 is two shadows at once, a tight contact
       shadow plus a wide ambient one. One blur cannot do both jobs, and the
       pair is most of the difference between "has a shadow" and "sits above
       the page".

     The glow is ours. Neither reference glows; a near-black canvas gives a
     1px accent-tinted ring somewhere to read, and it is how the featured card
     earns attention without being painted amber. */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .40);
  --shadow-2: 0 2px 6px rgba(0, 0, 0, .34), 0 8px 24px rgba(0, 0, 0, .40);
  --shadow-3: 0 4px 10px rgba(0, 0, 0, .40), 0 16px 48px rgba(0, 0, 0, .52);

  /* A 1px ring, not a bloom. Outer glows are on the design skill's banned list
     and they smear on a phone; a hairline of accent light does not. */
  --glow-ring:   0 0 0 1px rgba(247, 161, 26, .22);
  --glow-strong: 0 0 0 1px rgba(247, 161, 26, .40), 0 0 24px -6px rgba(247, 161, 26, .32);

  /* Top-lit micro-gradient. Barely visible on purpose: it gives a flat fill a
     direction so it reads as a surface rather than a rectangle. */
  --sheen:      linear-gradient(180deg, rgba(255, 255, 255, .055), rgba(255, 255, 255, 0) 42%);
  --sheen-warm: linear-gradient(180deg, rgba(247, 161, 26, .10), rgba(247, 161, 26, 0) 48%);

  /* ── The one chromatic moment ──────────────────────────────────────────
     The reference is a neutral dark app with exactly ONE saturated object in
     it: the credit card, a blue-to-pink mesh. Everything else is grey. That
     restraint is why the card reads as the subject rather than as decoration.

     Ours is the same idea in the brand's hue. Built as two radial stops over
     a diagonal base rather than a single linear ramp, because a flat ramp
     looks like a button and a mesh looks like a material.

     ONE PER SCREEN. If a second element gets this, neither is special. */
  --hero-grad:
    radial-gradient(at 76% 16%, rgba(255, 205, 112, .55) 0px, transparent 58%),
    radial-gradient(at 18% 88%, rgba(214, 88, 48, .45) 0px, transparent 55%),
    linear-gradient(135deg, #7d3f07 0%, #b9660b 44%, #f0980f 100%);
  /* Ink for text sitting ON that gradient. Near-white in both themes, because
     the gradient itself does not lighten in light mode. */
  --on-hero:      #fffaf2;
  --on-hero-mute: rgba(255, 250, 242, .76);

  /* ── Motion ────────────────────────────────────────────────────────────
     MOTION_INTENSITY 7. Every value below is a hover, tap or entrance on
     something the user is already touching, which is the skill's test: motion
     has to communicate hierarchy, feedback or a state change. Nothing here
     loops, and nothing animates on its own. */
  --ease:       cubic-bezier(.16, 1, .3, 1);   /* fast out, long settle */
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1); /* slight overshoot, for taps */
  --dur-fast:   120ms;
  --dur:        200ms;
  --dur-slow:   320ms;
  --lift:       -2px;      /* hover travel */
  --press:      .985;      /* tap scale */

  color-scheme: dark;
}

/* ── LIGHT ────────────────────────────────────────────────────────────────
   The canvas is a tinted grey and cards lift to pure white. Above level 1,
   elevation is carried by shadow, because lightness has run out.

   Shadows are tinted toward the canvas hue rather than pure black, which is
   the reference's rule in §4.4 of the design skill and the difference between
   a card that floats and one that looks dirty. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --canvas: #eef0f4;

    /* Light mode: cards are WHITE (2026-10-26). One set of surface tokens; e1 / e2 / glass point at it. */
    --surface-card:   #FFFFFF;
    --surface-input:  #FFFFFF;
    --surface-border: rgba(16, 24, 40, .10);
    --surface-track:  #EEF0F3;
    --e1-bg:     var(--surface-card);
    --e1-border: rgba(16, 24, 40, .05);
    --e1-shadow: 0 1px 2px rgba(16, 24, 40, .05);

    --e2-bg:     var(--surface-card);
    --e2-border: rgba(16, 24, 40, .08);
    --e2-shadow: 0 4px 16px rgba(16, 24, 40, .10);

    --e3-bg:     #ffffff;
    --e3-border: rgba(16, 24, 40, .14);
    --e3-shadow: 0 12px 32px rgba(16, 24, 40, .14);

    --hairline:        rgba(16, 24, 40, .09);
    --hairline-strong: rgba(16, 24, 40, .16);

    --wash:        rgba(16, 24, 40, .045);
    --wash-strong: rgba(16, 24, 40, .085);

    --glass: var(--surface-card);

  --aurora-a:  rgba(247, 161, 26, .30);
  --aurora-b:  rgba(214, 88, 48, .26);
  --grid-line: rgba(16, 24, 40, .075);

    /* Premium layer, light. Tinted toward the ink, per Stripe. */
    --shadow-1: 0 1px 2px rgba(16, 24, 40, .06);
    --shadow-2: 0 2px 6px rgba(16, 24, 40, .06), 0 8px 24px rgba(16, 24, 40, .08);
    --shadow-3: 0 4px 10px rgba(16, 24, 40, .08), 0 16px 48px rgba(16, 24, 40, .12);

    --glow-ring:   0 0 0 1px rgba(143, 90, 4, .18);
    --glow-strong: 0 0 0 1px rgba(143, 90, 4, .32), 0 0 24px -6px rgba(247, 161, 26, .34);

    --sheen:      linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(246, 247, 249, 0) 46%);
    --sheen-warm: linear-gradient(180deg, rgba(247, 161, 26, .08), rgba(247, 161, 26, 0) 50%);

    --shadow-color: rgba(16, 24, 40, .16);

    --ink:          #16171a;
    --ink-muted:    #3f434d;
    --ink-subtle:   #545a66;
    --ink-tertiary: #6b7280;

    --accent-hover: #e08f0c;
    --accent-focus: rgba(143, 90, 4, .40);
    --accent-wash:  rgba(247, 161, 26, .16);
    --accent-ink:   #8f5a04;   /* 5.07:1 on --canvas, 5.78:1 on white. Raw amber is 1.9:1 */

    --success:      #0a6340;
    --success-wash: rgba(18, 133, 90, .12);
    --danger:       #c5303a;
    --danger-wash:  rgba(197, 48, 58, .10);

    color-scheme: light;
  }
}

/* The explicit toggle. Beats the system preference in both directions.
   Values identical to the block above; kept as a separate rule rather than a
   shared selector list so that turning either path off is a one-block edit. */
:root[data-theme="light"] {
  --canvas: #eef0f4;

  --surface-card:   #FFFFFF;
  --surface-input:  #FFFFFF;
  --surface-border: rgba(16, 24, 40, .10);
  --surface-track:  #EEF0F3;
  --e1-bg:     var(--surface-card);
  --e1-border: rgba(16, 24, 40, .05);
  --e1-shadow: 0 1px 2px rgba(16, 24, 40, .05);

  --e2-bg:     var(--surface-card);
  --e2-border: rgba(16, 24, 40, .08);
  --e2-shadow: 0 4px 16px rgba(16, 24, 40, .10);

  --e3-bg:     #ffffff;
  --e3-border: rgba(16, 24, 40, .14);
  --e3-shadow: 0 12px 32px rgba(16, 24, 40, .14);

  --hairline:        rgba(16, 24, 40, .09);
  --hairline-strong: rgba(16, 24, 40, .16);

  --wash:        rgba(16, 24, 40, .045);
  --wash-strong: rgba(16, 24, 40, .085);

  --glass: var(--surface-card);

  --aurora-a:  rgba(247, 161, 26, .30);
  --aurora-b:  rgba(214, 88, 48, .26);
  --grid-line: rgba(16, 24, 40, .075);

  /* Premium layer, light. Tinted toward the ink, per Stripe. */
  --shadow-1: 0 1px 2px rgba(16, 24, 40, .06);
  --shadow-2: 0 2px 6px rgba(16, 24, 40, .06), 0 8px 24px rgba(16, 24, 40, .08);
  --shadow-3: 0 4px 10px rgba(16, 24, 40, .08), 0 16px 48px rgba(16, 24, 40, .12);

  --glow-ring:   0 0 0 1px rgba(143, 90, 4, .18);
  --glow-strong: 0 0 0 1px rgba(143, 90, 4, .32), 0 0 24px -6px rgba(247, 161, 26, .34);

  --sheen:      linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(246, 247, 249, 0) 46%);
  --sheen-warm: linear-gradient(180deg, rgba(247, 161, 26, .08), rgba(247, 161, 26, 0) 50%);

  --shadow-color: rgba(16, 24, 40, .16);

  --ink:          #16171a;
  --ink-muted:    #3f434d;
  --ink-subtle:   #545a66;
  --ink-tertiary: #6b7280;

  --accent-hover: #e08f0c;
  --accent-focus: rgba(143, 90, 4, .40);
  --accent-wash:  rgba(247, 161, 26, .16);
  --accent-ink:   #8f5a04;

  --success:      #0a6340;
  --success-wash: rgba(18, 133, 90, .12);
  --danger:       #c5303a;
  --danger-wash:  rgba(197, 48, 58, .10);

  color-scheme: light;
}

/* ── Honouring the user, not the design ──────────────────────────────────
   Both already exist in the app and must not regress. */
@media (prefers-reduced-motion: reduce) {
  /* Zeroing the duration alone is not enough at MOTION_INTENSITY 7: the
     transform still APPLIES, it just arrives instantly, so a hover lift
     becomes a jump, which is worse than the animation for exactly the people
     this query exists to protect. The travel and the press are neutralised
     too, so the hover changes colour and shadow and nothing moves. */
  :root {
    --dur-fast: 0ms; --dur: 0ms; --dur-slow: 0ms;
    --lift: 0px;
    --press: 1;
  }
}
@media (prefers-reduced-transparency: reduce) {
  :root                      { --hairline: rgba(255, 255, 255, .24); }
  :root[data-theme="light"]  { --hairline: rgba(16, 24, 40, .24); }
}

/* ══════════════════════════════════════════════════════════════════════════
   AMBIENT BACKGROUND
   Aurora + grid, behind everything, in both interfaces.

   This replaces `ambientWave`, which was removed earlier for good reasons and
   is worth recording so it does not come back in its old form:

     · it animated BACKGROUND-POSITION on a full-screen gradient, which is a
       paint on every frame across the whole viewport, forever, on a phone a
       rep holds all day. The design skill's §6.A is explicit: animate
       transform and opacity, nothing else.
     · it was hard-coded charcoal, so it ignored the token layer entirely and
       kept the page dark after light mode landed.

   This version fixes both. Everything here animates TRANSFORM only, on a
   fixed, pointer-events-none layer that is composited once and then moved by
   the GPU. Colours come from tokens, so it themes itself.

   It is deliberately faint. At these opacities it cannot shift the contrast
   of text sitting above it, which is why the WCAG suite still measures clean
   against the real surface colours rather than against a moving target.
   ══════════════════════════════════════════════════════════════════════════ */

.bgfx {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none;          /* never intercepts a tap */
  overflow: hidden;              /* the oversized children must not scroll the page */
  contain: strict;               /* isolate: nothing in here can invalidate app layout */
}

/* ── Aurora ────────────────────────────────────────────────────────────────
   Three soft blobs on one element. Inset far past the edges so the element
   can drift without ever revealing a corner. */
.bgfx__aurora {
  position: absolute; inset: -40%;
  background:
    radial-gradient(38% 38% at 22% 28%, var(--aurora-a) 0%, transparent 70%),
    radial-gradient(34% 34% at 78% 22%, var(--aurora-b) 0%, transparent 70%),
    radial-gradient(42% 42% at 60% 82%, var(--aurora-a) 0%, transparent 72%);
  will-change: transform;
  animation: auroraDrift 22s ease-in-out infinite alternate;
}

/* ── Grid ──────────────────────────────────────────────────────────────────
   One repeating pattern rather than many elements, so the whole thing is a
   single layer the compositor can translate. Sized in px, not %, so the
   squares stay square at every viewport. */
.bgfx__grid {
  position: absolute; inset: -10%;
  background-image:
    repeating-linear-gradient(0deg,   var(--grid-line) 0 1px, transparent 1px 64px),
    repeating-linear-gradient(90deg,  var(--grid-line) 0 1px, transparent 1px 64px);
  will-change: transform;
  animation: gridDrift 7s linear infinite;   /* one cell every 7 s (was 16 s, which read as still) */
  /* Fades out toward the edges so the pattern does not simply stop at the
     viewport boundary, which is what makes a grid look pasted on. */
  -webkit-mask-image: radial-gradient(70% 60% at 50% 40%, #000 0%, transparent 100%);
          mask-image: radial-gradient(70% 60% at 50% 40%, #000 0%, transparent 100%);
}

/* Travel is small: a few percent of the viewport over the better part of a
   minute. Large enough to be alive, small enough that you cannot watch it. */
@keyframes auroraDrift {
  0%   { transform: translate3d(-7%, -4%, 0) scale(1.00); }
  50%  { transform: translate3d( 7%,  6%, 0) scale(1.14); }
  100% { transform: translate3d( 4%, -6%, 0) scale(1.05); }
}
@keyframes gridDrift {
  0%   { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(64px, 64px, 0); }   /* exactly one cell: seamless */
}

/* The app shell sits above the layer. Without this the grid paints over the
   content instead of behind it. */
.app, .dash { position: relative; z-index: 1; }

/* ── Honouring the user ────────────────────────────────────────────────────
   Not "slower" — STOPPED. Someone who has asked their OS for less motion has
   asked for less motion, not for a gentler version of it. The layer stays
   visible as a static texture, so the design does not change shape. */
@media (prefers-reduced-motion: reduce) {
  .bgfx__aurora, .bgfx__grid { animation: none; }
}

/* Print: no decorative layer on paper. */
@media print { .bgfx { display: none; } }

/* ── Icon tiles (2026-10-09, on request: "all the icons with colours") ─────
   A white symbol on a coloured rounded tile, as in a phone's settings list.
   One colour per section, the same in both apps (Home is always orange,
   Accounts always teal). Each is dark enough for its white symbol to reach
   3:1 (WCAG non-text contrast) in both themes. */
:root {
  --tile-red: #D93F3F; --tile-orange: #D9730D; --tile-amber: #B7791F;
  --tile-green: #2E9A5A; --tile-teal: #0E8A80; --tile-blue: #3B6FE0;
  --tile-indigo: #5754D1; --tile-purple: #8B4FC9; --tile-pink: #C2357F;
  --tile-grey: #636B78; --tile-ink: #FFFFFF;
}

/* ── Loading placeholders (loading.js, v4.134) ───────────────────────────────
   While a connected page waits for the server's first answer, the big figures
   shimmer instead of reading 0. Both apps. */
body.is-loading :is(.dh-val, .md-val, .rp-stat__v, .ps-num, .ps-row-val, .mc-v, .lo-kpi__v, .cg-big__v, .dv-kpi b, .pc-total__v) {
  color: transparent !important; border-radius: 8px;
  background: linear-gradient(90deg, var(--e2-bg) 25%, var(--e3-bg) 50%, var(--e2-bg) 75%) !important;
  background-size: 200% 100% !important; animation: pmxShimmer 1.2s linear infinite;
}
body.is-loading :is(.dh-val, .md-val, .rp-stat__v, .ps-num, .ps-row-val, .mc-v, .lo-kpi__v, .cg-big__v, .dv-kpi b, .pc-total__v) * { visibility: hidden; }
@keyframes pmxShimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { body.is-loading :is(.dh-val, .md-val, .rp-stat__v, .ps-num, .ps-row-val, .mc-v, .lo-kpi__v, .cg-big__v, .dv-kpi b, .pc-total__v) { animation: none; } }

/* ── Getting started (guide.js, v4.134) — both apps ──────────────────────── */
.guide { display: flex; flex-direction: column; gap: 12px; padding: 18px 18px 14px; border-radius: 16px;
         background: var(--e1-bg); border: 1px solid var(--accent-wash); box-shadow: inset 0 3px 0 var(--accent); }
.guide__h { display: flex; align-items: flex-start; gap: 12px; }
.guide__h > div { flex: 1; min-width: 0; }
.guide__k { margin: 0; font-size: 12px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); }
.guide__t { margin: 2px 0 0; font-size: 19px; font-weight: 800; color: var(--ink); }
.guide__n { flex: none; align-self: center; font-size: 13px; font-weight: 700; color: var(--ink-subtle); }
.guide__x { flex: none; width: 34px; height: 34px; border-radius: 10px; border: 1px solid var(--hairline-strong); background: transparent; color: var(--ink-subtle); font-size: 20px; line-height: 1; cursor: pointer; }
.guide__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.guide__s { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 12px; background: var(--e2-bg); }
.guide__i { flex: none; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 14px;
            background: var(--accent-wash); color: var(--accent-ink); }
.guide__i svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.guide__s.is-done .guide__i { background: var(--success-wash); color: var(--success); }
.guide__b { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.guide__b b { font-size: 15px; color: var(--ink); }
.guide__b small { font-size: 13px; line-height: 1.35; color: var(--ink-subtle); }
.guide__s.is-done .guide__b b { text-decoration: line-through; color: var(--ink-subtle); }
.guide__go { flex: none; min-height: 38px; padding: 0 14px; border-radius: 10px; border: 0; background: var(--accent); color: var(--on-accent); font-weight: 800; font-size: 13.5px; cursor: pointer; }
.guide__ok { flex: none; font-size: 13px; font-weight: 700; color: var(--success); }

/* Refresh pressed (refresh.js): the icon turns while the server is asked. */
@keyframes pmx-refresh-spin { to { transform: rotate(360deg); } }
.is-refreshing svg, .is-refreshing .ico { animation: pmx-refresh-spin .9s linear infinite; }
.is-refreshing { opacity: .85; cursor: progress; }
@media (prefers-reduced-motion: reduce) { .is-refreshing svg, .is-refreshing .ico { animation: none; } }

/* Buttons never take the phone's own padding (v4.239, the user's iPhone screenshots: the refresh arrow pushed right in
   its circle, every emoji in the picture picker in the top-right corner of its tile). Many icon buttons centre their icon
   in a fixed box and set no padding of their own, so the icon sat wherever the browser's default button padding put it,
   and that default differs between iPhone versions. Zero specificity: any button that sets its own padding keeps it. */
:where(button) { padding: 0; }
