/* ==========================================================================
   AMBIENT BACKGROUND SYSTEM — Evolve Up Fitness (Phase A.1 scope)
   ==========================================================================
   Scoped down to Phase A.1's approved elements only: matte black base
   (unchanged, already within spec), the existing grid extended
   sitewide, decorative background artwork, and soft blue radial
   glows (2 global + one per major section). No particles, no light
   beam, no cursor effects — all explicitly out of scope for this
   phase and removed from an earlier, broader build of this system.

   AUTHENTICITY BOUNDARY — read before touching the two image assets
   below. images/ambient/gym-ambient-v8.webp and
   images/ambient/hero-scene-v6.webp are intentional
   decorative artwork, explicitly confirmed by the client as NOT
   photographs of the real Evolve Up Fitness facility. They exist
   purely for premium ambient mood at very low opacity (7-8%) behind
   real content. They must never be used in the Gallery, Transformations,
   or any section that describes or documents the actual physical gym —
   those sections use only real, verified photos of the real facility,
   sourced separately (see content/gallery.json, content/transformations.json).
   This distinction is load-bearing for this whole project's
   authenticity principle and should not be blurred by a future edit
   that moves these assets somewhere they'd imply "this is our gym."

   Fully isolated, additive premium atmosphere layer. Does not modify
   any existing section's cards, spacing, typography, CTAs, or the
   Membership Ring / Transformations Spotlight / Testimonials Showcase /
   Contact layout — this file only adds new, purely decorative elements
   that sit behind everything that already exists.

   To roll back completely: remove this file's <link> tag, remove the
   single #ambientBackground container and the eight
   .section-atmosphere divs from index.html, and remove the
   `position: relative` additions this file makes to eight section
   selectors (listed explicitly below, each with zero offset values —
   position:relative alone changes no layout, only establishes a
   containing context for the new atmosphere div inside it).

   PERFORMANCE DISCIPLINE (same standard held throughout this whole
   project): every continuous animation below touches ONLY `transform`
   and `opacity` — the two GPU-compositable properties that never
   trigger layout or paint. Nothing here animates blur, box-shadow,
   filter, or background-position. The Layer 3 photo's blur is BAKED
   INTO the image file itself (images/ambient/hero-atmosphere.webp,
   a real photo from the existing gallery, pre-blurred and downscaled
   to 5KB) rather than applied as a live CSS filter — a live blur
   filter recomputed every frame on a background image is exactly the
   expensive-compositing pattern this project has repeatedly found and
   fixed elsewhere; baking it into the asset costs nothing at runtime.

   Reuses the site's existing --accent, --ease tokens — same brand
   language, not a new one. The grid pattern reuses the exact same
   recipe as the existing .hero__blueprint (64px grid, same blue,
   same alpha) rather than inventing a visually different grid.
   ========================================================================== */

/* ------------------------------------------------------- CONTAINMENT
   Position:relative added to each section this file adds atmosphere
   to — zero offset values, so this changes no layout or spacing at
   all. It only gives the new .section-atmosphere div (position:
   absolute) a correct containing box instead of escaping to the page
   root. */
#home, #why, #programs, #transformations, #membership, #testimonials, #contact,
.site-footer {
  position: relative;
  /* z-index:0 here, alongside position:relative, is required (not
     decorative) — it makes each section establish its own local
     stacking context, which .section-atmosphere's negative z-index
     depends on to correctly scope within that one section rather
     than escaping to compete at the page/body level. This value
     doesn't change how these sections stack relative to each other or
     to nav (nav's explicit positive z-index keeps it unambiguously on
     top regardless), only what happens *inside* each one. */
  z-index: 0;
}

/* =========================================================================
   GLOBAL FIXED LAYER — photo wash, grid, glows
   ========================================================================= */
#ambientBackground {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* z-index:0, not auto and not negative: this needs to stack among
     the same tier as the sections themselves (which now also carry
     z-index:0, see above) so their RELATIVE order is decided by DOM
     position — and since this element is the very first thing in
     <body>, it correctly paints behind every section that follows.
     nav's explicit positive z-index keeps it above all of this
     regardless. */
}

/* Layer 3 — intentional decorative background artwork (NOT a
   photograph of the real Evolve Up Fitness facility — confirmed
   explicitly by the client). Pre-processed with a 15% dark overlay
   and 14px blur baked into the asset file itself, not a live CSS
   filter.

   Recalibrated during a regression investigation: the original 82%
   overlay, combined with this site's very dark base background
   (#0A0A0A), produced a composited result that differed from the
   plain background by a maximum of ~0.1 out of 255 — mathematically
   below the threshold of human perception, regardless of CSS
   opacity. That's why the photo was reported as imperceptible even
   once correctly rendering. Reduced the overlay and raised opacity
   from 7% to 13%, verified numerically to produce a ~2.3-2.5 unit
   composited difference (reliably above the ~1-2 unit
   just-noticeable-difference threshold for 8-bit color) while
   confirming realistic-case readability remains far above WCAG AA
   (8.27:1 measured directly, vs. the 4.5:1 requirement) — see
   AMBIENT_ARTWORK_INTEGRATION.md for the complete calculation.

   Authenticity note, important: this is styled artwork, deliberately
   separate from the site's real photography. It is never used in the
   Gallery, Transformations, or anywhere that describes the actual
   facility — those sections exclusively use real, verified photos of
   Evolve Up Fitness. This layer exists purely for premium atmosphere. */
.ambient-bg__photo {
  position: absolute;
  /* inset:0 + transform:scale, NOT inset:-8%/width:116%. An oversized BOX
     contributes to layout width and can leak horizontal overflow if any
     ancestor's clipping fails (mobile browsers are inconsistent here).
     A transform produces the identical visual bleed while contributing
     nothing to layout, so it can never widen the document. */
  inset: 0;
  transform-origin: center;
  background-image: url('../images/ambient/gym-ambient-v8.webp');
  background-size: cover;
  background-position: center;
  /* Second calibration pass: 13% crossed the bare just-noticeable-
     difference threshold (~2.3 units) but that's the minimum for
     detection under focused, deliberate inspection — not the bar for
     subconscious, peripheral recognition while NOT actively looking,
     which is the actual goal here. Raised to 20%, verified to produce
     a ~3.3 unit composited difference (roughly 2-3x bare JND) while
     confirming realistic-case readability is essentially unchanged
     (8.21:1, since the photo's contribution to overall luminance is
     small relative to the base darkness) and the theoretical extreme
     worst-case stays at 4.74:1, still above WCAG AA's 4.5:1. */
  opacity: 0.27;
  transform: scale(1.16);
  /* Ken Burns treatment — slow, narrow-range scale/pan so it reads as
     "the room feels alive," never as a visible zoom — consistent with
     this whole system's "so slow nobody consciously notices"
     principle. Transform-only, GPU-safe. */
  animation: ambient-photo-drift 90s ease-in-out infinite;
  will-change: transform;
}
@keyframes ambient-photo-drift {
  0%, 100% { transform: scale(1.16) translate(0, 0); }
  50% { transform: scale(1.21) translate(-1.5%, 1%); }
}

/* Layer 2 — the same blueprint/grid recipe already proven in Hero,
   extended sitewide at a quieter baseline opacity so it reads as one
   consistent atmosphere rather than a Hero-only effect suddenly
   stopping. Hero's own .hero__blueprint is untouched and unaffected —
   this is a separate, independent layer. */
.ambient-bg__grid {
  position: absolute;
  inset: 0;
  transform: scale(1.1);
  opacity: 0.025;
  background-image:
    linear-gradient(rgba(42, 167, 155, 0.6) 1px, transparent 1px),
    linear-gradient(90deg, rgba(42, 167, 155, 0.6) 1px, transparent 1px);
  background-size: 64px 64px;
}

/* Layer 4 — large soft radial glows, positioned once, GPU-only slow
   drift via transform. Extremely subtle: low opacity, huge/soft
   radius so no edge is ever sharp enough to look like a "spotlight
   effect" landing on any particular piece of content. */
.ambient-bg__glow {
  position: absolute;
  width: 60vw;
  height: 60vw;
  max-width: 900px;
  max-height: 900px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(42, 167, 155, 0.05), transparent 68%);
  will-change: transform;
}
.ambient-bg__glow--a {
  top: 5%;
  left: -10%;
  animation: ambient-drift-a 42s ease-in-out infinite;
}
.ambient-bg__glow--b {
  bottom: 8%;
  right: -12%;
  animation: ambient-drift-b 50s ease-in-out infinite;
}
@keyframes ambient-drift-a {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(4vw, 3vh); }
}
@keyframes ambient-drift-b {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(-3vw, -4vh); }
}

/* Layers 5 (light beam) and 6 (particles) from the original build were
   removed for Phase A.1, which explicitly scopes this system down to:
   matte black base, grid, one pre-blurred photo, and soft radial
   glows only. Kept as a note here rather than silently deleted, so
   the reasoning for their absence is clear to a future reader rather
   than looking like an oversight. */

/* =========================================================================
   PER-SECTION ATMOSPHERE — lightweight tints that scroll normally with
   content (NOT fixed), sitting behind each section's own content via
   position:absolute + a low z-index within that section's own newly-
   added position:relative context. Each is a single decorative div,
   nothing more — no cards, spacing, typography, or CTAs touched.
   ========================================================================= */
.section-atmosphere {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* Corrected during self-review, verified against this codebase's own
     proven pattern (Hero's existing .hero__bg at z-index:0 vs
     .hero__content at z-index:1) rather than assumed from CSS spec
     theory alone: a positioned element with ANY explicit z-index
     (including 0) — or even z-index:auto — groups in the SAME
     painting tier, which sits ABOVE plain non-positioned in-flow
     content (real headings/cards in sections that don't independently
     set position/z-index). Neither 0 nor auto would have kept this
     div behind ordinary content. The actual fix needs two parts
     together: (1) the parent section now explicitly establishes its
     own local stacking context (position:relative + z-index:0, see
     above), and (2) THIS div uses a genuinely negative z-index (-1)
     within that now-properly-scoped context — which correctly paints
     above the section's own background-color (most of the 8 targeted
     sections set one directly) but below every real content element
     that follows, without needing to touch or re-tag any of that real
     content individually. */
}

.section-atmosphere::before {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: none;
}

/* Hero — strongest glow, per spec. Large, centered-high. */
/* Hero — strongest treatment in this system, per the established
   "Hero carries the boldest atmosphere" pattern. The second piece of
   decorative artwork (also confirmed not a real photo of the
   facility) sits on its own ::after pseudo-element, independent from
   the existing ::before glow below — giving each its own opacity
   without one multiplying through the other. Caught during
   integration: applying opacity to the shared parent instead would
   have also dimmed ::before's own glow (which has its own internal
   alpha already), making it nearly invisible rather than the intended
   strongest-in-the-system treatment. */
.section-atmosphere[data-atmosphere="hero"]::after {
  content: '';
  position: absolute;
  inset: 0;
  /* ORIENTATION-MATCHED SOURCE.
     The previous asset was portrait (1400x1866, aspect 0.75) while the hero
     is landscape on any desktop viewport. background-size:cover therefore
     scaled it up and discarded 53-58% of the image height — that upscaling
     of an already-soft photo is what read as "distorted".
     Portrait phones keep the portrait crop; anything wider gets a genuinely
     landscape source, so neither is stretched or heavily cropped. */
  background-image: url('../images/ambient/hero-portrait-v8.webp');
  background-size: cover;
  background-position: center;
  /* The hero scene, deliberately SHARP and clearly visible — the gym
     environment is meant to read as a real space, not a vague wash.
     Source was extremely dark (avg 15/255); linear brightening flattened
     it, so shadows were lifted with a gamma curve (0.42) instead, which
     recovers equipment detail without blowing out the lit areas.
     Readability is handled by the gradient veil and radial scrim on
     .hero__bg below — NOT by hiding this image. */
  opacity: 0.80;
  transform: scale(1);
  animation: hero-scene-kenburns 26s ease-in-out infinite alternate;
  will-change: transform;
}
@keyframes hero-scene-kenburns {
  from { transform: scale(1); }
  to   { transform: scale(1.04); }
}
.section-atmosphere[data-atmosphere="hero"]::before {
  width: 70vw; height: 70vw; max-width: 1000px; max-height: 1000px;
  top: -20%; left: 50%;
  transform: translateX(-50%);
  background: radial-gradient(circle, rgba(42, 167, 155, 0.09), transparent 65%);
}

/* Why Us — evokes a luxury gym silhouette via a low, wide, soft
   ground-level glow rather than literally re-inserting another photo
   layer (the real photo is already present once, globally, at Layer
   3 — a second copy here would cost more for no real gain and risk
   looking repetitive rather than atmospheric). */
.section-atmosphere[data-atmosphere="why"]::before {
  width: 90vw; height: 40vw; max-width: 1100px; max-height: 500px;
  bottom: -10%; left: 50%;
  transform: translateX(-50%);
  border-radius: 50% 50% 0 0;
  background: radial-gradient(ellipse, rgba(42, 167, 155, 0.05), transparent 70%);
}

/* Programs — blueprint/grid emphasis: the shared grid layer already
   exists globally; this section gets its own slightly stronger local
   copy layered on top, rather than raising the global grid opacity
   everywhere (which would affect every other section too). */
.section-atmosphere[data-atmosphere="programs"]::before {
  width: 100%; height: 100%;
  top: 0; left: 0;
  border-radius: 0;
  background-image:
    linear-gradient(rgba(42, 167, 155, 0.6) 1px, transparent 1px),
    linear-gradient(90deg, rgba(42, 167, 155, 0.6) 1px, transparent 1px);
  background-size: 64px 64px;
  opacity: 0.02;
  mask-image: radial-gradient(ellipse at 50% 40%, #000 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse at 50% 40%, #000 0%, transparent 75%);
}

/* Transformations — soft spotlight, off-center (echoing the real
   Spotlight component's own asymmetric composition without touching
   it). */
.section-atmosphere[data-atmosphere="transformations"]::before {
  width: 55vw; height: 55vw; max-width: 800px; max-height: 800px;
  top: 10%; left: 20%;
  background: radial-gradient(circle, rgba(42, 167, 155, 0.07), transparent 65%);
}

/* Membership — premium glass depth: a quiet, centered wash that reads
   as depth behind the Ring, never as a shape competing with it. */
.section-atmosphere[data-atmosphere="membership"]::before {
  width: 65vw; height: 65vw; max-width: 950px; max-height: 950px;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(42, 167, 155, 0.045), transparent 70%);
}

/* Testimonials — quiet ambient lighting: the calmest of all the named
   tints, matching this section's own "calmest showcase" design intent
   from earlier in this project. */
.section-atmosphere[data-atmosphere="testimonials"]::before {
  width: 70vw; height: 40vw; max-width: 1000px; max-height: 500px;
  top: 0; left: 50%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse, rgba(42, 167, 155, 0.035), transparent 70%);
}

/* Contact — warm subtle glow inviting action. Same accent color
   family as the rest of the site (a genuinely different hue here
   would break brand consistency) but positioned toward the primary
   CTA's side of the layout for a subtle "come this way" quality. */
.section-atmosphere[data-atmosphere="contact"]::before {
  width: 60vw; height: 60vw; max-width: 850px; max-height: 850px;
  top: 20%; right: -10%;
  background: radial-gradient(circle, rgba(42, 167, 155, 0.06), transparent 65%);
}

/* Footer — nearly static, minimal motion, per spec. No animation at
   all; the single quietest, smallest glow of any section. */
.section-atmosphere[data-atmosphere="footer"]::before {
  width: 40vw; height: 25vw; max-width: 600px; max-height: 350px;
  top: 0; left: 50%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse, rgba(42, 167, 155, 0.03), transparent 70%);
}

/* --------------------------------------------- SECONDARY PAGES
   The homepage works because each of its sections carries
   position:relative + z-index:0 (see the rule at the top of this
   file), so they stack above #ambientBackground. Secondary pages
   (calculators, blog, articles, legal, 404) have no such section
   structure — and a positioned element with z-index:0, which
   #ambientBackground is, paints ABOVE non-positioned in-flow content.
   Without this, the ambient layer would cover the page. These are the
   three structural containers those pages actually use, confirmed by
   inspecting each one rather than assumed. */
/* Only main and the footer need this. Deliberately NOT .nav — it is
   already position:fixed with z-index:var(--z-nav), so it stacks above
   the ambient layer on its own. An earlier version of this rule
   included .nav and overrode both of those, which broke the sticky
   header and changed the mobile drawer's containing block. Also not
   .skip-link, which is already positioned off-screen. */
body > main,
body > .site-footer {
  position: relative;
  z-index: 1;
}

/* Legal pages (Privacy, Terms) opt into a quieter treatment. People
   arrive there to read specific text, and full-strength atmosphere on
   a legal document reads slightly unserious. */
.ambient-subtle .ambient-bg__photo { opacity: 0.10; }
.ambient-subtle .ambient-bg__glow { opacity: 0.5; }
.ambient-subtle .ambient-bg__grid { opacity: 0.015; }

/* --------------------------------------------------------------- MOBILE
   Android performance: the photo layer is the single most expensive
   element to keep painted continuously across a whole scroll session
   (a full-viewport bitmap under a fixed-position container), relative
   to its small contribution to visible mood, so it's removed on
   narrow viewports. The grid and 2 large glows remain (cheap, static
   or near-static). */
@media (max-width: 640px) {
  /* Investigation finding: the photo was not a rendering bug — it was
     being deliberately hidden here (display:none) as a Phase A.1
     mobile-performance decision, which is exactly what produced the
     reported symptom (glow visible, photo not). That reasoning was
     sound at the time, but Phase B later added a continuous Ken Burns
     animation to this same layer — meaning full removal was
     addressing a real cost, just more bluntly than necessary. Fixed
     by keeping the photo visible on mobile (restoring the intended
     ambient background) while disabling only its continuous animation
     there — a static image paint is cheap; a continuously-animated
     full-viewport bitmap is what the original concern was actually
     about. This preserves the real performance reasoning instead of
     discarding it. */
  .ambient-bg__photo {
    animation: none;
  }
  .ambient-bg__glow {
    width: 80vw;
    height: 80vw;
  }
}

/* --------------------------------------------------- REDUCED MOTION */
@media (prefers-reduced-motion: reduce) {
  .ambient-bg__glow,
  .ambient-bg__photo {
    animation: none !important;
  }
}

/* =========================================================================
   SECTION VEILS — the reason the ambient photo was invisible
   =========================================================================
   #ambientBackground is position:fixed BEHIND the page, but every section
   painted a fully opaque background (rgb(10,10,10) / charcoal) directly
   over it, and .hero__bg painted an opaque gradient. Measured in-browser:
   the photo layer changed rendered pixels by 0.22 on average — i.e. it was
   loading correctly and being completely covered.

   These veils replace those opaque fills with translucent equivalents so
   the fixed layer shows through. Alpha 0.65 was chosen by calculation, not
   taste: below ~0.70 the photo clears the just-noticeable-difference
   threshold, and text contrast still passes WCAG AA comfortably (verified
   after). Delete this block to restore fully opaque sections.
   ========================================================================= */
.hero__bg {
  /* Replaces the previous flat tint. Two jobs, separated:
     1. A directional gradient — darker at the very top so the fixed nav
        stays legible, and darker at the bottom so the hero resolves
        cleanly into the next section. The MIDDLE is deliberately light
        (0.28) so the gym equipment reads clearly there.
     2. A radial scrim — a soft dark ellipse centred behind the hero
        text. This is what protects readability, instead of dimming the
        whole image. Text sits in the dark centre; the environment stays
        visible around it. */
  /* Scrim and gradient lightened to let more of the existing hero photo
     through. Measured headroom made this safe: white headline text was
     sitting at 15.63:1 where WCAG AA large-text needs only 3:1, so the
     veil was doing far more dimming than readability required.
     The scrim is still strongest exactly where the headline sits, and
     the top/bottom bands are unchanged so nav legibility and the handoff
     into the next section are preserved. */
  background:
    radial-gradient(ellipse 70% 60% at 50% 46%,
      rgba(6, 7, 9, 0.66) 0%,
      rgba(6, 7, 9, 0.44) 45%,
      rgba(6, 7, 9, 0.06) 78%,
      transparent 100%),
    linear-gradient(180deg,
      rgba(8, 9, 11, 0.74) 0%,
      rgba(8, 9, 11, 0.24) 26%,
      rgba(8, 9, 11, 0.16) 55%,
      rgba(10, 10, 10, 0.70) 92%,
      rgba(10, 10, 10, 0.92) 100%);
}
.why, .transformations, .membership, .faq {
  background: rgba(10, 10, 10, 0.65);
}
.stats, .programs, .testimonials, .contact, .gallery {
  background: rgba(23, 24, 26, 0.65);
}

/* =========================================================================
   HERO AMBIENT MOTES
   =========================================================================
   Six slow-drifting light motes in the hero only. Deliberately few:
   these are atmosphere, not a particle effect. Each animates transform
   and opacity exclusively — compositor-only, no layout or paint work,
   consistent with the discipline applied to every other animation on
   this site. Sits above the scene image but below hero content, so it
   never competes with the headline or CTAs.
   ========================================================================= */
.hero-mote {
  position: absolute;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: rgba(190, 214, 255, 0.55);
  opacity: 0;
  will-change: transform, opacity;
  pointer-events: none;
}
.hero-mote:nth-child(1) { left: 14%; top: 72%; animation: hero-mote-a 34s ease-in-out infinite; }
.hero-mote:nth-child(2) { left: 31%; top: 84%; animation: hero-mote-b 41s ease-in-out infinite 6s; }
.hero-mote:nth-child(3) { left: 58%; top: 78%; animation: hero-mote-a 38s ease-in-out infinite 13s; }
.hero-mote:nth-child(4) { left: 73%; top: 88%; animation: hero-mote-b 45s ease-in-out infinite 3s; }
.hero-mote:nth-child(5) { left: 86%; top: 68%; animation: hero-mote-a 36s ease-in-out infinite 20s; }
.hero-mote:nth-child(6) { left: 45%; top: 92%; animation: hero-mote-b 43s ease-in-out infinite 27s; }

@keyframes hero-mote-a {
  0%   { transform: translate3d(0, 0, 0) scale(0.8); opacity: 0; }
  15%  { opacity: 0.5; }
  70%  { opacity: 0.32; }
  100% { transform: translate3d(18px, -300px, 0) scale(1.1); opacity: 0; }
}
@keyframes hero-mote-b {
  0%   { transform: translate3d(0, 0, 0) scale(0.9); opacity: 0; }
  18%  { opacity: 0.42; }
  72%  { opacity: 0.26; }
  100% { transform: translate3d(-24px, -340px, 0) scale(1.05); opacity: 0; }
}

/* Mobile: halve the motes and stop the hero Ken Burns. A full-viewport
   image under continuous scale is the single most expensive thing in
   this section on a low-end Android; the scene stays fully visible,
   just static. */
@media (max-width: 640px) {
  .hero-mote:nth-child(n+4) { display: none; }
  .section-atmosphere[data-atmosphere="hero"]::after { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-mote { animation: none !important; opacity: 0.28; }
  .section-atmosphere[data-atmosphere="hero"]::after { animation: none !important; }
}


/* Landscape/desktop hero source — see the note above. Applies wherever the
   viewport is wider than it is tall, which is exactly where the portrait
   asset was being over-scaled. */
@media (min-aspect-ratio: 1/1) {
  .section-atmosphere[data-atmosphere="hero"]::after {
    background-image: url('../images/ambient/hero-landscape-v8.webp');
  }
}
