/* ==========================================================================
   ANIMATIONS — subtle, CSS-driven, performance-safe
   Absolute Cosmetic Clinic
   --------------------------------------------------------------------------
   Rules this file follows:
   - Only `transform` and `opacity` are animated (compositor-only, no reflow)
   - Nothing above the fold animates in. The H1 is the LCP element; delaying
     it to fade it in would directly worsen Core Web Vitals
   - Every animation is disabled under prefers-reduced-motion
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. SCROLL REVEAL
   Elements marked [data-reveal] start hidden and are revealed by
   animations.js adding .is-visible via IntersectionObserver.

   If JS fails or is disabled, the .no-js fallback at the bottom of this file
   keeps everything visible — content is never dependent on script.
   -------------------------------------------------------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
  will-change: opacity, transform;
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  /* Released once the animation is done so the layer isn't promoted forever */
  will-change: auto;
}

/* Directional variants.

   The horizontal ones are NOT applied here. Below the large-desktop
   breakpoint every layout is stacked, so a sideways slide is meaningless —
   and more importantly a translateX on a full-width element starts it past
   the viewport edge, which reports as phantom horizontal overflow
   (html.scrollWidth > clientWidth) until the reveal completes.

   They are enabled in responsive.css at >=1280px, where the container is
   locked to 1200px and there is guaranteed slack on both sides.
   Until then, left/right simply use the default vertical rise. */
[data-reveal="fade"] { transform: none; }

/* Stagger — set as an inline custom property by animations.js so siblings
   cascade in rather than appearing all at once. Capped at 6 to stop long
   lists from feeling sluggish. */
[data-reveal] {
  transition-delay: calc(var(--reveal-delay, 0) * 60ms);
}

/* --------------------------------------------------------------------------
   2. KEYFRAMES
   -------------------------------------------------------------------------- */

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes slide-down {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes slide-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---- Hero entrance -------------------------------------------------------
   Deliberately NOT applied to the video/poster layer: the poster is the LCP
   element, and fading it in would push LCP back. Only a transform runs there,
   which the compositor handles and which LCP timing ignores. */

@keyframes hero-drift {
  from { transform: scale(1.09); }
  to   { transform: scale(1); }
}

@keyframes hero-rise {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: none; }
}

@keyframes hero-pill {
  from { opacity: 0; transform: translateY(-18px); }
  to   { opacity: 1; transform: none; }
}


/* The pill drops in first, then the copy cascades. Total run ~1.1s. */
.header__bar {
  animation: hero-pill 700ms var(--ease-out) 80ms both;
}

[data-hero-in] {
  animation: hero-rise 850ms var(--ease-out) both;
}

[data-hero-in="1"] { animation-delay: 180ms; }
[data-hero-in="2"] { animation-delay: 280ms; }
[data-hero-in="3"] { animation-delay: 420ms; }
[data-hero-in="4"] { animation-delay: 540ms; }
[data-hero-in="5"] { animation-delay: 640ms; }
[data-hero-in="6"] { animation-delay: 700ms; }

/* The trust-strip figures land one after another, closing the sequence.
   The <ul> itself is not animated — only its items — so the two do not
   compound into a double fade. */
.trust-strip__item {
  animation: hero-rise 700ms var(--ease-out) both;
}

.trust-strip__item:nth-child(1) { animation-delay: 760ms; }
.trust-strip__item:nth-child(2) { animation-delay: 830ms; }
.trust-strip__item:nth-child(3) { animation-delay: 900ms; }
.trust-strip__item:nth-child(4) { animation-delay: 970ms; }


/* A leaf drifting down: sway left/right while rotating, fading in at the top
   and out at the bottom so it never pops. */
/* Distances are in cqh (container query height) - a percentage in
   translateY() resolves against the ELEMENT's own height, not its container,
   so a 25px leaf would only ever travel 25px. cqh is a share of the leaf
   field's height, which is what "fall down the section" actually means. */
@keyframes leaf-fall {
  0%   { transform: translate3d(0, -60px, 0) rotate(0deg);                opacity: 0; }
  12%  {                                                                   opacity: var(--leaf-opacity, .35); }
  28%  { transform: translate3d(22px, 22cqh, 0) rotate(72deg); }
  50%  { transform: translate3d(-16px, 46cqh, 0) rotate(158deg); }
  74%  { transform: translate3d(18px, 72cqh, 0) rotate(244deg); }
  88%  {                                                                   opacity: var(--leaf-opacity, .35); }
  100% { transform: translate3d(-10px, calc(100cqh + 60px), 0) rotate(330deg); opacity: 0; }
}

/* Very slow, very small drift on the decorative leaves. Purely atmospheric. */
@keyframes leaf-drift {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-10px) rotate(1.5deg); }
}

@keyframes runline {
  to {
    --runline-angle: 360deg;
  }
}

/* --------------------------------------------------------------------------
   3. APPLIED ANIMATIONS
   -------------------------------------------------------------------------- */

.mobile-nav.is-open {
  animation: slide-down var(--dur-slow) var(--ease-out);
}

.about__leaf {
  animation: leaf-drift 12s var(--ease-soft) infinite;
}

.form__status {
  animation: slide-up var(--dur-slow) var(--ease-out);
}

/* --------------------------------------------------------------------------
   RUNNING LINE — a light that travels once around the button's edge.
   Applied to the booking and WhatsApp CTAs only, so it reads as emphasis on
   the two actions that matter rather than as general decoration.

   Built as a conic gradient rotated by an angle custom property and masked
   down to a 2px ring, so it paints ONLY the border and never touches the
   button's own background. That matters for the hero's WhatsApp button, which
   is deliberately translucent over the video — an opaque overlay would have
   flattened it.

   --runline-angle is registered with @property so the browser interpolates it
   as an angle. Without that registration a custom property animates
   discretely, and the highlight would jump once per cycle instead of
   travelling. Where @property is unsupported that jump is the fallback: still
   legible, just not smooth.
   -------------------------------------------------------------------------- */

@property --runline-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.btn--runline {
  position: relative;
}

.btn--runline::before {
  content: "";
  position: absolute;
  /* -1px so the ring sits on the button's outer edge: an absolutely
     positioned child is laid out against the PADDING box, which excludes
     .btn's 1px border. */
  inset: -1px;
  border-radius: inherit;
  padding: 2px;
  background: conic-gradient(from var(--runline-angle),
    transparent 0turn,
    transparent 0.58turn,
    var(--runline-tail) 0.80turn,
    var(--runline-core) 0.90turn,
    var(--runline-tail) 0.94turn,
    transparent 0.99turn,
    transparent 1turn);
  /* Two mask layers, the inner one subtracted, leaves just the ring. */
  -webkit-mask: linear-gradient(#000 0 0) content-box,
                linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box,
                linear-gradient(#000 0 0);
          mask-composite: exclude;
  animation: runline 3.2s linear infinite;
  /* Decorative, and it must never swallow a click on the CTA itself. */
  pointer-events: none;
}

/* On the solid green button the line reads as a highlight; on the white and
   translucent ones it has to carry the brand colour instead. */
.btn--runline.btn--primary {
  --runline-core: rgba(255, 255, 255, 0.95);
  --runline-tail: rgba(255, 255, 255, 0.25);
}

.btn--runline.btn--secondary {
  --runline-core: var(--color-primary);
  --runline-tail: rgba(21, 122, 60, 0.22);
}

.hero .btn--runline.btn--secondary {
  --runline-core: rgba(255, 255, 255, 0.95);
  --runline-tail: rgba(255, 255, 255, 0.28);
}

/* --------------------------------------------------------------------------
   4. HOVER / INTERACTION REFINEMENTS
   Card and button hovers live with their components. These are the shared
   micro-interactions.
   -------------------------------------------------------------------------- */

.category-card__media img,
.treatment-card__media img,
.location-card__media img {
  transition: transform var(--dur-slow) var(--ease-out);
}

.category-card:hover .category-card__media img,
.treatment-card:hover .treatment-card__media img,
.location-card:hover .location-card__media img {
  transform: scale(1.04);
}

.category-card__thumb,
.treatment-card__media {
  overflow: hidden;
  border-radius: var(--radius-md);
}

.location-card__media {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   5. REDUCED MOTION
   Belt and braces: reset.css already neutralises durations globally, but
   this removes the starting transforms too, so reveal content is simply
   present rather than transitioning from an offset.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   4b. WORK IN PROGRESS PAGE
   The draft sheet writes itself: each row's fill sweeps from the left, one
   after the next, then the sheet clears and starts again. It is a looping,
   decorative animation, so it is removed outright under reduced motion
   rather than paused - see section 5.
   -------------------------------------------------------------------------- */

@keyframes wip-write {
  0%,   8%  { transform: scaleX(0);   }
  38%, 78%  { transform: scaleX(1);   }
  96%, 100% { transform: scaleX(0);   }
}

@keyframes wip-pulse {
  0%, 100% { transform: scale(1);   opacity: 1;   }
  50%      { transform: scale(1.5); opacity: 0.4; }
}

.wip-sheet__fill {
  animation: wip-write 6s var(--ease-soft) infinite;
}

/* Staggered so the rows fill in reading order. The delays are a fraction of
   the 6s cycle, not round numbers, so the sheet never fills all at once. */
.wip-sheet__row:nth-child(2) .wip-sheet__fill { animation-delay: 0s;    }
.wip-sheet__row:nth-child(3) .wip-sheet__fill { animation-delay: 0.45s; }
.wip-sheet__row:nth-child(4) .wip-sheet__fill { animation-delay: 0.9s;  }
.wip-sheet__row:nth-child(5) .wip-sheet__fill { animation-delay: 1.35s; }

.wip__dot {
  animation: wip-pulse 2s var(--ease-soft) infinite;
}


/* --------------------------------------------------------------------------
   4c. FLOATING WHATSAPP BUTTON
   A slow outward ring. It draws the eye once without the button jumping
   around, and it is purely decorative - removed entirely in section 5.
   -------------------------------------------------------------------------- */

@keyframes wa-ring {
  0%        { transform: scale(1);    opacity: 0.45; }
  70%, 100% { transform: scale(1.65); opacity: 0;    }
}

.wa-float__ring {
  animation: wa-ring 2.6s var(--ease-soft) infinite;
}


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

  [data-reveal],
  [data-reveal].is-visible {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    will-change: auto;
  }

  .about__leaf,
  .mobile-nav.is-open,
  .form__status,
  .hero__video,
  .wip__dot {
    animation: none !important;
  }

  /* The ring only exists to pulse, so it is hidden rather than frozen -
     a stopped ring is just a smudge around the button. */
  .wa-float__ring {
    animation: none !important;
    display: none;
  }

  .wa-float:hover,
  .wa-float:focus-visible {
    transform: none;
  }

  /* The draft sheet loops forever, so it is exactly what this query is for.
     The fill is left at full width rather than zero: an empty sheet would
     read as a page that failed to load. */
  .wip-sheet__fill {
    animation: none !important;
    transform: scaleX(1) !important;
  }

  /* The running line is continuous, so it is exactly the kind of motion this
     query exists for. Removed outright rather than stopped mid-sweep, which
     would leave a stray arc on the button edge. */
  .btn--runline::before {
    animation: none !important;
    display: none;
  }

  /* The leaf field is motion for its own sake — remove it entirely rather
     than leaving eight static leaves scattered over the copy. */
  .leaf-fall {
    display: none !important;
  }

  /* The hero entrance is skipped entirely — content is simply present. */
  .header__bar,
  [data-hero-in],
  .trust-strip__item {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  .category-card:hover .category-card__media img,
  .treatment-card:hover .treatment-card__media img,
  .location-card:hover .location-card__media img {
    transform: none;
  }

  .category-card:hover,
  .treatment-card:hover,
  .step:hover,
  .btn:hover,
  .socials a:hover {
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   6. NO-JS FALLBACK
   The <html> element carries class="no-js"; main.js removes it immediately.
   If the script never runs, reveal elements stay visible.
   -------------------------------------------------------------------------- */

.no-js [data-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
}

/* With scripting off the video is never fetched, so the poster stands in.
   The entrance animation is pure CSS and still runs. */
.no-js .hero__video {
  animation: none;
}

/* --------------------------------------------------------------------------
   7. PRINT
   -------------------------------------------------------------------------- */

@media print {

  .header,
  .mobile-nav,
  .mobile-cta,
  .nav-toggle,
  .scroller__controls,
  .decor,
  .about__leaf,
  .cta-band__leaf,
  .hero__stage,
  .leaf-fall,
  .skip-link {
    display: none !important;
  }

  /* Hero copy is white-on-video on screen; make it readable on paper */
  .hero {
    min-height: 0 !important;
  }

  .hero__content,
  .hero .hero-headline,
  .hero .hero-subheadline {
    color: #000000 !important;
    text-shadow: none !important;
  }

  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Accordion panels open so the FAQ prints in full */
  .accordion__panel {
    grid-template-rows: 1fr !important;
  }

  body {
    background: #FFFFFF;
    color: #000000;
    font-size: 11pt;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: #555555;
  }
}
