(() => { const BOUND_FLAG = "__nodedcWheelOverrideBound"; const GLOBAL_SLIDER_BOUND_FLAG = "__nodedcGlobalSliderWheelOverrideBound"; const SYNTHETIC_WHEEL_FLAG = "__nodedcSyntheticWheel"; const SLIDER_WHEEL_MULTIPLIER = 3; const SLIDER_BASE_COOLDOWN = 220; const MAIL_BASE_COOLDOWN = 280; const MIN_SCROLL_SPEED = 0.2; const MAX_SCROLL_SPEED = 2; const DEFAULT_SCROLL_SPEED = 0.55; const ADAPTIVE_VIDEO_BOUND_FLAG = "__nodedcAdaptiveVideoBound"; const MEDIA_HANDLE_BOUND_FLAG = "__nodedcMediaHandleBound"; const DOCK_LINK_BOUND_FLAG = "__nodedcDockLinkBound"; const ORDERED_SLIDER_BOUND_FLAG = "__nodedcOrderedSliderBound"; const LAZY_MEDIA_VIDEO_BOUND_FLAG = "__nodedcLazyMediaVideoBound"; const SCROLL_HEIGHT_SYNC_FLAG = "__nodedcScrollHeightSyncBound"; const NOTIFICATION_STACK_BOUND_FLAG = "__nodedcNotificationStackBound"; const SCROLL_HEIGHT_SYNC_WARMUP_MS = 14000; const SCROLL_HEIGHT_SYNC_INTERVAL_MS = 90; const IMAGE_EXTENSIONS = new Set(["avif", "gif", "jpeg", "jpg", "png", "svg", "webp"]); const VIDEO_EXTENSIONS = new Set(["m4v", "mov", "mp4", "webm"]); let lastDocumentScrollHeight = 0; let scrollHeightSyncFrame = 0; let scrollHeightSyncShouldForce = false; let scrollHeightDeferredSync = 0; function isDesktopPointer() { return window.matchMedia?.("(pointer: fine)").matches ?? true; } function shouldIgnoreWheel(event) { return event.ctrlKey || !isDesktopPointer(); } function createSyntheticWheel(event, deltaX, deltaY) { if (typeof WheelEvent !== "function") return null; const syntheticEvent = new WheelEvent("wheel", { bubbles: true, cancelable: true, view: window, deltaX, deltaY, deltaZ: event.deltaZ || 0, deltaMode: event.deltaMode, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey, }); Object.defineProperty(syntheticEvent, SYNTHETIC_WHEEL_FLAG, { value: true }); return syntheticEvent; } function createSyntheticHorizontalWheel(event) { return createSyntheticWheel(event, event.deltaY * SLIDER_WHEEL_MULTIPLIER, 0); } function getPrimaryWheelDelta(event) { return Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY; } function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } function getDocumentScrollHeight() { return Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight || 0); } function notifyScrollHeightChange(force = false) { const nextHeight = getDocumentScrollHeight(); if (!nextHeight) return; if (!force && nextHeight === lastDocumentScrollHeight) return; lastDocumentScrollHeight = nextHeight; window.dispatchEvent(new Event("resize")); if (scrollHeightDeferredSync) { window.clearTimeout(scrollHeightDeferredSync); } scrollHeightDeferredSync = window.setTimeout(() => { scrollHeightDeferredSync = 0; if (getDocumentScrollHeight() === lastDocumentScrollHeight) { window.dispatchEvent(new Event("resize")); } else { scheduleScrollHeightSync(); } }, 320); } function scheduleScrollHeightSync(force = false) { scrollHeightSyncShouldForce = scrollHeightSyncShouldForce || force; if (scrollHeightSyncFrame) return; scrollHeightSyncFrame = window.requestAnimationFrame(() => { const shouldForce = scrollHeightSyncShouldForce; scrollHeightSyncShouldForce = false; scrollHeightSyncFrame = 0; notifyScrollHeightChange(shouldForce); }); } function bindScrollHeightSync() { if (document[SCROLL_HEIGHT_SYNC_FLAG]) return; document[SCROLL_HEIGHT_SYNC_FLAG] = true; scheduleScrollHeightSync(true); const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => { scheduleScrollHeightSync(); }) : null; resizeObserver?.observe(document.documentElement); if (document.body) resizeObserver?.observe(document.body); const mutationObserver = typeof MutationObserver === "function" ? new MutationObserver(() => { scheduleScrollHeightSync(); }) : null; mutationObserver?.observe(document.documentElement, { attributes: true, childList: true, subtree: true, }); const interval = window.setInterval(() => { scheduleScrollHeightSync(); }, SCROLL_HEIGHT_SYNC_INTERVAL_MS); window.setTimeout(() => { window.clearInterval(interval); mutationObserver?.disconnect(); }, SCROLL_HEIGHT_SYNC_WARMUP_MS); window.addEventListener("load", () => scheduleScrollHeightSync(true), { once: true }); if (document.fonts?.ready) { document.fonts.ready.then(() => scheduleScrollHeightSync(true)).catch(() => {}); } document.addEventListener( "load", (event) => { if (event.target instanceof HTMLImageElement || event.target instanceof HTMLVideoElement) { scheduleScrollHeightSync(); } }, true, ); } function getScrollSpeed(surface) { const rawValue = surface?.dataset?.scrollSpeed ?? surface?.closest?.("[data-scroll-speed]")?.dataset?.scrollSpeed; const value = Number.parseFloat(rawValue); if (!Number.isFinite(value)) return DEFAULT_SCROLL_SPEED; return clamp(value, MIN_SCROLL_SPEED, MAX_SCROLL_SPEED); } function getStepCooldown(baseCooldown, surface) { return Math.round(baseCooldown / getScrollSpeed(surface)); } function dispatchPointerStep(item) { if (typeof PointerEvent !== "function") return false; const rect = item.getBoundingClientRect(); const eventInit = { bubbles: true, cancelable: true, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, pointerId: 1, pointerType: "mouse", isPrimary: true, }; item.dispatchEvent(new PointerEvent("pointerdown", eventInit)); document.dispatchEvent(new PointerEvent("pointerup", eventInit)); return true; } function stepMailList(surface, event) { const now = Date.now(); const lastStep = Number(surface.__nodedcMailWheelLastStep || 0); if (now - lastStep < getStepCooldown(MAIL_BASE_COOLDOWN, surface)) return; const items = Array.from(surface.children); if (!items.length) return; const currentIndex = Math.max( 0, items.findIndex((item) => item.classList.contains("current")), ); const direction = event.deltaY > 0 ? 1 : -1; const nextIndex = Math.max(0, Math.min(items.length - 1, currentIndex + direction)); if (nextIndex === currentIndex) return; if (dispatchPointerStep(items[nextIndex])) { surface.__nodedcMailWheelLastStep = now; } } function stepSlider(surface, event) { const delta = getPrimaryWheelDelta(event); if (Math.abs(delta) < 1) return; const now = Date.now(); const lastStep = Number(surface.__nodedcSliderWheelLastStep || 0); if (now - lastStep < getStepCooldown(SLIDER_BASE_COOLDOWN, surface)) return; const slider = surface.__nodedcSlider; if (slider && typeof slider.goToNext === "function" && typeof slider.goToPrev === "function") { if (delta > 0) slider.goToNext(); else slider.goToPrev(); surface.__nodedcSliderWheelLastStep = now; return; } if (Math.abs(event.deltaY) >= Math.abs(event.deltaX)) { const syntheticEvent = createSyntheticHorizontalWheel(event); if (syntheticEvent) { surface.dispatchEvent(syntheticEvent); surface.__nodedcSliderWheelLastStep = now; } } } function findSliderAtPoint(event) { if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) return null; return Array.from(document.querySelectorAll('[data-module="slider"]')).find((surface) => { const rect = surface.getBoundingClientRect(); const left = Math.max(rect.left, 0); const right = Math.min(rect.right, window.innerWidth); return ( event.clientX >= left && event.clientX <= right && event.clientY >= rect.top && event.clientY <= rect.bottom ); }); } function bindGlobalSliderWheel() { if (document[GLOBAL_SLIDER_BOUND_FLAG]) return; document[GLOBAL_SLIDER_BOUND_FLAG] = true; document.addEventListener( "wheel", (event) => { if (shouldIgnoreWheel(event) || event[SYNTHETIC_WHEEL_FLAG]) return; if (event.target?.closest?.('[data-module="mail-list"]')) return; const slider = findSliderAtPoint(event); if (!slider) return; event.preventDefault(); event.stopImmediatePropagation(); stepSlider(slider, event); }, { capture: true, passive: false }, ); } function bindSliderWheel(surface) { if (!surface || surface[BOUND_FLAG]) return; surface[BOUND_FLAG] = true; surface.addEventListener( "wheel", (event) => { if (shouldIgnoreWheel(event) || event[SYNTHETIC_WHEEL_FLAG]) return; event.preventDefault(); event.stopImmediatePropagation(); stepSlider(surface, event); }, { capture: true, passive: false }, ); surface.addEventListener( "wheel", (event) => { if (shouldIgnoreWheel(event)) return; event.stopPropagation(); }, { passive: false }, ); } function bindMailListWheel(surface) { if (!surface || surface[BOUND_FLAG]) return; surface[BOUND_FLAG] = true; surface.addEventListener( "wheel", (event) => { if (shouldIgnoreWheel(event)) return; event.preventDefault(); if (Math.abs(event.deltaY) >= Math.abs(event.deltaX)) { stepMailList(surface, event); } }, { capture: true, passive: false }, ); surface.addEventListener( "wheel", (event) => { if (shouldIgnoreWheel(event)) return; event.stopPropagation(); }, { passive: false }, ); } function bindInteractiveScroll() { document.querySelectorAll('[data-module="mail-list"]').forEach(bindMailListWheel); } function bindOrderedSlider(surface) { if (!surface || surface[ORDERED_SLIDER_BOUND_FLAG]) return; const slider = surface.__nodedcSlider; if (!slider) return; surface[ORDERED_SLIDER_BOUND_FLAG] = true; const items = Array.from(surface.children); const startOffset = items.length > 1 ? -1 : 0; slider.config.infinite = true; slider.target = startOffset; slider.current = startOffset; slider.speed = 0; items.forEach((item) => { item.classList.remove("current"); }); slider.resize?.(); slider.update?.(); } function bindOrderedSliders() { document.querySelectorAll('[data-module="slider"][data-admin-order="true"]').forEach(bindOrderedSlider); } function bindDockLink(link) { if (!link || link[DOCK_LINK_BOUND_FLAG]) return; link[DOCK_LINK_BOUND_FLAG] = true; link.addEventListener( "click", (event) => { const href = String(link.getAttribute("href") || "").trim(); const targetId = String(link.dataset.target || "").trim(); const hasAppTarget = Boolean(targetId && document.getElementById(targetId)); const isInertHref = !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:"); if (!isInertHref) { event.preventDefault(); event.stopImmediatePropagation(); link.setAttribute("target", "_blank"); link.setAttribute("rel", "noopener noreferrer"); window.open(href, "_blank", "noopener,noreferrer"); window.requestAnimationFrame(() => link.blur()); return; } event.preventDefault(); window.requestAnimationFrame(() => link.blur()); if (!hasAppTarget) event.stopPropagation(); }, { capture: true }, ); } function bindDockLinks() { document.querySelectorAll('[data-module="dock"] a[data-app]').forEach(bindDockLink); } function refreshNotificationStack(wrapper) { const notifications = Array.from(wrapper.querySelectorAll(':scope > [data-module="notification"]')); const firstOffset = notifications[0]?.offsetTop || 0; const collapsedHeight = (notifications[0]?.offsetHeight || 0) + Math.min(Math.max(notifications.length - 1, 0), 3) * 5; wrapper.dataset.notificationStackReady = "true"; wrapper.style.setProperty("--notification-count", String(notifications.length)); wrapper.style.setProperty("--notification-collapsed-height", `${collapsedHeight}px`); notifications.forEach((notification, index) => { const naturalOffset = notification.offsetTop - firstOffset; const deckOffset = Math.min(index, 3) * 5; notification.style.setProperty("--notification-stack-y", `${deckOffset - naturalOffset}px`); notification.style.setProperty("--notification-z", String(index + 1)); }); } function scheduleNotificationStackRefresh(wrapper) { if (wrapper.__nodedcNotificationStackFrame) return; wrapper.__nodedcNotificationStackFrame = window.requestAnimationFrame(() => { wrapper.__nodedcNotificationStackFrame = 0; refreshNotificationStack(wrapper); }); } function bindNotificationStack(wrapper) { if (!wrapper || wrapper[NOTIFICATION_STACK_BOUND_FLAG]) return; wrapper[NOTIFICATION_STACK_BOUND_FLAG] = true; scheduleNotificationStackRefresh(wrapper); const mutationObserver = typeof MutationObserver === "function" ? new MutationObserver(() => scheduleNotificationStackRefresh(wrapper)) : null; mutationObserver?.observe(wrapper, { childList: true }); wrapper.addEventListener("pointerdown", (event) => { if (event.target?.closest?.("[data-close]")) return; wrapper.dataset.notificationExpanded = "true"; }); wrapper.addEventListener("mouseleave", () => { delete wrapper.dataset.notificationExpanded; wrapper.scrollTop = 0; scheduleNotificationStackRefresh(wrapper); }); document.addEventListener( "pointerdown", (event) => { if (wrapper.contains(event.target)) return; delete wrapper.dataset.notificationExpanded; wrapper.scrollTop = 0; }, { capture: true }, ); window.addEventListener("resize", () => scheduleNotificationStackRefresh(wrapper)); window.setTimeout(() => scheduleNotificationStackRefresh(wrapper), 120); } function bindNotificationStacks() { document.querySelectorAll('[data-module="notification-w"]').forEach(bindNotificationStack); } function mediaExtension(src) { const cleanSrc = String(src || "").split(/[?#]/)[0]; const match = /\.([a-z0-9]+)$/i.exec(cleanSrc); return match ? match[1].toLowerCase() : ""; } function createMediaElement(src) { const extension = mediaExtension(src); if (IMAGE_EXTENSIONS.has(extension)) { const image = document.createElement("img"); image.className = "video-el"; image.src = src; image.alt = ""; image.loading = "lazy"; image.decoding = "async"; return image; } if (!VIDEO_EXTENSIONS.has(extension)) return null; const video = document.createElement("video"); video.className = "video-el"; video.dataset.lazyVideo = ""; video.src = src; video.muted = true; video.loop = true; video.playsInline = true; video.preload = "none"; video.setAttribute("playsinline", "true"); return video; } function hydrateMediaHandle(handle) { if (!handle || handle[MEDIA_HANDLE_BOUND_FLAG]) return; const src = handle.dataset.mediaSrc; const media = createMediaElement(src); if (!media) return; handle[MEDIA_HANDLE_BOUND_FLAG] = true; handle.textContent = ""; handle.append(media); } function hydrateMediaHandles() { document.querySelectorAll("[data-media-handle]").forEach(hydrateMediaHandle); } function applyAdaptiveVideoAspect(video) { const surface = video.closest("[data-adaptive-video]"); if (!surface || !video.videoWidth || !video.videoHeight) return; const windowEl = video.closest("[data-adaptive-video-window]"); const sourceAspect = video.videoWidth / video.videoHeight; const targets = windowEl ? [surface, windowEl] : [surface]; targets.forEach((target) => { target.style.setProperty("--nodedc-video-source-aspect", sourceAspect.toFixed(6)); }); } function bindAdaptiveVideo(video) { if (!video || video[ADAPTIVE_VIDEO_BOUND_FLAG]) return; video[ADAPTIVE_VIDEO_BOUND_FLAG] = true; video.addEventListener("loadedmetadata", () => applyAdaptiveVideoAspect(video)); applyAdaptiveVideoAspect(video); } function bindAdaptiveVideos() { document.querySelectorAll("[data-adaptive-video] video").forEach(bindAdaptiveVideo); } function playLazyVideo(video) { video.preload = "auto"; const playPromise = video.play(); if (playPromise && typeof playPromise.catch === "function") { playPromise.catch(() => {}); } } function bindLazyMediaVideo(video) { if (!video || video[LAZY_MEDIA_VIDEO_BOUND_FLAG]) return; video[LAZY_MEDIA_VIDEO_BOUND_FLAG] = true; video.muted = true; video.loop = true; video.playsInline = true; video.preload = "none"; video.removeAttribute("autoplay"); if (typeof IntersectionObserver !== "function") { playLazyVideo(video); return; } const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { playLazyVideo(video); } else { video.pause(); } }); }, { rootMargin: "80px 0px", threshold: 0.01 }, ); observer.observe(video); } function bindLazyMediaVideos() { document.querySelectorAll("video[data-lazy-video]").forEach(bindLazyMediaVideo); } bindScrollHeightSync(); hydrateMediaHandles(); bindNotificationStacks(); if (document.readyState === "loading") { document.addEventListener( "DOMContentLoaded", () => { bindScrollHeightSync(); hydrateMediaHandles(); bindInteractiveScroll(); bindOrderedSliders(); bindDockLinks(); bindNotificationStacks(); bindAdaptiveVideos(); bindLazyMediaVideos(); scheduleScrollHeightSync(true); }, { once: true }, ); } else { bindScrollHeightSync(); hydrateMediaHandles(); bindInteractiveScroll(); bindOrderedSliders(); bindDockLinks(); bindNotificationStacks(); bindAdaptiveVideos(); bindLazyMediaVideos(); scheduleScrollHeightSync(true); } window.addEventListener("load", () => { bindScrollHeightSync(); hydrateMediaHandles(); bindInteractiveScroll(); bindOrderedSliders(); bindDockLinks(); bindNotificationStacks(); bindAdaptiveVideos(); bindLazyMediaVideos(); scheduleScrollHeightSync(true); window.setTimeout(() => { hydrateMediaHandles(); bindInteractiveScroll(); bindOrderedSliders(); bindDockLinks(); bindNotificationStacks(); bindAdaptiveVideos(); bindLazyMediaVideos(); scheduleScrollHeightSync(true); }, 500); }); })();