/** * Collaboration framed carousel — dots + arrows (no pause/progress bars). * Distinct from the full-bleed promo hero under the atelier section. */ export function initCollabBanners() { const root = document.querySelector("[data-collab-banners]"); if (!(root instanceof HTMLElement)) return; const slides = [...root.querySelectorAll("[data-collab-slide]")]; const dots = [...root.querySelectorAll("[data-collab-goto]")]; const nextBtn = root.querySelector("[data-collab-next]"); const prevBtn = root.querySelector("[data-collab-prev]"); if (slides.length < 1) return; const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; let index = 0; let timer = 0; let paused = reduce; const durationOf = (i) => { const raw = Number(slides[i]?.getAttribute("data-cbs-duration") || 7000); return Number.isFinite(raw) && raw > 0 ? raw : 7000; }; const show = (next) => { const i = ((next % slides.length) + slides.length) % slides.length; index = i; slides.forEach((slide, si) => { const on = si === i; slide.classList.toggle("is-active", on); slide.setAttribute("aria-hidden", on ? "false" : "true"); }); dots.forEach((dot, di) => { const on = di === i; dot.classList.toggle("is-active", on); dot.setAttribute("aria-current", on ? "true" : "false"); }); root.dataset.active = String(i); }; const stop = () => { window.clearInterval(timer); timer = 0; }; const start = () => { stop(); if (paused || reduce || slides.length < 2) return; timer = window.setInterval(() => { show(index + 1); }, durationOf(index)); }; const goTo = (i) => { show(i); start(); }; dots.forEach((dot) => { dot.addEventListener("click", () => { const i = Number(dot.getAttribute("data-collab-goto")); if (!Number.isFinite(i)) return; goTo(i); }); }); nextBtn?.addEventListener("click", () => goTo(index + 1)); prevBtn?.addEventListener("click", () => goTo(index - 1)); root.addEventListener("mouseenter", () => { paused = true; stop(); }); root.addEventListener("mouseleave", () => { paused = reduce; start(); }); root.addEventListener("focusin", () => { paused = true; stop(); }); root.addEventListener("focusout", (e) => { if (e.relatedTarget instanceof Node && root.contains(e.relatedTarget)) return; paused = reduce; start(); }); let touchX = 0; let locked = ""; root.addEventListener( "touchstart", (e) => { const t = e.changedTouches[0]; if (!t) return; touchX = t.clientX; locked = ""; }, { passive: true } ); root.addEventListener( "touchmove", (e) => { const t = e.changedTouches[0]; if (!t || locked === "y") return; const dx = t.clientX - touchX; if (!locked && Math.abs(dx) > 12) locked = "x"; }, { passive: true } ); root.addEventListener( "touchend", (e) => { const t = e.changedTouches[0]; if (!t || locked !== "x") return; const dx = t.clientX - touchX; if (Math.abs(dx) < 48) return; if (dx < 0) goTo(index + 1); else goTo(index - 1); }, { passive: true } ); const io = new IntersectionObserver( (entries) => { const e = entries[0]; if (!e) return; if (!e.isIntersecting) stop(); else if (!paused) start(); }, { threshold: 0.25 } ); io.observe(root); show(0); if (!reduce) start(); }