/* ═══════════════════════════════════════════════════
   FormPeak — Flow background
   Ported from the Infonetax theme (_c-flow-bg.css), retinted to the
   FormPeak palette: OLED black base, lime (#BEF264) as the light source.
   ═══════════════════════════════════════════════════

   A living background with no WebGL, no canvas and no library: a static
   base gradient plus four large soft blobs whose ONLY animated properties
   are `transform` and `opacity`, so every frame is compositor work and the
   main thread stays free. Softness comes from the radial-gradient falloff,
   never from `filter: blur()` (which re-rasterises a full-width layer on
   every frame).

   Markup — put it inside a `position: relative; overflow: hidden` box:

     <div class="c-flow-bg" data-flow-bg aria-hidden="true">
       <span class="c-flow-bg__blob c-flow-bg__blob--1"></span>
       <span class="c-flow-bg__blob c-flow-bg__blob--2"></span>
       <span class="c-flow-bg__blob c-flow-bg__blob--3"></span>
       <span class="c-flow-bg__blob c-flow-bg__blob--4"></span>
     </div>

   Variants:
     .c-flow-bg            full strength — public pages (landing, auth,
                           marketplace, coach profile)
     .c-flow-bg--calm      muted, slower — behind the app panel, where the
                           background must never compete with charts and
                           numbers for attention
     .c-flow-bg--no-grain  drops the film grain

   Theming: every colour is a `--flow-*` token, redefined per theme below and
   overridable per instance. FormPeak is light by default and dark under
   `html.dark`, so both sets live here.

   Motion budget: `data-flow-bg` lets js/flow-bg.js freeze the loop while the
   box is off-screen or the tab is hidden; `prefers-reduced-motion` freezes it
   outright. A frozen first keyframe is a finished composition on its own.
*/

/* ── Tokens: light theme (default) ──────────────────────────────────────
   Daylight FormPeak is near-white (#FAFAFA). The background may only
   whisper here: a hint of warm lime haze over off-white, never a wash of
   colour, or every surface above it turns muddy. */
.c-flow-bg {
  --flow-deep:   #E4E9D8; /* soft shadow corners */
  --flow-base:   #FAFAFA; /* body — matches --bg */
  --flow-mid:    #F3F7EA; /* lower body, faintly lime */
  --flow-accent: #ECFCCB; /* halo — the accent-soft token */
  --flow-bright: #DCF7A0; /* light pool */
  --flow-grain:  0.10;
  --flow-grain-color: #A3E635;
  --flow-speed:  1;       /* multiplies every duration: > 1 slower */

  /* Blob alphas: light theme needs far less than dark, because the base is
     already bright and contrast builds up fast. */
  --flow-a1: 0.55;
  --flow-a2: 0.50;
  --flow-a3: 0.28;
  --flow-a4: 0.30;

  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  isolation: isolate; /* blobs composite against THIS base, never the page */
  contain: strict;    /* sized by inset:0 — layout and paint fully isolated */
  background:
    radial-gradient(55% 60% at 100% 0%, var(--flow-mid) 0%, transparent 70%),
    linear-gradient(180deg, var(--flow-base) 0%, var(--flow-base) 45%, var(--flow-mid) 100%);
}

/* ── Tokens: dark theme ─────────────────────────────────────────────────
   True black stays the body (the OLED promise). Lime is a light source
   behind fog, not a fill: #BEF264 is a very luminous colour, so its blob
   runs at a fraction of the alpha the Infonetax blue used. */
html.dark .c-flow-bg {
  --flow-deep:   #000000; /* shadow corners — deeper than the base */
  --flow-base:   #08090A; /* body */
  --flow-mid:    #0D1109; /* lower body, greened by a hair */
  --flow-accent: #2A3D12; /* green glow around the light */
  --flow-bright: #BEF264; /* the lime light source itself */
  --flow-grain:  0.30;
  --flow-grain-color: #BEF264;

  --flow-a1: 0.42;
  --flow-a2: 0.24;
  --flow-a3: 0.70;
  --flow-a4: 0.55;
}

/* ── Calm variant: behind the panel ─────────────────────────────────────
   Same mechanism, pulled back until it reads as texture rather than motion.
   Achieved through the tokens alone — the blobs move closer to the base
   colour and the whole loop slows down — so nothing here fights the
   animation for control of `opacity`. */
.c-flow-bg--calm {
  --flow-speed: 2.2;
  --flow-grain: 0.05;
  --flow-accent: #F4F8EC;
  --flow-bright: #EDF6D8;
  --flow-deep:   #EEF1E6;
  --flow-a1: 0.30;
  --flow-a2: 0.26;
  --flow-a3: 0.14;
  --flow-a4: 0.16;
}

html.dark .c-flow-bg--calm {
  --flow-grain: 0.14;
  --flow-accent: #16200B;
  --flow-bright: #4A6B1E;
  --flow-deep:   #000000;
  --flow-a1: 0.30;
  --flow-a2: 0.24;
  --flow-a3: 0.55;
  --flow-a4: 0.40;
}

/* ── Film grain ─────────────────────────────────────────────────────────
   One static 200px tile of specks used as an alpha MASK over a flat colour,
   so the specks take --flow-grain-color. Static means painted once. */
.c-flow-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--flow-grain-color);
  -webkit-mask: url('../img/flow/grain.svg') 0 0 / 200px 200px repeat;
  mask: url('../img/flow/grain.svg') 0 0 / 200px 200px repeat;
  opacity: var(--flow-grain);
}

/* Retina: keep one speck = one DEVICE pixel. A 200px CSS tile would upscale
   every speck into a 2×2 blotch — the "dirty screen" look. */
@media (min-resolution: 1.5dppx) {
  .c-flow-bg::after {
    -webkit-mask-size: 100px 100px;
    mask-size: 100px 100px;
  }
}

.c-flow-bg--no-grain::after {
  content: none;
}

/* ── Blobs ──────────────────────────────────────────────────────────────
   A blob is a soft radial ellipse. --_s is its size as a % of box width,
   --_ar the width/height ratio (1 = disc), --_rot a tilt for the diagonal
   folds; the negative margins centre it on its left/top anchor. */
.c-flow-bg__blob {
  --_c: var(--flow-bright);
  --_s: 70%;
  --_ar: 1;
  --_rot: 0deg;
  position: absolute;
  width: var(--_s);
  aspect-ratio: var(--_ar);
  margin-left: calc(var(--_s) / -2);
  margin-top: calc(var(--_s) / var(--_ar) / -2);
  border-radius: 50%;
  background: radial-gradient(closest-side,
    var(--_c) 0%,
    color-mix(in srgb, var(--_c) 70%, transparent) 30%,
    color-mix(in srgb, var(--_c) 25%, transparent) 60%,
    transparent 78%);
  /* No will-change: an animated transform/opacity promotes the layer by
     itself, and the hint would hold four viewport-sized GPU layers even
     while the loop is frozen. */
  transform: translate3d(0, 0, 0) rotate(var(--_rot));
}

/* Halo behind the content: a wide, lighter ellipse centred on the copy. */
.c-flow-bg__blob--1 {
  --_c: var(--flow-accent);
  --_s: 64%;
  --_ar: 1.45;
  left: 50%;
  top: 40%;
  opacity: var(--flow-a1);
  animation: flow-drift-a calc(14s * var(--flow-speed)) ease-in-out infinite alternate;
}

/* The lime pool along the bottom — the light source of the whole scene. */
.c-flow-bg__blob--2 {
  --_c: var(--flow-bright);
  --_s: 96%;
  --_ar: 2.4;
  left: 58%;
  top: 104%;
  opacity: var(--flow-a2);
  animation: flow-drift-b calc(17s * var(--flow-speed)) ease-in-out infinite alternate;
  animation-delay: -7s;
}

/* The dark "wave": an irregular hook (img/flow/flow-wave.svg as an alpha mask
   over --flow-deep) — a rounded cap under the top edge on the right, curling
   into a diagonal tail. The shape, its blur and its fade live in the SVG. */
.c-flow-bg__blob--3 {
  --_c: var(--flow-deep);
  --_s: 46%;
  --_ar: 1.333; /* the SVG is 800×600 — a plain number: it is divided in calc() */
  left: 76%;
  top: 40%;
  opacity: var(--flow-a3);
  border-radius: 0;
  background: var(--_c);
  -webkit-mask: url('../img/flow/flow-wave.svg') center / 100% 100% no-repeat;
  mask: url('../img/flow/flow-wave.svg') center / 100% 100% no-repeat;
  /* Its own, calmer drift: a strong scale-down would pack the mask into a
     hard dark stroke, so the breathing happens in position and opacity. */
  animation: flow-drift-wave calc(16s * var(--flow-speed)) ease-in-out infinite alternate;
  animation-delay: -4s;
}

/* Softer shadow in the top-left corner. */
.c-flow-bg__blob--4 {
  --_c: var(--flow-deep);
  --_s: 52%;
  --_ar: 1.5;
  --_rot: 18deg;
  left: 6%;
  top: 2%;
  opacity: var(--flow-a4);
  animation: flow-drift-b calc(19s * var(--flow-speed)) ease-in-out infinite alternate;
  animation-delay: -12s;
}

/* ── Drift ──────────────────────────────────────────────────────────────
   Up to ~15% of a blob's own size, with a visible breathe in scale and a few
   degrees of tilt. Three variants so the four blobs never move in lock-step;
   `alternate` keeps every loop seam-free.

   Each keyframe scales the blob's base alpha token rather than hard-coding a
   value, so the calm variant stays calm while it animates. */
@keyframes flow-drift-a {
  0%   { transform: translate3d(0, 0, 0) scale(1) rotate(var(--_rot)); opacity: calc(var(--flow-a1) * 0.85); }
  35%  { transform: translate3d(-12%, 9%, 0) scale(1.18) rotate(calc(var(--_rot) + 4deg)); opacity: var(--flow-a1); }
  70%  { transform: translate3d(9%, -11%, 0) scale(0.9) rotate(calc(var(--_rot) - 3deg)); opacity: calc(var(--flow-a1) * 0.7); }
  100% { transform: translate3d(14%, 6%, 0) scale(1.1) rotate(var(--_rot)); opacity: calc(var(--flow-a1) * 0.95); }
}

@keyframes flow-drift-b {
  0%   { transform: translate3d(0, 0, 0) scale(1) rotate(var(--_rot)); opacity: calc(var(--flow-a2) * 0.8); }
  40%  { transform: translate3d(-15%, -8%, 0) scale(1.22) rotate(calc(var(--_rot) - 5deg)); opacity: var(--flow-a2); }
  100% { transform: translate3d(11%, -14%, 0) scale(0.88) rotate(calc(var(--_rot) + 3deg)); opacity: calc(var(--flow-a2) * 0.65); }
}

@keyframes flow-drift-wave {
  0%   { transform: translate3d(0, 0, 0) scale(1) rotate(var(--_rot)); opacity: calc(var(--flow-a3) * 0.9); }
  45%  { transform: translate3d(-9%, 6%, 0) scale(1.06) rotate(calc(var(--_rot) + 4deg)); opacity: calc(var(--flow-a3) * 0.75); }
  100% { transform: translate3d(7%, -8%, 0) scale(1.02) rotate(calc(var(--_rot) - 3deg)); opacity: var(--flow-a3); }
}

/* ── Motion budget ──────────────────────────────────────────────────────
   Off-screen, hidden tab (both set by js/flow-bg.js) and reduced motion all
   freeze the loop rather than tear it down. */
[data-flow-paused] .c-flow-bg__blob,
.c-flow-bg[data-flow-paused] .c-flow-bg__blob {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .c-flow-bg__blob {
    animation: none;
  }
}

/* ── Page-wide layer ────────────────────────────────────────────────────
   One fixed layer per page, behind everything, instead of a copy inside
   every section. The scene then stays put while the page scrolls, which
   reads like a window onto it rather than wallpaper dragged along.

   It works by moving the page background off <body>: the layer sits at
   z-index -1 (below all content, above the root background), <html> keeps
   a solid colour as the fallback beneath it, and <body> goes transparent
   so it stops painting over the layer. Utility classes on <body>
   (`bg-bg dark:bg-dark`) are overridden here on purpose — that background
   is exactly what the layer replaces. */
html:has(body > .c-flow-bg--page) {
  background: var(--bg);
}

body:has(> .c-flow-bg--page) {
  background: transparent !important;
}

.c-flow-bg--page {
  position: fixed;
  z-index: -1;
}

/* Sections that carry their own flow background (landing hero, CTA band)
   need a stacking context, or the fixed page layer would be painted over
   their content. */
.c-flow-host {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.c-flow-host > *:not(.c-flow-bg) {
  position: relative;
  z-index: 1;
}

/* ── Letting the layer through the content ──────────────────────────────
   Without this the background only shows in the gutters between cards: every
   surface above it is opaque. Making the cards translucent is what turns it
   from a strip around the edges into something the whole page sits on.

   Scoped to pages that actually carry the layer, so a view without it keeps
   its solid surfaces. Modals, drop-downs and sticky bars are deliberately NOT
   included — content scrolls underneath those, not background, and a
   see-through modal is just noise.

   --flow-surface: how much of the original surface colour survives. Lower =
   more background comes through. Tuned per theme: dark surfaces sit close to
   the base so they can give up more; light ones grey out fast. */
body:has(> .c-flow-bg--page) {
  --flow-surface: 62%;
  --flow-edge: color-mix(in srgb, #FFFFFF 65%, transparent);
}

html.dark body:has(> .c-flow-bg--page) {
  --flow-surface: 38%;
  --flow-edge: color-mix(in srgb, #FFFFFF 11%, transparent);
}

/* The complete set of surfaces, from an audit of every rule painting
   var(--surface*) on the public pages — listing them by hand is exactly how
   half the page ended up opaque and half translucent.

   The surface-2 boxes are mixed from --surface as well: at this strength the
   difference between #121214 and #1B1B1F is invisible, and one list is one
   fewer thing to keep in sync. */
body:has(> .c-flow-bg--page) :is(
  .card, .ld-card, .ld-step, .ld-plan, .ld-plan-banner, .ld-pricing-toggle,
  .ld-plan-featured, .ld-faq-item, .ld-hero-mockup, .ld-hero-float, .features, .integrations,
  .goal-card, .invitation-card, .inquiry-card,
  .chart-card, .admin-stat-card, .admin-chart-card
) {
  background: color-mix(in srgb, var(--surface) var(--flow-surface), transparent);
}

/* Blur only where cards are counted in tens, not in fifties: the public pages.
   Behind the panel (the calm variant) 49 blurred layers would be repainted on
   every scroll — a real cost on a phone — and the calm background is muted
   enough that the blur buys almost nothing. */
body:has(> .c-flow-bg--page:not(.c-flow-bg--calm)) :is(
  .card, .ld-card, .ld-step, .ld-plan, .ld-plan-banner, .ld-pricing-toggle,
  .ld-plan-featured, .ld-faq-item, .ld-hero-mockup, .ld-hero-float, .features, .integrations
) {
  backdrop-filter: blur(12px) saturate(1.1);
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
}

/* ── Blur budget on small screens ───────────────────────────────────────
   A backdrop-filter over a MOVING background is recomputed every frame, with
   no scroll and no interaction needed — the animation alone keeps it busy.
   On a phone that is continuous GPU work and a real battery drain, so below
   900px the glass is dropped and the surfaces just stay translucent. */
@media (max-width: 900px) {
  body:has(> .c-flow-bg--page) :is(
    .ld-nav, .card, .ld-card, .ld-step, .ld-plan, .ld-plan-banner,
    .ld-pricing-toggle, .ld-plan-featured, .ld-faq-item, .ld-hero-mockup, .ld-hero-float,
    .features, .integrations
  ) {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* The final CTA is the last full block painting over the layer. In the dark
   theme it is a plain raised surface, so it can go translucent like the cards.
   In the light theme it is an INVERTED block (dark fill, `color: var(--surface)`
   for its text) — making that translucent would put near-white text on a
   near-white page, so it is deliberately left alone there. */
html.dark body:has(> .c-flow-bg--page) .ld-cta {
  background: color-mix(in srgb, var(--surface-2) var(--flow-surface), transparent);
}

/* The edge is 5px and translucent on purpose. backdrop-filter blurs the whole
   border-box, not just the padding-box, so a see-through border gets the same
   blur as the fill — the frame reads as thick glass rather than as a drawn
   line. This only works while background-clip stays at its default
   (border-box); clipping to padding-box would stop the blur at the border. */
body:has(> .c-flow-bg--page:not(.c-flow-bg--calm)) :is(
  .card, .ld-card, .ld-step, .ld-plan, .ld-plan-banner, .ld-pricing-toggle,
  .ld-plan-featured, .ld-faq-item, .ld-hero-mockup, .ld-hero-float, .features, .integrations
) {
  border-width: 5px;
  border-style: solid;
  border-color: var(--flow-edge);
}
