/*
 * animations.css — FRONT-END ONLY (not loaded in the Bricks builder)
 * ──────────────────────────────────────────────────────────────────────────
 * Every CSS rule tied to the animation system lives here:
 *   • "initial states" that hide / dim / pre-transform elements before the
 *     JavaScript reveals or animates them,
 *   • layout that only exists to support an animation (carousel, gallery
 *     stacking, injected overlays), and
 *   • the pure-CSS motion / hover effects.
 *
 * WHY IT'S SEPARATE FROM style.css:
 * functions.php enqueues this file INSIDE the builder-guarded loader
 * (theme_gsap_script()), so it loads ONLY on the live front-end — never in the
 * Bricks editor. The animation JavaScript also doesn't run in the editor, so if
 * these "start hidden" rules loaded there, animated elements (hero lines,
 * load-blur-words headings, offering images, etc.) would sit invisible/dimmed and
 * you couldn't see them while building the page. Keeping them here lets the
 * editor show everything normally while the live site still gets the full
 * animated treatment.
 *
 * Static design (typography, fonts, colours, scrollbar) lives in style.css,
 * which loads everywhere including the editor.
 */

/* ─── load-blur-words.js initial states ──────────────────────────────────── */

.load-blur-words {
    visibility: hidden;
}
.load-blur-words.animated {
    visibility: visible;
}
.load-blur-words .word {
    display: inline-block;
}

/* ─── load-fade-in.js initial state ──────────────────────────────────────── */

.load-fade-in {
    opacity: 0;
}

/* ─── Work grid (hover reveal) ───────────────────────────────────────────── */

.work-bg {
    opacity: 0.15;
    filter: grayscale(100%);
    transition: opacity 0.3s ease, filter 0.3s ease;
}

.work-box:hover + .work-bg,
.work-box:hover .work-bg {
    opacity: 0.9;
    filter: grayscale(0%);
}

/* ─── Glow button (cursor-follow radial glow on hover) ───────────────────── */

.glow-btn::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: radial-gradient(circle at var(--x, 50%) var(--y, 50%), rgba(255, 255, 255, 0.1), transparent 100%);
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
}

.glow-btn:hover::before {
    opacity: 1;
}

/* ─── Shimmer text ───────────────────────────────────────────────────────── */

.shimmer-element {
    background: linear-gradient(
        90deg,
        white 10%,
        #505153 20%,
        white 30%
    );
    background-size: 200% 100%;
    background-position: 100%;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.shimmer-element.shimmer-hover {
    animation: shimmer-once 6s ease-out forwards;
}

@keyframes shimmer-inf {
    0%   { background-position: 100% 0; }
    75%  { background-position: -100% 0; }
    100% { background-position: -100% 0; }
}

@keyframes shimmer-once {
    0%   { background-position: 70% 0; }
    25%  { background-position: -100% 0; }
    100% { background-position: -100% 0; }
}

/* ─── Scrolling box ──────────────────────────────────────────────────────── */

.scrolling-box {
    transform: translateY(20px);
    will-change: transform;
    transition: transform 0.05s linear;
}

@media (prefers-reduced-motion: reduce) {
    .scrolling-box {
        transform: none !important;
    }
}

/* ─── VSL video wrapper (animated gradient bg + matching gradient glow) ───── */

/* The animated multi-radial gradient and an OUTER GLOW that is the same gradient,
   blurred and bleeding past the wrapper's edges (::before, behind everything).
   The gradient stack is held in --vsl-grad so the wrapper and the glow stay identical.
   NOTE: the glow halo extends OUTSIDE the box, so .vsl-video-wrapper (and its ancestors)
   must NOT clip overflow — put any overflow:hidden / border-radius clipping on an INNER
   element (e.g. the video itself), or the glow gets cut off. */
.vsl-video-wrapper {
    --vsl-grad:
        radial-gradient(at 0% 100%,  hsla(34,100%,79%,1) 0px, transparent 50%),
        radial-gradient(at 100% 100%, hsla(195,80%,90%,1) 0px, transparent 50%),
        radial-gradient(at 49% 100%, hsla(322,42%,84%,1) 0px, transparent 50%),
        radial-gradient(at 19% 56%,  hsla(4,86%,71%,1)  0px, transparent 50%),
        radial-gradient(at 78% 58%,  hsla(247,38%,61%,1) 0px, transparent 50%);

    position: relative;
    background: var(--vsl-grad);
    background-size: 200% 200%;
    animation: vsl-gradient 15s linear infinite;
}

.vsl-video-wrapper::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;                 /* sit behind the wrapper's content (the video) */
    border-radius: inherit;      /* match the wrapper's rounding, if any */
    background: var(--vsl-grad);
    background-size: 200% 200%;
    animation: vsl-gradient 15s linear infinite;
    filter: blur(45px);          /* the glow — bigger blur = softer/wider halo */
    opacity: 0.75;               /* glow strength */
    pointer-events: none;
}

@keyframes vsl-gradient {
    0%   { background-position: 0% 70%; }
    50%  { background-position: 60% 50%; }
    100% { background-position: 0% 70%; }
}

/* Honour reduced-motion: keep the gradient + glow, drop the looping animation. */
@media (prefers-reduced-motion: reduce) {
    .vsl-video-wrapper,
    .vsl-video-wrapper::before {
        animation: none;
    }
}

/* ─── scrub-blur-in.js initial states ────────────────────────────────────── */

.scrub-blur-chars {
    visibility: hidden;
}
.scrub-blur-chars.ready {
    visibility: visible;
}
.scrub-blur-chars .char {
    display: inline-block; /* required for per-char transform and filter to work */
}
.scrub-blur-in,
.scrub-scale-in {
    opacity: 0;
}

/* ─── scrub-fade-in.js / scrub-fade-out.js (opacity-only scrub) ───────────── */

/* fade-in starts hidden so it doesn't flash fully visible before the scrubbed fromTo first
   renders (JS gsap.set()s the start opacity, then scrubs to 1). fade-out has no initial state —
   it starts at its natural visible opacity (same as .scrub-blur-out). */
.scrub-fade-in {
    opacity: 0;
}

/* ─── scrub-darken.js (scroll-scrubbed black overlay) ────────────────────── */

/* .scrub-darken hosts a JS-injected black overlay (GSAP can't animate ::before/::after);
   position:relative anchors that overlay to the element. The overlay starts clear and GSAP
   scrubs its opacity up to data-opacity; pointer-events:none keeps content beneath clickable,
   z-index:2 lays it over the element's normal-flow content. */
.scrub-darken {
    position: relative;
}
.scrub-darken-overlay {
    position: absolute;
    inset: 0;
    background-color: #000;
    pointer-events: none;
    z-index: 2;
}

/* ─── rise-up-text.js initial states ─────────────────────────────────────── */

.rise-up-text {
    visibility: hidden;   /* hidden until JS splits + .ready reveals (no flash) */
}
.rise-up-text.ready {
    visibility: visible;
}
/* The overflow mask itself needs no CSS — SplitText's mask:"lines" wraps each line
   in an element with overflow:clip applied inline. Lines are block-level (SplitText
   sets their display), so no inline-block helper is needed. */

/* ─── reveal-divider.js initial state ────────────────────────────────────── */

/* Collapsed before the JS reveals it (scaleX 0→1); JS sets transform-origin per data-origin. */
.reveal-divider .line,
.reveal-divider .elementor-divider-separator {
    transform: scaleX(0);
}

/* ─── scrub-border.js initial state ──────────────────────────────────────── */

/* Hidden until JS sets the compressed start state and adds .ready — without this the box
   (at the top of the page) flashes at full size before the scrubbed fromTo first renders.
   background-color = the frosted INITIAL state GSAP darkens from; no !important (that would beat
   GSAP's inline write and freeze the darken). Don't set a background on this element in Bricks —
   let this rule own it. blur(16px) is a constant frosted-glass backdrop (not animated). */
.scrub-border-main {
    visibility: hidden;
    background-color: rgba(0, 0, 0, 0.5);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
}
.scrub-border-main.ready {
    visibility: visible;
}

/* ─── scrub-header.js initial state ──────────────────────────────────────── */

/* Hidden until JS sets the start state (narrower + lowered) and adds .ready — without this
   the header flashes at its final width/position before the scrubbed fromTo first renders. */
.scrub-header {
    visibility: hidden;
}
.scrub-header.ready {
    visibility: visible;
}

/* ─── Image gallery z-depth (depth-gallery.js) ───────────────────────────── */

.depth-img-1 { filter: blur(2px); }
.depth-img-2 { filter: blur(1px); }
.depth-img-4 { filter: blur(3px); }

.gsap-overlay {
    position: absolute;
    inset: 0;
    background-color: #010202;
    pointer-events: none;
    z-index: 1;
}

/* ─── Offerings scroll cycler (offerings.js) ─────────────────────────────── */

.offering-img-wrapper {
    display: grid;
}
.offering-img-wrapper > * {
    grid-area: 1 / 1;   /* stack all image widgets in the same cell */
}
.offering-img {
    opacity: 0;          /* JS fades in the active slide */
}

/* ─── Testimonial carousel (testimonial-scroller.js) ─────────────────────── */

/* The section is the ONLY element with overflow:hidden — it's the masked viewport
   that clips the rows extending beyond it. user-select:none stops text from being
   selected while dragging. */
.testimonial-section {
    user-select: none;
    -webkit-user-select: none;
}

/* The row must be free to extend past the section — NO overflow here. GSAP owns the
   translateX, so suppress any builder transition on it and hint the GPU. Draggable
   swaps `grab` → `grabbing` automatically during a drag. */
.testimonial-row {
    will-change: transform;
    cursor: grab;
}

/* Cards never compress (flex-shrink:0 + min-width) — they're static passengers the
   row carries. Keep all cards the same width so the cloned sets align seamlessly. */
.testimonial-card {
    width: 33vw;
    min-width: 33vw;
    flex-grow: 0;
    flex-shrink: 0;
}

/* Hover-dim (desktop only, ≥769px): hovering ONE card darkens every OTHER card with a black overlay; stop
   hovering and they all clear. By default NO card is dimmed (overlay opacity:0) — the
   dim only appears once some card is hovered. Pure CSS — :has() detects any hovered
   card, :not(:hover) spares the one under the cursor. Works on the cloned cards too
   (no listeners to break, unlike testimonial-readmore.js).

   The overlay is a ::after pseudo at z-index:20 so it sits above the card's content
   (text / image / read-more button), darkening the whole card; pointer-events:none
   keeps that button clickable. position:relative anchors it; border-radius:inherit
   matches the card's rounded corners. The overlay's opacity is CSS-owned; the card's
   GSAP-owned drag-press scale (testimonial-scroller.js) is a TRANSFORM, so the two
   never compete. During auto-scroll the "hovered" card hands off as cards slide under a
   stationary cursor — by design. */
.testimonial-card {
    position: relative;
}
.testimonial-card::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 20;                   /* above the card's inner content, so it dims everything */
    pointer-events: none;          /* keep the read-more button clickable through it */
    border-radius: inherit;        /* match the card's rounded corners */
    background: rgba(0, 0, 0, 0.7);
    opacity: 0;                    /* no dim until a sibling card is hovered */
    transition: opacity 0.25s ease;
}
/* Desktop only (≥769px): on touch a tap leaves a sticky :hover that would dim the OTHER
   cards until you tap elsewhere — unwanted on mobile. Gating activation to ≥769px (the same
   breakpoint as the gallery overlays) means the overlay never lights up on mobile; the
   ::after still exists (opacity:0, non-interactive) but does nothing. */
@media (min-width: 769px) {
    .testimonial-section:has(.testimonial-card:hover) .testimonial-card:not(:hover)::after {
        opacity: 1;
    }
}
@media (prefers-reduced-motion: reduce) {
    .testimonial-card::after {
        transition: none;   /* overlay snaps in/out instead of fading */
    }
}


/* Read more / less (testimonial-readmore.js). Front-end only (animations.css), so the Bricks
   editor shows the full, editable text. The paragraph is clamped to 4 lines with an automatic
   "…"; .expanded (toggled by the JS on click) removes the clamp. Change the 4 to adjust. */
.testimonial-para {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    line-clamp: 4;
    overflow: hidden;
}
.testimonial-card.expanded .testimonial-para {
    -webkit-line-clamp: unset;
    line-clamp: unset;
    overflow: visible;
}
.testimonial-readmore-btn {
    cursor: pointer;
}

/* ─── Testimonial gallery overlays (.testimonial-gallery-wrapper / -image) ────
   Front-end only, pure CSS, no GSAP — so these CSS filters/transitions fight
   nothing (none of these classes are GSAP-animated; don't also tag them with a
   GSAP filter effect like .depth-img-N or the two will clash). The whole block is
   gated to ≥769px, so NOTHING shows ≤768px (the theme's mobile breakpoint).

   The image is a Bricks Image widget = a raw <img>, which can't host an overlay
   child OR ::before/::after (replaced element). So the "0.5 black overlay that
   clears on hover" is done with filter: brightness(): compositing black at alpha
   0.5 over a pixel yields pixel × (1 − 0.5) = pixel × 0.5 — exactly brightness(0.5).
   Identical result, no overlay element, works on the bare <img>. Hover → brightness(1).

   The wrapper IS a container, so its static bottom gradient (unchanged on hover)
   uses a ::before; pointer-events:none keeps hover reaching the <img> beneath.
   Needs position:relative — set here; harmless unless the wrapper was positioned
   absolute/fixed in Bricks (a plain "wrapper" is normally static/relative). */
@media (min-width: 769px) {
    .testimonial-gallery-image {
        filter: brightness(0.5);            /* ≡ an rgba(0,0,0,0.5) black overlay */
        transition: filter 0.3s ease;
    }
    .testimonial-gallery-image:hover {
        filter: brightness(1);              /* overlay "goes away" on hover */
    }

    .testimonial-gallery-wrapper {
        position: relative;
    }
    .testimonial-gallery-wrapper::before {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 10;
        pointer-events: none;
        background: linear-gradient(
            to right,
            rgba(13, 13, 13, 0) 0%,
            rgba(13, 13, 13, 0) 65%,
            rgba(13, 13, 13, 1) 100%
        );
    }
}

/* ─── Button hover (gradient blur-in, hover-animations.js) ───────────────── */

.btn-gd {
    opacity: 0;   /* hidden until .btn-primary hover; hover-animations.js blurs + fades it in */
}

/* ─── Expand-out (scrub-expand-out.js) ───────────────────────────────────── */

/* ─── scrub-slide-in.js initial state ────────────────────────────────────── */

/* Hidden until JS sets the offset start state and adds .ready — without this the element would
   flash at its natural position (and clarity/opacity) before the scrubbed fromTo first renders. */
.scrub-slide-in {
    visibility: hidden;
}
.scrub-slide-in.ready {
    visibility: visible;
}

/* ─── Hero entrance (load-hero.js) ───────────────────────────────────────── */

/* Hidden on load so they don't flash before the JS sequence reveals them. load-hero.js splits
   each line into masked words and rises them up (transform only — the rise-up-text.js principle),
   all lines at once. (.hero-tail is no longer part of the intro, so it's not hidden here.) */
.hero-1,
.hero-2,
.hero-3,
.hero-4 {
    visibility: hidden;
}
.hero-word {
    display: inline-block;        /* required for the per-word rise transform */
}

/* .scrub-header-wrapper fades in as the last beat of the load-hero intro — start it hidden so
   it doesn't flash before the fade. On reduced-motion or non-hero pages, load-hero sets it
   visible instantly. */
.scrub-header-wrapper {
    opacity: 0;
}

/* Mobile-only header scroll background. scrub-header.js toggles .is-scrolled on the WRAPPER
   (full-width) once scrolled past data-bg-distance px (default 140). Scoped to ≤768px so it's
   mobile-only. The transition is narrowed to the two colour props ON PURPOSE, so only the
   background/border fade — opacity (owned by load-hero's GSAP fade) and width/transform aren't
   listed and stay un-transitioned. Needs a sticky/fixed header for this to be visible. */
@media (max-width: 768px) {
    .scrub-header-wrapper {
        border-bottom: 1px solid transparent;
        transition: background-color 0.3s ease, border-bottom-color 0.3s ease;
    }
    .scrub-header-wrapper.is-scrolled {
        background-color: #000000;
        border-bottom-color: #1a1a1a;
    }
}

/* .hero-video and .blur-logo-bg-wrapper fade in mid-intro (load-hero.js, at EXTRAS_START) — start
   them hidden so they don't flash before the fade. GSAP owns the opacity. Front-end only, so the
   Bricks editor still shows them; on reduced-motion / non-hero pages load-hero reveals them instantly. */
.hero-video,
.blur-logo-bg-wrapper {
    opacity: 0;
}

/* ─── mobile-menu.js initial states ──────────────────────────────────────── */

/* The fixed wrapper starts hidden + non-interactive so it can't capture taps over its
   area while closed; mobile-menu.js reveals it (autoAlpha) only while the menu is open.
   The wrapper being hidden also hides the panel, so there's no flash before JS parks it —
   we deliberately do NOT set transform here: GSAP owns it entirely (mobile-menu.js parks
   .mobile-menu-main at yPercent:100). A CSS translateY(100%) would STACK with GSAP's
   yPercent (GSAP parses the CSS px translate into its own y), leaving the panel 200% down
   and off-screen even when open. Front-end only — the Bricks editor keeps showing the menu
   editable. */
.mobile-menu-wrapper {
    visibility: hidden;
    opacity: 0;
    background-color: rgba(0, 0, 0, 0);            /* dim overlay — transparent at rest */
    /* Fade ONLY the overlay colour. opacity + visibility stay GSAP-owned (autoAlpha),
       so they're deliberately NOT in this transition list — same narrowing trick as
       .scrub-header-wrapper's mobile scroll background. Matches the 0.5s panel slide. */
    transition: background-color 0.5s ease;
}
.mobile-menu-wrapper.is-open {
    background-color: rgba(0, 0, 0, 0.3);          /* mobile-menu.js toggles .is-open */
}
.mobile-menu-main {
    /* Frosted-glass blur of whatever sits behind the panel. Only visible if the panel's
       own background is (semi-)transparent — an opaque bg paints over the blur. */
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}

/* Per-item rise mask. mobile-menu.js wraps each .mobile-menu-nav-item / -bottom-item in a
   <div class="mobile-menu-item-mask"> at init; overflow:clip hides the parked (yPercent:100)
   item behind its own edge so it slides up into view — the block-level equivalent of
   rise-up-text.js's SplitText mask:"lines". Injected by JS + front-end only, so the Bricks
   editor markup is untouched. */
.mobile-menu-item-mask {
    overflow: clip;
}

@media (prefers-reduced-motion: reduce) {
    .mobile-menu-wrapper {
        transition: none;  /* overlay snaps in, matching the instant (0s) panel slide */
    }
}

/* ─── hover-icon-btn.js ──────────────────────────────────────────────────── */

/* Icon-button hover. The ::before/::after glow overlays FADE IN on hover (pseudo-elements
   can't be GSAP-animated, so this stays pure CSS); hover-icon-btn.js GSAP-scales the icon
   wrapper. .hover-btn-outer needs position+overflow for the overlays: the -20px top inset is
   clipped away so the top glow band doesn't show (same trick as .icon-box-big). */
.hover-btn-outer {
    position: relative;
    overflow: hidden;
}

.hover-btn-outer::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: 8px;
    box-shadow: inset 0 0 8px rgba(255, 255, 255, 0.4);
    z-index: 10;
    opacity: 0;
    transition: opacity 0.3s ease;   /* fade in on hover (matches the default scale duration) */
}

.hover-btn-outer:hover::before {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    /* Overlays snap (no fade); the GSAP scale is skipped entirely in JS. */
    .hover-btn-outer::before,
    .hover-btn-outer::after {
        transition: none;
    }
}

/* ─── guide-index.js ─────────────────────────────────────────────────────── */

/* Hover-triggered module index overlay. Show/hide + collapse are INSTANT (no
   transitions, by request). Front-end only, so the Bricks editor shows the card
   and every sub-module expanded and editable. guide-index.js toggles .is-active
   on the section and .is-open on the single open sub-module. */

/* Inactive: the fixed full-screen section must not block the page behind it, so
   it's click-through — except .index-btn, which stays interactive so it can be
   hovered/tapped to open. The window-wrapper (card + its overline links) is hidden
   until active; the bare .guide-index-window is hidden too as a fallback for markup
   without the wrapper. Scoping the hide to :not(.is-active) means the active
   wrapper/card keeps whatever display its own CSS/Bricks gives it — none imposed here. */
.guide-index-section:not(.is-active) {
    pointer-events: none;
}
.guide-index-section:not(.is-active) .guide-index-window-wrapper,
.guide-index-section:not(.is-active) .guide-index-window {
    display: none;
}
.index-btn {
    pointer-events: auto;   /* re-enable the trigger while the section is click-through */
}

/* Active: 80% black backdrop over the page + the whole section becomes interactive
   (card scrolls/clicks; tapping the backdrop dismisses on touch). Set NO background
   on the section in Bricks so this owns the dim — otherwise a Bricks bg would show
   even when inactive. */
.guide-index-section.is-active {
    pointer-events: auto;
    background-color: rgba(0, 0, 0, 0.8);
}

/* Sub-modules collapsed by default; guide-index.js adds .is-open to exactly one at
   a time. Scoping to :not(.is-open) leaves the open one's display to its own CSS.
   (Icon state is exposed as aria-expanded="true" on .guide-index-title-dropdown-icon-wrapper
   if you want to style an open-indicator — kept static here, no animation.) */
.guide-index-sub-module-wrapper:not(.is-open) {
    display: none;
}
