/* =============================================================================
  ANIMATIONS.CSS — Non-critical decorative layer
  =============================================================================
  Everything in this file is PURELY decorative (aria-hidden, pointer-events:
  none, zero effect on layout or content). Hero (Three.js canvas), About
  (code-glyph float), and the Preloader's rising bubbles are NOT here — all
  three are load-bearing or need to render before this file has a chance to
  arrive, so they live in sections.css on the critical path instead.

  This file is loaded via a preload+swap pattern in index.html:
    <link rel="preload" href="css/animations.css" as="style" onload="this.rel='stylesheet'">
  ...which means it never blocks first paint. The trade-off: for a brief
  window after first paint, these elements are invisible. That's intentional
  and handled by a tiny guard rule in sections.css:
    .decor-layer { opacity: 0; }
  (opacity composites down the whole subtree regardless of any opacity a
  descendant sets on itself, so one rule on the wrapper is enough) — so
  there is zero flash of unstyled/unpositioned decoration. Elements simply
  fade into existence once this stylesheet arrives, which for decorative
  chrome is a perfectly fine trade against not blocking render for it.

  All motion respects prefers-reduced-motion (see base.css's global
  `*` rule for CSS animations, plus the explicit `display:none` block near
  the bottom of the Services block for the one bit of SVG SMIL animation
  that the global CSS rule can't reach).

  RESPONSIVENESS: every layer here stays ON at every viewport width —
  nothing is display:none'd below a breakpoint. Instead, each layer is
  positioned and sized to remain correct at any width:
    - Portfolio bubbles rise horizontally from each side wall using %-based
      `left`/`right` keyframes, and Experience bubbles rise vertically using
      %-based `left`/`right` starting positions across the FULL width of
      their section (not just the gutters) — both percentages resolve
      against their own container's box, so they scale automatically at
      every breakpoint with zero hand-maintained per-breakpoint offsets.
    - Services blobs and their corner-bleed offset scale down together at
      each breakpoint so the "amount of corner peeking through" stays
      proportional instead of a fixed-size blob swallowing a phone screen.
    - Social's frame and the Contact/footer glow were already responsive
      (clamp() / aspect-ratio-driven) and needed no changes here.
============================================================================= */

.decor-layer {
  opacity: 1;
  /* overrides the sections.css guard now that this file has loaded */
}

/* -----------------------------------------------------------------------
  SERVICES — corner-anchored SVG blobs, real path-morph via SMIL <animate>
  (not a border-radius trick). Sits in the wrapper's own negative-space
  corners, clipped by the wrapper's existing overflow:hidden, so it reads
  as ambient light bleeding in from outside the card stack rather than
  something fighting the cards for attention.
----------------------------------------------------------------------- */
.services-decor {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.service-blob {
  position: absolute;
  filter: blur(30px);
}

.service-blob--a {
  top: -110px;
  left: -120px;
  width: 380px;
  height: 380px;
  opacity: 0.32;
  /* was 0.16 — too faint to register at a glance */
}

.service-blob--b {
  bottom: -130px;
  right: -110px;
  width: 320px;
  height: 320px;
  opacity: 0.28;
  /* was 0.13 */
}

.grad-stop-primary {
  stop-color: var(--color-primary);
}

.grad-stop-secondary {
  stop-color: var(--color-secondary);
}

.services-track {
  /* Explicit stacking guard: without this, an auto/0 z-index positioned
    element (.services-track) painting after .services-decor in DOM order
    happens to work today, but relying on paint-order tie-breaking instead
    of an explicit value is fragile the moment someone reorders markup. */
  z-index: 1;
}

/* Blob size + corner-bleed offset scale down together so smaller viewports
  get a proportionally smaller "amount of glow peeking in," never a blob
  that swallows the whole card. */
@media (max-width: 992px) {
  .service-blob--a {
    top: -90px;
    left: -95px;
    width: 300px;
    height: 300px;
  }

  .service-blob--b {
    bottom: -100px;
    right: -90px;
    width: 260px;
    height: 260px;
  }
}

@media (max-width: 768px) {
  .service-blob--a {
    top: -70px;
    left: -75px;
    width: 240px;
    height: 240px;
  }

  .service-blob--b {
    bottom: -80px;
    right: -70px;
    width: 200px;
    height: 200px;
  }
}

@media (max-width: 480px) {
  .service-blob--a {
    top: -55px;
    left: -58px;
    width: 180px;
    height: 180px;
  }

  .service-blob--b {
    bottom: -60px;
    right: -55px;
    width: 160px;
    height: 160px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .service-blob {
    display: none;
  }
}

/* -----------------------------------------------------------------------
  PORTFOLIO — bubbles rising inward from BOTH side walls, converging toward
  the section's center and fading out before they get there. Replaces the
  old bouncing-ball pair, which read as playful/"cute" rather than premium
  and didn't fit the rest of the site's decorative language. Bubbles are
  already established elsewhere (preloader, Experience below) — reusing
  that same visual family here keeps the site consistent instead of
  introducing a third decorative idiom.

  Mechanically this is the exact same rising-bubble idea used by the
  Preloader and Experience section, just rotated 90°: instead of animating
  `bottom` → up (a vertical rise), each bubble animates its `left` (or
  `right`) offset outward from its wall toward the center (a horizontal
  rise).

  Deliberate choice: travel distance is expressed in %, not px. `left`/
  `right` percentages resolve against the CONTAINING BLOCK'S width
  (.portfolio-decor, `inset:0` on the full section) — so as the section
  narrows at each breakpoint, every bubble's travel distance shrinks by
  the exact same proportion automatically. The vertical bubbles elsewhere
  in this file use a fixed-px `transform: translateY()` and therefore need
  hand-maintained duration corrections at the 768px breakpoint (see the
  Experience block below) to avoid visibly speeding up when the section's
  HEIGHT changes with the grid's column count. Using % for a HORIZONTAL
  rise sidesteps that entire class of bug — .portfolio-grid's column count
  doesn't change the section's WIDTH, only its height, so no breakpoint
  hack is needed here at all.
----------------------------------------------------------------------- */
.portfolio-decor {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.pf-bubble {
  position: absolute;
  border-radius: 50%;
  border: 1px solid var(--border-glass);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.pf-bubble--primary {
  background: rgba(var(--color-primary-rgb), 0.3);
}

.pf-bubble--secondary {
  background: rgba(var(--color-secondary-rgb), 0.3);
}

/* LEFT WALL — starts just off-screen past the left edge, animates its
  `left` offset inward. */
.pf-bubble--left {
  left: -18px;
  animation-name: pfBubbleFromLeft;
}

/* RIGHT WALL — mirror image, animates `right` inward from the right edge. */
.pf-bubble--right {
  right: -18px;
  animation-name: pfBubbleFromRight;
}

/* 6 bubbles per wall (12 total — "medium" density, matching the site's
  established preloader/experience bubble count of 6). Left-wall bubbles
  sit at 4/20/36/52/68/84% and right-wall at 12/28/44/60/76/92%, so the two
  sets interleave at ~8% spacing across the full 0–100% height — every band
  of the section has a bubble passing through it somewhere in the loop,
  with no dead vertical zones, while staggered negative delays keep them
  from ever firing in lockstep. */
.pf-bubble--l1 {
  top: 4%;
  width: 14px;
  height: 14px;
  animation-duration: 10s;
  animation-delay: 0s;
}

.pf-bubble--l2 {
  top: 20%;
  width: 9px;
  height: 9px;
  animation-duration: 8.5s;
  animation-delay: -5s;
}

.pf-bubble--l3 {
  top: 36%;
  width: 18px;
  height: 18px;
  animation-duration: 12s;
  animation-delay: -3s;
}

.pf-bubble--l4 {
  top: 52%;
  width: 11px;
  height: 11px;
  animation-duration: 9.5s;
  animation-delay: -7s;
}

.pf-bubble--l5 {
  top: 68%;
  width: 16px;
  height: 16px;
  animation-duration: 11s;
  animation-delay: -1.5s;
}

.pf-bubble--l6 {
  top: 84%;
  width: 10px;
  height: 10px;
  animation-duration: 13s;
  animation-delay: -9s;
}

.pf-bubble--r1 {
  top: 12%;
  width: 12px;
  height: 12px;
  animation-duration: 11.5s;
  animation-delay: -6s;
}

.pf-bubble--r2 {
  top: 28%;
  width: 17px;
  height: 17px;
  animation-duration: 9s;
  animation-delay: -2s;
}

.pf-bubble--r3 {
  top: 44%;
  width: 10px;
  height: 10px;
  animation-duration: 13.5s;
  animation-delay: -8.5s;
}

.pf-bubble--r4 {
  top: 60%;
  width: 15px;
  height: 15px;
  animation-duration: 10s;
  animation-delay: -4s;
}

.pf-bubble--r5 {
  top: 76%;
  width: 9px;
  height: 9px;
  animation-duration: 8s;
  animation-delay: -0.5s;
}

.pf-bubble--r6 {
  top: 92%;
  width: 19px;
  height: 19px;
  animation-duration: 12.5s;
  animation-delay: -10s;
}

/* Each bubble travels 44% of the section's own width inward from its wall
  — close enough to read as "converging on the center" without the two
  streams ever actually meeting past the midpoint. Per spec, opacity fades
  CONTINUOUSLY across the whole trip rather than staying bright until a
  sudden drop near the end: faintest right as it leaves the wall, peaks
  briefly mid-journey, then eases back down to nothing well before it
  would reach center. */
@keyframes pfBubbleFromLeft {
  0% {
    left: -18px;
    opacity: 0;
    transform: scale(0.85);
  }

  10% {
    opacity: 0.85;
  }

  45% {
    opacity: 0.6;
  }

  100% {
    left: 44%;
    opacity: 0;
    transform: scale(1.05);
  }
}

@keyframes pfBubbleFromRight {
  0% {
    right: -18px;
    opacity: 0;
    transform: scale(0.85);
  }

  10% {
    opacity: 0.85;
  }

  45% {
    opacity: 0.6;
  }

  100% {
    right: 44%;
    opacity: 0;
    transform: scale(1.05);
  }
}

@media (prefers-reduced-motion: reduce) {
  .pf-bubble {
    display: none;
  }
}

/* A touch smaller on very narrow phones purely for visual proportion —
  travel distance is already %-based so it scales correctly on its own;
  this is polish, not a fix (same rationale as the Services blobs above). */
@media (max-width: 480px) {

  .pf-bubble--l1,
  .pf-bubble--l3,
  .pf-bubble--l5,
  .pf-bubble--r2,
  .pf-bubble--r4,
  .pf-bubble--r6 {
    width: 11px;
    height: 11px;
  }

  .pf-bubble--l2,
  .pf-bubble--l4,
  .pf-bubble--l6,
  .pf-bubble--r1,
  .pf-bubble--r3,
  .pf-bubble--r5 {
    width: 7px;
    height: 7px;
  }
}

/* -----------------------------------------------------------------------
  EXPERIENCE — rising glass bubbles spanning the FULL WIDTH of the section
  base (left: 8/20/35%, right: 35/20/8% — the exact same spread as the
  Preloader's own bubbles in sections.css), not just the side gutters.
  Previously these sat at fixed px offsets tucked into the left/right
  edges only, leaving the whole center of the section's base with no
  bubbles rising through it at all — a visible dead zone. Switching the
  anchor points from small fixed px to %, spread across the full width,
  closes that gap while keeping the same "medium" 6-bubble count already
  established as this site's standard (see Preloader).
  Styled to match the site's own .glass language (translucent fill + light
  top rim) rather than looking like a foreign decorative element. Fades
  in/out at the top and bottom of the loop so the reset-to-bottom is
  invisible instead of an abrupt teleport-and-pop. The bubbleRise keyframe
  itself is defined once in sections.css (it's needed early for the
  preloader's own bubbles), and referenced from here.
----------------------------------------------------------------------- */
.experience-decor {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.exp-bubble {
  position: absolute;
  bottom: -60px;
  border-radius: 50%;
  border: 1px solid var(--border-glass);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation-name: bubbleRise;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.exp-bubble--primary {
  background: rgba(var(--color-primary-rgb), 0.3);
}

/* was 0.16 — too faint to register */
.exp-bubble--secondary {
  background: rgba(var(--color-secondary-rgb), 0.3);
}

/* %-based anchors resolve against .experience-decor's own width (the full
  section), so unlike the old px gutter offsets, these scale correctly and
  stay evenly spread across the base at every viewport width automatically. */
.exp-bubble--1 {
  left: 8%;
  width: 18px;
  height: 18px;
  animation-duration: 11s;
  animation-delay: 0s;
}

.exp-bubble--2 {
  left: 20%;
  width: 11px;
  height: 11px;
  animation-duration: 14s;
  animation-delay: -6s;
}

.exp-bubble--3 {
  left: 35%;
  width: 8px;
  height: 8px;
  animation-duration: 9s;
  animation-delay: -3s;
}

.exp-bubble--4 {
  right: 35%;
  width: 17px;
  height: 17px;
  animation-duration: 12.5s;
  animation-delay: -4s;
}

.exp-bubble--5 {
  right: 20%;
  width: 12px;
  height: 12px;
  animation-duration: 10s;
  animation-delay: -7s;
}

.exp-bubble--6 {
  right: 8%;
  width: 8px;
  height: 8px;
  animation-duration: 15s;
  animation-delay: -1s;
}

/* Same root cause and same fix as the Portfolio balls above: .experience-grid
  collapses from 3 columns to 1 on mobile, making the section roughly 2x
  taller, so the bubbles' percentage-based rise would otherwise cover that
  distance in the same fixed duration and visibly speed up. Duration and
  delay are scaled by the same factor (2x) so each bubble's relative
  position in its own cycle — not just its raw timing — stays identical to
  desktop; only the perceived speed is corrected. */
@media (max-width: 768px) {
  .exp-bubble--1 {
    animation-duration: 22s;
    animation-delay: 0s;
  }

  .exp-bubble--2 {
    animation-duration: 28s;
    animation-delay: -12s;
  }

  .exp-bubble--3 {
    animation-duration: 18s;
    animation-delay: -6s;
  }

  .exp-bubble--4 {
    animation-duration: 25s;
    animation-delay: -8s;
  }

  .exp-bubble--5 {
    animation-duration: 20s;
    animation-delay: -14s;
  }

  .exp-bubble--6 {
    animation-duration: 30s;
    animation-delay: -2s;
  }
}

/* -----------------------------------------------------------------------
  SOCIAL — a "snake" of light chasing the inside boundary of the section,
  reusing the exact conic-gradient + inset-mask technique already
  established by .logo-container in sections.css. Deliberately NOT a new
  visual idiom — it's the site's own brand mark language, scaled up.
  Already fully responsive via clamp(); no changes needed here.
----------------------------------------------------------------------- */
.social-decor-frame {
  position: absolute;
  inset: clamp(16px, 4vw, 40px);
  border-radius: 32px;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.social-decor-frame::before {
  content: '';
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  background: conic-gradient(transparent 0deg,
      transparent 250deg,
      var(--color-secondary) 295deg,
      var(--color-primary) 325deg,
      transparent 360deg);
  animation: snakeSpin 7s linear infinite;
}

.social-decor-frame::after {
  content: '';
  position: absolute;
  inset: 3px;
  background: var(--bg-base);
  border-radius: 28px;
}

@keyframes snakeSpin {
  to {
    transform: rotate(360deg);
  }
}

/* -----------------------------------------------------------------------
  CONTACT & FOOTER — pulsing gradient glow (breathing opacity + scale),
  now actually animated per design update (previously static/zero-motion
  by design — that call has been revisited: a slow, subtle pulse reads as
  "alive" without competing with the CTA the way a fast or large-amplitude
  animation would).

  Positioning fixes:
  - Contact's glow was only centered on the X axis (`left:50%` + a matching
    translateX) with a `top:15%` guess for vertical placement — it visibly
    drifted off-center vertically depending on how tall the section's
    content was. Now centered on BOTH axes with `top:50%; left:50%;
    transform: translate(-50%,-50%)`, so it's always dead-center in the
    section regardless of content height or viewport size.
  - Footer's glow was anchored with `right:8%`, offsetting it toward the
    right edge instead of the center. Switched to `left:50%` + a matching
    translateX so it's horizontally centered while staying near the
    bottom via the existing `bottom` offset.

  Sizing stays aspect-ratio + clamp() (see prior note: fixed width+height
  with only a max-width safety net let the "circle" quietly become a tall
  oval on narrow viewports since nothing constrained height — aspect-ratio
  fixes that structurally instead of patching it with a max-height guess).
----------------------------------------------------------------------- */
#contact::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(360px, 60vw, 720px);
  aspect-ratio: 1 / 1;
  background: radial-gradient(circle, rgba(var(--color-primary-rgb), 0.24), transparent 70%);
  filter: blur(14px);
  pointer-events: none;
  z-index: 0;
  animation: glowPulseCentered 6s ease-in-out infinite;
}

footer::before {
  content: '';
  position: absolute;
  bottom: -15%;
  left: 50%;
  width: clamp(260px, 45vw, 520px);
  aspect-ratio: 1 / 1;
  background: radial-gradient(circle, rgba(var(--color-secondary-rgb), 0.24), transparent 70%);
  filter: blur(14px);
  pointer-events: none;
  z-index: 0;
  animation: glowPulseBottom 7s ease-in-out infinite;
  animation-delay: -2s;
  /* desyncs it from the Contact glow's pulse so the two don't breathe in lockstep */
}

/* Transform is baked into every keyframe step (not just the base rule)
  because the centering translate and the breathing scale both need to
  apply simultaneously — a keyframe `transform` always REPLACES the
  computed value rather than composing with the static rule's transform,
  so omitting the translate from these steps would snap the glow off-center
  the instant the animation starts. */
@keyframes glowPulseCentered {

  0%,
  100% {
    opacity: 0.65;
    transform: translate(-50%, -50%) scale(1);
  }

  50% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.15);
  }
}

@keyframes glowPulseBottom {

  0%,
  100% {
    opacity: 0.6;
    transform: translateX(-50%) scale(1);
  }

  50% {
    opacity: 1;
    transform: translateX(-50%) scale(1.18);
  }
}

@media (prefers-reduced-motion: reduce) {

  #contact::before,
  footer::before {
    animation: none;
  }
}
