import Lenis from "lenis"; const REVEAL_SELECTOR = ".fade-item:not(.revealed)"; const REVEAL_SCROLL_RATIO = 0.88; const REVEAL_INITIAL_RATIO = 0.92; const INITIAL_REVEAL_DELAY_MS = 150; /** * Inertial scrolling plus the two scroll-driven effects that depend on it: * the top progress bar and the `.fade-item` reveal. */ export default defineNuxtPlugin((nuxtApp) => { const lenis = new Lenis({ duration: 1.2, easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), orientation: "vertical", smoothWheel: true, wheelMultiplier: 1.05, touchMultiplier: 1.8, }); let pending: HTMLElement[] = []; let progressEl: HTMLElement | null = null; const collect = () => { pending = Array.from(document.querySelectorAll(REVEAL_SELECTOR)); progressEl = document.querySelector(".scroll-progress"); }; const reveal = (ratio: number) => { if (!pending.length) return; const limit = window.innerHeight * ratio; const hits: HTMLElement[] = []; pending = pending.filter((el) => { if (!el.isConnected) return false; if (el.getBoundingClientRect().top <= limit) { hits.push(el); return false; } return true; }); for (const el of hits) el.classList.add("revealed"); }; lenis.on("scroll", (e: Lenis) => { if (progressEl) { const total = document.documentElement.scrollHeight - window.innerHeight; const progress = total > 0 ? e.scroll / total : 0; progressEl.style.transform = `scaleX(${progress})`; } reveal(REVEAL_SCROLL_RATIO); }); let rafId = 0; const loop = (time: number) => { lenis.raf(time); rafId = requestAnimationFrame(loop); }; rafId = requestAnimationFrame(loop); const initialReveal = () => { collect(); window.setTimeout(() => { collect(); reveal(REVEAL_INITIAL_RATIO); }, INITIAL_REVEAL_DELAY_MS); }; nuxtApp.hook("app:mounted", initialReveal); nuxtApp.hook("page:finish", initialReveal); nuxtApp.vueApp.onUnmount(() => { cancelAnimationFrame(rafId); lenis.destroy(); }); return { provide: { lenis } }; });