///////////////////////////////////////////// // Initialize when DOM content is loaded ///////////////////////////////////////////// document.addEventListener("DOMContentLoaded", () => { document.documentElement.classList.add("ready"); }); ///////////////////////////////////////////// // Navbar Scroll Down/Up Open/Close ///////////////////////////////////////////// (function initNavbarScrollBehavior() { let previousScrollPos = window.pageYOffset; window.addEventListener("scroll", () => { const nav = document.querySelector("[nav]"); if (!nav) return; const currentScrollPos = window.pageYOffset; nav.classList.toggle( "scrolled", currentScrollPos > window.innerHeight * 0.1, ); nav.classList.toggle("scroll-down", currentScrollPos > previousScrollPos); previousScrollPos = currentScrollPos; }); })(); // Navbar mobile menu toggle (function initMobileMenuToggle() { const menuButtons = document.querySelectorAll("[menu-btn]"); const nav = document.querySelector("[nav]"); if (!menuButtons.length || !nav) return; menuButtons.forEach((btn) => { btn.addEventListener("click", () => { nav.classList.toggle("open"); }); }); })(); ///////////////////////////////////////////// // Lenis ///////////////////////////////////////////// const lenis = new Lenis({ lerp: 0.1, wheelMultiplier: 0.7, gestureOrientation: "vertical", }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); $("[data-lenis-start]").on("click", () => lenis.start()); $("[data-lenis-stop]").on("click", () => lenis.stop()); $("[data-lenis-toggle]").on("click", function toggleLenis() { $(this).toggleClass("stop-scroll"); lenis[$(this).hasClass("stop-scroll") ? "stop" : "start"](); }); ///////////////////////////////////////////// // MODAL ///////////////////////////////////////////// (function initModals() { const openers = document.querySelectorAll("[open-modal]"); if (!openers.length) return; openers.forEach((btn) => { btn.addEventListener("click", () => { const target = btn.getAttribute("open-modal"); const modal = document.querySelector(`[modal="${target}"]`); if (modal) modal.classList.add("open"); }); }); document.querySelectorAll("[close-modal]").forEach((btn) => { btn.addEventListener("click", () => { const target = btn.getAttribute("close-modal"); if (target) { const modal = document.querySelector(`[modal="${target}"]`); if (modal) modal.classList.remove("open"); return; } const nearestModal = btn.closest("[modal]"); if (nearestModal) nearestModal.classList.remove("open"); }); }); })(); ///////////////////////////////////////////// // HOMEPAGE // EPISODE CARDS - Active State ///////////////////////////////////////////// setTimeout(function () { const episodeCards = document.querySelectorAll(".episode-card"); if (episodeCards.length === 0) return; // Group cards by their common parent (each "component" of cards) const groups = new Map(); episodeCards.forEach(function (card) { const parent = card.parentElement; if (!groups.has(parent)) groups.set(parent, []); groups.get(parent).push(card); }); // For each group, activate the first child and scope click handling to that group groups.forEach(function (cardsInGroup) { cardsInGroup[0].classList.add("active"); cardsInGroup.forEach(function (card) { card.addEventListener("click", function () { cardsInGroup.forEach(function (c) { c.classList.remove("active"); }); card.classList.add("active"); }); }); }); }, 2000); //////////////////////////////// // MIRROR CLICK //////////////////////////////// handleMirrorBtn(); function handleMirrorBtn() { let mirrorBtns = document.querySelectorAll("[mirror-trigger-btn]"); if (mirrorBtns.length < 1) return; mirrorBtns.forEach((btn) => { btn.addEventListener("click", () => { document.querySelector(btn.getAttribute("mirror-trigger-btn")).click(); }); }); } //////////////////////////////// // BORDER GLOW HOVER //////////////////////////////// function initHoverBorderGlow(root = document) { root.querySelectorAll("[hover-border-glow]").forEach((el) => { if (el.dataset.hbgReady) return; el.dataset.hbgReady = "true"; const color = el.getAttribute("hover-border-glow") || "#99ffe8"; el.style.setProperty("--glow-color", color); const size = el.getAttribute("hover-border-glow-size"); const width = el.getAttribute("hover-border-glow-width"); if (size) el.style.setProperty("--glow-size", size); if (width) el.style.setProperty("--glow-width", width); }); } const DEFAULT_MARGIN = 100; function updateGlowProximity(mouseX, mouseY) { document.querySelectorAll("[hover-border-glow]").forEach((el) => { const margin = parseFloat(el.getAttribute("hover-border-glow-margin")) || DEFAULT_MARGIN; const rect = el.getBoundingClientRect(); const dx = Math.max(rect.left - mouseX, 0, mouseX - rect.right); const dy = Math.max(rect.top - mouseY, 0, mouseY - rect.bottom); const dist = Math.sqrt(dx * dx + dy * dy); if (dist > margin) { el.style.setProperty("--glow-opacity", 0); return; } const proximity = 1 - dist / margin; el.style.setProperty("--glow-opacity", proximity.toFixed(3)); const x = ((mouseX - rect.left) / rect.width) * 100; const y = ((mouseY - rect.top) / rect.height) * 100; el.style.setProperty("--x", `${x}%`); el.style.setProperty("--y", `${y}%`); }); } let hbgLatestX = 0; let hbgLatestY = 0; let hbgTicking = false; function onHbgMouseMove(e) { hbgLatestX = e.clientX; hbgLatestY = e.clientY; if (!hbgTicking) { hbgTicking = true; requestAnimationFrame(() => { updateGlowProximity(hbgLatestX, hbgLatestY); hbgTicking = false; }); } } function isDesktopPointer() { return window.matchMedia("(hover: hover) and (pointer: fine)").matches; } function setupHoverBorderGlow() { if (!isDesktopPointer()) return; if (!document.querySelector("[hover-border-glow]")) return; initHoverBorderGlow(); window.addEventListener("mousemove", onHbgMouseMove); const hbgObserver = new MutationObserver(() => { if (isDesktopPointer() && document.querySelector("[hover-border-glow]")) { initHoverBorderGlow(); window.addEventListener("mousemove", onHbgMouseMove); } }); hbgObserver.observe(document.body, { childList: true, subtree: true }); } document.addEventListener("DOMContentLoaded", setupHoverBorderGlow); /* ============================================================ VIDEO — Vidstack players, bg videos, modal videos, controls Now supports: native