/* premium.css — the ONE shared premium surface for every Crystal app page.
   Lifted from index.html's exact glassmorphism system (palette + glass cards + layered depth +
   accent glow) with a COMPLETE body.light-mode remap so light mode works everywhere. Load AFTER
   theme.css (theme.css owns the animated background + theme panel + accent vars; this owns the
   page surface — cards, header, tabs, tables, inputs, pills). Pages must NOT redefine these; add
   only page-specific layout. This is the fix for "every page invented its own bleak CSS". */

/* ═══════════════════════════════════════════════════════════════════════════
   THE TYPEFACE — self-hosted, because this is a PWA.
   A CDN font is a blank page the first time somebody opens the app on a bad
   signal, and an offline install with no type at all. Latin subset, variable
   weight: 27KB + 31KB for the pair.
   ═══════════════════════════════════════════════════════════════════════════ */
@font-face{
  font-family:'Plus Jakarta Sans';
  src:url('/static/fonts/PlusJakartaSans-latin.woff2') format('woff2');
  font-weight:200 800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'JetBrains Mono';
  src:url('/static/fonts/JetBrainsMono-latin.woff2') format('woff2');
  font-weight:400 700; font-style:normal; font-display:swap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN TOKENS v2 — added ALONGSIDE the originals, never replacing them, so
   the other pages keep rendering exactly as they do until each is migrated.
   Everything here keys off --accent, so the user's colour scheme still drives
   the whole surface — the composition changes, the customisation does not.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{
  /* — Type scale. A ratio, not five arbitrary sizes. ————————————— */
  --t-3xl:44px; --t-2xl:32px; --t-xl:24px; --t-lg:19px;
  --t-md:16px;  --t-base:14px; --t-sm:12.5px; --t-xs:11px;
  --lh-tight:1.12; --lh-snug:1.3; --lh-base:1.55;
  --tr-tight:-.022em;   /* display sizes need negative tracking to look set, not typed */
  --tr-wide:.08em;      /* only for small uppercase labels */
  --font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  /* — Spacing. 4pt base; every gap is one of these. ————————————— */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px; --s8:64px;

  /* — Radii. Three, not eight. ————————————————————————————————— */
  --r2:10px; --r2-lg:16px; --r2-xl:22px;

  /* — Surfaces. Deep navy, not black: black + heavy shadow reads cheap,
       navy + hairline reads engineered. Tinted with the live accent so a
       theme change moves the whole field, not just the buttons. ————— */
  --field:#080c14;
  --field-2:rgba(var(--accent-rgb),.045);
  /* TINTED BY THE LIVE ACCENT. A neutral grey card leaves the theme showing only in text and
     buttons — pick Emerald or Violet and the app still reads as the same grey product. A few
     percent of the accent mixed into every surface and hairline makes the whole field carry the
     theme, which is what choosing one is for. Kept very low (4-5% on surfaces, 10-16% on borders) so
     it registers as a temperature rather than a colour wash.
     Dark mode only — light mode re-anchors these below and stays paper-white.

     Dark and translucent, NOT a faint white wash. A card has to stay readable over whatever
     the user put behind it — a bright photo, a pale aurora, anything. A 2.8%-white card was
     legible only over near-black, which is exactly the assumption that broke personalisation. */
  /* METALLIC GLASS, NOT METAL. At .78 these were effectively opaque: the wallpaper
     stopped at the edge of every panel, so the surfaces read as solid metal sitting ON
     a photograph rather than glass over it — the whole point of putting a picture back
     there is that you can still see it.

     The comment this replaces was right that a translucent card over a bright photo
     becomes unreadable — but the fix for that is BLUR, not opacity. Heavy blur destroys
     the high-frequency detail that competes with text while leaving colour and shape, so
     the panel is visibly see-through and the words stay crisp. Rendered side by side:
     .44 with blur 8 is unreadable; .50 with blur 34 is clean. Opacity alone was solving
     the legibility problem by removing the feature. */
  --card:color-mix(in srgb, rgb(var(--accent-rgb)) 8%, rgba(12,18,30,.50));
  --card-2:color-mix(in srgb, rgb(var(--accent-rgb)) 12%, rgba(16,24,40,.62));
  --card-solid:#0d1424;
  --line:color-mix(in srgb, rgb(var(--accent-rgb)) 24%, rgba(255,255,255,.12));
  --line-hi:color-mix(in srgb, rgb(var(--accent-rgb)) 42%, rgba(255,255,255,.24));

  /* — Elevation. 2090 Specular Depth + Ambient Laser Light ——— */
  --sh1:0 6px 20px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.14);
  --sh2:0 12px 36px rgba(0,0,0,.55), 0 0 20px rgba(var(--accent-rgb),.20), inset 0 1px 0 rgba(255,255,255,.22);
  --sh3:0 24px 64px rgba(0,0,0,.70), 0 0 40px rgba(var(--accent-rgb),.35), inset 0 1px 0 rgba(255,255,255,.30);

  /* — METAL. What separates a surface from a coloured rectangle. ————————
       A flat fill plus one hairline border is the generic-dashboard signature;
       real materials are lit unevenly and have edges with thickness. Three
       tokens do the whole job, and every component that uses them is one the
       shared sheet already owns, so all 30 pages inherit it.

       --metal-k is the USER'S dial (Appearance → Metallic, `metalGloss` pref).
       0 turns the entire layer off and restores the flat surfaces exactly;
       1 is the default; the slider goes to 1.5 for anyone who wants it harder.
       Every value below is multiplied by it, so there is one number to move. */
  --metal-k:1;

  /* 1. THE SURFACE. Bright at the top-left, falling through a shaded belly,
        with a return highlight bottom-right — how light falls on a slightly
        convex sheet. The spread has to be REAL: a 5%-white wash over a dark
        card is invisible, which is the mistake to avoid here. Top-to-belly is
        ~26 points of lightness. */
  --metal:linear-gradient(148deg,
     rgba(255,255,255,calc(.155 * var(--metal-k))) 0%,
     rgba(255,255,255,calc(.055 * var(--metal-k))) 26%,
     rgba(0,0,0,calc(.10 * var(--metal-k)))        58%,
     rgba(0,0,0,calc(.13 * var(--metal-k)))        78%,
     rgba(255,255,255,calc(.075 * var(--metal-k))) 100%);

  /* 2. THE BEVEL — lit top edge, shaded underside, plus a hairline rim light
        all the way round. The rim is what reads as a POLISHED edge rather than
        a drawn border, and it is the cheapest part of the effect. */
  --bevel:inset 0 1px 0 rgba(255,255,255,calc(.20 * var(--metal-k))),
          inset 0 -1px 0 rgba(0,0,0,calc(.55 * var(--metal-k))),
          inset 0 0 0 1px rgba(255,255,255,calc(.05 * var(--metal-k)));

  /* 3. ELEVATION, layered, carrying a whisper of the accent — so the chosen
        theme is present in the LIGHT around a card, not only in its text. */
  --sh-metal:0 1px 2px rgba(0,0,0,.45),
             0 12px 30px rgba(0,0,0,calc(.42 * var(--metal-k))),
             0 0 24px rgba(var(--accent-rgb),calc(.09 * var(--metal-k)));

  /* A FILLED button's version — a specular highlight OVER the flat accent, not
     a two-tone gradient (that was a named generated-UI tell, and the hardcoded
     second stop never followed the user's theme). Exposed as tokens rather
     than inlined into `.btn` so a page with its own primary button can reuse
     the exact treatment instead of re-inventing a gradient. */
  --gloss:linear-gradient(160deg,
     rgba(255,255,255,calc(.22 * var(--metal-k))) 0%,
     rgba(255,255,255,0) 46%,
     rgba(0,0,0,calc(.15 * var(--metal-k))) 100%);
  --gloss-sh:inset 0 1px 0 rgba(255,255,255,calc(.30 * var(--metal-k))),
             inset 0 -1px 0 rgba(0,0,0,calc(.26 * var(--metal-k))),
             0 1px 2px rgba(0,0,0,calc(.35 * var(--metal-k)));

  /* Inputs invert it: a field is RECESSED, a card is RAISED. Getting this the
     wrong way round is what makes a form look like a row of buttons. */
  --bevel-in:inset 0 1px 0 rgba(0,0,0,calc(.34 * var(--metal-k))),
             inset 0 -1px 0 rgba(255,255,255,calc(.07 * var(--metal-k)));
  --metal-in:linear-gradient(180deg,
     rgba(0,0,0,calc(.17 * var(--metal-k))), rgba(255,255,255,0) 42%);

  --dur:140ms;                 /* fast enough to feel instant, slow enough to read */
  --ease:cubic-bezier(.2,.7,.3,1);

  color-scheme: dark;   /* render native controls (esp. <select> option popups) dark — without this,
                           Windows/Chrome draws the popup light while option text stays white = invisible */
  /* THE ORIGINAL GLASS TOKENS, RE-BASED. These were a 3.5%/6% WHITE wash — legible only over
     near-black, and invisible the moment a user sets a background image. Every page in the app
     builds its cards, panels and rows on them, so re-basing here fixes the whole surface in one
     move rather than page by page. Dark and translucent: the blur still reads as glass, but the
     text on top stays readable over anything behind it. */
  --glass-bg:color-mix(in srgb, rgb(var(--accent-rgb)) 4%, rgba(9,13,20,.52));
  --glass-bg2:color-mix(in srgb, rgb(var(--accent-rgb)) 5%, rgba(9,13,20,.68));
  /* Borders carry the tint too — most pages draw their card edges from these, so leaving them
     neutral white would put a grey outline around every tinted surface. */
  --glass-border:color-mix(in srgb, rgb(var(--accent-rgb)) 10%, rgba(255,255,255,.08));
  --glass-border-hi:color-mix(in srgb, rgb(var(--accent-rgb)) 16%, rgba(255,255,255,.14));
  /* 20px was tuned for near-opaque panels. Now that they are genuinely translucent the
     blur is what carries legibility, so it does the heavier lifting. saturate() is what
     keeps the colour behind the glass alive rather than grey. */
  /* --glass-k is the user's Glass blur slider (Appearance → Glass), in pixels. The blur
     is composed from it rather than hardcoded so one preference drives every frosted
     surface at once — panels, cards, the rail, the top bar — instead of each having its
     own number to keep in step. The small variant tracks it at ~35%, so the relationship
     between a big panel and a small chip holds at every setting.
     saturate() deliberately does NOT scale: it is what keeps the colour behind the glass
     alive rather than grey, and it is still wanted at low blur. */
  --glass-k:34;
  --glass-blur:blur(calc(var(--glass-k) * 1px)) saturate(192%);
  --glass-blur-sm:blur(calc(var(--glass-k) * .35px)) saturate(160%);
  /* --muted was #888898: acceptable on flat black, hopeless as a section heading over a
     photo. Lifted for contrast — these carry labels the user has to read, not decoration. */
  --text:#f4f5f7; --muted:#9aa2b1; --muted2:#c3cad6;
  /* ── DARK-FIRST ESCAPE HATCHES ────────────────────────────────────────────
     Pages get written on a dark canvas, so they accumulate raw rgba(255,255,255)
     washes and rgba(0,0,0) shadows. Both are correct in dark and wrong in light:
     a white wash vanishes on a white card, and a black shadow turns to mud.
     These three tokens let a page keep ONE declaration that is right in both.
       --sh-ink / --sh-k : shadow colour + a per-theme alpha multiplier, so
                           rgba(var(--sh-ink),calc(.5*var(--sh-k))) stays exactly
                           rgba(0,0,0,.5) in dark and goes feather-light in light.
       --sheen           : the inset top highlight. A white sheen is the thing
                           that makes a dark card look like glass; on a light card
                           it is invisible, so light drops it rather than inverting
                           it into a stray dark line.
       --tok-base        : the dark floor that accent-tinted chips mix against. */
  --sh-ink:0,0,0; --sh-k:1;
  --sheen:rgba(255,255,255,.20); --sheen-hi:rgba(255,255,255,.35);
  --tok-base:rgba(0,0,0,.6);
  --surface:rgba(255,255,255,.04); --surface2:rgba(255,255,255,.07);
  --border:rgba(255,255,255,.09);
  --danger:#f05252; --warn:#f6a623; --success:#34d399; --cyan:#22d3ee; --purple:#b77aff;
  --glow-red:0 0 32px rgba(var(--accent-rgb),.35);
  --r:14px; --r-sm:10px;
  /* The app had no typeface of its own — it wore whatever the OS handed it
     (Segoe on Windows, SF on Mac, Roboto on Android), which is the loudest
     generic-dashboard tell there is. One real family now, everywhere. */
  --font:'Plus Jakarta Sans',system-ui,-apple-system,sans-serif;
  --shadow-card:0 10px 34px rgba(0,0,0,.42),inset 0 1px 0 rgba(255,255,255,.05);
  --shadow-hi:0 24px 70px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.07),0 0 60px rgba(var(--accent-rgb),.05);
}

/* ── The base typeface — APPLIED, not just declared ───────────────────── */
/* `--font` existed here from the start but nothing ever used it: every page that looked right
   was setting `font-family:var(--font)` on its own `html,body`, and any page that forgot fell
   back to the browser's default SERIF. That is a shared-system job, not something each page
   should have to remember, so it lives here now (found on crystal_training.html 2026-07-27;
   deals.html and teach.html had the same defect and are fixed by this one line).
   Pages that still declare it themselves are unaffected — they use the same variable. */
html,body{ font-family:var(--font); }
/* NO SIDEWAYS SCROLL, EVER.
   The appearance drawer is parked off the right edge on every page that loads theme.js, and a
   parked fixed element still extends the scrollable area — so the whole page could be dragged
   ~300px sideways on a phone, which is exactly what "everything is off-centre" feels like.
   `clip` rather than `hidden`: `hidden` would silently kill every `position:sticky` header in
   the app. This is also the backstop for any page whose own content overflows. */
html,body{ overflow-x:clip; }

/* THE SCRIM — the layer that makes text on the page legible over ANY background.
   Cards carry their own surface, so they were always readable. Everything OUTSIDE a card —
   the greeting, section headings, filter labels, "+5 more waiting" — is bare text sitting
   directly on the user's wallpaper, and a bright image (chrome, snow, silver) swallows muted
   grey completely. `bgOverlay` is the user's own dial and belongs to them; this is a separate,
   gentler veil the app applies so its own type is always readable.
   Sits above `body::before` (same z-index, later in tree order), below all content (z-index 1).
   Weighted to the top, where the greeting and headings live. */
body::after{
  content:''; position:fixed; inset:0; z-index:0; pointer-events:none;
  background:linear-gradient(180deg,
    rgba(6,8,12,.74) 0%, rgba(6,8,12,.56) 34%, rgba(6,8,12,.5) 62%, rgba(6,8,12,.66) 100%);
}
body.light-mode::after{
  background:linear-gradient(180deg,
    rgba(244,246,250,.8) 0%, rgba(244,246,250,.66) 34%, rgba(244,246,250,.6) 62%, rgba(244,246,250,.74) 100%);
}
/* Form controls and buttons do NOT inherit the page font by default — without this every
   input and textarea renders in the browser's monospace/system default. */
input,select,textarea,button{ font-family:inherit; }

/* ── Glass mixins ─────────────────────────────────────────────────────── */
.glass{ background:var(--glass-bg); backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur); border:1px solid var(--glass-border); }
.glass-hi{ background:var(--glass-bg2); backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur); border:1px solid var(--glass-border-hi); }

/* ── Premium card (frosted, layered depth, faint top-edge highlight) ──── */
/* HAIRLINE, NOT CLOUD. The old card carried a 34px/42%-black shadow plus a second 44px shadow
   and an accent glow on hover, over a 3.5%-white fill — heavy and murky, and only legible on a
   near-black background. Now: a dark translucent surface that stays readable over ANY user
   background, one hairline border, and a shadow you have to look for. */
/* `.card` IS A SHARED COMPONENT (2026-08-16). It is the most-used surface in the app — 44 uses in
   finance.html alone — and it was defined PRIVATELY in ten separate pages, none of which this sheet
   knew about. That is why "unified styling" kept turning out not to be: yesterday's work unified
   the TOKENS, so anything saying `var(--glass-bg)` moved together, but a shared COMPONENT change
   (the metal, a radius, a shadow) could never reach a class the shared sheet had never heard of.
   Two of the ten (admin, tenant_admin) were still on the pre-re-base `rgba(255,255,255,.04)` wash
   and so had silently missed the "readable over any background" fix as well.
   Surface only — pages keep their own padding/margins, which legitimately differ. */
.panel,.pcard,.kpi,.card{
  position:relative; background:var(--card);
  backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur);
  border:1px solid var(--line); border-radius:var(--r2-lg);
  box-shadow: 0 6px 20px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.12);
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
.panel::before,.pcard::before,.kpi::before,.card::before{
  content:''; position:absolute; top:0; left:0; right:0; height:1px;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.20), transparent);
  opacity:.8; pointer-events:none; border-radius:var(--r2-lg) var(--r2-lg) 0 0;
}
.pcard,.kpi{ padding:var(--s4) var(--s5); }
.pcard:hover,.kpi:hover,.card:hover{
  border-color:rgba(var(--accent-rgb),.5); background:var(--card-2);
  transform:translateY(-3px);
  box-shadow: 0 14px 36px rgba(0,0,0,.5), 0 0 20px rgba(var(--accent-rgb),.16), inset 0 1px 0 rgba(255,255,255,.22);
}
.panel:hover{ border-color:var(--line-hi); }

/* Universal 2090 Telemetry Beacons */
.pulse-beacon{ display:inline-block; width:8px; height:8px; border-radius:50%; background:var(--accent);
  margin-right:8px; vertical-align:middle; animation:beaconRipple 2s infinite ease-in-out; }
@keyframes beaconRipple{
  0%{ box-shadow:0 0 0 0 rgba(var(--accent-rgb),.7); }
  70%{ box-shadow:0 0 0 7px rgba(var(--accent-rgb),0); }
  100%{ box-shadow:0 0 0 0 rgba(var(--accent-rgb),0); }
}

/* Universal 2090 Code / Monogram Pill */
.code-pill,.pcode-pill{ font-family:var(--font-mono); font-size:11px; font-weight:700; padding:2px 7px;
  border-radius:6px; background:color-mix(in srgb, var(--accent) 12%, transparent);
  border:1px solid color-mix(in srgb, var(--accent) 30%, transparent); color:var(--text); letter-spacing:.03em; }

/* 2090 Orbital Radial Progress Gauge */
.radial-orbit{ width:44px; height:44px; flex:none; position:relative; display:flex; align-items:center; justify-content:center; }
.radial-orbit svg{ transform:rotate(-90deg); width:100%; height:100%; overflow:visible; }
.radial-orbit circle{ fill:none; stroke-width:3.5; stroke-linecap:round; }
.radial-orbit .bg-ring{ stroke:color-mix(in srgb, var(--text) 10%, transparent); }
.radial-orbit .val-ring{ stroke:url(#gauge-grad); transition:stroke-dashoffset .6s cubic-bezier(.2,.7,.3,1); }
.radial-orbit .pct-txt{ position:absolute; font-family:var(--font-mono); font-size:10.5px; font-weight:800; color:var(--text); }

/* the 2px accent bar across the top of every KPI was decoration, not information */
.kpi .l{ color:var(--muted); font-size:var(--t-xs); text-transform:uppercase;
         letter-spacing:var(--tr-wide); font-weight:700; }
/* The number is the headline, and it must align digit-to-digit with the one beside it. */
.kpi .v{ font-size:var(--t-2xl); font-weight:800; margin-top:var(--s2); line-height:1.05;
         letter-spacing:var(--tr-tight); white-space:nowrap;
         font-variant-numeric:tabular-nums; font-feature-settings:'tnum' 1; font-family:var(--font-mono); }
.kpi .s{ color:var(--muted); font-size:var(--t-sm); margin-top:var(--s1); }
/* ── A KPI NUMBER MUST FIT ITS OWN CARD ───────────────────────────────────────
   `.kpi .v` is 32px monospace with white-space:nowrap. Put five KPIs across a
   Finance dashboard on a 1440px screen and each card has roughly 175px of content
   width for a string like "R 237,712.26" that wants about 230px — so the last
   digits were simply CLIPPED. A truncated money figure is worse than a small one:
   R 237,712.26 rendered as "R 237,712.2" is not hard to read, it is WRONG.

   Sized against the CARD, not the viewport. A viewport media query cannot answer
   this: the same 1440px screen shows four KPIs comfortably and five clipped, and
   the number of KPIs is a per-page decision. The card knows its own width, so it
   is the card that gets asked.

   The old 620px breakpoint stays as the floor for browsers without container
   queries — it is a fallback now, not the mechanism. */
.kpi{ container-type:inline-size; }
@container (max-width:300px){ .kpi .v{ font-size:26px; } }
@container (max-width:250px){ .kpi .v{ font-size:22px; } }
@container (max-width:200px){ .kpi .v{ font-size:19px; letter-spacing:-.5px; } }
@container (max-width:160px){ .kpi .v{ font-size:16px; } }
@media(max-width:620px){ .kpi .v{ font-size:clamp(20px,7.2vw,var(--t-2xl)); } }

/* ── Header / topbar ──────────────────────────────────────────────────── */
.topbar{
  position:sticky; top:0; z-index:20; display:flex; align-items:center; gap:8px; padding:0 20px; height:56px;
  background:rgba(10,14,24,.92); backdrop-filter:blur(30px) saturate(200%); -webkit-backdrop-filter:blur(30px) saturate(200%);
  border-bottom:1px solid rgba(var(--accent-rgb),.35);
  box-shadow:0 1px 0 rgba(var(--accent-rgb),.2), 0 8px 32px rgba(0,0,0,.6);
}
.brand{ font-weight:900; font-size:16.5px; letter-spacing:-.3px;
  background:linear-gradient(135deg,#fff 25%,var(--accent) 70%,var(--cyan) 100%);
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
  filter:drop-shadow(0 0 10px rgba(var(--accent-rgb),.4)); }
.brand small{ -webkit-text-fill-color:var(--muted); color:var(--muted); font-weight:600; filter:none; }
/* LIGHT MODE: ink start */
body.light-mode .brand{
  background:linear-gradient(135deg,#0f172a 22%,var(--accent) 76%,var(--accent2));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
body.light-mode .brand small{ -webkit-text-fill-color:var(--muted); color:var(--muted); }
.sp{ flex:1; }
.tl{ color:var(--muted); text-decoration:none; font-weight:650; font-size:12.5px; padding:7px 12px; border-radius:9px; border:1px solid transparent; cursor:pointer; background:none; transition:all .15s; }
/* WHITE IS NOT A COLOUR, IT IS A DARK-MODE ASSUMPTION. These carried `color:#fff` and white
   washes, and light mode overrides `.glass`/`.sheet`/`.topbar` but NOT `.btn`/`.tl`/`.btn-ghost`
   — so hovering a nav link or a ghost button in light mode turned its label white on a
   near-white background and it vanished, on all 30 pages. `var(--text)` and a text-derived
   wash give the same effect in dark and the inverse in light. White INSETS are left alone:
   they are gloss, and fading out on a light surface is correct. */
.tl:hover{ color:var(--text); background:color-mix(in srgb,var(--text) 8%,transparent); border-color:var(--line-hi); box-shadow:0 0 12px rgba(var(--accent-rgb),.2); }

/* ── Buttons (2090 Specular Gloss + Ambient Glow) ─────────────────────── */
.btn{ cursor:pointer; border:1px solid var(--line-hi); border-radius:var(--r2); font-family:inherit;
  font-weight:700; font-size:13.5px; padding:9px 18px; min-height:38px;
  color:#fff;
  /* SPLIT ON PURPOSE — do not collapse back into the `background` shorthand. The shorthand puts
     the accent in background-IMAGE and resets background-COLOR to transparent, and the metal pass
     at the bottom of this file then overwrites background-image with var(--gloss). The accent was
     painted straight out: white text on nothing, invisible in light mode on every page (T455).
     .btn-ghost never had the bug because its colour is a plain colour, so it survives in
     background-color with --metal layered over — this makes .btn behave the same way, which is
     what the gloss comment down there always claimed ("the fill stays FLAT accent"). */
  background-color: var(--accent);
  background-image: linear-gradient(145deg, var(--accent) 0%, color-mix(in srgb, var(--accent) 80%, #000) 100%);
  box-shadow: 0 4px 16px rgba(var(--accent-rgb),.35), inset 0 1px 0 rgba(255,255,255,.32); letter-spacing:0;
  transition:all var(--dur) var(--ease); }
.btn:hover{ filter:brightness(1.15); box-shadow: 0 6px 24px rgba(var(--accent-rgb),.55), inset 0 1px 0 rgba(255,255,255,.45); transform:translateY(-1px); }
.btn:active{ filter:brightness(.95); transform:translateY(1px); }
.btn-ghost{ background:var(--card-2); color:var(--text); border:1px solid var(--line-hi);
  box-shadow:0 4px 12px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.1); font-weight:650; }
.btn-ghost:hover{ background:color-mix(in srgb,var(--text) 10%,transparent); border-color:var(--accent); color:var(--text); box-shadow:0 6px 20px rgba(0,0,0,.4), 0 0 12px rgba(var(--accent-rgb),.25); transform:translateY(-1px); }
.btn-sm{ padding:6px 14px; font-size:12.5px; border-radius:8px; min-height:30px; }

/* ── A table cell full of action buttons ──────────────────────────────────────
   Buttons are inline-level boxes with a min-height (30px at btn-sm), but a table
   cell spaces WRAPPED LINES by its line-height, which is about 19px here. So the
   moment a row had five actions — Send, Pay link, Accept, Decline, Print — the
   overflow line was laid out 19px below a 30px box and the buttons visibly
   collided with each other and the row beneath.

   Flex removes the line-box arithmetic entirely: the container measures its
   children, so a wrapped row simply grows. `gap` also replaces the whitespace
   between tags as the spacing mechanism, which is what made the gaps uneven. */
/* ══════════════════════════════════════════════════════════════════════════
   HIGH-DENSITY TABLE CELLS — stacking and micro-visuals
   ══════════════════════════════════════════════════════════════════════════
   Our tables put one flat string in each of ten columns, so a row is a line of
   undifferentiated text and the eye has nothing to land on. The fix is not more
   columns, it is FEWER columns each carrying more: a primary fact, its
   qualifier beneath, and its badges — so an invoice reads as a document rather
   than a spreadsheet row.

   TOKENS ONLY, NO PALETTE. The methodology this follows ships a light-mode
   scheme of literal hexes (#0d5cae headers, #ffffff cards, #f2faf4 row tints).
   Adopting those would fight the metallic-glass surfaces and break light mode,
   and hardcoded hex is the one thing the frontend rules here forbid outright.
   The TECHNIQUES carry over; the colours come from --accent/--success/--warn/
   --danger and re-anchor per theme like everything else.

   Every one of these is a shared component so the treatment cannot diverge
   table by table — which is how thirty pages end up looking like thirty
   products. */

/* Primary fact, its qualifier, its badges — one cell instead of three columns. */
.tstack{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.tstack .t1{ font-weight:700; color:var(--text); line-height:1.25; }
.tstack .t2{ font-size:11.5px; color:var(--muted); line-height:1.3;
             overflow:hidden; text-overflow:ellipsis; }
.tstack .t3{ display:flex; gap:4px; flex-wrap:wrap; margin-top:2px; }

/* A number that IS the point of its column — big, tabular, aligned digit to digit. */
.tnum{ font-family:var(--font-mono); font-variant-numeric:tabular-nums;
       font-weight:800; font-size:14.5px; color:var(--text); white-space:nowrap; }
.tnum.pos{ color:var(--success); }
.tnum.neg{ color:var(--danger); }
.tsub{ font-size:10.5px; color:var(--muted); white-space:nowrap; }

/* 5px progress track. Deliberately thin: it is a glance, not a chart, and a
   heavy bar in a dense row competes with the number above it. */
.tbar{ height:5px; border-radius:3px; overflow:hidden; margin:3px 0 2px;
       background:color-mix(in srgb,var(--text) 12%,transparent); max-width:120px; }
.tbar > i{ display:block; height:100%; border-radius:3px; background:var(--accent); }
.tbar > i.ok{   background:var(--success); }
.tbar > i.warn{ background:var(--warn); }
.tbar > i.bad{  background:var(--danger); }
td.r .tbar{ margin-left:auto; }

/* Zero-dependency column sparkline — flex columns, proportional heights. No
   library, no canvas, and it survives a theme switch because the bar colour is
   a token rather than a fill baked at render time. */
.tspark{ display:flex; gap:3px; align-items:flex-end; justify-content:flex-end; height:30px; }
.tspark > span{ display:flex; flex-direction:column; align-items:center; min-width:15px; }
.tspark > span > i{ display:block; width:7px; border-radius:2px 2px 0 0;
                    background:var(--accent); min-height:3px; }
.tspark > span > b{ font-size:8.5px; font-weight:700; line-height:1; color:var(--muted2); }
.tspark > span > u{ font-size:7.5px; text-decoration:none; line-height:1; color:var(--muted);
                    margin-top:2px; }

/* Row tints carry MEANING, not decoration: a left edge, so the row is still
   readable over a wallpaper where a full-row background wash would not be. */
tr.row-ok  > td:first-child{ box-shadow:inset 3px 0 0 var(--success); }
tr.row-warn> td:first-child{ box-shadow:inset 3px 0 0 var(--warn); }
tr.row-bad > td:first-child{ box-shadow:inset 3px 0 0 var(--danger); }

/* Denser rows once cells stack — the vertical rhythm comes from the stack, so
   the old padding leaves the table looking gappy. */
/* STANDALONE, not scoped to table.list/table.data. The invoice table carries no class at all,
   so scoping this meant the padding silently did not apply to the very table it was written
   for — the cells stacked but kept the old roomy spacing. A utility that only works on tables
   someone remembered to class is a utility that fails quietly. */
td.dense{ padding:8px 12px; vertical-align:top; }

td.rowacts, .rowacts{
  display:flex; flex-wrap:wrap; gap:6px;
  justify-content:flex-end; align-items:center;
}

/* ── Inputs ───────────────────────────────────────────────────────────── */
/* READABLE OVER ANY BACKGROUND. These were `--surface2` — a 7%-white wash that vanishes over
   an uploaded photo, so the filter row read as floating text on wallpaper. Same fix as the
   cards: a dark translucent base with a blur behind it. Also a real 42px control height
   (44px on touch) — 9px of padding gave a target well under the mobile minimum. */
/* :not([type=checkbox]):not([type=radio]) — a checkbox IS an input, so `min-height:42px` +
   padding + a card background turned every tick box in the platform into a 42px grey slab
   (visible beside "Open only" on /projects, and anywhere else a checkbox sits in a row).
   Native controls want the accent colour and their own size, nothing else. */
input[type=checkbox],input[type=radio]{
  width:18px; height:18px; min-height:0; padding:0; flex:0 0 auto;
  accent-color:var(--accent); cursor:pointer; background:none; border:none; box-shadow:none; }
@media(max-width:720px){ input[type=checkbox],input[type=radio]{ width:20px; height:20px; } }
input:not([type=checkbox]):not([type=radio]),select,textarea{ background:var(--card-2); border:1px solid var(--line-hi); color:var(--text);
  border-radius:var(--r2); padding:0 12px; min-height:42px; font-size:13.5px; font-family:inherit;
  outline:none; backdrop-filter:var(--glass-blur-sm); -webkit-backdrop-filter:var(--glass-blur-sm);
  transition:border-color var(--dur) var(--ease), background var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
textarea{ padding:10px 12px; min-height:88px; }
input:focus,select:focus,textarea:focus{ border-color:rgba(var(--accent-rgb),.6);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb),.16); }
@media(max-width:720px){ input,select{ min-height:44px; font-size:16px; } }
/* 16px on mobile is not a style choice: iOS zooms the whole page when you focus a field
   under 16px, which then drags the layout sideways. */
/* Native <option> popups: --surface2 is translucent glass (can't back a popup over the OS's white),
   so give options a SOLID dark bg + light text. Cross-browser backstop to color-scheme above — fixes
   the white-on-white dropdown items reported on Windows/Chrome dark mode. */
select option,select optgroup{ background:#1b1b22; color:#f0f0f0; }
input::placeholder,textarea::placeholder{ color:var(--muted); }
input[readonly]{ opacity:.85; }

/* ── Tables ───────────────────────────────────────────────────────────── */
table.list,table.data{ width:100%; border-collapse:collapse; }
table.list th,table.data th{ text-align:left; font-size:var(--t-xs); letter-spacing:var(--tr-wide); text-transform:uppercase; color:var(--muted); font-weight:700; padding:12px 14px; border-bottom:1px solid var(--line); }
/* Every figure in a table lines up — a money column that doesn't is the fastest way to look
   amateur on a finance screen, and there was no `tabular-nums` anywhere in the app. */
table.list td,table.data td{ font-variant-numeric:tabular-nums; font-feature-settings:'tnum' 1; }
table.list td,table.data td{ padding:11px 14px; border-bottom:1px solid rgba(255,255,255,.05); font-size:13.5px; }
table.data th{ padding:8px 10px; } table.data td{ padding:8px 10px; font-size:13px; }
table.list tbody tr{ cursor:pointer; transition:background .12s; }
table.list tbody tr:hover td{ background:rgba(255,255,255,.03); }
table.data tfoot td{ font-weight:800; border-top:1px solid var(--glass-border); }

/* ── Pills / status ───────────────────────────────────────────────────── */
.pill{ display:inline-block; font-size:11.5px; font-weight:700; padding:3px 11px; border-radius:20px; background:rgba(var(--accent-rgb),.14); color:var(--text); border:1px solid var(--glass-border-hi); }
.ph-intake{ background:rgba(100,120,255,.18); border-color:rgba(100,120,255,.3); }
.ph-quote{ background:rgba(246,166,35,.18); color:var(--warn); border-color:rgba(246,166,35,.32); }
.ph-wip{ background:rgba(52,211,153,.16); color:var(--success); border-color:rgba(52,211,153,.3); }
.ph-resolution{ background:rgba(148,163,184,.18); color:var(--muted2); border-color:rgba(148,163,184,.28); }
/* A phase pill for the FAILED/REJECTED end of any lifecycle. The set had no danger variant, so
   every module that needed one invented a local class — which is how the shared system decays. */
.ph-failed{ background:rgba(248,113,113,.15); color:var(--danger); border-color:rgba(248,113,113,.3); }
.ph-closure{ background:rgba(148,163,184,.1); color:var(--muted); }

/* ── Tabs ─────────────────────────────────────────────────────────────── */
.tabs{ display:flex; gap:6px; flex-wrap:wrap; padding:12px 20px 0; border-bottom:1px solid var(--glass-border); position:sticky; background:rgba(16,16,20,.92); backdrop-filter:var(--glass-blur); z-index:2; }
.tab{ cursor:pointer; font-size:13px; font-weight:650; padding:9px 14px; border-radius:9px 9px 0 0;
  color:var(--muted); border:1px solid transparent; border-bottom:none; transition:all .15s;
  /* MUST be explicit. Every page but one uses .tab on a <div>, which is transparent by
     default — so the missing declaration never showed. On a <button> the UA default
     `buttonface` renders as a solid grey slab (rgb(107,107,107) in dark colour-scheme).
     Same reason `font:inherit` is set on controls further up. */
  background:none; font-family:inherit; }
.tab:hover{ color:var(--text); background:rgba(255,255,255,.05); }
.tab.on{ color:var(--accent2); background:var(--glass-bg2); border-color:var(--glass-border-hi); box-shadow:inset 0 2px 0 var(--accent); }
.tabpane{ display:none; } .tabpane.on{ display:block; }

/* status chips (job workspace) */
.statuses{ display:flex; flex-wrap:wrap; gap:6px; margin:6px 0 4px; }
.st{ cursor:pointer; font-size:12px; font-weight:640; padding:6px 12px; border-radius:18px; border:1px solid var(--glass-border); background:var(--glass-bg); color:var(--muted); transition:all .15s; }
.st:hover{ color:var(--text); border-color:rgba(var(--accent-rgb),.5); }
.st.on{ background:rgba(var(--accent-rgb),.2); color:var(--text); border-color:rgba(var(--accent-rgb),.6); box-shadow:0 0 18px rgba(var(--accent-rgb),.2); }

/* ── Fields / sections ────────────────────────────────────────────────── */
.fld{ margin-bottom:11px; } .fld label{ display:block; font-size:11.5px; color:var(--muted); font-weight:600; margin-bottom:5px; }
.fld input,.fld select,.fld textarea{ width:100%; }
.sec{ font-size:11px; letter-spacing:.09em; text-transform:uppercase; color:var(--muted); font-weight:800; margin:20px 0 10px; border-bottom:1px solid var(--glass-border); padding-bottom:7px; }
.sec:first-child{ margin-top:4px; }
.muted{ color:var(--muted); font-size:13px; }
.empty{ color:var(--muted); text-align:center; padding:44px; }

/* ── Modal / sheet overlays ───────────────────────────────────────────── */
.backdrop{ position:fixed; inset:0; z-index:60; background:rgba(0,0,0,.6); backdrop-filter:blur(4px); display:none; align-items:flex-start; justify-content:center; padding:30px 16px; overflow:auto; }
.backdrop.on{ display:flex; }
.sheet,.modal{ width:100%; background:rgba(18,18,22,var(--bg-overlay-opacity,.92)); backdrop-filter:blur(28px) saturate(180%); -webkit-backdrop-filter:blur(28px) saturate(180%);
  border:1px solid var(--glass-border-hi); border-radius:18px; box-shadow:var(--shadow-hi); }
.sheet{ max-width:1080px; } .modal{ max-width:720px; }
.mhead{ display:flex; align-items:center; justify-content:space-between; padding:16px 22px; border-bottom:1px solid var(--glass-border); position:sticky; top:0; background:rgba(18,18,22,.96); z-index:3; border-radius:18px 18px 0 0; }
.mtitle{ font-weight:750; font-size:16px; }
.mx{ cursor:pointer; background:rgba(255,255,255,.05); border:1px solid var(--glass-border); border-radius:8px; color:var(--muted); font-size:16px; width:30px; height:30px; }
.mx:hover{ color:var(--text); background:rgba(255,255,255,.1); }
.mbody{ padding:18px 22px 26px; }
.save-bar{ display:flex; justify-content:flex-end; gap:8px; margin-top:18px; }

/* ── Toast ────────────────────────────────────────────────────────────── */
.toast{ position:fixed; bottom:22px; left:50%; transform:translateX(-50%); z-index:90; background:rgba(20,20,24,.96); backdrop-filter:var(--glass-blur); border:1px solid var(--glass-border-hi); border-left:3px solid var(--accent); border-radius:10px; padding:11px 18px; font-size:13px; box-shadow:0 14px 40px rgba(0,0,0,.5); display:none; }

/* ══════════════════════════════════════════════════════════════════════
   LIGHT MODE — complete remap (theme.js toggles body.light-mode).
   Solid white cards, soft neutral shadows, backdrop-filter OFF, dark text.
   ══════════════════════════════════════════════════════════════════════ */
body.light-mode{
  color-scheme: light;   /* native controls render light in light mode */
  /* ── THE ACCENT REACHES LIGHT MODE TOO ──────────────────────────────────────
     These were pure white — #fff, rgba(255,255,255,.82) — while the DARK palette
     mixes the accent into every surface (--card is accent 8% over near-black).
     The result was that choosing a theme colour did nothing at all in light mode:
     card, panel, modal, field and page all landed on the same white, so nothing
     separated from anything and the whole screen read as glare.

     The percentages are deliberately lower than dark's. On near-black an 8% mix
     is a hint; on white the same 8% is a colour. 5-8% is the band where a surface
     is visibly OF the theme without becoming a coloured rectangle, and where
     stacked surfaces (page < panel < card < modal) stay distinguishable. */
  --glass-bg:color-mix(in srgb, rgb(var(--accent-rgb)) 4%, rgba(255,255,255,.90));
  --glass-bg2:color-mix(in srgb, rgb(var(--accent-rgb)) 3%, #fff);
  --glass-border:color-mix(in srgb, rgb(var(--accent-rgb)) 30%, rgba(0,0,0,.10));
  --glass-border-hi:color-mix(in srgb, rgb(var(--accent-rgb)) 42%, rgba(0,0,0,.14));
  /* --muted was #6b7280, which measured 3.66:1 against the now-tinted light surfaces —
     under the 4.5:1 AA floor. Tinting the surfaces without moving this would have traded
     glare for unreadable secondary text. #5d636f is the smallest darkening that clears AA
     on every accent (4.57:1 worst case) while staying clearly secondary to --text (2.94:1). */
  --text:#111827; --muted:#5d636f; --muted2:#4b5563;
  --surface:color-mix(in srgb, rgb(var(--accent-rgb)) 8%, rgba(0,0,0,.04));
  --surface2:color-mix(in srgb, rgb(var(--accent-rgb)) 3%, #fff);
  --border:color-mix(in srgb, rgb(var(--accent-rgb)) 16%, rgba(0,0,0,.10));
  --danger:#dc2626; --warn:#d97706; --success:#059669; --cyan:#0891b2; --purple:#7c3aed;
  --glow-red:0 0 20px rgba(var(--accent-rgb),.1);
  --shadow-card:0 2px 10px rgba(0,0,0,.08),0 1px 3px rgba(0,0,0,.05);
  --shadow-hi:0 24px 60px rgba(0,0,0,.18);
}
body.light-mode .panel,body.light-mode .pcard,body.light-mode .kpi{ background:#fff !important; backdrop-filter:none !important; -webkit-backdrop-filter:none !important; border-color:rgba(0,0,0,.09); box-shadow:var(--shadow-card); }
body.light-mode .pcard:hover,body.light-mode .kpi:hover{ border-color:rgba(0,0,0,.16); box-shadow:0 6px 20px rgba(0,0,0,.1); }
body.light-mode .kpi::after{ opacity:.35; }
body.light-mode .glass{ background:rgba(255,255,255,.85) !important; backdrop-filter:none !important; border-color:rgba(0,0,0,.09); }
body.light-mode .glass-hi{ background:#fff !important; backdrop-filter:none !important; border-color:rgba(0,0,0,.12); }
body.light-mode .topbar{ background:rgba(255,255,255,.95) !important; border-bottom-color:rgba(0,0,0,.1) !important; box-shadow:0 1px 0 rgba(0,0,0,.06),0 4px 16px rgba(0,0,0,.07) !important; }
body.light-mode .tl{ color:#374151; }
body.light-mode .tl:hover{ color:#111; background:rgba(0,0,0,.06); border-color:rgba(0,0,0,.14); }
body.light-mode .btn-ghost{ background:rgba(0,0,0,.04); color:#111; border-color:rgba(0,0,0,.14); }
body.light-mode .btn-ghost:hover{ background:rgba(0,0,0,.08); }
body.light-mode input,body.light-mode select,body.light-mode textarea{ background:#fff; border-color:rgba(0,0,0,.14); color:#111; }
body.light-mode select option,body.light-mode select optgroup{ background:#fff; color:#111; }
body.light-mode input:focus,body.light-mode select:focus,body.light-mode textarea:focus{ border-color:rgba(var(--accent-rgb),.5); background:rgba(var(--accent-rgb),.03); }
body.light-mode input[readonly]{ background:#f3f4f6; }
body.light-mode table.list th,body.light-mode table.data th{ color:#374151; background:#f3f4f6; border-bottom-color:rgba(0,0,0,.09); }
body.light-mode table.list td,body.light-mode table.data td{ border-bottom-color:rgba(0,0,0,.06); color:#111; }
body.light-mode table.list tbody tr:hover td{ background:rgba(0,0,0,.025); }
body.light-mode table.data tfoot td{ border-top-color:rgba(0,0,0,.12); }
body.light-mode .tabs{ background:rgba(248,249,252,.95); border-bottom-color:rgba(0,0,0,.09); }
body.light-mode .tab{ color:#374151; }
body.light-mode .tab:hover{ color:#111; background:rgba(0,0,0,.05); }
body.light-mode .tab.on{ color:var(--accent); background:#fff; border-color:rgba(0,0,0,.1); box-shadow:inset 0 2px 0 var(--accent); }
body.light-mode .st{ background:rgba(0,0,0,.04); border-color:rgba(0,0,0,.12); color:#374151; }
body.light-mode .st:hover{ color:#111; }
body.light-mode .st.on{ background:rgba(var(--accent-rgb),.1); color:var(--accent); border-color:rgba(var(--accent-rgb),.35); box-shadow:none; }
body.light-mode .pill{ color:#111; border-color:rgba(0,0,0,.12); }
body.light-mode .ph-intake{ color:#3730a3; background:rgba(99,102,241,.1); border-color:rgba(99,102,241,.22); }
body.light-mode .ph-quote{ color:#92400e; background:rgba(217,119,6,.1); border-color:rgba(217,119,6,.22); }
body.light-mode .ph-failed{ color:#b91c1c; background:rgba(220,38,38,.09); border-color:rgba(220,38,38,.22); }
body.light-mode .ph-wip{ color:#065f46; background:rgba(5,150,105,.1); border-color:rgba(5,150,105,.22); }
body.light-mode .ph-resolution{ color:#374151; background:rgba(75,85,99,.08); border-color:rgba(75,85,99,.16); }
body.light-mode .ph-closure{ color:#6b7280; background:rgba(107,114,128,.06); }
body.light-mode .sec{ color:#6b7280; border-bottom-color:rgba(0,0,0,.08); }
body.light-mode .muted,body.light-mode .empty{ color:#6b7280; }
body.light-mode .sheet,body.light-mode .modal{ background:#fff !important; backdrop-filter:none !important; -webkit-backdrop-filter:none !important; border-color:rgba(0,0,0,.1); box-shadow:var(--shadow-hi); }
body.light-mode .mhead{ background:#fff; border-bottom-color:rgba(0,0,0,.08); }
body.light-mode .mx{ background:rgba(0,0,0,.05); border-color:rgba(0,0,0,.1); color:#374151; }
body.light-mode .mx:hover{ background:rgba(0,0,0,.09); color:#111; }
body.light-mode .toast{ background:#fff; border-color:rgba(0,0,0,.12); box-shadow:0 14px 40px rgba(0,0,0,.18); color:#111; }

/* ── Embedded in the /home shell (body.embedded) ──────────────────────────────
   The shell owns appearance, profile and logout. Hide the per-page duplicates so
   they never double up inside the workspace iframe. Applies to every page that
   loads premium.css and sets body.embedded on ?embed=1. Pages whose whole topbar
   is redundant hide it outright (their own CSS); this covers the chrome trio for
   pages that keep an operational bar. */
body.embedded [onclick*="toggleThemePanel"],
body.embedded #wolf-profile-btn,
body.embedded [onclick*="doLogout"],
body.embedded [onclick*="logout("]{ display:none !important; }


/* ═══════════════════════════════════════════════════════════════════════════
   ELITE LAYER — the v2 composition. Prefixed `e-` so it can live beside the
   existing components while pages migrate one at a time.

   What this fixes, concretely:
     · numbers that don't line up (no tabular figures anywhere in the app)
     · a centred 1100px column on a 34" monitor (full-bleed instead)
     · glass blurring over flat black, so the depth had nothing to describe
     · 24px KPI values that read as labels rather than headlines
   ═══════════════════════════════════════════════════════════════════════════ */

/* — FIGURES. Money and counts must align digit-to-digit, always. ————————— */
.e-num,.e-kpi .v,table.data td.num,table.list td.num,.e-money{
  font-variant-numeric:tabular-nums; font-feature-settings:'tnum' 1,'ss01' 1;
}
.e-mono{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; letter-spacing:-.01em; }

/* — THE FIELD. A branded layer behind everything, keyed to the live accent,
     so the glass has something to be in front of. Pure CSS: no asset to ship,
     no request to make, and it re-tints instantly when the user changes their
     scheme. Swap the gradients for a real illustration when one exists. — */
/* TRANSPARENT ON PURPOSE — it TINTS the user's background, it does not replace it.
   The first version painted an opaque --field over everything, which hid the background
   image the user chose. That is the personalisation the whole theme system exists for; a
   redesign that quietly deletes it is not a redesign anyone can accept. So: accent wash and
   lattice only, and `body::before` (mesh, aurora, blueprint, or an uploaded photo) shows
   through underneath. This is also what finally gives the glass something to sit in front of. */
.e-field{ position:fixed; inset:0; z-index:0; pointer-events:none; background:
    radial-gradient(1200px 700px at 12% -8%, rgba(var(--accent-rgb),.14), transparent 62%),
    radial-gradient(900px 600px at 92% 4%, rgba(var(--accent-rgb),.08), transparent 60%),
    radial-gradient(1000px 800px at 60% 108%, rgba(120,160,255,.05), transparent 65%);
}
/* A faint engineered lattice — the "designed on purpose" cue, at 3% opacity so
   it is felt rather than seen. */
.e-field::after{
  content:''; position:absolute; inset:0; opacity:.5;
  background-image:
    linear-gradient(rgba(255,255,255,.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.028) 1px, transparent 1px);
  background-size:64px 64px, 64px 64px;
  mask-image:radial-gradient(1400px 900px at 50% 0%, #000 30%, transparent 78%);
  -webkit-mask-image:radial-gradient(1400px 900px at 50% 0%, #000 30%, transparent 78%);
}

/* — LAYOUT. Full-bleed with breathing room, not a narrow centred column. — */
.e-wrap{ position:relative; z-index:1; padding:var(--s5) var(--s6) var(--s8);
         max-width:2100px; margin:0 auto; }
@media(max-width:900px){ .e-wrap{ padding:var(--s4) var(--s4) var(--s7); } }
.e-grid{ display:grid; gap:var(--s4); }
.e-cols-4{ grid-template-columns:repeat(4,minmax(0,1fr)); }
.e-cols-3{ grid-template-columns:repeat(3,minmax(0,1fr)); }
.e-split{ grid-template-columns:minmax(0,1.9fr) minmax(0,1fr); }
@media(max-width:1180px){ .e-split{ grid-template-columns:minmax(0,1fr); } .e-cols-4{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
/* On a phone a KPI still reads fine at half width, and icon tiles MUST stay multi-column —
   collapsing six tiles to one per row turns a glanceable grid into a scroll. */
@media(max-width:620px){ .e-cols-4{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
/* A money figure must never wrap. "R 1,284,0…" broken across two lines and clipped is worse
   than a smaller number, so the value shrinks to fit rather than breaking. */
.e-kpi .v{ white-space:nowrap; }
@media(max-width:620px){
  .e-kpi .v{ font-size:clamp(20px, 7.2vw, var(--t-2xl)); }
  .e-kpi{ padding:var(--s3) var(--s4); }
}

/* — TYPE ————————————————————————————————————————————————————————— */
.e-h1{ font-size:var(--t-3xl); line-height:var(--lh-tight); letter-spacing:var(--tr-tight);
       font-weight:800; margin:0; }
.e-sub{ color:var(--muted2); font-size:var(--t-md); margin:var(--s2) 0 0; font-weight:400; }
.e-h2{ font-size:var(--t-lg); line-height:var(--lh-snug); letter-spacing:-.012em;
       font-weight:700; margin:0; }
.e-label{ font-size:var(--t-xs); letter-spacing:var(--tr-wide); text-transform:uppercase;
          font-weight:700; color:var(--muted); }
@media(max-width:620px){ .e-h1{ font-size:var(--t-2xl); } }

/* — SURFACES. Hairline border, near-invisible shadow, no hover lift. ——— */
.e-card{ position:relative; background:var(--card); border:1px solid var(--line);
         border-radius:var(--r2-lg); box-shadow:var(--sh1);
         backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur);
         transition:border-color var(--dur) var(--ease), background var(--dur) var(--ease); }
.e-card:hover{ border-color:var(--line-hi); background:var(--card-2); }
.e-card-pad{ padding:var(--s5); }

/* — KPI. The number is the headline: 44px, tabular, colour = meaning. ——— */
.e-kpi{ padding:var(--s4) var(--s5); }
.e-kpi .l{ font-size:var(--t-xs); letter-spacing:var(--tr-wide); text-transform:uppercase;
           font-weight:700; color:var(--muted); }
.e-kpi .v{ font-size:var(--t-3xl); line-height:1.05; letter-spacing:var(--tr-tight);
           font-weight:800; margin-top:var(--s2); }
.e-kpi .s{ font-size:var(--t-sm); color:var(--muted); margin-top:var(--s1); }
.e-kpi .v.pos{ color:var(--success); } .e-kpi .v.neg{ color:var(--danger); }
.e-kpi .v.warn{ color:var(--warn); }   .e-kpi .v.acc{ color:var(--accent); }

/* — ICON TILE. Replaces emoji: a real glyph on a consistent grid. ———— */
.e-ico{ width:40px; height:40px; border-radius:var(--r2); display:grid; place-items:center;
        background:rgba(var(--accent-rgb),.14); border:1px solid rgba(var(--accent-rgb),.22);
        color:var(--accent); flex:none; }
.e-ico svg{ width:20px; height:20px; stroke:currentColor; fill:none; stroke-width:1.75;
            stroke-linecap:round; stroke-linejoin:round; }
.e-ico-lg{ width:52px; height:52px; border-radius:var(--r2-lg); }
.e-ico-lg svg{ width:24px; height:24px; }

/* — ACTION TILE (the Workflows grid). ————————————————————————————— */
.e-tile{ display:flex; flex-direction:column; align-items:center; gap:var(--s2);
         padding:var(--s4) var(--s2); border-radius:var(--r2-lg); text-decoration:none;
         background:var(--card); border:1px solid var(--line); color:var(--text);
         transition:border-color var(--dur) var(--ease), background var(--dur) var(--ease);
         min-height:88px; justify-content:center; }
.e-tile:hover{ border-color:rgba(var(--accent-rgb),.4); background:var(--card-2); }
.e-tile .t{ font-size:var(--t-xs); letter-spacing:.06em; text-transform:uppercase; font-weight:700; }

/* — BUTTONS. Flat fill, no two-tone gradient, no coloured glow, no lift. — */
.e-btn{ display:inline-flex; align-items:center; gap:var(--s2); min-height:40px;
        padding:0 var(--s4); border-radius:var(--r2); border:1px solid var(--line-hi);
        background:var(--card-2); color:var(--text); font:inherit; font-size:var(--t-base);
        font-weight:600; cursor:pointer; text-decoration:none;
        transition:background var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.e-btn:hover{ background:rgba(255,255,255,.09); border-color:var(--line-hi); }
.e-btn-pri{ background:var(--accent); border-color:var(--accent); color:#fff; }
.e-btn-pri:hover{ filter:brightness(1.08); background:var(--accent); }
.e-btn svg{ width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:2;
            stroke-linecap:round; stroke-linejoin:round; }

/* — CHIP ———————————————————————————————————————————————————————— */
.e-chip{ display:inline-flex; align-items:center; gap:6px; height:30px; padding:0 var(--s3);
         border-radius:999px; background:var(--card-2); border:1px solid var(--line);
         font-size:var(--t-sm); font-weight:600; color:var(--muted2); cursor:pointer;
         transition:border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
.e-chip:hover{ color:var(--text); border-color:rgba(var(--accent-rgb),.42); }

/* — STATUS DOT ————————————————————————————————————————————————— */
.e-dot{ width:9px; height:9px; border-radius:50%; background:var(--success); flex:none;
        box-shadow:0 0 0 3px rgba(52,211,153,.16); }

/* — LIGHT MODE. Same structure, inverted field — every token above is
     re-anchored, so nothing needs a bespoke light override. ——————————— */
body.light-mode{
  /* Same reasoning as the glass tokens above: the card is the surface the user looks
     at most, so if the theme does not reach here it does not reach anywhere. --card-2
     stays a touch stronger than --card so the hover state still reads as a lift. */
  --field:color-mix(in srgb, rgb(var(--accent-rgb)) 6%, #f2f4f8);
  --card:color-mix(in srgb, rgb(var(--accent-rgb)) 3%, rgba(255,255,255,.95));
  --card-2:color-mix(in srgb, rgb(var(--accent-rgb)) 5%, rgba(255,255,255,.98));
  --card-solid:color-mix(in srgb, rgb(var(--accent-rgb)) 2%, #fff);
  --line:color-mix(in srgb, rgb(var(--accent-rgb)) 34%, rgba(16,24,40,.10));
  --line-hi:color-mix(in srgb, rgb(var(--accent-rgb)) 46%, rgba(16,24,40,.16));
  /* Same three, re-anchored. Shadows drop to ~18% of their dark weight, the sheen
     switches off, and accent chips mix against white instead of black. */
  --sh-ink:16,24,40; --sh-k:.18;
  --sheen:transparent; --sheen-hi:transparent;
  --tok-base:rgba(255,255,255,.92);
  --sh1:0 2px 6px rgba(16,24,40,.10), 0 8px 24px rgba(16,24,40,.08);
  --sh2:0 4px 16px rgba(16,24,40,.07);
  --sh3:0 18px 48px rgba(16,24,40,.1);

  /* METAL, light: a cool SILVER rather than the dark version inverted — the
     dark gradient's black belly on white reads as dirt, not depth.
     PAPER-WHITE STAYS THE BASE. The first attempt carried opaque silver
     through the middle two stops and turned every light-mode card into a grey
     wash: the sheen has to GRAZE the surface, not cover it. White holds the
     top two-thirds and the silver appears only as a band in the lower-right,
     which is where a light source above-left would actually leave it. */
  /* THE METAL SHEEN IS TINTED TOO — otherwise it erases the tint underneath.
     These stops were pure rgba(255,255,255,…) at 55–95% opacity, painted with
     !important OVER --card. So tinting --card did almost nothing on a card: the
     accent went in and a wall of white went straight over it, which is exactly
     why the first attempt read as "the changes are there but extremely subtle".
     Fields showed the tint (no metal layer); cards did not (metal layer).

     The stops now mix toward an accent-tinted white, so the sheen carries the
     theme instead of bleaching it. The silver band keeps a smaller accent share:
     it is the shadowed edge, and tinting it as hard as the highlights makes the
     card look coloured rather than lit. */
  --metal-lite:color-mix(in srgb, rgb(var(--accent-rgb)) 5%, #fff);
  --metal-edge:color-mix(in srgb, rgb(var(--accent-rgb)) 4%, #cbd5e4);
  --metal:linear-gradient(148deg,
     color-mix(in srgb, var(--metal-lite) calc(95% * var(--metal-k)), transparent) 0%,
     color-mix(in srgb, var(--metal-lite) calc(55% * var(--metal-k)), transparent) 32%,
     color-mix(in srgb, var(--metal-edge) calc(32% * var(--metal-k)), transparent) 70%,
     color-mix(in srgb, var(--metal-lite) calc(60% * var(--metal-k)), transparent) 100%);
  --bevel:inset 0 1px 0 rgba(255,255,255,var(--metal-k)),
          inset 0 -1px 0 rgba(15,23,42,calc(.14 * var(--metal-k))),
          inset 0 0 0 1px rgba(255,255,255,calc(.7 * var(--metal-k)));
  --sh-metal:0 1px 2px rgba(16,24,40,.08),
             0 10px 26px rgba(16,24,40,calc(.13 * var(--metal-k))),
             0 0 18px rgba(var(--accent-rgb),calc(.06 * var(--metal-k)));
  --bevel-in:inset 0 1px 1px rgba(15,23,42,calc(.10 * var(--metal-k))),
             inset 0 -1px 0 rgba(255,255,255,var(--metal-k));
  --metal-in:linear-gradient(180deg,
     rgba(15,23,42,calc(.055 * var(--metal-k))), rgba(255,255,255,0) 45%);
}
body.light-mode .e-field{ background:
    radial-gradient(1200px 700px at 12% -8%, rgba(var(--accent-rgb),.1), transparent 62%),
    radial-gradient(900px 600px at 92% 4%, rgba(var(--accent-rgb),.06), transparent 60%),
    var(--field); }
body.light-mode .e-field::after{
  background-image:
    linear-gradient(rgba(16,24,40,.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(16,24,40,.045) 1px, transparent 1px); }
body.light-mode .e-btn:hover{ background:rgba(16,24,40,.05); }

/* ══════════════════════════════════════════════════════════════════════════
   THE METAL LAYER — applied
   ══════════════════════════════════════════════════════════════════════════
   Deliberately LAST in the file. Light mode re-maps cards with
   `background:#fff !important` (the `background` shorthand resets
   background-image), so the metal has to arrive later and equally important or
   it is silently erased in light mode — and light mode is the half nobody
   checks. Everything here is driven by --metal-k; set it to 0 and every rule
   below evaluates to fully transparent, restoring the flat surfaces exactly.

   THE GRADIENT RIDES ON background-image, NOT background. `background-color`
   stays whatever the component (or the page) set it to, so a page with its own
   card colour keeps its colour AND gains the sheen. That is what stops this
   fighting the pages that carry private surface rules.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Raised surfaces ──────────────────────────────────────────────────── */
.panel,.pcard,.kpi,.card,.glass,.glass-hi,.e-card{
  background-image:var(--metal) !important;
  box-shadow:var(--bevel),var(--sh-metal);
}
.pcard:hover,.kpi:hover,.card:hover,.e-card:hover{
  box-shadow:var(--bevel),
    0 1px 2px rgba(0,0,0,.45),
    0 16px 36px rgba(0,0,0,calc(.46 * var(--metal-k))),
    0 0 28px rgba(var(--accent-rgb),calc(.15 * var(--metal-k)));
}

/* ══════════════════════════════════════════════════════════════════════════
   THE GLINT — an occasional shine travelling across the panels
   ══════════════════════════════════════════════════════════════════════════
   The metal treatment above is a STATIC highlight: it says "moulded". This is
   the same light MOVING, which is what actually makes a surface read as metal —
   real metal announces itself when the light or your eye moves across it.

   AMBIENT, NOT ON HOVER. The point is that it happens by itself, every so
   often, the way a room light catches a brushed surface. Hover-only would mean
   you never see it unless you go looking.

   SUBTLE IS THE ENTIRE BRIEF, and the two things that keep it that way are the
   duty cycle and the stagger:
     - the sweep is ~1.6s of a 15s cycle, so any given panel is inert ~90% of
       the time. A shine you can set your watch by is a screensaver;
     - panels are staggered by nth-of-type so they never flash in unison. All
       of them lighting at once is a light show and instantly reads as cheap.
   Together they also keep it cheap to paint: with a ~11% duty cycle only one
   or two panels on a screen are ever animating at the same moment.

   IMPLEMENTED AS A BACKGROUND LAYER, NOT A PSEUDO-ELEMENT. ::before is already
   the accent hairline on every card and ::after is the tone bar on the home
   cards, so a glint competing for a pseudo-element could only ever work on
   part of the app. As a background layer it composes with --metal by sitting
   in front of it in the same stack, and works anywhere the metal does.

   Honours --metal-k (the Metallic slider) through the band's own alpha, so
   turning metal down turns the glint down with it and 0 removes it entirely. */
@keyframes wf-glint{
  0%     { background-position:-60% 0, 0 0; }
  11%    { background-position:160% 0, 0 0; }
  100%   { background-position:160% 0, 0 0; }
}
.glint,
.panel,.pcard,.kpi,.card,.glass,.glass-hi,.e-card{
  --glint-band:linear-gradient(105deg,
     transparent 38%,
     rgba(255,255,255,calc(.05 * var(--metal-k))) 46%,
     rgba(255,255,255,calc(.13 * var(--metal-k))) 50%,
     rgba(255,255,255,calc(.05 * var(--metal-k))) 54%,
     transparent 62%);
  background-image:var(--glint-band), var(--metal) !important;
  background-repeat:no-repeat, repeat;
  background-size:220% 100%, auto;
  background-position:-60% 0, 0 0;
  animation:wf-glint 15s linear infinite;
}
/* The stagger. Prime-ish offsets so the pattern does not visibly loop. */
.glint:nth-of-type(2n),  .panel:nth-of-type(2n),  .pcard:nth-of-type(2n),  .kpi:nth-of-type(2n){ animation-delay:-2.3s; }
.glint:nth-of-type(3n),  .panel:nth-of-type(3n),  .pcard:nth-of-type(3n),  .kpi:nth-of-type(3n){ animation-delay:-5.7s; }
.glint:nth-of-type(4n),  .panel:nth-of-type(4n),  .pcard:nth-of-type(4n),  .kpi:nth-of-type(4n){ animation-delay:-8.1s; }
.glint:nth-of-type(5n),  .panel:nth-of-type(5n),  .pcard:nth-of-type(5n),  .kpi:nth-of-type(5n){ animation-delay:-11.4s; }
.glint:nth-of-type(7n),  .panel:nth-of-type(7n),  .pcard:nth-of-type(7n),  .kpi:nth-of-type(7n){ animation-delay:-13.2s; }

/* Light mode's version is a travelling SHADOW: a white band over near-white
   paper is invisible, so the same gesture has to be made in the dark direction. */
body.light-mode .glint,
body.light-mode .panel,body.light-mode .pcard,body.light-mode .kpi,
body.light-mode .card,body.light-mode .glass,body.light-mode .glass-hi,body.light-mode .e-card{
  --glint-band:linear-gradient(105deg,
     transparent 38%,
     rgba(16,24,40,calc(.022 * var(--metal-k))) 46%,
     rgba(16,24,40,calc(.045 * var(--metal-k))) 50%,
     rgba(16,24,40,calc(.022 * var(--metal-k))) 54%,
     transparent 62%);
}
/* A travelling highlight is precisely the kind of motion this preference means. */
@media (prefers-reduced-motion:reduce){
  .glint,.panel,.pcard,.kpi,.card,.glass,.glass-hi,.e-card{ animation:none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE ACCENT CARD — the /projects treatment, made shared (2026-08-22)
   ══════════════════════════════════════════════════════════════════════════
   `/projects` looked like a product while fourteen other pages using the SAME
   `.pcard`/`.kpi` classes looked flat, and the difference lived in a private
   <style> block on that one page. Promoted here so the look is the platform's,
   not one page's.

   DELTAS ONLY — three properties. The metallic-glass system above already owns
   the texture, the bevel and the per-theme shadows, and it is right; the local
   version was overpainting it with a hardcoded dark gradient. What actually
   distinguished the projects card was (1) an accent-coloured top hairline
   instead of a plain white one, (2) an accent-tinted border instead of a
   neutral line, and (3) a firmer lift on hover. Those three carry the whole
   impression, so those three are what move.

   Every value is accent- or token-derived, so it follows the user's chosen
   accent and inverts with the theme rather than assuming a dark canvas.
   Placed after the metal rules deliberately: earlier, and the cascade eats it. */
.pcard,.kpi{ border-color:rgba(var(--accent-rgb),.32); }
.pcard::before,.kpi::before{
  height:2px;
  background:linear-gradient(90deg, transparent 0%, var(--accent) 30%, var(--cyan) 70%, transparent 100%);
  opacity:.9;
}
.pcard:hover,.kpi:hover{
  border-color:var(--accent);
  transform:translateY(-5px) scale(1.012);
}
/* The lift is the one thing a person with reduced-motion preferences must not get. */
@media (prefers-reduced-motion:reduce){
  .pcard:hover,.kpi:hover{ transform:none; }
}

/* The layout half of the same promotion — pure geometry, no texture, so it composes with the
   metal system rather than competing with it. `.kpi .v` is deliberately NOT redefined here:
   the shared rule above already sets tabular figures, which is better than the page-local
   version was (a KPI row has to align digit-to-digit with the one beside it). */
.kpis{ display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr));
  gap:14px; margin-bottom:20px; }
.pcard h3{ margin:0 0 4px; font-size:16.5px; font-weight:850; letter-spacing:-.3px;
  line-height:1.25; color:var(--text); }
.pcard .meta{ display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:14px; }
body.light-mode .panel,body.light-mode .pcard,body.light-mode .kpi,body.light-mode .card,
body.light-mode .glass,body.light-mode .glass-hi,body.light-mode .e-card{
  background-image:var(--metal) !important;
  box-shadow:var(--bevel),var(--sh-metal) !important;
}

/* ── Overlays. A modal is the most-looked-at surface in the app. ───────── */
.sheet,.modal,.toast{
  background-image:var(--metal);
  box-shadow:var(--bevel),0 28px 80px rgba(0,0,0,calc(.6 * var(--metal-k))),
             0 0 40px rgba(var(--accent-rgb),calc(.07 * var(--metal-k)));
}
body.light-mode .sheet,body.light-mode .modal,body.light-mode .toast{
  background-image:var(--metal) !important;
  box-shadow:var(--bevel),0 24px 64px rgba(16,24,40,calc(.18 * var(--metal-k))) !important;
}

/* ── The header. A metallic top rail is most of the "engineered" read. ─── */
.topbar{
  background-image:var(--metal);
  box-shadow:inset 0 1px 0 rgba(255,255,255,calc(.14 * var(--metal-k))),
             0 1px 0 rgba(var(--accent-rgb),.06),
             0 4px 24px rgba(0,0,0,.4);
}
body.light-mode .topbar{ background-image:var(--metal) !important; }

/* ── Buttons. The fill stays FLAT accent (no two-tone gradient — that was a
      named AI tell); this is a specular highlight over it, which is a
      different thing: it says "moulded", not "decorated". ───────────────── */
.btn{ background-image:var(--gloss); box-shadow:var(--gloss-sh); }
.btn:hover{ box-shadow:inset 0 1px 0 rgba(255,255,255,calc(.34 * var(--metal-k))),
                       inset 0 -1px 0 rgba(0,0,0,calc(.26 * var(--metal-k))),
                       0 2px 6px rgba(0,0,0,calc(.4 * var(--metal-k))); }
.btn:active{ box-shadow:inset 0 2px 4px rgba(0,0,0,calc(.4 * var(--metal-k))); }
.btn-ghost{ background-image:var(--metal); box-shadow:var(--bevel),0 1px 2px rgba(0,0,0,.3); }
body.light-mode .btn-ghost{ background-image:var(--metal) !important;
  box-shadow:var(--bevel),0 1px 2px rgba(16,24,40,.07) !important; }

/* ── Inputs — recessed, the inverse of everything above. ───────────────── */
input:not([type=checkbox]):not([type=radio]),select,textarea{ background-image:var(--metal-in); box-shadow:var(--bevel-in); }
input:focus,select:focus,textarea:focus{
  box-shadow:var(--bevel-in),0 0 0 3px rgba(var(--accent-rgb),.16); }
body.light-mode input,body.light-mode select,body.light-mode textarea{
  background-image:var(--metal-in) !important; box-shadow:var(--bevel-in); }

/* ── Table headers. A lit strip instead of a bare rule — the header stops
      floating and starts reading as the top edge of the sheet. ─────────── */
table.list thead tr,table.data thead tr{
  background-image:linear-gradient(180deg,
    rgba(255,255,255,calc(.06 * var(--metal-k))), rgba(255,255,255,0));
}
body.light-mode table.list thead tr,body.light-mode table.data thead tr{
  background-image:linear-gradient(180deg,
    rgba(15,23,42,calc(.045 * var(--metal-k))), rgba(255,255,255,0)); }

/* ── Small parts. Pills, chips and the active tab are little raised objects;
      leaving them flat while the cards gained depth looks unfinished. ──── */
.pill,.st,.tab.on,.e-chip,.e-tile{
  background-image:linear-gradient(160deg,
    rgba(255,255,255,calc(.14 * var(--metal-k))),
    rgba(0,0,0,calc(.08 * var(--metal-k))));
  box-shadow:inset 0 1px 0 rgba(255,255,255,calc(.14 * var(--metal-k)));
}
.tab.on{ box-shadow:inset 0 2px 0 var(--accent),
                    inset 0 3px 0 rgba(255,255,255,calc(.12 * var(--metal-k))); }
body.light-mode .pill,body.light-mode .st,body.light-mode .tab.on,
body.light-mode .e-chip,body.light-mode .e-tile{
  background-image:linear-gradient(160deg,
    rgba(255,255,255,calc(.9 * var(--metal-k))),
    rgba(15,23,42,calc(.05 * var(--metal-k))));
  box-shadow:inset 0 1px 0 rgba(255,255,255,var(--metal-k)); }

/* ── "Reduce glow" is an existing accessibility preference and it owns the
      accent halo. Without this the metal would quietly reintroduce the exact
      thing that toggle exists to remove. ───────────────────────────────── */
body.no-glow{
  --sh-metal:0 1px 2px rgba(0,0,0,.45),0 12px 30px rgba(0,0,0,calc(.42 * var(--metal-k)));
}
body.no-glow.light-mode{
  --sh-metal:0 1px 2px rgba(16,24,40,.08),0 10px 26px rgba(16,24,40,calc(.13 * var(--metal-k)));
}
body.no-glow .pcard:hover,body.no-glow .kpi:hover,body.no-glow .card:hover,body.no-glow .e-card:hover{
  box-shadow:var(--bevel),0 1px 2px rgba(0,0,0,.45),
             0 16px 36px rgba(0,0,0,calc(.46 * var(--metal-k)));
}

/* ── Anyone who has asked the OS for less motion/effect gets the flat app.
      A heavy specular treatment is exactly what that request is about. ─── */
@media (prefers-reduced-transparency:reduce){
  :root{ --metal-k:0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   .railnav — THE side rail, and .phead — THE page head.
   ══════════════════════════════════════════════════════════════════════════
   finance and fintelligence2 sit next to each other in the same Finance menu
   and agreed on nothing: rail 180px vs 212px, radius 8 vs 9, active state
   `.active`/600 vs `.on`/700, title 19px inside main vs 16px in the top bar,
   content full-bleed vs capped at 1240, and two different mobile mechanisms
   (rail-collapses vs a separate `.tabbar`). Both hand-rolled a clone of
   `.topbar` rather than using it. That is what "different styles everywhere"
   actually is — not colour, but every page inventing its own furniture.
   ══════════════════════════════════════════════════════════════════════════ */

.railnav{
  width:200px; flex:none; box-sizing:border-box;
  padding:var(--s3) var(--s2);
  border-right:1px solid var(--line);
  background:var(--card); background-image:var(--metal);
  backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur);
}
.railnav .rail-brand{ font-weight:800; font-size:var(--t-sm); letter-spacing:var(--tr-wide);
  text-transform:uppercase; color:var(--muted2); padding:6px 10px 12px; }
/* Group headings vs clickable items.
   Both used to be var(--muted) at similar weight, so a heading like WORK read as
   just another item and you could not tell what was clickable. The headings now
   recede -- dimmer, smaller, sitting on a hairline that shows where each group
   starts -- while the items below step forward to var(--muted2). Interactivity
   should always be the more prominent of the two. */
.railnav .cat{
  color:var(--muted); opacity:.62;
  font-size:10px; text-transform:uppercase;
  letter-spacing:.12em; font-weight:800;
  padding:16px 10px 6px; margin-top:6px;
  border-top:1px solid var(--line);
  cursor:default; user-select:none;
}
/* The first group sits directly under the Workspace brand -- no divider needed. */
.railnav .rail-brand + .cat{ border-top:none; margin-top:0; padding-top:2px; }
.railnav button,.railnav a{
  display:block; width:100%; box-sizing:border-box; text-align:left;
  padding:9px 12px; margin:2px 0; min-height:40px;
  border-radius:var(--r2); border:1px solid transparent;
  background:none; color:var(--muted2); text-decoration:none;
  font:inherit; font-size:13.5px; font-weight:600; cursor:pointer;
  transition:color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.railnav button:hover,.railnav a:hover{ color:var(--text); background:var(--surface); }
/* BOTH `.on` and `.active`. The two pages that had a rail each picked a different
   name for the same state; accepting both is cheaper and safer than editing two
   JS files to agree, and any future rail works whichever it uses. */
.railnav button.on,.railnav button.active,.railnav a.on,.railnav a.active{
  background:var(--card-2); background-image:var(--metal);
  color:var(--text); font-weight:700; border-color:var(--line-hi);
  box-shadow:inset 2px 0 0 var(--accent), var(--bevel);
}
/* ONE mobile mechanism — the rail becomes a horizontal pill scroller. This
   replaces fintelligence2's separate `.tabbar`, which was a second nav to keep
   in sync and carried its own hardcoded `#990018` gradient. */
@media(max-width:900px){
  .railnav{
    width:100%; display:flex; gap:6px; overflow-x:auto; white-space:nowrap;
    border-right:none; border-bottom:1px solid var(--line);
    padding:8px 10px; position:sticky; top:0; z-index:9;
    -webkit-overflow-scrolling:touch;
  }
  .railnav .cat,.railnav .rail-brand{ display:none; }
  .railnav button,.railnav a{
    display:inline-block; width:auto; margin:0; padding:8px 14px;
    border-radius:20px; border-color:var(--line);
  }
  .railnav button.on,.railnav button.active{ box-shadow:var(--bevel); }
}

/* The page head. One size, one position: in the CONTENT, above the work —
   never in the top bar, which belongs to the app, not to the page. */
.phead{ display:flex; align-items:flex-start; gap:var(--s4); flex-wrap:wrap;
        margin:0 0 var(--s5); }
.phead .pt{ min-width:0; }
.phead h1{ font-size:clamp(19px,2.2vw,23px); font-weight:800;
           letter-spacing:var(--tr-tight); margin:0; line-height:1.15; }
.phead .sub,.phead .ps{ color:var(--muted); font-size:var(--t-sm); margin:5px 0 0; }
.phead .sp{ flex:1; }
.phead .pacts{ display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap; }

/* A card's own heading is a LABEL, not a document heading — same treatment as
   `.sec`. fintelligence2 had this right privately and finance rendered a raw
   <h3>, so the same markup came out as a small caps label on one page and a
   19px title on the other. */
.card>h3,.pcard>h3,.panel>h3,.kpi>h3{
  font-size:var(--t-xs); text-transform:uppercase; letter-spacing:var(--tr-wide);
  color:var(--muted); font-weight:800; margin:0 0 var(--s3); }

/* ── EMBEDDED IN THE SHELL ───────────────────────────────────────────────────────────────────
   home.html embeds every module in an iframe and draws the product's top bar itself. A page that
   also draws one stacks two — the defect the owner met following one of Crystal's own internal
   links. The class is set by a head script BEFORE this sheet paints, so the second bar never
   appears even for a frame.
   Only the page's own chrome goes; everything else is untouched, and opening the same page in its
   own tab is unchanged. */
html.in-shell .topbar { display: none !important; }
/* The wrap usually clears a fixed topbar. With no topbar there is nothing to clear, and the gap
   reads as a broken layout rather than breathing room. */
html.in-shell body > .wrap:first-of-type,
html.in-shell .wrap.has-topbar { padding-top: 12px; }
