/* =============================================================================
   Konek — Shared Design Token Foundation
   -----------------------------------------------------------------------------
   ONE premium product family, TWO surfaces:
     • Portal (public, mobile, LIGHT)  — "fintech-meets-arcade": warm, trustworthy.
     • Admin  (operator, desktop, DARK) — "mission-control / premium SaaS".

   Everything here is GENERIC + variable-driven so both apps consume the same
   primitives and read like one product. See docs/ART-DIRECTION.md.

   RUNTIME THEMING (do not break):
     • Accent is injected onto :root at runtime from config.
       - Portal main.js sets  --accent  and  --accent-2
       - Admin  app.js  sets  --accent  and  --accent2   (no dash)
       We normalise BOTH spellings below into a single --accent-2 so every
       derived tint/shade flows from the operator's one brand colour.
     • Dark surface is toggled by EITHER  [data-scheme='dark']  (portal)  OR
       [data-theme='dark']  (admin). Both selectors are wired below.
   ========================================================================== */

/* --- Inter, offline-safe. local()-only chain: we never reach for the network,
   so a missing font silently falls through to the system UI stack. --- */
@font-face {
  font-family: 'InterVar';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: local('Inter var'), local('Inter'), local('Inter-Regular');
}
@font-face {
  font-family: 'InterUI';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Inter'), local('Inter-Regular'), local('Helvetica Neue'), local('Arial');
}
@font-face {
  font-family: 'InterUI';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: local('Inter SemiBold'), local('Inter-SemiBold'), local('Helvetica Neue');
}
@font-face {
  font-family: 'InterUI';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: local('Inter Bold'), local('Inter-Bold'), local('Helvetica Neue');
}
@font-face {
  font-family: 'InterUI';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: local('Inter ExtraBold'), local('Inter-ExtraBold'), local('Helvetica Neue');
}

:root {
  /* =====================================================================
     1. BRAND ACCENT  — the single source of brand colour.
     --accent / --accent-2 (or --accent2) are overwritten at runtime.
     Everything accent-flavoured is DERIVED from these via color-mix so an
     operator's brand colour reskins the entire product family.
     ===================================================================== */
  --accent: #14e0a0;

  /* Secondary accent. Two spellings exist in the wild:
       - Portal main.js injects  --accent-2  (dashed)
       - Admin  app.js  injects  --accent2   (no dash)
     --accent-2 is our CANONICAL token; every internal derivation reads it.
     Portal sets it directly. The admin scheme block below maps the admin
     spelling onto it (--accent-2: var(--accent2)) so the operator's one
     brand pair reskins everything regardless of which app is running. */
  --accent-2: #0ea5e9;
  --accent2: #0ea5e9;

  /* On-accent ink: text/icons that sit on top of an accent fill.
     --accent-ink is the ink for an accent we have NEVER SEEN — a custom operator
     colour, or a config with no decoration — so it stays white; it is also read
     directly by admin's .btn-accent-soft, which is a pale accent TINT, not a fill.
     --on-accent is the one every full accent FILL uses, and each shipped preset
     re-points it to a MEASURED value in the on-accent floor block at the end of
     this file (white failed AA on all nine). */
  --accent-ink: #ffffff;
  --on-accent: var(--accent-ink);

  /* Derived accent ramp (tints -> shades). color-mix; JS fallback in apps.
     Use these instead of opacity hacks so tints stay crisp on any surface. */
  --accent-50:  color-mix(in srgb, var(--accent)  6%, var(--surface, #fff));
  --accent-100: color-mix(in srgb, var(--accent) 12%, var(--surface, #fff));
  --accent-200: color-mix(in srgb, var(--accent) 24%, var(--surface, #fff));
  --accent-300: color-mix(in srgb, var(--accent) 42%, var(--surface, #fff));
  --accent-400: color-mix(in srgb, var(--accent) 70%, var(--surface, #fff));
  --accent-500: var(--accent);
  --accent-600: color-mix(in srgb, var(--accent) 78%, #000);
  /* 700 is the readable-accent-TEXT step on light surfaces; darkened enough that
     even light accents (amber/gold — PisoBee/Sinag) clear WCAG AA on white. */
  --accent-700: color-mix(in srgb, var(--accent) 52%, #000);

  /* Translucent accent washes (work over any surface incl. glass). */
  --accent-soft:   color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-softer: color-mix(in srgb, var(--accent)  8%, transparent);
  --accent-wash:   color-mix(in srgb, var(--accent) 18%, transparent);
  --accent-ring:   color-mix(in srgb, var(--accent) 60%, transparent);
  --accent-line:   color-mix(in srgb, var(--accent) 32%, transparent);

  /* Focus OUTLINE — solid, unlike the translucent --accent-ring glow.
     --accent-ring is accent@60% over whatever is behind it, and outline-offset
     lands the ring on the PAGE, so a light accent washed out against --bg:
     Bubuyog 1.39:1, Sigla 1.96, Fiesta 2.25, Bula 2.31, Kislap 2.45, Kidlat 2.54
     — all under the 3:1 of WCAG 2.2 SC 1.4.11. Mixing toward --ink instead of
     transparent keeps the brand hue but pins the value to the scheme: those six
     become 3.93 / 6.18 / 6.80 / 7.77 / 8.45 / 8.73, and the three that already
     passed rise (Signal 4.59->13.14, Lawa 3.38->10.61, Sinag 5.09->14.59).
     The BASE value is --accent-ring on purpose: on Chromium 87 a var() holding a
     color-mix is invalid at computed-value time, which would drop the whole
     `outline` declaration and leave the kiosk with NO focus ring at all — and
     main.js/app.js already pin --accent-ring to a plain rgba there. The mix twin
     is in the @supports gate after the dark block. --accent-ring itself must stay
     translucent: the focus GLOW box-shadows and ::selection ride on it. */
  --accent-focus:  var(--accent-ring);

  /* The gradient + glow used on every hero, CTA and ring. */
  --accent-grad:  linear-gradient(135deg, var(--accent), var(--accent-2));
  --accent-grad-soft: linear-gradient(135deg, var(--accent-soft), color-mix(in srgb, var(--accent-2) 14%, transparent));
  /* The CTA fill. Same gradient by default; the two presets whose stops straddle
     mid-luminance re-point it at a lifted twin (see the on-accent floor block at
     the end of this file). Split from --accent-grad so only the TEXT-bearing CTA
     moves — the shared gradient keeps its brand value for the text-free progress
     bars and rails, which need 3:1 against the page, not 4.5:1. */
  --accent-grad-cta: var(--accent-grad);
  --accent-glow:  0 12px 34px color-mix(in srgb, var(--accent) 34%, transparent);
  --accent-glow-sm: 0 6px 18px color-mix(in srgb, var(--accent) 28%, transparent);

  /* =====================================================================
     2. NEUTRAL RAMP — LIGHT (portal default).
     50 = page, 900 = ink. Semantic surface/ink aliases sit on top so app
     code never references raw ramp steps directly.
     ===================================================================== */
  --n-50:  #f6f8fc;
  --n-100: #eef2f8;
  --n-150: #e6ecf4;
  --n-200: #dde4ee;
  --n-300: #cbd5e3;
  --n-400: #94a3b8;
  --n-500: #64748b;
  --n-600: #475569;
  --n-700: #334155;
  --n-800: #1e293b;
  --n-900: #0f172a;

  /* Surfaces (light). */
  --bg: var(--n-50);
  --bg-grad-1: #eef4ff;
  --bg-grad-2: #f8fbff;
  --surface: #ffffff;
  --surface-2: var(--n-100);
  --surface-3: var(--n-150);
  --elevate: #ffffff;

  /* Glass (light): translucent panel for frosted bars / overlays.
     Plain rgba FIRST (hand-resolved to the identical colours) for the same reason
     the status tokens below carry plain hex: on Falkon/Chromium 87 a var() whose
     value is color-mix() is invalid at computed-value time, so every surface
     painted with it — portal toasts, the Pause/Redeem action bar, the admin
     mobile rail — renders with NO background at all. The color-mix twins live in
     the @supports gate after the dark block and upgrade modern engines. */
  --glass-bg: rgba(255,255,255,.72);
  --glass-bg-strong: rgba(255,255,255,.86);
  --glass-border: rgba(15,23,42,.08);
  --glass-blur: 16px;

  /* Ink scale (light). */
  --ink: var(--n-900);
  --ink-2: var(--n-700);
  --ink-3: var(--n-500);
  --ink-faint: var(--n-400);

  /* Lines / hairlines. */
  --line: var(--n-200);
  --line-2: var(--n-300);
  --line-strong: var(--n-400);
  /* Plain rgba first (same Chromium-87 reason as the glass tokens above) — this
     one rides inside every .card/.panel box-shadow, so an invalid value drops the
     whole shadow list, not just the hairline. */
  --hairline: inset 0 0 0 1px rgba(15,23,42,.06);

  /* =====================================================================
     3. SEMANTIC STATUS — paired fill / soft wash / ink for chips & banners.
     Plain hex/rgba here so the on-box Falkon/Chromium-87 admin still gets
     colored status dots/text — a var() whose value is color-mix() is invalid
     at computed-value time there (and inline var(--x, #hex) fallbacks never
     fire for a DECLARED property). The theme-derived color-mix versions live
     in the @supports gate after the dark block and upgrade modern engines.
     ===================================================================== */
  --ok: #0ca678;
  --ok-soft:  rgba(12,166,120,.14);
  --ok-ink:   #09815e;
  --warn: #f59e0b;
  --warn-soft: rgba(245,158,11,.16);
  --warn-ink:  #b07208;
  --danger: #ef4444;
  --danger-soft: rgba(239,68,68,.14);
  --danger-ink:  #b13232;
  --info: #0ea5e9;
  --info-soft: rgba(14,165,233,.14);
  --info-ink:  #0a77a8;

  /* =====================================================================
     4. ELEVATION — layered shadow set + colored glow + crisp inset edge.
     shadow-1 (rest) .. shadow-4 (modal/lifted). Tuned for light surfaces;
     overridden for dark below.
     ===================================================================== */
  --shadow-1: 0 1px 2px rgba(15,23,42,.05), 0 1px 3px rgba(15,23,42,.07);
  --shadow-2: 0 2px 6px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.08);
  --shadow-3: 0 8px 24px rgba(15,23,42,.10), 0 4px 10px rgba(15,23,42,.07);
  --shadow-4: 0 24px 60px rgba(15,23,42,.18), 0 8px 22px rgba(15,23,42,.12);
  /* Back-compat aliases (existing app CSS references these names). */
  --shadow-sm: var(--shadow-1);
  --shadow-md: var(--shadow-2);
  --shadow-lg: var(--shadow-4);
  --shadow-accent: var(--accent-glow);

  /* =====================================================================
     4b. THEME CONTRACT (L1) — the `--th-*` dials a theme may set.
     =====================================================================
     WHY THIS LAYER EXISTS
     A "theme" used to mean a palette plus a background pattern, so every preset
     read as the same product in a different colour. These dials let a theme change
     SHAPE, DENSITY, MOTION and TYPE PROPORTION as well — the things that actually
     make two designs feel like two products.

     CONTRACT: a theme sets `--th-*` ONLY. Everything downstream (§5 radius, §6
     spacing, §8 motion) DERIVES from these. Never let a theme rebind the --r- or
     --s- ramps directly; that is what made shape half-freezable before.
     (Do not write a star followed by a slash in these comments — it closes the
     comment early and the parser then drops every rule that follows.)

     Every default below reproduces the previous hard-coded value EXACTLY, so a
     theme that sets nothing looks identical to how it looked before this layer.
     ===================================================================== */
  /* SHAPE — radius ramp + border weight */
  --th-r-1: 8px;    /* -> --r-xs  */
  --th-r-2: 12px;   /* -> --r-sm  */
  --th-r-3: 16px;   /* -> --r-md  */
  --th-r-4: 22px;   /* -> --r-lg  */
  --th-r-5: 28px;   /* -> --r-xl  */
  --th-r-6: 36px;   /* -> --r-2xl */
  --th-r-pill: 999px;
  --th-edge-w: 1px;

  /* DENSITY — one multiplier over the whole spacing ramp. */
  --th-density: 1;
  --th-gap-k: 1;

  /* MOTION — `--th-dur-k` is the gait: it scales the ENTIRE duration ramp, so
     every theme has a motion personality instead of only the two tokens the old
     decoration block happened to rebind. --th-travel is press depth (a machine
     moves DOWN when you push it). */
  --th-dur-k: 1;
  --th-travel: 0px;

  /* TYPE PROPORTION — the highest-leverage dial in the set. A 1.18 ratio reads
     calm; 1.36 reads like an arcade HUD. Same content, different product. */
  --th-type-ratio: 1.25;
  --th-type-k: 1;
  --th-w-body: 400;
  --th-w-title: 700;
  --th-w-display: 800;
  --th-track-display: -0.01em;
  --th-case-display: none;

  /* =====================================================================
     5. RADIUS — DERIVED from the shape dials above.
     ===================================================================== */
  --r-xs: var(--th-r-1);
  --r-sm: var(--th-r-2);
  --r-md: var(--th-r-3);
  --r-lg: var(--th-r-4);
  --r-xl: var(--th-r-5);
  --r-2xl: var(--th-r-6);
  --r-pill: var(--th-r-pill);

  /* =====================================================================
     6. SPACING — 4px base ramp, scaled by --th-density.
     calc() keeps this valid on Chromium 87 (the kiosk's Falkon); no clamp(),
     no round(), no registered custom properties.
     ===================================================================== */
  --s-1: calc(4px * var(--th-density));
  --s-2: calc(8px * var(--th-density));
  --s-3: calc(12px * var(--th-density));
  --s-4: calc(16px * var(--th-density));
  --s-5: calc(20px * var(--th-density));
  --s-6: calc(24px * var(--th-density));
  --s-7: calc(32px * var(--th-density));
  --s-8: calc(40px * var(--th-density));
  --s-9: calc(56px * var(--th-density));
  --s-10: calc(72px * var(--th-density));

  /* =====================================================================
     7. TYPOGRAPHY — Inter; fluid modular scale via clamp; weight + tracking.
     fs-* fixed sizes kept for back-compat; t-* are the fluid display scale.
     ===================================================================== */
  --font-ui: 'InterUI', 'InterVar', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-num: 'InterVar', 'InterUI', ui-monospace, 'SF Mono', 'Segoe UI', system-ui, sans-serif;

  /* Fixed step scale (back-compat with existing app CSS). */
  --fs-xs: 12px;
  --fs-sm: 14px;
  --fs-md: 16px;
  --fs-lg: 19px;
  --fs-xl: 24px;
  --fs-2xl: 32px;
  --fs-3xl: 44px;

  /* Fluid modular scale — preferred for new work. */
  --t-display: clamp(40px, 9vw, 64px);
  --t-h1: clamp(28px, 5.2vw, 40px);
  --t-h2: clamp(22px, 3.4vw, 28px);
  --t-h3: clamp(18px, 2.4vw, 21px);
  --t-body: clamp(15px, 1.6vw, 16px);
  --t-sm: 14px;
  --t-xs: 12px;
  --t-2xs: 11px;

  /* Weights. */
  --w-regular: 400;
  --w-medium: 500;
  --w-semibold: 600;
  --w-bold: 700;
  --w-black: 800;

  /* Tracking (letter-spacing). Tighten large display; open up tiny caps. */
  --track-tight: -0.02em;
  --track-snug: -0.01em;
  --track-normal: 0;
  --track-wide: 0.02em;
  --track-caps: 0.08em;

  --leading-tight: 1.08;
  --leading-snug: 1.25;
  --leading-normal: 1.5;

  /* =====================================================================
     8. MOTION — durations + easings. All large motion is reduced-motion gated.
     ===================================================================== */
  /* Scaled by the theme's gait (--th-dur-k). One multiplier gives every theme a
     motion personality: 0.72 reads urgent/arcade, 1.40 reads calm/unhurried. */
  --dur-instant: calc(90ms * var(--th-dur-k));
  --dur-fast: calc(140ms * var(--th-dur-k));
  --dur: calc(240ms * var(--th-dur-k));
  --dur-slow: calc(420ms * var(--th-dur-k));
  --dur-slower: calc(720ms * var(--th-dur-k));
  --ease: cubic-bezier(.22, 1, .36, 1);       /* standard ease-out */
  --ease-in: cubic-bezier(.4, 0, 1, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1); /* overshoot / springy pop */

  /* =====================================================================
     9. LAYOUT.
     ===================================================================== */
  --maxw: 520px;          /* portal column */
  --maxw-wide: 1240px;    /* admin content */
  --rail-w: 248px;        /* admin nav rail */

  color-scheme: light;
}

/* Admin runs under [data-theme] and injects the NO-DASH spelling (--accent2).
   Fold it onto our canonical --accent-2 so admin's brand pair drives every
   derived tint/gradient. (Portal sets --accent-2 directly, so it's untouched.)
   Applies to both admin light and dark. */
:root[data-theme] {
  --accent-2: var(--accent2, #0ea5e9);
}

/* =========================================================================
   DARK SURFACE — admin default + opt-in dark portal.
   Wired to BOTH [data-scheme='dark'] (portal) and [data-theme='dark'] (admin).
   ========================================================================= */
:root[data-scheme='dark'],
:root[data-theme='dark'] {
  /* Neutral ramp inverts: 50 = darkest page, 900 = brightest ink. */
  --n-50:  #0a0e17;
  --n-100: #0e1320;
  --n-150: #131a2b;
  --n-200: #1b2436;
  --n-300: #273249;
  --n-400: #3a4860;
  --n-500: #64748b;
  --n-600: #8a99af;
  --n-700: #aebccf;
  --n-800: #d6deea;
  --n-900: #f2f6fc;

  --bg: #080c15;
  --bg-grad-1: #0c1322;
  --bg-grad-2: #080b14;
  --surface: #101827;
  --surface-2: #0d1422;
  --surface-3: #16203a;
  --elevate: #16203a;

  /* Glass (dark). Plain rgba first — see the light block. */
  --glass-bg: rgba(15,23,38,.64);
  --glass-bg-strong: rgba(16,24,39,.82);
  --glass-border: rgba(255,255,255,.10);

  --ink: var(--n-900);
  --ink-2: var(--n-700);
  /* Tertiary ink on dark: --n-500 (#64748b) only reaches ~3.7-4.1:1 on the
     darkest portal/admin surfaces — below AA 4.5:1. Step up to --n-600
     (#8a99af, ~6.1-6.8:1) so tertiary text (e.g. portal .insert-sub, admin
     .kpi-label) clears AA while staying dimmer than --ink-2 (--n-700). */
  --ink-3: var(--n-600);
  --ink-faint: var(--n-400);

  --line: #1e293b;
  --line-2: #2a384e;
  --line-strong: #3a4860;
  --hairline: inset 0 0 0 1px rgba(255,255,255,.07);

  /* On dark, derived tints mix toward surface (dark), so accent washes glow
     rather than wash out. Status inks brighten for AA contrast. Plain hex
     (hand-resolved mixes toward #fff) so Falkon/Chromium 87 keeps colored
     status; the color-mix versions upgrade in the @supports gate below. */
  --ok-ink:     #86d3bc;
  --warn-ink:   #f9c56d;
  --danger-ink: #f69898;
  --info-ink:   #7acef3;
  /* Accent "dark steps" must LIGHTEN on dark, not darken toward #000 like :root —
     otherwise accent-tinted TEXT (queue eyebrow, insert-sub[mine], voucher step
     numbers, link buttons) is nearly invisible on dark surfaces (violet 2.3:1,
     azure 2.4:1). Brighten toward #fff for AA, mirroring the status inks above. */
  --accent-600: color-mix(in srgb, var(--accent) 80%, #fff);
  --accent-700: color-mix(in srgb, var(--accent) 60%, #fff);

  /* Deeper, softer shadows for dark; glow carries the elevation cue. */
  --shadow-1: 0 1px 2px rgba(0,0,0,.45);
  --shadow-2: 0 6px 18px rgba(0,0,0,.5);
  --shadow-3: 0 14px 40px rgba(0,0,0,.55);
  --shadow-4: 0 28px 70px rgba(0,0,0,.62);

  color-scheme: dark;
}

/* Status ink/wash upgrade for modern engines: theme-derived color-mix values.
   MUST live behind @supports — a bare `--x: color-mix(...)` is a valid custom-
   property token stream even on Chromium 87, so it would win the cascade over
   the plain hex above and then fail at var() substitution (invisible dot,
   inherited text color). Chromium 87 evaluates this condition false and keeps
   the hand-resolved hex/rgba; newer browsers re-derive from --ok/--warn/etc. */
@supports (color: color-mix(in srgb, red 50%, white)) {
  :root {
    --ok-soft:  color-mix(in srgb, var(--ok) 14%, transparent);
    --ok-ink:   color-mix(in srgb, var(--ok) 78%, #000);
    --warn-soft: color-mix(in srgb, var(--warn) 16%, transparent);
    --warn-ink:  color-mix(in srgb, var(--warn) 72%, #000);
    --danger-soft: color-mix(in srgb, var(--danger) 14%, transparent);
    --danger-ink:  color-mix(in srgb, var(--danger) 74%, #000);
    --info-soft: color-mix(in srgb, var(--info) 14%, transparent);
    --info-ink:  color-mix(in srgb, var(--info) 72%, #000);
    /* Focus outline, anchored to the ink so a pale accent can't wash out — see
       --accent-focus in §1. Declared on :root, so var(--ink) resolves against
       whichever scheme won on :root; no dark twin needed. */
    --accent-focus: color-mix(in srgb, var(--accent) 60%, var(--ink));
    /* Glass + hairline twins of the plain rgba in §2. Same resolved colours —
       they live here purely so the ramp stays derived on modern engines. */
    --glass-bg: color-mix(in srgb, #ffffff 72%, transparent);
    --glass-bg-strong: color-mix(in srgb, #ffffff 86%, transparent);
    --glass-border: color-mix(in srgb, var(--n-900) 8%, transparent);
    --hairline: inset 0 0 0 1px color-mix(in srgb, var(--n-900) 6%, transparent);
  }
  /* Dark re-brightens toward #fff, mirroring the plain-hex dark block above. */
  :root[data-scheme='dark'],
  :root[data-theme='dark'] {
    --ok-ink:     color-mix(in srgb, var(--ok) 50%, #fff);
    --warn-ink:   color-mix(in srgb, var(--warn) 60%, #fff);
    --danger-ink: color-mix(in srgb, var(--danger) 55%, #fff);
    --info-ink:   color-mix(in srgb, var(--info) 55%, #fff);
    --glass-bg: color-mix(in srgb, #0f1726 64%, transparent);
    --glass-bg-strong: color-mix(in srgb, #101827 82%, transparent);
    --glass-border: color-mix(in srgb, #ffffff 10%, transparent);
    --hairline: inset 0 0 0 1px color-mix(in srgb, #ffffff 7%, transparent);
  }
}

/* Auto dark for an explicit data-scheme='auto' page (portal default stays light). */
@media (prefers-color-scheme: dark) {
  :root[data-scheme='auto'] {
    --n-50:#0a0e17; --n-100:#0e1320; --n-150:#131a2b; --n-200:#1b2436;
    --n-300:#273249; --n-400:#3a4860; --n-500:#64748b; --n-600:#8a99af;
    --n-700:#aebccf; --n-800:#d6deea; --n-900:#f2f6fc;
    --bg:#080c15; --bg-grad-1:#0c1322; --bg-grad-2:#080b14;
    --surface:#101827; --surface-2:#0d1422; --surface-3:#16203a; --elevate:#16203a;
    /* Plain rgba (same Chromium-87 reason as the light/dark blocks above). */
    --glass-bg: rgba(15,23,38,.64);
    --glass-bg-strong: rgba(16,24,39,.82);
    --glass-border: rgba(255,255,255,.10);
    --hairline: inset 0 0 0 1px rgba(255,255,255,.07);
    --ink:var(--n-900); --ink-2:var(--n-700); --ink-3:var(--n-600); --ink-faint:var(--n-400);
    --line:#1e293b; --line-2:#2a384e; --line-strong:#3a4860;
    --shadow-1:0 1px 2px rgba(0,0,0,.45); --shadow-2:0 6px 18px rgba(0,0,0,.5);
    --shadow-3:0 14px 40px rgba(0,0,0,.55); --shadow-4:0 28px 70px rgba(0,0,0,.62);
    color-scheme: dark;
  }
}

/* =========================================================================
   RESET / BASE
   ========================================================================= */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body {
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  line-height: var(--leading-normal);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  -webkit-tap-highlight-color: transparent;
  font-feature-settings: 'cv11', 'ss01';
}
button, input, select, textarea { font-family: inherit; }
::selection { background: var(--accent-ring); color: var(--ink); }

/* =========================================================================
   TYPOGRAPHY UTILITIES
   ========================================================================= */
.t-display { font-size: var(--t-display); font-weight: var(--w-black); letter-spacing: var(--track-tight); line-height: var(--leading-tight); }
.t-h1 { font-size: var(--t-h1); font-weight: var(--w-bold); letter-spacing: var(--track-tight); line-height: var(--leading-tight); }
.t-h2 { font-size: var(--t-h2); font-weight: var(--w-bold); letter-spacing: var(--track-snug); line-height: var(--leading-snug); }
.t-h3 { font-size: var(--t-h3); font-weight: var(--w-semibold); letter-spacing: var(--track-snug); }
.t-body { font-size: var(--t-body); font-weight: var(--w-regular); }
.t-sm { font-size: var(--t-sm); }
.t-xs { font-size: var(--t-xs); }
.t-muted { color: var(--ink-3); }
.t-faint { color: var(--ink-faint); }
.t-caps { text-transform: uppercase; letter-spacing: var(--track-caps); font-size: var(--t-2xs); font-weight: var(--w-semibold); color: var(--ink-3); }
.tabular { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
/* Money / time readouts: tabular figures so digits don't jitter as they tick. */
.num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; letter-spacing: var(--track-snug); }

/* =========================================================================
   SURFACE UTILITIES
   ========================================================================= */
/* Frosted glass: translucent fill + blur + hairline. Use for action bars,
   overlays, sticky headers. Falls back to a solid strong fill w/o blur. */
.glass {
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-3), var(--hairline);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .glass {
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(140%);
    backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  }
}
.glass-strong { background: var(--glass-bg-strong); }

/* Ambient brand background washes — drop on a page/body for depth. */
.aura-bg {
  background:
    radial-gradient(60% 50% at 12% 0%, var(--accent-soft), transparent 60%),
    radial-gradient(55% 45% at 100% 8%, color-mix(in srgb, var(--accent-2) 12%, transparent), transparent 62%),
    linear-gradient(180deg, var(--bg-grad-1), var(--bg-grad-2) 40%, var(--bg));
}
.mesh-bg {
  background:
    radial-gradient(color-mix(in srgb, var(--accent-2) 22%, transparent) 1.2px, transparent 1.4px),
    radial-gradient(color-mix(in srgb, var(--accent) 16%, transparent) 1.2px, transparent 1.4px),
    radial-gradient(40% 40% at 18% 22%, var(--accent-softer), transparent 70%),
    radial-gradient(38% 38% at 82% 14%, color-mix(in srgb, var(--accent-2) 10%, transparent), transparent 70%),
    radial-gradient(50% 50% at 60% 100%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 70%),
    var(--bg);
  background-size: 40px 40px, 80px 80px, auto, auto, auto, auto;
  background-position: 0 0, 20px 20px, 0 0, 0 0, 0 0, 0 0;
}

/* Focus ring — single source of truth. Apply to interactive elements.
   Two copies on purpose: Chromium ≤87 (the kiosk's Falkon) drops a whole rule
   when a selector LIST contains :where() (Chrome 88+), so a plain expanded list
   comes first for it; the :where() twin below keeps zero specificity on modern
   engines. Keep the declarations in sync. */
.focus-ring:focus-visible,
a:focus-visible,
button:focus-visible,
[role='button']:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent-focus);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
.focus-ring:focus-visible,
:where(a, button, [role='button'], input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--accent-focus);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* Scrollbar styling (thin, themed). */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line-2) transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line-2);
  border-radius: var(--r-pill);
  border: 3px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--line-strong); background-clip: content-box; }

/* =========================================================================
   SHARED PRIMITIVE: .tab-hero
   The header band on every admin module + the portal hero. Animated accent
   aura sweeps slowly behind the title (reduced-motion: static).
   ========================================================================= */
.tab-hero {
  position: relative;
  isolation: isolate;
  padding: var(--s-6) var(--s-5) var(--s-5);
  background:
    radial-gradient(120% 140% at 0% 0%, var(--accent-soft), transparent 55%),
    var(--accent-grad);
  color: var(--on-accent);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  overflow: hidden;
}
/* Slow drifting aura blob behind the hero content. */
.tab-hero::before {
  content: '';
  position: absolute;
  inset: -40% -20%;
  z-index: -1;
  background:
    radial-gradient(40% 60% at 20% 30%, color-mix(in srgb, #fff 26%, transparent), transparent 60%),
    radial-gradient(45% 55% at 85% 60%, color-mix(in srgb, var(--accent-2) 45%, transparent), transparent 62%);
  filter: blur(6px);
  opacity: .9;
  animation: aurora 18s var(--ease-in-out) infinite;
}
.tab-hero h1 {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: var(--w-bold);
  letter-spacing: var(--track-snug);
}
.tab-hero .sub {
  margin: var(--s-1) 0 0;
  opacity: .92;
  font-size: var(--fs-sm);
}

/* =========================================================================
   SHARED PRIMITIVE: .card / .panel
   ========================================================================= */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1), var(--hairline);
  padding: var(--s-5);
}
.panel {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2), var(--hairline);
  padding: var(--s-6);
}
.panel-glass { composes: glass; }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-bottom: var(--s-4); }
.panel-title { margin: 0; font-size: var(--fs-lg); font-weight: var(--w-bold); letter-spacing: var(--track-snug); }

/* =========================================================================
   SHARED PRIMITIVE: .btn family
   48px min tap target; springy press; accent CTA glows; ghost + danger.
   ========================================================================= */
.btn {
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 0 var(--s-5);
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-md);
  font-weight: var(--w-semibold);
  letter-spacing: var(--track-snug);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  box-shadow: var(--shadow-1);
  transition: transform var(--dur-fast) var(--ease),
    box-shadow var(--dur) var(--ease),
    background var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    filter var(--dur-fast) var(--ease);
}
.btn:hover { box-shadow: var(--shadow-2); }
.btn:active { transform: scale(.97); }
.btn:focus-visible { outline: 3px solid var(--accent-focus); outline-offset: 2px; }
.btn[disabled], .btn:disabled { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }
.btn svg { width: 1.15em; height: 1.15em; flex: none; }
/* An author `display` beats the UA `[hidden] { display: none }`, so without this
   every button JS marks hidden kept its box AND stayed tappable — the operator
   turns vouchers or pause off and the customer taps a dead full-width CTA, plus a
   ghost of it is painted on every cold load until /api/me resolves. Same bug the
   portal already documents for .low-banner. */
.btn[hidden] { display: none; }

/* Accent CTA — gradient fill, brand glow, crisp inset highlight. */
.btn-accent, .btn-primary {
  background: var(--accent-grad-cta);
  color: var(--on-accent);
  border-color: transparent;
  box-shadow: var(--accent-glow-sm), inset 0 1px 0 color-mix(in srgb, #fff 28%, transparent);
}
.btn-accent:hover, .btn-primary:hover { filter: brightness(1.05) saturate(1.05); box-shadow: var(--accent-glow); }
.btn-accent:active, .btn-primary:active { filter: brightness(.98); }

/* Ghost — quiet, outline only. */
.btn-ghost {
  background: transparent;
  border-color: var(--line-2);
  color: var(--ink-2);
  box-shadow: none;
}
.btn-ghost:hover { background: var(--surface-2); border-color: var(--line-strong); box-shadow: none; }

/* Danger — destructive. */
.btn-danger {
  background: var(--danger);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--danger) 30%, transparent);
}
.btn-danger:hover { filter: brightness(1.05); }

.btn-sm { min-height: 36px; padding: 0 var(--s-4); font-size: var(--fs-sm); }
.btn-lg { min-height: 60px; font-size: var(--fs-lg); padding: 0 var(--s-6); }
.btn-block { width: 100%; }
.btn-icon { padding: 0; width: 44px; height: 44px; min-height: 44px; border-radius: var(--r-pill); }

/* =========================================================================
   SHARED PRIMITIVE: .pill / .badge
   ========================================================================= */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-3);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: var(--fs-sm);
  font-weight: var(--w-semibold);
  color: var(--ink-2);
  line-height: 1;
}
.pill svg { width: 1em; height: 1em; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  font-size: var(--t-2xs);
  font-weight: var(--w-bold);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  line-height: 1.4;
}
.badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .9; }
.badge-accent { background: var(--accent-soft); color: var(--accent-700); }
.badge-ok     { background: var(--ok-soft);     color: var(--ok-ink); }
.badge-warn   { background: var(--warn-soft);   color: var(--warn-ink); }
.badge-danger { background: var(--danger-soft); color: var(--danger-ink); }
.badge-info   { background: var(--info-soft);   color: var(--info-ink); }
.badge-neutral{ background: var(--surface-3);   color: var(--ink-3); }
/* Trend chip (admin KPI): up = ok, down = danger. */
.trend { display:inline-flex; align-items:center; gap:4px; font-size: var(--t-2xs); font-weight: var(--w-bold); padding: 2px 7px; border-radius: var(--r-pill); }
.trend-up { background: var(--ok-soft); color: var(--ok-ink); }
.trend-down { background: var(--danger-soft); color: var(--danger-ink); }
.trend-flat { background: var(--surface-3); color: var(--ink-3); }

/* =========================================================================
   SHARED PRIMITIVE: form controls (.field / .input / .switch / .pin-input)
   ========================================================================= */
.field { display: flex; flex-direction: column; gap: var(--s-2); }
.field > label, .field-label {
  font-size: var(--fs-sm);
  font-weight: var(--w-semibold);
  color: var(--ink-2);
}
.field-hint { font-size: var(--t-xs); color: var(--ink-3); }

.input, .select, .textarea {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  min-height: 48px;
  padding: 0 var(--s-4);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-md);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.textarea { min-height: 96px; padding: var(--s-3) var(--s-4); line-height: 1.5; resize: vertical; }
.input::placeholder, .textarea::placeholder { color: var(--ink-faint); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--line-strong); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
  background: var(--surface);
}

/* Toggle switch. */
.switch { position: relative; display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .track {
  width: 46px; height: 28px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  border: 1px solid var(--line-2);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.switch .thumb {
  position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-1);
  transition: transform var(--dur) var(--ease-spring);
}
.switch input:checked + .track { background: var(--accent); border-color: transparent; }
.switch input:checked + .track + .thumb,
.switch input:checked ~ .thumb { transform: translateX(18px); }
.switch input:focus-visible + .track { box-shadow: 0 0 0 3px var(--accent-ring); }

/* PIN input — large, tracked, tabular. */
.pin-input {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  text-align: center;
  letter-spacing: 0.5em;
  font-size: var(--fs-2xl);
  font-weight: var(--w-bold);
  min-height: 64px;
  border-radius: var(--r-md);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.pin-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-ring); }

/* =========================================================================
   SHARED PRIMITIVE: .kpi shell (admin KPI tiles)
   Accent edge + soft glow on hover; consumers slot value/label/spark/trend.
   ========================================================================= */
.kpi {
  --kpi-accent: var(--accent);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-5);
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-1), var(--hairline);
  overflow: hidden;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.kpi::before {
  content: '';
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--kpi-accent);
  opacity: .85;
}
.kpi::after {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(120% 80% at 100% 0%, color-mix(in srgb, var(--kpi-accent) 10%, transparent), transparent 60%);
  pointer-events: none;
}
.kpi:hover { transform: translateY(-2px); box-shadow: var(--shadow-3), var(--hairline); }
.kpi .kpi-label { font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-3); font-weight: var(--w-semibold); }
.kpi .kpi-value { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); font-weight: var(--w-bold); letter-spacing: var(--track-snug); line-height: 1.05; }

/* Accessibility helper. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

/* =========================================================================
   MOTION — named keyframes utilities. All gated by prefers-reduced-motion.
   ========================================================================= */
@keyframes fade-up { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes pop { 0% { transform: scale(.8); opacity: 0; } 60% { transform: scale(1.06); opacity: 1; } 100% { transform: scale(1); } }
@keyframes shimmer { from { background-position: -150% 0; } to { background-position: 250% 0; } }
@keyframes pulse-ring { 0% { transform: scale(.92); opacity: .6; } 70% { transform: scale(1.25); opacity: 0; } 100% { opacity: 0; } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes aurora { 0%, 100% { transform: translate3d(0,0,0) rotate(0deg); } 33% { transform: translate3d(3%, -2%, 0) rotate(4deg); } 66% { transform: translate3d(-2%, 2%, 0) rotate(-3deg); } }
@keyframes spin { to { transform: rotate(360deg); } }

.anim-fade-up { animation: fade-up var(--dur-slow) var(--ease) both; }
.anim-pop { animation: pop var(--dur) var(--ease-spring) both; }
.anim-float { animation: float 4s var(--ease-in-out) infinite; }
.anim-spin { animation: spin 1s linear infinite; }

/* Shimmer skeleton placeholder. */
.skeleton {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
  border-radius: var(--r-sm);
  color: transparent;
}

/* Pulse ring ping (live feed dot, coin pulse). Place as ::after on a dot. */
.ping { position: relative; }
.ping::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: var(--accent);
  animation: pulse-ring 1.8s var(--ease) infinite;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .tab-hero::before, .anim-float, .ping::after, .skeleton { animation: none !important; }
}


/* =========================================================================
   THEME PERSONALITY  -  [data-decoration]  (Coined Light)
   -------------------------------------------------------------------------
   A brand is not a recolor. `theme.decoration` is promoted to a data-decoration
   root attribute by portal main.js + admin app.js; each value below re-binds a
   PERSONALITY across four axes without forking component CSS or touching the
   accent engine:
     SHAPE   - overrides the upper radius ramp only (--r-md .. --r-2xl);
               --r-xs / --r-sm / hairline / --r-pill stay fixed so dense admin
               tables, inputs and pill buttons never break.
     TEXTURE - one tileable inline-SVG (white, low-opacity) painted on the
               shared .tab-hero band via ::after. Static => zero low-end cost,
               reduced-motion-safe by construction.
     MOTION  - re-binds --dur / --ease (gait); still neutralised by the global
               prefers-reduced-motion block far below.
     TYPE    - heading font-feature-settings / tracking only; money & time stay
               tabular everywhere. Uses the already-bundled variable Inter.
   Greyscale Test (ship gate): desaturate the UI and each brand must still be
   identifiable by corner-sharpness + texture + gait alone.
   ========================================================================= */

/* Texture layer for the shared hero band. Sits above the accent-grad fill,
   below the title (negative z-index inside .tab-hero's isolate context). */
.tab-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--theme-texture, none);
  background-size: var(--theme-texture-size, 40px 40px);
  background-repeat: repeat;
  opacity: var(--theme-texture-opacity, 0);
  mix-blend-mode: soft-light;
}


/* Fiesta / KonekOS - festive confetti */
[data-decoration='fiesta'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2740%27%20height%3D%2740%27%20viewBox%3D%270%200%2040%2040%27%3E%3Cg%20fill%3D%27white%27%3E%3Cpath%20d%3D%27M8%206l4%207h-8z%27%2F%3E%3Cpath%20d%3D%27M28%2021l4%207h-8z%27%2F%3E%3Ccircle%20cx%3D%2731%27%20cy%3D%279%27%20r%3D%272%27%2F%3E%3Ccircle%20cx%3D%2711%27%20cy%3D%2731%27%20r%3D%272%27%2F%3E%3Crect%20x%3D%2720%27%20y%3D%272%27%20width%3D%273%27%20height%3D%273%27%20transform%3D%27rotate%2820%2021%203%29%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 40px 40px;
  --theme-texture-opacity: 0.08;
}

/* Kislap - arcade dot-grid HUD */
[data-decoration='grid'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --ease: cubic-bezier(.2,.85,.2,1);   /* gait comes from --th-dur-k (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2724%27%20height%3D%2724%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cg%20fill%3D%27white%27%3E%3Ccircle%20cx%3D%272%27%20cy%3D%272%27%20r%3D%271%27%2F%3E%3C%2Fg%3E%3Cg%20stroke%3D%27white%27%20stroke-width%3D%27.4%27%20opacity%3D%27.5%27%3E%3Cpath%20d%3D%27M0%2012h24M12%200v24%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 24px 24px;
  --theme-texture-opacity: 0.10;
}
[data-decoration='grid'] .tab-hero h1,[data-decoration='grid'] .t-display,[data-decoration='grid'] .t-h1 { font-feature-settings:'zero' 1,'ss01' 1,'tnum' 1;letter-spacing:-.01em; }

/* Lawa - moonlit tide */
[data-decoration='waves'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --ease: cubic-bezier(.4,0,.2,1);   /* gait comes from --th-dur-k (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2748%27%20height%3D%2718%27%20viewBox%3D%270%200%2048%2018%27%3E%3Cpath%20d%3D%27M0%209q12-9%2024%200t24%200%27%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%271.5%27%2F%3E%3C%2Fsvg%3E");
  --theme-texture-size: 48px 18px;
  --theme-texture-opacity: 0.08;
}
[data-decoration='waves'] .tab-hero h1,[data-decoration='waves'] .t-display,[data-decoration='waves'] .t-h1 { letter-spacing:.01em; }

/* Kidlat - PCB traces */
[data-decoration='circuit'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --ease: cubic-bezier(.2,.85,.2,1);   /* gait comes from --th-dur-k (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2740%27%20height%3D%2740%27%20viewBox%3D%270%200%2040%2040%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%271%27%3E%3Cpath%20d%3D%27M0%2020h14V6h26M20%2040V26h20%27%2F%3E%3C%2Fg%3E%3Cg%20fill%3D%27white%27%3E%3Ccircle%20cx%3D%2714%27%20cy%3D%276%27%20r%3D%272%27%2F%3E%3Ccircle%20cx%3D%2720%27%20cy%3D%2726%27%20r%3D%272%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 40px 40px;
  --theme-texture-opacity: 0.10;
}
[data-decoration='circuit'] .tab-hero h1,[data-decoration='circuit'] .t-display,[data-decoration='circuit'] .t-h1 { font-feature-settings:'zero' 1,'tnum' 1; }

/* Bubuyog - hex hive */
[data-decoration='honeycomb'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2728%27%20height%3D%2724%27%20viewBox%3D%270%200%2028%2024%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%271%27%3E%3Cpath%20d%3D%27M7%200h14l7%2012-7%2012H7L0%2012z%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 28px 24px;
  --theme-texture-opacity: 0.10;
}

/* Bula - candy bubbles */
[data-decoration='bubbles'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --ease: cubic-bezier(.34,1.56,.64,1);   /* gait comes from --th-dur-k (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2740%27%20height%3D%2740%27%20viewBox%3D%270%200%2040%2040%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%271.5%27%3E%3Ccircle%20cx%3D%2710%27%20cy%3D%2713%27%20r%3D%276%27%2F%3E%3Ccircle%20cx%3D%2730%27%20cy%3D%2729%27%20r%3D%279%27%2F%3E%3Ccircle%20cx%3D%2733%27%20cy%3D%278%27%20r%3D%273%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 40px 40px;
  --theme-texture-opacity: 0.08;
}

/* Sigla - organic leaves */
[data-decoration='leaves'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --ease: cubic-bezier(.4,0,.2,1);   /* gait comes from --th-dur-k (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2736%27%20height%3D%2736%27%20viewBox%3D%270%200%2036%2036%27%3E%3Cg%20fill%3D%27white%27%3E%3Cpath%20d%3D%27M6%2020q10-14%2024-6-10%2014-24%206z%27%2F%3E%3Cpath%20d%3D%27M6%2020q12-2%2024-6%27%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%27.6%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 36px 36px;
  --theme-texture-opacity: 0.08;
}
[data-decoration='leaves'] .tab-hero h1,[data-decoration='leaves'] .t-display,[data-decoration='leaves'] .t-h1 { letter-spacing:.01em; }

/* Sinag - golden sunburst */
[data-decoration='rays'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --theme-texture: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2740%27%20height%3D%2740%27%20viewBox%3D%270%200%2040%2040%27%3E%3Cg%20stroke%3D%27white%27%20stroke-width%3D%271%27%3E%3Cpath%20d%3D%27M0%2040L40%200M-8%2032L32-8M8%2048L48%208%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --theme-texture-size: 40px 40px;
  --theme-texture-opacity: 0.10;
}
[data-decoration='rays'] .tab-hero h1,[data-decoration='rays'] .t-display,[data-decoration='rays'] .t-h1 { letter-spacing:.02em;font-weight:800; }

/* Signal - the KonekOS website "tech" look: PCB circuit field in signal-green
   (house / flagship default). Reuses the exact circuit.svg the marketing site
   masks in body::before, so the portal background matches konekos.com. */
[data-decoration='signal'] {
  /* shape now comes from the --th-r-* contract (see THEME STRUCTURE below) */
  --ease: cubic-bezier(.2,.8,.2,1);   /* gait comes from --th-dur-k (see THEME STRUCTURE below) */
  --theme-texture: url("/portal/img/circuit.svg");
  --theme-texture-size: 150px 150px;
  --theme-texture-opacity: 0.05;
}
[data-decoration='signal'] .tab-hero h1,[data-decoration='signal'] .t-display,[data-decoration='signal'] .t-h1 { font-feature-settings:'tnum' 1;letter-spacing:-.01em; }

/* =========================================================================
   THE PISO MINT  —  a chrome peso coin drops into the slot (portal coin-insert;
   reusable by admin coin-rain). Additive eye-candy; the functional strike is
   handled separately (pulsePanel). The global prefers-reduced-motion block
   collapses this to an instant flash.
   ========================================================================= */
@keyframes piso-mint-drop {
  0%   { transform: translate(-50%, -64px) scale(.7) rotate(-24deg); opacity: 0; }
  30%  { opacity: 1; }
  68%  { transform: translate(-50%, 10px) scale(1.06) rotate(7deg); opacity: 1; }
  82%  { transform: translate(-50%, 18px) scaleX(1.12) scaleY(.82) rotate(2deg); }
  100% { transform: translate(-50%, 22px) scaleX(.97) scaleY(1.02); opacity: 0; clip-path: inset(0 0 50% 0); }
}
.piso-coin {
  position: absolute; left: 50%; top: 6px;
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background:
    radial-gradient(circle at 34% 30%, rgba(255,255,255,.85), transparent 46%),
    conic-gradient(from 210deg, #e6a52e, #ffe89a 20%, #cf8f22 50%, #ffdf87 78%, #e6a52e);
  box-shadow: 0 3px 10px rgba(0,0,0,.28), inset 0 0 0 2px rgba(255,255,255,.4);
  color: #7a4d10; font: 800 15px/1 var(--font-num, sans-serif);
  pointer-events: none; z-index: 6;
  animation: piso-mint-drop 640ms var(--ease) forwards;
}
.piso-coin::before { content: '₱'; }

/* --- Per-theme page PATTERN FIELD (portal body::before mask): each brand
   wears its own texture across the whole page, tinted by its accent. --- */
[data-decoration='fiesta'] { --theme-field: var(--theme-texture); --theme-field-size: 92px 92px; --theme-field-opacity: .34; }
[data-decoration='grid'] { --theme-field: var(--theme-texture); --theme-field-size: 34px 34px; --theme-field-opacity: .24; }
[data-decoration='waves'] { --theme-field: var(--theme-texture); --theme-field-size: 116px 44px; --theme-field-opacity: .26; }
[data-decoration='circuit'] { --theme-field: var(--theme-texture); --theme-field-size: 104px 104px; --theme-field-opacity: .24; }
[data-decoration='honeycomb'] { --theme-field: var(--theme-texture); --theme-field-size: 70px 62px; --theme-field-opacity: .34; }
[data-decoration='bubbles'] { --theme-field: var(--theme-texture); --theme-field-size: 100px 100px; --theme-field-opacity: .33; }
[data-decoration='leaves'] { --theme-field: var(--theme-texture); --theme-field-size: 88px 88px; --theme-field-opacity: .34; }
[data-decoration='rays'] { --theme-field: var(--theme-texture); --theme-field-size: 132px 132px; --theme-field-opacity: .26; }
[data-decoration='signal'] { --theme-field: url("/portal/img/circuit.svg"); --theme-field-size: 300px 300px; --theme-field-opacity: .07; }

/* ======================================================================
   THEME STRUCTURE — the part that makes a theme a different PRODUCT
   ======================================================================
   Before this block a "theme" changed colour and a background pattern, so all
   nine presets read as one design in nine costumes. Each preset below now also
   sets its SHAPE (a whole radius ramp + border weight), its GAIT (--th-dur-k
   scales every duration), its PRESS DEPTH, its DENSITY, and its TYPE PROPORTION
   (--th-type-ratio) — the dials that actually change how a design feels.

   These set `--th-` dials only. The ramps in §5/§6/§8 derive from them, so nothing
   here reaches past the contract to rebind the --r- or --s- ramps directly.

   Specificity note: [data-decoration='x'] and :root both score (0,1,0) and match
   the same <html> element, so these later rules win by source order. Keep this
   block LAST.

   Chromium 87 (the kiosk's Falkon) safe: plain custom properties + calc() only.
   ====================================================================== */

/* SIGNAL — the house look. Clean, lit, neutral gait. Ships as the default, so it
   deliberately keeps the baseline ramp. */
[data-decoration='signal'] {
  --th-r-1: 8px;  --th-r-2: 12px; --th-r-3: 16px;
  --th-r-4: 22px; --th-r-5: 28px; --th-r-6: 36px;
  --th-dur-k: 1;      --th-travel: 3px;   --th-density: 1;
  --th-type-ratio: 1.25; --th-w-title: 700; --th-w-display: 800;
}

/* KALSADA (grid) — jeepney signboard. Hard corners, tight, fast, shouty caps.
   The most structurally distant theme from SIGNAL. */
[data-decoration='grid'] {
  --th-r-1: 2px;  --th-r-2: 3px;  --th-r-3: 4px;
  --th-r-4: 6px;  --th-r-5: 8px;  --th-r-6: 10px;
  --th-r-pill: 6px;            /* a signboard has no pills */
  --th-edge-w: 2px;
  --th-dur-k: .72;    --th-travel: 2px;   --th-density: .94;  --th-gap-k: .9;
  --th-type-ratio: 1.36; --th-w-body: 500; --th-w-title: 900; --th-w-display: 900;
  --th-track-display: .02em; --th-case-display: uppercase;
}

/* KIDLAT (circuit) — PCB / solder mask. Small hard radii, quick, dense. */
[data-decoration='circuit'] {
  --th-r-1: 3px;  --th-r-2: 5px;  --th-r-3: 8px;
  --th-r-4: 10px; --th-r-5: 12px; --th-r-6: 16px;
  --th-r-pill: 10px;
  --th-dur-k: .75;    --th-travel: 2px;   --th-density: .92;  --th-gap-k: .92;
  --th-type-ratio: 1.32; --th-w-title: 800; --th-w-display: 800;
}

/* LAWA (waves) — lagoon. The calm end of the range: big soft radii, slow gait,
   light weights, generous spacing. */
[data-decoration='waves'] {
  --th-r-1: 14px; --th-r-2: 20px; --th-r-3: 26px;
  --th-r-4: 34px; --th-r-5: 42px; --th-r-6: 52px;
  --th-dur-k: 1.4;    --th-travel: 4px;   --th-density: 1.08; --th-gap-k: 1.12;
  --th-type-ratio: 1.16; --th-w-body: 400; --th-w-title: 500; --th-w-display: 600;
  --th-track-display: .005em;
}

/* BANIG (fiesta) — daylight market, woven mat. Warm, roomy, unhurried-but-bright. */
[data-decoration='fiesta'] {
  --th-r-1: 10px; --th-r-2: 14px; --th-r-3: 18px;
  --th-r-4: 22px; --th-r-5: 26px; --th-r-6: 32px;
  --th-dur-k: 1.2;    --th-travel: 4px;   --th-density: 1.06; --th-gap-k: 1.08;
  --th-type-ratio: 1.18; --th-w-title: 700; --th-w-display: 800;
}

/* SINAG (rays) — painted sun, brass. Display caps, medium gait. */
[data-decoration='rays'] {
  --th-r-1: 6px;  --th-r-2: 9px;  --th-r-3: 12px;
  --th-r-4: 16px; --th-r-5: 20px; --th-r-6: 26px;
  --th-dur-k: 1.1;    --th-travel: 3px;   --th-density: 1;
  --th-type-ratio: 1.28; --th-w-title: 800; --th-w-display: 800;
  --th-track-display: .02em; --th-case-display: uppercase;
}

/* BUBUYOG (honeycomb) — comb geometry. Small faceted radii, brisk. */
[data-decoration='honeycomb'] {
  --th-r-1: 4px;  --th-r-2: 6px;  --th-r-3: 8px;
  --th-r-4: 10px; --th-r-5: 14px; --th-r-6: 18px;
  --th-dur-k: 1.05;   --th-travel: 3px;   --th-density: 1;
  --th-type-ratio: 1.22; --th-w-title: 700; --th-w-display: 800;
}

/* LEAVES — organic, gentle. */
[data-decoration='leaves'] {
  --th-r-1: 10px; --th-r-2: 14px; --th-r-3: 18px;
  --th-r-4: 24px; --th-r-5: 30px; --th-r-6: 38px;
  --th-dur-k: 1.15;   --th-travel: 3px;   --th-density: 1.04; --th-gap-k: 1.06;
  --th-type-ratio: 1.20; --th-w-body: 400; --th-w-title: 600; --th-w-display: 700;
}

/* BUBBLES — the roundest theme. Playful, buoyant. */
[data-decoration='bubbles'] {
  --th-r-1: 14px; --th-r-2: 18px; --th-r-3: 24px;
  --th-r-4: 30px; --th-r-5: 38px; --th-r-6: 48px;
  --th-dur-k: 1.25;   --th-travel: 4px;   --th-density: 1.05; --th-gap-k: 1.1;
  --th-type-ratio: 1.20; --th-w-title: 600; --th-w-display: 700;
}

/* ----------------------------------------------------------------------
   Accessibility + hardware floors. These run AFTER every theme so no preset
   can dial itself into something unusable on a phone in a shop.
   ---------------------------------------------------------------------- */
:root {
  /* A touch target never shrinks below 48px no matter how dense the theme is —
     this is what lets the radius ramp be fully themeable instead of half-frozen. */
  --control-h: 48px;
}

/* ----------------------------------------------------------------------
   ON-ACCENT INK FLOOR — picked per preset from MEASURED contrast.
   ----------------------------------------------------------------------
   --on-accent was pinned to #ffffff, and white failed AA on all nine presets,
   because not one of the nine accents is actually dark — every gradient stop is
   bright or mid. This is the label on the button the customer taps right after
   dropping a coin, at 16px/600, so the floor is 4.5:1.

   WCAG 2.x, worst of the two --accent-grad stops:

     preset             accent    accent2    white   ink #0a0f1a
     Signal (default)   #14e0a0   #14c8e6    1.72     9.52
     Kislap             #a855f7   #22d3ee    1.81     4.84
     Kidlat             #2d7ff9   #18e0ff    1.60     5.03
     Lawa               #06b6a4   #38bdf8    2.14     7.51
     Sinag              #ffcb3c   #ff8a00    1.52     8.11
     Bubuyog            #ffb300   #fb6107    1.79     6.22
     Sigla              #16a34a   #a3e635    1.51     5.81
     Fiesta             #ff2d87   #7c4dff    3.51     3.98  <- CTA lift below
     Bula               #9d4edd   #ff5da2    2.86     4.17  <- CTA lift below

   This is a per-preset pick, not a blanket swap: a genuinely dark accent would
   still want white, which is exactly why --accent-ink stays #ffffff in §1 and
   why an unlisted decoration (or 'none') inherits it untouched.

   The ink is a LITERAL, not a neutral token: every step of the neutral ramp and
   the ink scale INVERTS with the scheme, and this one has to stay dark on a bright
   fill in both. data-decoration is already on <html> before the stylesheets load —
   index.html ships it and the anti-flash block restores the cached one — so this
   lands on the first frame with no JS.
   ---------------------------------------------------------------------- */
[data-decoration='signal'],
[data-decoration='grid'],
[data-decoration='circuit'],
[data-decoration='waves'],
[data-decoration='rays'],
[data-decoration='fiesta'],
[data-decoration='honeycomb'],
[data-decoration='bubbles'],
[data-decoration='leaves'] {
  --on-accent: #0a0f1a;
}

/* Fiesta and Bula are the two presets whose stops STRADDLE mid-luminance, so no
   single ink can clear 4.5:1 on a two-stop fill: Fiesta's #7c4dff is too dark for
   the ink (3.98) while its #ff2d87 is too light for white (3.51), and Bula is the
   same trap mirrored. Lift the CTA gradient 16% toward white and the ink clears —
   Fiesta #ff4f9a/#9169ff = 5.17, Bula #ad6ae2/#ff77b1 = 5.42.
   Only --accent-grad-cta moves. Lifting the shared --accent-grad would have
   dragged the text-free progress bars UNDER the 3:1 graphical floor (Bula's fill
   on --surface-2 goes 2.55 -> 2.19), which is why the CTA got its own token.
   Gated: on Chromium 87 the block is skipped and the CTA keeps var(--accent-grad)
   exactly as it renders today. */
@supports (color: color-mix(in srgb, red 50%, white)) {
  [data-decoration='fiesta'],
  [data-decoration='bubbles'] {
    --accent-grad-cta: linear-gradient(135deg,
      color-mix(in srgb, var(--accent) 84%, #fff),
      color-mix(in srgb, var(--accent-2) 84%, #fff));
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Collapse every theme's gait to near-zero rather than leaving nine different
     "slow" values in play. */
  :root { --th-dur-k: .01; --th-travel: 0px; }
}
