/* Maintains packages/share/src/scripts/lenis.ts as an authored share script module so agents can evolve it without rediscovering local boundaries. Do not manage app-specific UI states (e.g. scroll-to-top button visibility). RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1175 AC-5: add data-lenis-initialized marker after the last addEventListener — SMOKE-INIT-01 compliance surfaced when component.smoke.init-contract.validate was revived for the single-chunk orchestrator. */ // @ai-invariant: This is a high-risk module. Preserve its core logic and minimize external side effects during modification. export type LenisInstance = import("lenis").default; export type InitLenisOptions = { prefersReducedMotion?: boolean; headerOffset?: number; duration?: number; onScroll?: (lenis: LenisInstance) => void; }; let lenisInstance: LenisInstance | null = null; export async function initLenis( options: InitLenisOptions = {}, ): Promise { const { prefersReducedMotion = false, headerOffset = 0, duration = 1.2, onScroll } = options; if (prefersReducedMotion) { (window as Window & { wgLenis?: LenisInstance }).wgLenis = undefined; return; } const Lenis = await import("lenis"); lenisInstance = new Lenis.default({ duration, easing: (t: number) => Math.min(1, 1.001 - 2 ** (-10 * t)), orientation: "vertical", gestureOrientation: "vertical", smoothWheel: true, wheelMultiplier: 1, touchMultiplier: 2, }); (window as Window & { wgLenis?: LenisInstance }).wgLenis = lenisInstance; if (onScroll) { lenisInstance.on("scroll", () => onScroll(lenisInstance!)); } window.dispatchEvent( new CustomEvent("lenis:ready", { detail: { lenis: lenisInstance }, }), ); const activeLenis = lenisInstance; // Handle anchor links for smooth scrolling document.addEventListener("click", (e) => { const target = e.target as HTMLElement; const link = target.closest('a[href^="#"]'); if (link) { const href = link.getAttribute("href"); if (href && href !== "#") { const targetElement = document.querySelector(href); if (targetElement) { // Skip if it's a modal or something that should handle itself if (targetElement.classList.contains("wl-modal")) { return; } e.preventDefault(); activeLenis.scrollTo(targetElement as HTMLElement, { offset: -headerOffset, immediate: prefersReducedMotion, }); history.replaceState(null, "", href); } } } }); // Scroll to hash target on page load (cross-page anchor navigation) if (window.location.hash) { const hashTarget = document.querySelector(window.location.hash); if (hashTarget && !hashTarget.classList.contains("wl-modal")) { // Defer until after the browser's initial scroll attempt requestAnimationFrame(() => { activeLenis.scrollTo(hashTarget as HTMLElement, { offset: -headerOffset, immediate: prefersReducedMotion, }); }); } } function raf(time: number) { activeLenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); // RFC-1175: smoke-test init marker (SMOKE-INIT-01) — set after the last // addEventListener so component-smoke can synchronize on Lenis readiness. document.documentElement.setAttribute("data-lenis-initialized", "true"); return activeLenis; }