/* ============================================================================
   Silhouette background — shared across the marketing pages.
   A document-tall decorative layer of traced SVG figures lit by a pink-violet
   halo. Figures drift, their halos flicker like candlelight on a separate
   period, and one figure's hair sways.

   The layer spans the whole document rather than the viewport, and js/silhouettes.js
   lays the figures out one band per screenful: a different set of figures meets
   the reader at each section, spread across the full width, instead of one fixed
   composition sliding around behind everything. Placement lives in the JS; this
   file owns look and motion only.

   Everything here is presentational: the container is aria-hidden and
   pointer-events:none, it sits at z-index 0 under all content, and every
   animated property is transform/opacity so it stays on the compositor.
   All motion is disabled under prefers-reduced-motion.

   Loaded only by the public marketing pages. It is deliberately NOT on app or
   legal surfaces (checkout, admin, bot, account, terms, privacy), and NOT on the
   homepage, whose hero carries its own self-contained copy of this effect.
   ============================================================================ */

/* Near-black figures read against a crimson/violet haze: the glow is what
   reveals them on a #0C080D page, exactly the backlit look in the reference.
   Everything here is presentational only — aria-hidden on the container and
   pointer-events:none so it is invisible to screen readers and to the mouse. */
/* Absolutely positioned at the document origin and given its full height in JS, so
   it scrolls with the page. It is appended to <body> and never nested inside a
   section: several layout parents here carry overflow clipping or transforms,
   either of which would crop or re-anchor it. overflow:hidden contains the figures
   that deliberately bleed past the left and right edges. */
.bg-sils {
  position: absolute; top: 0; left: 0; width: 100%; height: 0;
  z-index: -1; pointer-events: none; overflow: hidden;
  --glow-boost: 2.1;   /* see .sil-glow — one dial for the whole layer */
  --glow-spread: 1.32;
}
/* Getting the layer UNDERNEATH the content without touching the content.
   The obvious rule - body > *:not(.bg-sils){position:relative;z-index:1} - lifts every
   sibling above the layer, and in doing so silently overrode `nav{position:fixed}` from
   global.css on every page that loaded this file. The nav then scrolled away with the
   page, and because the hero still reserves clearance for a fixed bar, that clearance
   was counted twice and opened a gap above the first line of copy.
   Instead: make body a stacking context and drop the layer to z-index -1 inside it. A
   negative child paints above its parent's background but below in-flow content, so the
   layer lands exactly where it needs to be and not one sibling has its position changed. */
body { position: relative; z-index: 0; }
/* `translate` is a separate property from `transform`, so any scroll-driven offset
   composes with the ambient bgSilDrift animation rather than overwriting it. */
.bg-sils .sil {
  position: absolute; pointer-events: none;
  translate: var(--sx, 0px) var(--sy, 0px);
  opacity: calc(var(--base-op, 1) * var(--scroll-op, 1));
}
/* GLOW INTENSITY — one dial.
   --glow-boost multiplies every alpha stop; --glow-spread widens the halo to match,
   because past about 1.5 the core alpha clamps at 1 and extra brightness has to come
   from area rather than opacity. Both are set once on .bg-sils below. Alphas over 1
   clamp on their own, which is what makes the top of the range safe to ask for. */
.bg-sils .sil-glow {
  position: absolute; display: block; border-radius: 50%;
  inset: calc(-22% * var(--glow-spread, 1)) calc(-34% * var(--glow-spread, 1));
  background: radial-gradient(ellipse at 50% 45%,
    rgba(214,94,114, calc(0.62 * var(--glow-boost, 1))) 0%,   /* --rose-text, the hot pink core */
    rgba(194,68,90,  calc(0.46 * var(--glow-boost, 1))) 26%,  /* --rose */
    rgba(139,95,192, calc(0.27 * var(--glow-boost, 1))) 56%,  /* --violet-l, the violet bleed */
    rgba(107,63,160, calc(0.12 * var(--glow-boost, 1))) 72%,  /* --violet */
    transparent 84%);
  filter: blur(calc(48px * var(--glow-spread, 1)));
}
.bg-sils .sil-fig { position: relative; display: block; width: 100%; height: auto; fill: #060407; }

/* == AMBIENT MOTION ======================================================
   Three decoupled cycles, all transform/opacity only, so every frame stays on
   the compositor and never touches layout or paint:
     1. the figure drifts and tips a fraction of a degree   (a weight shift)
     2. its glow drifts on its OWN period and axis          (light moving over her)
     3. one figure's hair sways                             (the only rigged part)
   The mismatch between 1 and 2 is the point: it is what stops a figure and its
   halo reading as one flat cutout sliding around, and it costs nothing because
   the halo is already a separate element. Every period is randomised per figure
   in the JS, so the composition never visibly re-syncs.
   Hair is the only part rigged because hair has no joint - it cannot hinge
   wrongly, and because the hair path OVERLAYS the full body path, rotating it
   can only ever add silhouette on the swing side. No seam can open. */
@keyframes bgSilDrift {
  from { transform: translate3d(0,0,0) rotate(0deg); }
  to   { transform: translate3d(var(--dx,7px), var(--dy,-11px), 0) rotate(var(--rot,0.3deg)); }
}
@keyframes bgSilGlowShift {
  from { transform: translate3d(0,0,0) scale(0.97); }
  to   { transform: translate3d(var(--gdx,-9px), var(--gdy,7px), 0) scale(var(--gsc,1.07)); }
}
/* Candlelight, not a pulse. A sine wave reads as a machine breathing, so the
   brightness runs through unevenly spaced stops that mostly sit high and dip
   occasionally and by varying amounts, which is how a real flame behaves. It is
   opacity ONLY, so it composites alongside the scale/translate above instead of
   fighting it for the transform property, and its period is deliberately unrelated
   to the movement period so the two never line up into a visible beat. */
@keyframes bgCandleFlicker {
  0%   { opacity: 0.88; }
  6%   { opacity: 1;    }
  11%  { opacity: 0.66; }
  19%  { opacity: 0.95; }
  26%  { opacity: 0.48; }
  33%  { opacity: 0.90; }
  41%  { opacity: 1;    }
  47%  { opacity: 0.60; }
  55%  { opacity: 0.98; }
  62%  { opacity: 0.74; }
  69%  { opacity: 1;    }
  74%  { opacity: 0.54; }
  82%  { opacity: 0.93; }
  89%  { opacity: 0.70; }
  95%  { opacity: 0.98; }
  100% { opacity: 0.88; }
}
@keyframes bgSilHairSway {
  from { transform: rotate(-1.5deg); }
  to   { transform: rotate(10deg); }
}
/* The hard line across the top of the page was this: global.css gives `nav` a
   `border-bottom: 1px solid rgba(139,26,47,0.2)`. Over the page background that
   computes to a full-width step of ~11 in luminance - fine on a flat page, but
   against a lit one it reads as a drawn line. Softened to a barely-there hairline
   here only; global.css is shared by every other page and is left alone. */
nav { border-bottom-color: transparent !important; }

/* The nav is position:fixed with a 0.9-alpha fill and a 20px backdrop blur, so it
   dims and softens everything under it. With a bright halo starting immediately
   below, that produced a hard horizontal seam right across the page at the nav's
   bottom edge - a full-width luminance step of ~11. This feathers the halo in
   underneath instead: held at the nav's own darkness through the nav band, then
   ramped off over the next ~150px so there is no edge to see. It lives here rather
   than on `nav` because that rule is in the shared global.css used by every page. */
/* see js/silhouettes.js: the layer only carries the feather when it starts at the
   top of the document. Starting below a hero, this would be a dark band mid-page. */
.bg-sils--below::after { content: none !important; }
.bg-sils::after {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 240px;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(12,8,13,0.92) 0px, rgba(12,8,13,0.92) 90px,
    rgba(12,8,13,0.62) 135px, rgba(12,8,13,0.30) 180px,
    rgba(12,8,13,0.10) 212px, transparent 240px);
}
.bg-sils .sil { animation: bgSilDrift var(--dur,41s) ease-in-out var(--delay,0s) infinite alternate; }
.bg-sils .sil-glow {
  animation: bgSilGlowShift var(--gdur,29s) ease-in-out var(--gdelay,0s) infinite alternate,
             bgCandleFlicker var(--fdur,7s) ease-in-out var(--fdelay,0s) infinite;
}
.bg-sils .sil-hair { transform-box: view-box; transform-origin: 593.7px 61.5px;
            animation: bgSilHairSway var(--hdur,5s) ease-in-out infinite alternate; }

@media (prefers-reduced-motion: reduce) {
  .bg-sils .sil, .bg-sils .sil-glow, .bg-sils .sil-hair { animation: none !important; }
}

/* ── TEXT PROTECTION ──────────────────────────────────────────────────────
   The halo is bright and now sits behind every section rather than one hero, so
   copy needs the same scrim the hero got. Every selector below was identified by
   DIFFERENTIAL measurement - rendering each page with the layer on and off and
   comparing - not by eye. Do not blanket-apply this: the scrim costs a little of
   the glow wherever it lands, so it goes only where text measurably dropped
   below WCAG AA. Add to these lists if a new page regresses.

   All targets were checked to be block-level with no existing ::before before
   attaching one. */

/* single copy blocks */
/* .gb-trust is the 11.5px "Secure checkout, powered by Stripe" line. It was fine at the
   original glow and fails at 4.48 against a 4.5 threshold once the glow is boosted —
   small, low-contrast, and sitting under a halo on a phone. It is also the one piece of
   copy on the page doing trust work at the moment of purchase, so it gets a scrim. */
/* The four below were added when the glow was boosted. Each was measured failing only
   with the layer on, and each is small type: the drip-card month labels (16px), open FAQ
   answers (14px), and the footer legal line (9.9px). All sit in the clear parts of a
   section where the halo is strongest. */
/* `isolation: isolate` is load-bearing, not decoration. Without it these elements are
   positioned but have z-index auto, so they are NOT stacking contexts, and a ::before at
   z-index -1 resolves all the way up to <body> - landing BENEATH the card the text sits
   on, where it darkens nothing that matters. Proven on .plan-note: darkening its card
   moved the layer-off reading and left the layer-on reading untouched. Isolating scopes
   the pseudo-element to its own box: above the card fill, below the text. */
/* Homepage additions (the layer now runs below its hero, so its copy needs the same
   treatment the other pages already had): .truth-pull is the 43px pull-quote, and the
   three small notes plus the tier label all measured below AA only with the layer on.
   .talk-schedule label is the one form label on talk that sits clear of the card fill. */
/* .pricing-lede deliberately has NO scrim: the owner found the panel behind
   "Every man gets the same curriculum on the same clock" an eyesore and asked for the
   text to sit directly on the background. Contrast re-measured after removal. */
.faq-a, .gb-subtitle, .gb-pricing-note, .gb-trust,
.drip-month-n, .footer-copy, .plan-note,
.truth-pull, .receipts-note, .tiers-note, .tier-card-name,
.truth-text, .gb-card-period, .talk-schedule label, .sil-scrim { position: relative; isolation: isolate; }
.faq-a::before, .gb-subtitle::before,
.gb-pricing-note::before, .gb-trust::before, .drip-month-n::before,
.footer-copy::before, .plan-note::before, .truth-pull::before,
.receipts-note::before, .tiers-note::before, .tier-card-name::before,
.truth-text::before, .gb-card-period::before,
.talk-schedule label::before, .sil-scrim::before {
  content: ''; position: absolute; inset: -12% -8%; z-index: -1; pointer-events: none;
  /* A fill, not a backdrop-filter. brightness() was tried here and measured WORSE:
     14 contrast regressions against 0, because it cannot darken a bright halo as far
     as an 0.86 fill can. The trade is known and deliberate - over a DARK area this
     fill can read faintly as a panel, which is the cost of keeping small copy legible
     over the glow. It fades to nothing on every side so it never draws an edge. */
  background: radial-gradient(ellipse 124% 148% at 50% 50%,
    rgba(9,6,10,0.86) 0%,
    rgba(9,6,10,0.72) 34%,
    rgba(9,6,10,0.48) 55%,
    rgba(9,6,10,0.22) 72%,
    rgba(9,6,10,0.07) 86%,
    transparent 96%);
}

/* Text-dense pages get ONE scrim on their single content wrapper rather than a
   scrim per element - apply, support and talk spread small copy down a long
   column, and tagging each block would be fragile and endless. A tall wrapper
   needs a flatter profile than a single paragraph does, hence the separate fill. */
.apply-wrap, .legal-shell, .talk-wrap { position: relative; }
.apply-wrap::before, .legal-shell::before, .talk-wrap::before {
  content: ''; position: absolute; inset: -2% -14%; z-index: -1; pointer-events: none;
  /* A column this tall needs a flatter profile than one ellipse gives, so it keeps a
     vertical ramp - but the ramp now starts and ends at transparent, and a mask feathers
     the left and right edges the ramp cannot. Same rule as above: no visible terminus. */
  background: linear-gradient(180deg,
    transparent 0%, rgba(9,6,10,0.44) 7%, rgba(9,6,10,0.42) 50%,
    rgba(9,6,10,0.44) 93%, transparent 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 9%, #000 91%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 9%, #000 91%, transparent 100%);
}

@media (max-width: 900px) {
  /* These wrappers are full-viewport-width on a phone, so the -14% horizontal
     bleed that softens the scrim on desktop pushed scrollWidth 55px past the
     viewport. body{overflow-x:hidden} was masking it, which is not something to
     rely on. The radial already fades out by 92%, so squaring off the sides here
     costs nothing visible. */
  .apply-wrap::before, .legal-shell::before, .talk-wrap::before { inset: -2% 0; }
  /* Same trap, same fix, for the single-block scrims: their -8% horizontal bleed pushed
     scrollWidth 3px past the viewport on a phone. body{overflow-x:hidden} hides that,
     which is exactly what must not be relied on. The ellipse reaches transparent at 96%,
     so squaring the sides off here costs nothing visible. */
  .faq-a::before, .gb-subtitle::before, .gb-pricing-note::before,
  .gb-trust::before, .drip-month-n::before, .footer-copy::before,
  .plan-note::before, .truth-pull::before, .receipts-note::before,
  .tiers-note::before, .tier-card-name::before, .truth-text::before,
  .gb-card-period::before, .talk-schedule label::before,
  .sil-scrim::before { inset: -12% 0; }
  /* Phones keep all three cycles but at roughly half the throw: same idea, fewer
     dirty pixels per frame on a mid-range handset. */
  .bg-sils .sil { --dx: 4px; --dy: -6px; --rot: 0.18deg; }
  /* on phones the page's own 88px top padding means this layer already begins at
     the nav's lower edge, so the ramp starts immediately instead of holding first */
  .bg-sils::after {
    height: 150px;
    background: linear-gradient(180deg,
      rgba(12,8,13,0.90) 0px, rgba(12,8,13,0.54) 52px,
      rgba(12,8,13,0.22) 98px, transparent 150px);
  }
  .bg-sils .sil-hair { --hdur: 6s; }
}

/* ── SCREEN-ONE COMPOSITION (opt-in, `data-sil-hero` on the script tag) ─────
   A page can ask for the original hand-placed arrangement across its first
   screenful, with the per-section system picking up below it. This is not a
   second engine: the figures are the same elements in the same layer, the JS
   just emits nine fixed slots inside a 100vh box at the top instead of bands,
   and starts banding at y = 100vh.

   The wrapper is exactly one viewport tall, which is what lets these
   percentages mean what they meant when the layer itself was viewport-fixed —
   they are copied across unchanged. Unlike the old layer these scroll away with
   the page rather than following the reader down it. */
.bg-sils .sil-screen1 { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; }

.bg-sils .sil-screen1 .sil-a { left: -5%;   top: 16%;    width: 30%; }  /* behind the headline, not the eyebrow */
.bg-sils .sil-screen1 .sil-b { left: -7%;   bottom: -3%; width: 13%; }  /* stand, lower left */
.bg-sils .sil-screen1 .sil-c { left: 48.9%; top: 12%;    width: 10%; }  /* half behind the glass panel's left edge */
.bg-sils .sil-screen1 .sil-d { left: 17%;   bottom: -1%; width: 19%; }  /* legs, floor left-of-centre */
.bg-sils .sil-screen1 .sil-e { left: 34%;   bottom: 0%;  width: 8%;  }
.bg-sils .sil-screen1 .sil-f { right: -2%;  top: 5%;     width: 13%; }  /* stand, head above the panel */
.bg-sils .sil-screen1 .sil-g { right: 0%;   bottom: -4%; width: 28%; }  /* recline, mostly below the panel */
.bg-sils .sil-screen1 .sil-h { right: 23%;  bottom: -2%; width: 10%; }
.bg-sils .sil-screen1 .sil-i { right: 11%;  top: 11%;    width: 17%; }  /* top:11% clears the 88px nav */

.bg-sils .sil-screen1 .sil-a { --dur: 11s; --gdur: 23s; --dx: 9px;  --dy: -8px;  --rot: 0.25deg;  --delay: -3s;  --gdelay: -8s;  --fdur: 6.5s; --fdelay: -1.7s; }
.bg-sils .sil-screen1 .sil-b { --dur: 7s;  --gdur: 19s; --dx: -6px; --dy: -12px; --rot: -0.30deg; --delay: -6s;  --gdelay: -11s; --fdur: 8.3s; --fdelay: -4.1s; }
.bg-sils .sil-screen1 .sil-c { --dur: 6s;  --gdur: 27s; --dx: 7px;  --dy: -10px; --rot: 0.30deg;  --delay: -13s; --gdelay: -4s;  --fdur: 5.9s; --fdelay: -2.9s; }
.bg-sils .sil-screen1 .sil-d { --dur: 13s; --gdur: 13s; --dx: -8px; --dy: -6px;  --rot: 0.20deg;  --delay: -21s; --gdelay: -17s; --fdur: 9.7s; --fdelay: -6.3s; }
.bg-sils .sil-screen1 .sil-e { --base-op: 0.7;  --dur: 5s;  --gdur: 17s; --dx: 6px;  --dy: -9px; --rot: -0.35deg; --delay: -9s;  --gdelay: -25s; --fdur: 7.1s; --fdelay: -0.8s; }
.bg-sils .sil-screen1 .sil-f { --dur: 12s; --gdur: 29s; --dx: -7px; --dy: -11px; --rot: 0.28deg;  --delay: -17s; --gdelay: -31s; --fdur: 6.1s; --fdelay: -5.2s; }
.bg-sils .sil-screen1 .sil-g { --dur: 10s; --gdur: 11s; --dx: 8px;  --dy: -7px;  --rot: -0.22deg; --delay: -3s;  --gdelay: -8s;  --fdur: 8.9s; --fdelay: -3.4s; }
.bg-sils .sil-screen1 .sil-h { --base-op: 0.72; --dur: 8s; --gdur: 31s; --dx: -9px; --dy: -8px; --rot: 0.32deg;  --delay: -25s; --gdelay: -13s; --fdur: 5.3s; --fdelay: -7.1s; }
.bg-sils .sil-screen1 .sil-i { --base-op: 0.62; --dur: 9s; --gdur: 9s;  --dx: 7px;  --dy: -5px; --rot: -0.20deg; --delay: -11s; --gdelay: -20s; --fdur: 7.7s; --fdelay: -2.2s; }

@media (max-width: 900px) {
  /* Phone composition: the first screen is one centred column, so figures sit on
     the two side margins and the floor, framing the text rather than sitting under
     it. top:12% clears the 88px nav that overlays the top of this layer. */
  .bg-sils .sil-screen1 .sil-a { left: -7%;  top: 12%;  width: 54%; }
  /* bottom:auto is load-bearing: the desktop rule sets bottom, and setting top here
     without clearing it stretched this box from 266px to 690px, which stretched its
     glow into a tall ellipse and made it collide with the figure below. */
  .bg-sils .sil-screen1 .sil-b { right: -8%;  left: auto; top: 13%; bottom: auto; width: 27%; }
  /* 16%, not 20%: this figure is 4.21:1 where the old one was 3.28:1, so at the
     same width it hung ~70px lower and clipped .sil-e again at 414-430px. */
  .bg-sils .sil-screen1 .sil-c { left: -10%; top: 29%;   width: 16%; }
  .bg-sils .sil-screen1 .sil-d { right: -4%; left: auto; bottom: 7%; width: 31%; }
  .bg-sils .sil-screen1 .sil-e { left: -3%;  bottom: 0%; width: 18%; --base-op: 0.7; }
  /* the four desktop-only right-side figures would crowd a 390px column */
  .bg-sils .sil-screen1 .sil-f, .bg-sils .sil-screen1 .sil-g,
  .bg-sils .sil-screen1 .sil-h, .bg-sils .sil-screen1 .sil-i { display: none; }
  .bg-sils .sil-screen1 .sil-a { --dur: 13s; --gdur: 29s; }
  .bg-sils .sil-screen1 .sil-b { --dur: 11s; --gdur: 26s; }
  .bg-sils .sil-screen1 .sil-c { --dur: 8s;  --gdur: 31s; }
  .bg-sils .sil-screen1 .sil-d { --dur: 15s; --gdur: 22s; }
  .bg-sils .sil-screen1 .sil-e { --dur: 7s;  --gdur: 19s; }
}
