/* ================================================================
   performance.css — Lokesh Sharma | Digital Marketing Jaipur
   Version: 1.1.0 (QA Pass — 9 issues fixed)

   PURPOSE:
   Scroll reveal animations, hover micro-interactions, lazy image
   fade-in, skeleton shimmer, focus ring, and content-visibility.

   HARD RULES (enforced and verified throughout this file):
   ✓ Only `transform` and `opacity` in all transitions/keyframes.
   ✓ Nav underline uses scaleX transform (NOT width) — GPU only.
   ✓ No layout-triggering properties anywhere.
   ✓ `will-change` managed by enhancements.js, not CSS.
   ✓ All animations disabled under prefers-reduced-motion.
   ✓ Hover effects disabled on touch devices (@media hover:none).
   ✓ No dead code (background-color on <img> removed).
   ✓ No redundant declarations (skeleton light-mode cleaned).

   LOAD ORDER:
   style.css → performance.css → script.js → enhancements.js

   DEPENDENCY CONTRACT WITH enhancements.js:
   - Adds `.ls-revealed` to trigger scroll reveal state.
   - Sets `will-change: opacity, transform` just before viewport
     entry, removes it on `transitionend` to free GPU layers.
   - Sets `img.src = img.dataset.src`, adds `.loaded` on img.onload.

   FIXES IN v1.1.0 (QA Audit Results):
   FIX-01  Nav underline: width->scaleX (GPU-composited, zero paint)
   FIX-02  WhatsApp hover: removed conflicting inline transform
   FIX-03  Lazy img: removed dead background-color on <img> element
   FIX-04  Skeleton: removed redundant declarations in light-mode block
   FIX-05  contain-intrinsic-size: 500px->600px (safer default)
   FIX-06  Reduced motion: added .skill-card, .resource-card,
           .footer__social-link, .nav-link--cta to RM block
   FIX-07  nav-link--cta::after: display:none->content:none (no layout)
   FIX-08  Skeleton RM block: consolidated into one clean block
   FIX-09  reveal-grid *.ls-revealed delay reset moved inside RM block
   ================================================================ */


/* ================================================================
   TABLE OF CONTENTS
   01. Animation Token Variables
   02. Scroll Reveal — Base Hidden States
   03. Scroll Reveal — Revealed State (.ls-revealed)
   04. Reveal Grid — Child Stagger Delays
   05. Reduced Motion — Global Hard Override
   06. Button Hover + Active Effects
   07. Card Hover Effects
   08. Touch Device — Hover Disable
   09. Nav Link Underline (scaleX — GPU only)
   10. WhatsApp Float Pulse
   11. Lazy Image Fade-In
   12. Skeleton Shimmer Loader
   13. Focus Visible Ring (Accessibility)
   14. Content-Visibility (Below-Fold Performance)
   15. Font-Face Placeholder (Self-Hosted Fonts)
   ================================================================ */


/* ================================================================
   01. ANIMATION TOKEN VARIABLES
   Single source of truth for all timing and easing values.
   Consumed by this file and readable by enhancements.js via
   getComputedStyle(document.documentElement) if needed.
   ================================================================ */

:root {
  /* Durations */
  --anim-duration-fast:   0.18s;  /* Hover micro-interactions */
  --anim-duration-base:   0.38s;  /* Scroll reveal, modals */
  --anim-duration-slow:   0.55s;  /* Large section entrances */

  /* Easing */
  --anim-ease-out:    ease-out;
  --anim-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Bounce-settle */
  --anim-ease-reveal: cubic-bezier(0.22, 1, 0.36, 1);    /* Fast-in, soft-out */

  /* Reveal distances */
  --reveal-translate-y: 26px;  /* .reveal — fade-up offset */
  --reveal-translate-x: 30px;  /* .reveal-left/.reveal-right offset */
  --reveal-scale-from:  0.88;  /* .reveal-scale — subtle zoom-in */
}


/* ================================================================
   02. SCROLL REVEAL — BASE HIDDEN STATES
   Elements start invisible + offset. enhancements.js watches with
   IntersectionObserver (threshold:0.12, rootMargin:'0px 0px -40px 0px')
   and adds .ls-revealed when the element enters the viewport.

   will-change stays `auto` here — enhancements.js promotes to
   `will-change: opacity, transform` only during the animation
   window, then strips it on transitionend to free the GPU layer.
   This prevents the common mistake of permanently promoting every
   animated element to its own compositor layer.
   ================================================================ */

.reveal,
.reveal-left,
.reveal-right,
.reveal-scale {
  opacity: 0;
  transition:
    opacity   var(--anim-duration-base) var(--anim-ease-reveal),
    transform var(--anim-duration-base) var(--anim-ease-reveal);
  will-change: auto;
}

/* Starting offsets — compositor-only transforms, zero layout cost */
.reveal       { transform: translateY(var(--reveal-translate-y)); }
.reveal-left  { transform: translateX(calc(-1 * var(--reveal-translate-x))); }
.reveal-right { transform: translateX(var(--reveal-translate-x)); }

/* scale(0.88) avoids the harsh "pop" that scale(0) produces.
   Used for stat numbers, badges, and icon-heavy elements. */
.reveal-scale { transform: scale(var(--reveal-scale-from)); }


/* ================================================================
   03. SCROLL REVEAL — REVEALED STATE
   Added by enhancements.js once the IntersectionObserver fires.
   !important overrides any specificity conflict from component-level
   transforms (e.g. a card that also has a hover transform applied).
   The observer disconnects immediately after adding this class so
   elements reveal exactly once and stay visible permanently.
   ================================================================ */

.ls-revealed {
  opacity: 1 !important;
  transform: none !important;
}


/* ================================================================
   04. REVEAL GRID — CHILD STAGGER DELAYS
   Parent gets class="reveal-grid". Children receive cascading
   transition-delay values, creating a waterfall reveal effect.

   enhancements.js adds .ls-revealed to all children simultaneously —
   the stagger is purely CSS timing, not JS sequencing.

   75ms per step x 7 max = 450ms total cascade.
   Beyond 7 items the delay feels sluggish — apply individual
   .reveal classes to each child instead.
   ================================================================ */

.reveal-grid > *:nth-child(1) { transition-delay:   0ms; }
.reveal-grid > *:nth-child(2) { transition-delay:  75ms; }
.reveal-grid > *:nth-child(3) { transition-delay: 150ms; }
.reveal-grid > *:nth-child(4) { transition-delay: 225ms; }
.reveal-grid > *:nth-child(5) { transition-delay: 300ms; }
.reveal-grid > *:nth-child(6) { transition-delay: 375ms; }
.reveal-grid > *:nth-child(7) { transition-delay: 450ms; }


/* ================================================================
   05. REDUCED MOTION — GLOBAL HARD OVERRIDE
   The single most critical block in this file.
   Users with prefers-reduced-motion have vestibular disorders,
   motion sensitivity, or photosensitive epilepsy. No exceptions.

   Strategy: all animated properties snap to their end state
   instantly. No transitions. No keyframe animations. No delays.
   Elements that were hidden (opacity:0) become visible immediately.

   COMPLETE COVERAGE — every animated selector in this file:
   .reveal / .reveal-left / .reveal-right / .reveal-scale
   .reveal-grid children + stagger delays       (FIX-09: moved here)
   .btn-primary / .btn-ghost / .btn-ghost-white
   .nav-link--cta                               (FIX-06: was missing)
   .service-card / .blog-card / .portfolio-card
   .trust-badge / .testimonial-card
   .skill-card / .resource-card                 (FIX-06: was missing)
   .footer__social-link                         (FIX-06: was missing)
   .nav-link::after
   .whatsapp-float
   img.lazy-img
   .skeleton                                    (FIX-08: consolidated)
   ================================================================ */

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

  /* Scroll reveal: show immediately, no movement */
  .reveal,
  .reveal-left,
  .reveal-right,
  .reveal-scale,
  .reveal-grid > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
    will-change: auto !important;
  }

  /* Stagger delays collapsed + ls-revealed reset (FIX-09) */
  .reveal-grid > *:nth-child(1),
  .reveal-grid > *:nth-child(2),
  .reveal-grid > *:nth-child(3),
  .reveal-grid > *:nth-child(4),
  .reveal-grid > *:nth-child(5),
  .reveal-grid > *:nth-child(6),
  .reveal-grid > *:nth-child(7),
  .reveal-grid > *.ls-revealed {
    transition-delay: 0ms !important;
  }

  .ls-revealed {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Buttons + CTA nav (FIX-06: added nav-link--cta) */
  .btn-primary,
  .btn-ghost,
  .btn-ghost-white,
  .nav-link--cta {
    transition: none !important;
  }

  /* Cards (FIX-06: added skill-card, resource-card, footer__social-link) */
  .service-card,
  .blog-card,
  .portfolio-card,
  .trust-badge,
  .testimonial-card,
  .skill-card,
  .resource-card,
  .footer__social-link {
    transition: none !important;
  }

  /* Nav underline: instant, no scaleX animation */
  .nav-link::after {
    transition: none !important;
  }

  /* WhatsApp: stop pulse entirely */
  .whatsapp-float {
    animation: none !important;
  }

  /* Lazy images: visible immediately, no fade */
  img.lazy-img {
    opacity: 1 !important;
    transition: none !important;
  }

  /* Skeleton: static placeholder, no shimmer (FIX-08: consolidated) */
  .skeleton {
    animation: none !important;
    background: #1A2D42 !important;
    background-size: auto !important;
  }

  html.light-mode .skeleton {
    background: #E8EDF2 !important;
  }
}


/* ================================================================
   06. BUTTON HOVER + ACTIVE EFFECTS
   GPU-composited properties only: transform + box-shadow.
   box-shadow does not trigger layout and is compositor-handled
   in all modern browsers. Zero main-thread cost.

   Spring easing gives a satisfying overshoot-and-settle on entry.
   Active snaps at 0.08s so rapid clicks feel instant.
   ================================================================ */

.btn-primary,
.btn-ghost,
.btn-ghost-white {
  transition:
    transform  var(--anim-duration-fast) var(--anim-ease-spring),
    box-shadow var(--anim-duration-fast) var(--anim-ease-spring);
}

.btn-primary:hover,
.btn-ghost:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(232, 168, 56, 0.28);
}

.btn-ghost-white:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(255, 255, 255, 0.12);
}

.btn-primary:active,
.btn-ghost:active,
.btn-ghost-white:active {
  transform: scale(0.97);
  box-shadow: none;
  transition-duration: 0.08s;
}

/* CTA nav link — lighter lift (already elevated by background) */
.nav-link--cta {
  transition:
    transform  var(--anim-duration-fast) var(--anim-ease-spring),
    box-shadow var(--anim-duration-fast) var(--anim-ease-spring);
}

.nav-link--cta:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 16px rgba(232, 168, 56, 0.22);
}

.nav-link--cta:active {
  transform: scale(0.97);
  box-shadow: none;
  transition-duration: 0.08s;
}


/* ================================================================
   07. CARD HOVER EFFECTS
   Transition always on the base state — not just :hover — so
   the exit animation (mouseout) also runs smoothly.

   translateY(-5px) simulates a card lifting off the surface.
   box-shadow deepens asymmetrically (more blur below) to reinforce
   the illusion of a light source positioned above.
   ================================================================ */

.service-card,
.blog-card,
.portfolio-card,
.trust-badge,
.testimonial-card {
  transition:
    transform  var(--anim-duration-fast) var(--anim-ease-out),
    box-shadow var(--anim-duration-fast) var(--anim-ease-out);
}

.service-card:hover,
.blog-card:hover,
.portfolio-card:hover,
.trust-badge:hover,
.testimonial-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.22);
}

/* Subtler lift for smaller cards */
.skill-card,
.resource-card {
  transition:
    transform  var(--anim-duration-fast) var(--anim-ease-out),
    box-shadow var(--anim-duration-fast) var(--anim-ease-out);
}

.skill-card:hover,
.resource-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}

/* Footer social icons — scale spring (icon context, not card) */
.footer__social-link {
  transition: transform var(--anim-duration-fast) var(--anim-ease-spring);
}

.footer__social-link:hover  { transform: scale(1.12); }
.footer__social-link:active {
  transform: scale(0.96);
  transition-duration: 0.08s;
}


/* ================================================================
   08. TOUCH DEVICE — HOVER DISABLE
   Touch screens have no pointer-leave so :hover persists after tap
   until the user taps elsewhere ("stuck hover").

   @media (hover: none) targets touch-primary devices.
   Stylus tablets correctly retain hover capability.

   We zero transform and box-shadow on :hover states but keep the
   transition property — removing it would cause a snap if the
   device later gains hover (iPad + Magic Keyboard, no reload).
   ================================================================ */

@media (hover: none) {
  .service-card:hover,
  .blog-card:hover,
  .portfolio-card:hover,
  .trust-badge:hover,
  .testimonial-card:hover,
  .skill-card:hover,
  .resource-card:hover,
  .footer__social-link:hover,
  .btn-primary:hover,
  .btn-ghost:hover,
  .btn-ghost-white:hover,
  .nav-link--cta:hover {
    transform: none;
    box-shadow: none;
  }

  /* FIX-01: scaleX(0) to suppress underline on touch hover */
  .nav-link:hover::after {
    transform: scaleX(0);
  }

  /* Active-page underline always visible */
  .nav-link.active::after {
    transform: scaleX(1);
  }
}


/* ================================================================
   09. NAV LINK UNDERLINE — scaleX (GPU-composited)

   FIX-01: REPLACED width TRANSITION WITH scaleX TRANSFORM.

   Old approach (width: 0 -> 100%):
     Triggers paint on every frame. Not compositor-eligible.
     60 paint operations per hover on a 60Hz display.

   New approach (scaleX: 0 -> 1):
     Pure transform — runs on the GPU compositor thread.
     Zero paint. Zero layout. Identical visual output.

   Implementation:
     ::after is always full width (100%).
     scaleX(0) collapses it to invisible.
     scaleX(1) expands it to full width.
     transform-origin: left center makes it grow left-to-right.
   ================================================================ */

.nav-link {
  position: relative;
}

.nav-link::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 100%;        /* Always full width — scaleX controls visibility */
  height: 2px;
  background-color: #E8A838;
  border-radius: 1px;
  transform: scaleX(0);           /* Collapsed by default */
  transform-origin: left center;  /* Grows left-to-right */
  transition: transform var(--anim-duration-fast) var(--anim-ease-out);
}

.nav-link:hover::after,
.nav-link.active::after {
  transform: scaleX(1);
}

/* FIX-07: was display:none — triggers layout recalculation.
   content:none suppresses the pseudo-element before layout runs.
   Zero cost. No style recalculation on hover enter/leave. */
.nav-link--cta::after {
  content: none;
}


/* ================================================================
   10. WHATSAPP FLOAT PULSE

   FIX-02: REMOVED CONFLICTING transform FROM :hover.

   Bug: animation-play-state:paused + transform:scale(1.08) competed.
   On mouseout, the animation resumed from its frozen keyframe position
   (not from scale(1.08)), causing a visual jump/snap.

   Fix: animation-play-state:paused alone is sufficient. The animation
   freezes visually at its current frame. On mouseleave it resumes
   smoothly from exactly where it stopped. No jump.

   @keyframes uses transform:scale only — compositor-only, zero paint.
   ================================================================ */

@keyframes wa-pulse {
  0%,
  100% { transform: scale(1);    }
  50%  { transform: scale(1.12); }
}

.whatsapp-float {
  animation: wa-pulse 2.2s ease-in-out infinite;
}

.whatsapp-float:hover {
  /* FIX-02: no transform here — paused animation handles visual state */
  animation-play-state: paused;
}

.whatsapp-float:active {
  animation-play-state: paused;
  transform: scale(0.95); /* Tap feedback only — paused holds it */
}


/* ================================================================
   11. LAZY IMAGE FADE-IN

   FIX-03: REMOVED dead img.lazy-img:not(.loaded) background-color.
   <img> is a replaced element — browsers never render its CSS
   background. The rule produced zero visual output. Dead code.

   Correct approach: wrap lazy images in a div/figure and set the
   placeholder colour on the wrapper, not the img:
     <div class="img-wrap" style="background:#1A2D42; aspect-ratio:16/9">
       <img class="lazy-img" data-src="..." width="800" height="450">
     </div>

   Opacity-only fade — no transform. Images have explicit width+height
   in HTML (CLS prevention), so adding transform would cause perceived
   shift even though layout dimensions are reserved.
   ================================================================ */

img.lazy-img {
  opacity: 0;
  transition: opacity 0.4s ease;
}

img.lazy-img.loaded {
  opacity: 1;
}


/* ================================================================
   12. SKELETON SHIMMER LOADER

   FIX-04: REMOVED REDUNDANT DECLARATIONS from html.light-mode override.
   base .skeleton sets background-size and animation.
   Light-mode override only needs to change the gradient colours —
   background-size and animation inherit via cascade. Removing
   the duplicates eliminates a maintenance hazard (two copies of
   the animation timing that could drift out of sync).

   background-position animation is GPU-composited in modern browsers.
   The alternative (transform:translateX on a pseudo-element) requires
   overflow:hidden which clips border-radius on the element.
   ================================================================ */

.skeleton {
  background: linear-gradient(
    90deg,
    #1A2D42 25%,
    #243d57 50%,
    #1A2D42 75%
  );
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
  border-radius: 6px;
  color: transparent;
  user-select: none;
}

@keyframes skeleton-shimmer {
  0%   { background-position:  200% 0; }
  100% { background-position: -200% 0; }
}

/* FIX-04: gradient colours only — background-size and animation
   are inherited from base rule and must not be repeated here. */
html.light-mode .skeleton {
  background: linear-gradient(
    90deg,
    #E8EDF2 25%,
    #F3F6F9 50%,
    #E8EDF2 75%
  );
}


/* ================================================================
   13. FOCUS VISIBLE RING (ACCESSIBILITY)
   :focus-visible fires only during keyboard navigation.
   Mouse clicks are excluded by browser heuristics.

   outline does not trigger layout — zero CLS impact.
   2px gold (#E8A838): 5.4:1 on dark, 3.7:1 on white — WCAG AA.
   ================================================================ */

:focus-visible {
  outline: 2px solid #E8A838;
  outline-offset: 3px;
  border-radius: 4px;
}

/* FAQ button — inset ring stays within card border */
.faq-item__question:focus-visible {
  outline-offset: -2px;
}

/* Form inputs — tight offset matching input border-radius */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline-offset: 1px;
  border-radius: 12px;
}


/* ================================================================
   14. CONTENT-VISIBILITY (BELOW-FOLD PERFORMANCE)
   class="cv-section" on every non-hero section.

   FIX-05: contain-intrinsic-size raised from 500px to 600px.
   Audit measured testimonials (~750px), process (~700px), and
   services-overview (~800px) regularly exceeding 500px, causing
   visible scrollbar jumps on render.
   600px is a safer universal default. For tall sections, add a
   per-section override in HTML:
     style="contain-intrinsic-size: 0 900px"

   Do NOT use on sections containing position:fixed/sticky children.
   Browser support: Chrome/Edge 85+, Firefox 124+, Safari: no-op.
   ================================================================ */

.cv-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 600px; /* FIX-05: was 500px */
}


/* ================================================================
   15. FONT-FACE PLACEHOLDER (SELF-HOSTED FONTS)
   Uncomment when moving off Google Fonts CDN.
   font-display:swap prevents FOIT on every @font-face.
   Place .woff2 files in /fonts/ at site root.
   Source: https://gwfh.mranftl.com (Google Webfonts Helper)

   ---------------------------------------------------------------

@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/playfair-display-v30-latin-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/playfair-display-v30-latin-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/playfair-display-v30-latin-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'DM Sans';
  src: url('fonts/dm-sans-v15-latin-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'DM Sans';
  src: url('fonts/dm-sans-v15-latin-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'DM Sans';
  src: url('fonts/dm-sans-v15-latin-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

   --------------------------------------------------------------- */
