/* =========================================================================
   SPORTS PERFORMANCE CEO — EFFECT LAYER

   ⚠️  THIS IS THE AUTHORISED DEVIATION LAYER. IT IS NOT PART OF THE
       COMPLIANT KIT, AND LINKING IT IS AN OPT-IN, NEVER A DEFAULT.

   DESIGN.md still says what it has always said, and it is still correct:

     The One Azure Rule — "There is exactly one accent. No secondary brand
     color, no gradient, no second hue for 'variety.'"
     Don't — "introduce a second accent color or any gradient."

   This file exists because that rule, applied strictly, produces a page that
   reads flat. Cody was shown the rule and the reason it exists — neon
   gradient-on-dark is the most reliable visual tell of AI-generated UI — and
   authorised the break twice:

     2026-08-10  The High-Profit Sports Performance Model landing page
     2026-08-10  The Year-Round Athlete Workshop funnel (this extraction)

   The second authorisation is why this file exists at all. One page can keep
   its effect tokens inline; two pages sharing a look by copy-paste is how
   drift starts.

   WHAT IS AND IS NOT OVERRIDDEN
     Overridden : gradients, ambient glow, blurred orbs, and ONE secondary
                  hue — violet #7c5cff — alongside the azure #0094ff.
     NOT         : red is still out. The Not-A-Gym Rule stands; the palette is
                  azure plus violet, never the rose-red the kit names. The type stack
                  is untouched. The logo rules are untouched.

   CONSEQUENCE: kit-audit.mjs WILL report the violet and the gradients on any
   page linking this file. Those errors ARE the deviation, not a regression.
   Do not silence them, do not edit brand.json, and do not add the
   cast-wide:build-tool marker to a real page. A visible, explained failure is
   the correct end state.

   ---------------------------------------------------------------------------
   HOW TO USE IT, AND THE TRAP

   Link AFTER tokens.css. It reads the blue and neutral ramps from there.

   (Written without a wildcard-slash next to a hyphen on purpose: the two
   characters that end a CSS comment are easy to type by accident inside one.
   This file did exactly that on its first draft — "--blue-*" followed by a
   slash closed the comment 23 lines early, and CSS error recovery then
   swallowed the entire light block below. Every light-mode effect token
   resolved to nothing and the page would have rendered flat, silently.)

     <link rel="stylesheet" href="assets/tokens.css">
     <link rel="stylesheet" href="assets/effects.css">

   THE TRAP, learned the hard way on 2026-08-10:
   An undefined custom property does NOT fall back to a sensible default — it
   invalidates the whole declaration. A second page linked tokens.css, missed
   these tokens entirely, and `opacity: var(--fx-noise-op)` painted at 1
   instead of 0.02, washing the page in grey. No console error, no 404. It
   looked like a design choice.

   So: give every load-bearing use an inline fallback anyway.

       opacity: var(--fx-noise-op, 0.02);     ✅
       opacity: var(--fx-noise-op);           ❌ one missing link and it is 1

   And note the tokens are declared THREE times below, once per canvas. Light
   and dark are equal defaults in this brand. Porting only the first block
   gives a page that is right in light and wrong in dark, which is exactly the
   kind of half-fix that survives review.

   See: lessons-learned/2026-08-10__effect-tokens-lived-in-the-page-not-the-kit-so-page-two-broke.md
   ========================================================================= */

/* ---- LIGHT (the unset default) ------------------------------------------ */
:root,
[data-theme='light'] {
  --fx-orb-a:       rgba(0, 148, 255, 0.20);
  --fx-orb-b:       rgba(124, 92, 255, 0.16);
  --fx-orb-c:       rgba(0, 148, 255, 0.12);
  --fx-orb-blur:    120px;
  --fx-noise-op:    0.02;
  --fx-noise-blend: multiply;
  --fx-grid:        rgba(0, 30, 60, 0.055);
  --fx-glow:        0 10px 28px -10px rgba(0, 148, 255, 0.55);
  --fx-glow-hi:     0 18px 44px -12px rgba(0, 148, 255, 0.7);
  --fx-glow-soft:   0 8px 30px -12px rgba(0, 30, 60, 0.25);
  --fx-card-wash:   rgba(0, 148, 255, 0.07);
  --fx-card-edge:   rgba(0, 148, 255, 0.55);
  --fx-plate:       rgba(0, 148, 255, 0.28);
  --fx-plate-2:     rgba(124, 92, 255, 0.26);
  --fx-ghost:       rgba(0, 30, 60, 0.07);
  --fx-ghost-hi:    rgba(0, 148, 255, 0.16);
  --fx-dots:        rgba(0, 30, 60, 0.06);
  --fx-scrim:       rgba(255, 255, 255, 0.84);
  --fx-veil:        rgba(255, 255, 255, 0.55);
  --fx-shine-1:     var(--n-900);
  --fx-shine-2:     var(--n-400);
  --fx-shine-b1:    var(--blue-700);
  --fx-shine-b2:    var(--blue-400);
  --fx-sweep:       rgba(255, 255, 255, 0.45);
}

/* ---- DARK, explicit opt-in ---------------------------------------------- */
[data-theme='dark'] {
  --fx-orb-a:       rgba(0, 148, 255, 0.22);
  --fx-orb-b:       rgba(124, 92, 255, 0.20);
  --fx-orb-c:       rgba(0, 148, 255, 0.13);
  --fx-orb-blur:    140px;
  --fx-noise-op:    0.05;
  --fx-noise-blend: overlay;
  --fx-grid:        rgba(255, 255, 255, 0.045);
  --fx-glow:        0 0 44px rgba(0, 148, 255, 0.42);
  --fx-glow-hi:     0 0 68px rgba(0, 148, 255, 0.62);
  --fx-glow-soft:   0 0 30px rgba(0, 148, 255, 0.22);
  --fx-card-wash:   rgba(0, 148, 255, 0.10);
  --fx-card-edge:   rgba(0, 148, 255, 0.55);
  --fx-plate:       rgba(0, 148, 255, 0.34);
  --fx-plate-2:     rgba(124, 92, 255, 0.32);
  --fx-ghost:       rgba(255, 255, 255, 0.055);
  --fx-ghost-hi:    rgba(0, 148, 255, 0.20);
  --fx-dots:        rgba(255, 255, 255, 0.035);
  --fx-scrim:       rgba(0, 0, 0, 0.66);
  --fx-veil:        rgba(0, 0, 0, 0.45);
  --fx-shine-1:     var(--spc-white);
  --fx-shine-2:     var(--n-400);
  --fx-shine-b1:    var(--blue-200);
  --fx-shine-b2:    var(--blue-500);
  --fx-sweep:       rgba(255, 255, 255, 0.3);
}

/* ---- DARK, following the OS, unless light was chosen explicitly ---------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --fx-orb-a:       rgba(0, 148, 255, 0.22);
    --fx-orb-b:       rgba(124, 92, 255, 0.20);
    --fx-orb-c:       rgba(0, 148, 255, 0.13);
    --fx-orb-blur:    140px;
    --fx-noise-op:    0.05;
    --fx-noise-blend: overlay;
    --fx-grid:        rgba(255, 255, 255, 0.045);
    --fx-glow:        0 0 44px rgba(0, 148, 255, 0.42);
    --fx-glow-hi:     0 0 68px rgba(0, 148, 255, 0.62);
    --fx-glow-soft:   0 0 30px rgba(0, 148, 255, 0.22);
    --fx-card-wash:   rgba(0, 148, 255, 0.10);
    --fx-card-edge:   rgba(0, 148, 255, 0.55);
    --fx-plate:       rgba(0, 148, 255, 0.34);
    --fx-plate-2:     rgba(124, 92, 255, 0.32);
    --fx-ghost:       rgba(255, 255, 255, 0.055);
    --fx-ghost-hi:    rgba(0, 148, 255, 0.20);
    --fx-dots:        rgba(255, 255, 255, 0.035);
    --fx-scrim:       rgba(0, 0, 0, 0.66);
    --fx-veil:        rgba(0, 0, 0, 0.45);
    --fx-shine-1:     var(--spc-white);
    --fx-shine-2:     var(--n-400);
    --fx-shine-b1:    var(--blue-200);
    --fx-shine-b2:    var(--blue-500);
    --fx-sweep:       rgba(255, 255, 255, 0.3);
  }
}

/* =========================================================================
   THE AMBIENT PRIMITIVES
   Drop these elements into a positioned container and the depth comes free.
   All are pointer-events:none and aria-hidden by convention — they are
   texture, never content.
   ========================================================================= */

.fx-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

/* Blurred colour orbs. The violet one is the deviation made visible. */
.fx-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(var(--fx-orb-blur, 120px));
  will-change: transform;
}
.fx-orb--a { top: -12%; left: -10%; width: 42vw; height: 42vw; background: var(--fx-orb-a, rgba(0,148,255,0.2)); }
.fx-orb--b { top: 8%; right: -14%; width: 38vw; height: 38vw; background: var(--fx-orb-b, rgba(124,92,255,0.16)); }
.fx-orb--c { bottom: -18%; left: 28%; width: 34vw; height: 34vw; background: var(--fx-orb-c, rgba(0,148,255,0.12)); }

/* Engineering grid. Reads as precision, which is the whole pitch. */
.fx-grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--fx-grid, rgba(255,255,255,0.045)) 1px, transparent 1px),
    linear-gradient(to bottom, var(--fx-grid, rgba(255,255,255,0.045)) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(ellipse 80% 60% at 50% 40%, black 40%, transparent 100%);
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 40%, black 40%, transparent 100%);
}

.fx-dots {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--fx-dots, rgba(255,255,255,0.035)) 1.5px, transparent 1.5px);
  background-size: 22px 22px;
}

/* =========================================================================
   THE SHINE — a two-tone display headline.
   background-clip:text needs a real gradient, which is the deviation in its
   most visible form. Falls back to a solid --text-strong where unsupported.
   ========================================================================= */
.fx-shine {
  background-image: linear-gradient(180deg, var(--fx-shine-1, white) 0%, var(--fx-shine-2, gray) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.fx-shine--blue {
  background-image: linear-gradient(180deg, var(--fx-shine-b1, #99d5ff) 0%, var(--fx-shine-b2, #0094ff) 100%);
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .fx-shine, .fx-shine--blue { color: var(--text-strong); background-image: none; }
}

/* =========================================================================
   SURFACES
   ========================================================================= */

/* A card that sits above the ambient layer with a faint azure wash. */
.fx-card {
  position: relative;
  background:
    linear-gradient(180deg, var(--fx-card-wash, rgba(0,148,255,0.1)) 0%, transparent 60%),
    var(--surface-1);
  border: var(--bw-base) solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--fx-glow-soft, 0 8px 30px -12px rgba(0,30,60,0.25));
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.fx-card:hover {
  transform: translateY(-3px);
  border-color: var(--fx-card-edge, rgba(0,148,255,0.55));
  box-shadow: var(--fx-glow-hi, 0 18px 44px -12px rgba(0,148,255,0.7));
}

/* A small floating stat plate, for laying over media. */
.fx-plate {
  background: var(--fx-scrim, rgba(0,0,0,0.66));
  border: var(--bw-base) solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--fx-glow-soft, 0 8px 30px -12px rgba(0,30,60,0.25));
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.fx-glow    { box-shadow: var(--fx-glow, 0 0 44px rgba(0,148,255,0.42)); }
.fx-glow-hi { box-shadow: var(--fx-glow-hi, 0 0 68px rgba(0,148,255,0.62)); }

/* Oversized numeral sitting behind a card. Texture, never read aloud. */
.fx-ghost {
  color: var(--fx-ghost, rgba(255,255,255,0.055));
  pointer-events: none;
  user-select: none;
}

/* The live dot on a status pill. The one thing on the page that moves on its
   own, so it has to mean "this is live" and nothing else. */
.fx-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--fx-ghost-hi, rgba(0,148,255,0.2));
  animation: fxPulse 2.4s var(--ease-out) infinite;
  flex: none;
}
@keyframes fxPulse {
  0%   { box-shadow: 0 0 0 0 var(--fx-ghost-hi, rgba(0,148,255,0.2)); }
  70%  { box-shadow: 0 0 0 10px rgba(0, 148, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(0, 148, 255, 0); }
}

/* =========================================================================
   REVEAL — paired with reveal() in page.js.

   NEVER gate visibility on a class that only JS can add without also handling
   the no-JS case: if the observer never runs, the whole page stays invisible.
   Hence @media (scripting: none) below, and reveal() adds .fx-reveal-ready to
   <html> before it starts observing.
   ========================================================================= */
.fx-reveal-ready .fx-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
.fx-reveal-ready .fx-reveal.is-in { opacity: 1; transform: none; }

@media (scripting: none) {
  .fx-reveal { opacity: 1 !important; transform: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .fx-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .fx-dot { animation: none; }
  .fx-card:hover { transform: none; }
}
