/**
 * PHARAON MOTION DESIGN SYSTEM
 * ------------------------------------------------------------------
 * Phase: PHARAON-MOTION-03
 *
 * Tokens and primitives. The JavaScript core only toggles one class per
 * element - every transition below is CSS, so reveals run on the
 * compositor and cost nothing on the main thread.
 *
 * NO-JS SAFETY (XXXI)
 * Every hidden state is scoped under `html.ph-motion`, a class the motion
 * core adds only once it has successfully initialised. If the script is
 * blocked, errors, or never runs, none of these rules match and the page
 * renders fully visible. Content is never hidden waiting for JavaScript
 * that might not arrive.
 *
 * MOTION CHARACTER (LXXIII)
 * Precise, controlled, architectural. Short travel, calm duration, a
 * decelerating curve that settles rather than bounces. Nothing flies in
 * from 100px, nothing overshoots, nothing repeats for attention.
 */

/* ==================================================================
   IX. MOTION TOKENS
   ==================================================================
   Tuned against the existing homepage layer (PHARAON-HOME-MOTION-01),
   which already read as the right character at ~80ms stagger and small
   distances - these generalise that instead of inventing a new feel. */

:root {
  /* Duration. `base` is the section reveal; `fast` is interaction
     feedback, which must always beat a reveal (XXV). */
  --ph-dur-fast: 220ms;
  --ph-dur-base: 620ms;
  --ph-dur-slow: 900ms;

  /* Distance. Deliberately small: premium motion settles, it does not
     travel (IV, X.1 "không bay từ 100px"). */
  --ph-dist-xs: 8px;
  --ph-dist-sm: 16px;
  --ph-dist-md: 28px;

  /* Stagger. Capped hard in the core so a long grid cannot make the last
     card wait (X.3). */
  --ph-stagger-tight: 50ms;
  --ph-stagger-normal: 80ms;
  --ph-stagger-wide: 120ms;

  /* Easing. `premium` is an expo-out: fast commitment, long quiet
     settle - the architectural feel. `standard` for smaller moves,
     `emphasis` for state changes that should feel deliberate. */
  --ph-ease-standard: cubic-bezier(0.33, 0.68, 0.32, 1);
  --ph-ease-premium: cubic-bezier(0.16, 1, 0.30, 1);
  --ph-ease-emphasis: cubic-bezier(0.65, 0, 0.35, 1);

  /* Scale ceilings, so no component invents its own zoom (XXVI). */
  --ph-scale-settle: 1.04;
  --ph-scale-hover: 1.04;
}

/* ==================================================================
   X. MOTION PRIMITIVES
   ==================================================================
   Hidden state first (armed only under html.ph-motion), revealed state
   second. Every primitive animates transform + opacity only (XL). */

html.ph-motion .ph-reveal,
html.ph-motion .ph-reveal-soft,
html.ph-motion .ph-image-reveal,
html.ph-motion .ph-scale-settle,
html.ph-motion .ph-stagger > * {
  will-change: transform, opacity;
}

/* --- 10.1 ph-reveal : paragraphs, supporting content, normal sections */
html.ph-motion .ph-reveal {
  opacity: 0;
  transform: translate3d(0, var(--ph-dist-md), 0);
  transition:
    opacity var(--ph-dur-base) var(--ph-ease-premium),
    transform var(--ph-dur-base) var(--ph-ease-premium);
}

/* --- 10.2 ph-reveal-soft : certificates, logos, metadata, small UI */
html.ph-motion .ph-reveal-soft {
  opacity: 0;
  transform: translate3d(0, var(--ph-dist-xs), 0);
  transition:
    opacity var(--ph-dur-base) var(--ph-ease-standard),
    transform var(--ph-dur-base) var(--ph-ease-standard);
}

/* --- 10.3 ph-stagger : card / product / project / team groups.
   The per-item delay is set inline by the core, which resets it per row
   so a 9-item grid never queues 9 deep. */
html.ph-motion .ph-stagger > * {
  opacity: 0;
  transform: translate3d(0, var(--ph-dist-sm), 0);
  transition:
    opacity var(--ph-dur-base) var(--ph-ease-premium),
    transform var(--ph-dur-base) var(--ph-ease-premium);
}

/* --- 10.4 ph-line-reveal : hero statements and major editorial headings.
   Line level, never character level (XX). The core wraps each line in
   .ph-line > .ph-line__in; the mask is the outer element's overflow. */
html.ph-motion .ph-line {
  display: block;
  overflow: hidden;
}
html.ph-motion .ph-line__in {
  display: block;
  opacity: 0;
  transform: translate3d(0, 100%, 0);
  transition:
    opacity var(--ph-dur-slow) var(--ph-ease-premium),
    transform var(--ph-dur-slow) var(--ph-ease-premium);
}

/* Flattened lines. The core adds this when the block-level wrapping would
   have changed the heading's height; the spans stay in the DOM but return
   to inline flow, so the rendered layout is byte-identical to the original
   markup and the element reveals through .ph-reveal instead. */
html.ph-motion .ph-line-flat .ph-line {
  display: inline;
  overflow: visible;
}
html.ph-motion .ph-line-flat .ph-line__in {
  display: inline;
  opacity: 1;
  transform: none;
  transition: none;
}

/* --- 10.5 ph-image-reveal : key product / project / factory imagery.
   clip-path on the wrapper, so the image's own hover transform stays
   untouched (XXIII - one property, one owner). */
html.ph-motion .ph-image-reveal {
  clip-path: inset(0 0 100% 0);
  transition: clip-path var(--ph-dur-slow) var(--ph-ease-premium);
}

/* --- 10.7 ph-scale-settle : hero and major visual entries only. */
html.ph-motion .ph-scale-settle {
  opacity: 0;
  transform: scale(var(--ph-scale-settle));
  transition:
    opacity var(--ph-dur-slow) var(--ph-ease-premium),
    transform var(--ph-dur-slow) var(--ph-ease-premium);
}

/* --- Revealed state. One class, set by the shared IntersectionObserver.
   `.ph-group.ph-in` resolves descendants too, which is what makes the
   system survive Swiper loop clones created after boot: the clone
   inherits the revealed state through CSS instead of needing its own
   observer entry. */
html.ph-motion .ph-reveal.ph-in,
html.ph-motion .ph-reveal-soft.ph-in,
html.ph-motion .ph-group.ph-in .ph-reveal,
html.ph-motion .ph-group.ph-in .ph-reveal-soft,
html.ph-motion .ph-stagger.ph-in > *,
html.ph-motion .ph-group.ph-in .ph-stagger > * {
  opacity: 1;
  transform: none;
}

html.ph-motion .ph-line-reveal.ph-in .ph-line__in,
html.ph-motion .ph-group.ph-in .ph-line__in {
  opacity: 1;
  transform: none;
}

html.ph-motion .ph-image-reveal.ph-in,
html.ph-motion .ph-group.ph-in .ph-image-reveal {
  clip-path: inset(0 0 0 0);
}

html.ph-motion .ph-scale-settle.ph-in {
  opacity: 1;
  transform: none;
}

/* Once a reveal has played it will never play again, so drop the
   compositor hint rather than leaving a permanent GPU layer (XLI). */
html.ph-motion .ph-in,
html.ph-motion .ph-group.ph-in * {
  will-change: auto;
}

/* ==================================================================
   XXXII / XLIV. EDITOR SAFETY AND THE SHARED `ph-` NAMESPACE
   ==================================================================
   The first-party section modules in wp-content/mu-plugins
   (pharaon-company-profile, pharaon-why-choose-us) already ship their own
   `ph-` classes AND their own `.ph-reveal` hidden state, driven by their
   own reveal logic. That is a genuine namespace overlap, so the boundary
   is stated explicitly:

     - This system OWNS the `.ph-reveal` / `.ph-reveal-soft` /
       `.ph-line-reveal` / `.ph-image-reveal` / `.ph-stagger` primitives
       and reveals them with `.ph-in`.
     - The motion core's adoption pass picks up any element already
       carrying one of those classes, whoever stamped it, and wires it to
       the same observer. One primitive, one owner.

   Measured on /company-profile/ before this phase: six widgets carrying
   the modules' `.ph-reveal` stayed at opacity 0 even after scrolling the
   whole page, because nothing revealed them. Adoption fixes that on the
   frontend.

   The Elementor canvas is the remaining case. There this system stays
   deliberately unarmed (`html.ph-motion` is never added), so it cannot
   reveal anything - and the modules' own hidden state would leave those
   widgets invisible to the editor. Nothing may be invisible while
   editing, so force every primitive open on the canvas regardless of
   which system hid it. */

body.elementor-editor-active .ph-reveal,
body.elementor-editor-active .ph-reveal-soft,
body.elementor-editor-active .ph-stagger > *,
body.elementor-editor-active .ph-line__in,
body.elementor-editor-active .ph-scale-settle,
body.elementor-editor-preview .ph-reveal,
body.elementor-editor-preview .ph-reveal-soft,
body.elementor-editor-preview .ph-stagger > *,
body.elementor-editor-preview .ph-line__in,
body.elementor-editor-preview .ph-scale-settle {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

body.elementor-editor-active .ph-image-reveal,
body.elementor-editor-preview .ph-image-reveal {
  clip-path: none !important;
  opacity: 1 !important;
}

/* ==================================================================
   XXIV-XXVI. MICROINTERACTION
   ==================================================================
   Interaction feedback is always faster than a section reveal, so the
   interface never feels like it is catching up with the visitor. */

.ph-hover-media {
  overflow: hidden;
}
.ph-hover-media img {
  transition: transform var(--ph-dur-fast) var(--ph-ease-standard);
}
@media (hover: hover) and (pointer: fine) {
  .ph-hover-media:hover img,
  a:hover > .ph-hover-media img {
    transform: scale(var(--ph-scale-hover));
  }
}

/* Arrow nudge on links that carry one. Transform only - no layout. */
.ph-hover-arrow i,
.ph-hover-arrow svg {
  transition: transform var(--ph-dur-fast) var(--ph-ease-standard);
}
@media (hover: hover) and (pointer: fine) {
  .ph-hover-arrow:hover i,
  .ph-hover-arrow:hover svg {
    transform: translate3d(0.25em, 0, 0);
  }
}

/* ==================================================================
   XXVIII-XXIX. RESPONSIVE INTENSITY
   ==================================================================
   Tablet trims the travel; mobile trims it further and shortens the
   duration, because a phone visitor should be reading, not waiting. */

@media (max-width: 1024px) {
  :root {
    --ph-dist-md: 20px;
    --ph-dist-sm: 12px;
    --ph-stagger-normal: 60ms;
    --ph-stagger-wide: 80ms;
  }
}

@media (max-width: 767px) {
  :root {
    --ph-dur-base: 460ms;
    --ph-dur-slow: 620ms;
    --ph-dist-md: 14px;
    --ph-dist-sm: 10px;
    --ph-dist-xs: 6px;
    --ph-stagger-tight: 35ms;
    --ph-stagger-normal: 45ms;
    --ph-stagger-wide: 55ms;
  }

  /* Masked line reveals cost a clip on every text line; on a phone the
     effect is barely legible and the cost is not. Fall back to the plain
     reveal, which the same .ph-in class already drives. */
  html.ph-motion .ph-line__in {
    transform: translate3d(0, var(--ph-dist-xs), 0);
  }
  html.ph-motion .ph-image-reveal {
    clip-path: none;
    opacity: 0;
    transition: opacity var(--ph-dur-base) var(--ph-ease-standard);
  }
  html.ph-motion .ph-image-reveal.ph-in,
  html.ph-motion .ph-group.ph-in .ph-image-reveal {
    opacity: 1;
  }
}

/* ==================================================================
   XXX. REDUCED MOTION
   ==================================================================
   Not "smaller motion" - no motion. Everything resolves to its final
   state immediately, including anything the core may already have
   armed before the query was evaluated. */

@media (prefers-reduced-motion: reduce) {
  html.ph-motion .ph-reveal,
  html.ph-motion .ph-reveal-soft,
  html.ph-motion .ph-stagger > *,
  html.ph-motion .ph-line__in,
  html.ph-motion .ph-scale-settle {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    transition-delay: 0ms !important;
  }

  html.ph-motion .ph-image-reveal {
    clip-path: none !important;
    opacity: 1 !important;
    transition: none !important;
  }

  .ph-hover-media img,
  .ph-hover-arrow i,
  .ph-hover-arrow svg {
    transition: none !important;
  }

  /* Decorative perpetual motion has no reduced-motion justification. */
  .title-icon,
  .ph-spin {
    animation: none !important;
  }
}

/* ==================================================================
   Legacy decorative spin
   ==================================================================
   `.title-icon` used to be a GSAP `repeat: -1` tween, which keeps a tween
   alive on the global timeline for the entire session and, on mobile
   where Lenis is off, holds the GSAP ticker's rAF loop open purely to
   rotate a decorative glyph. As CSS it runs on the compositor, and the
   core pauses it whenever it scrolls out of view. */
.ph-spin {
  animation: ph-spin 10s linear infinite;
  transform-origin: 50% 50%;
}
.ph-spin[data-ph-paused] {
  animation-play-state: paused;
}
@keyframes ph-spin {
  to { transform: rotate(360deg); }
}
