window.addEventListener("DOMContentLoaded", () => { if (!window.gsap || !window.ScrollTrigger) return; gsap.registerPlugin(ScrollTrigger); const stage = document.querySelector(".scroll_stage"); const pinInner = document.querySelector(".pin_inner"); const flyer = document.querySelector(".hero_section .fs_template"); const slot = document.querySelector("#gallery-slot"); const content = document.querySelector(".apartment_content"); if (!stage || !pinInner || !flyer || !slot || !content) return; const rem = () => parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; const layoutWidth = () => document.documentElement.clientWidth; const layoutHeight = () => window.innerHeight; const easeFly = gsap.parseEase("sine.inOut"); const invertSteep = 0.22; const invertCoverFloor = 0.08; const invertStartCover = 0.82; let start = { left: 0, top: 0, width: 0, height: 0 }; let end = { left: 0, top: 0, width: 0, height: 0 }; function overlapRatio(a, b) { const left = Math.max(a.left, b.left); const top = Math.max(a.top, b.top); const right = Math.min(a.right, b.right); const bottom = Math.min(a.bottom, b.bottom); const w = Math.max(0, right - left); const h = Math.max(0, bottom - top); const inter = w * h; const base = Math.max(1, b.width * b.height); return inter / base; } function invertFromCover(cover) { const c = Math.max(0, Math.min(1, cover)); if (c >= invertStartCover) return 0; if (c <= invertCoverFloor) return 100; const span = Math.max(1e-6, invertStartCover - invertCoverFloor); const t = (invertStartCover - c) / span; const steep = Math.pow(Math.min(1, t), invertSteep); return Math.min(100, Math.round(steep * 100)); } function measure() { const vw = layoutWidth(); const vh = layoutHeight(); const marginRightPx = 2 * rem(); const marginBottomPx = 0.2 * rem(); gsap.set(flyer, { left: 0, top: 0, width: vw, height: vh, x: 0, y: 0, scale: 1 }); start = { left: 0, top: 0, width: vw, height: vh }; const r = slot.getBoundingClientRect(); end = { width: r.width, height: r.height, top: vh - marginBottomPx - r.height, left: vw - marginRightPx - r.width }; } function apply(p) { p = Math.max(0, Math.min(1, p)); const pe = easeFly(p); const curLeft = start.left + (end.left - start.left) * pe; const curTop = start.top + (end.top - start.top) * pe; const curW = start.width + (end.width - start.width) * pe; const curH = start.height + (end.height - start.height) * pe; gsap.set(flyer, { left: curLeft, top: curTop, width: curW, height: curH, margin: 0, maxWidth: "none" }); const fr = flyer.getBoundingClientRect(); const cr = content.getBoundingClientRect(); content.style.filter = `invert(${invertFromCover(overlapRatio(fr, cr))}%)`; } const mm = gsap.matchMedia(); // desktop only (>= 992px) mm.add("(min-width: 992px)", () => { gsap.set(flyer, { position: "fixed", top: 0, left: 0, right: "auto", bottom: "auto", margin: 0, maxWidth: "none", width: layoutWidth(), height: layoutHeight(), x: 0, y: 0, scale: 1, rotation: 0, skewX: 0, skewY: 0 }); measure(); const st = ScrollTrigger.create({ trigger: stage, start: "top top", end: "bottom bottom", pin: pinInner, pinSpacing: true, pinType: "fixed", anticipatePin: 1, scrub: 0.75, invalidateOnRefresh: true, onRefresh(self) { measure(); apply(self.progress); }, onUpdate(self) { apply(self.progress); } }); const imgs = Array.from( document.querySelectorAll(".fs_template img, #gallery-slot img") ); let pending = imgs.length; const done = () => { pending--; if (pending <= 0) ScrollTrigger.refresh(); }; if (!pending) ScrollTrigger.refresh(); imgs.forEach((img) => { if (img.complete) done(); else { img.addEventListener("load", done, { once: true }); img.addEventListener("error", done, { once: true }); } }); const onResize = () => ScrollTrigger.refresh(); window.addEventListener("resize", onResize); requestAnimationFrame(() => { ScrollTrigger.refresh(); apply(st.progress); }); return () => { window.removeEventListener("resize", onResize); st.kill(); content.style.filter = ""; }; }); });