(() => { "use strict"; const slides = Array.from(document.querySelectorAll(".slide")); const currentPage = document.getElementById("currentPage"); const totalPages = document.getElementById("totalPages"); const progressBar = document.getElementById("progressBar"); const prevButton = document.getElementById("prevButton"); const nextButton = document.getElementById("nextButton"); const tocButton = document.getElementById("tocButton"); const tocCloseButton = document.getElementById("tocCloseButton"); const tocDialog = document.getElementById("tocDialog"); const tocList = document.getElementById("tocList"); const fullscreenButton = document.getElementById("fullscreenButton"); const printButton = document.getElementById("printButton"); let activeIndex = 0; let touchStartX = null; const formatPage = (value) => String(value).padStart(2, "0"); function indexFromHash() { const match = window.location.hash.match(/^#slide-(\d+)$/); if (!match) return 0; return Math.min(Math.max(Number(match[1]) - 1, 0), slides.length - 1); } function render(index, updateHash = true) { activeIndex = Math.min(Math.max(index, 0), slides.length - 1); slides.forEach((slide, slideIndex) => { const isActive = slideIndex === activeIndex; slide.classList.toggle("is-active", isActive); slide.setAttribute("aria-hidden", String(!isActive)); }); currentPage.textContent = formatPage(activeIndex + 1); totalPages.textContent = formatPage(slides.length); progressBar.style.width = `${((activeIndex + 1) / slides.length) * 100}%`; prevButton.disabled = activeIndex === 0; nextButton.disabled = activeIndex === slides.length - 1; document.title = `${slides[activeIndex].dataset.title} | Data Analyst Portfolio`; if (updateHash) history.replaceState(null, "", `#slide-${activeIndex + 1}`); window.scrollTo({ top: 0, behavior: "instant" }); } function move(delta) { render(activeIndex + delta); } function buildToc() { slides.forEach((slide, index) => { const button = document.createElement("button"); button.type = "button"; button.innerHTML = `${formatPage(index + 1)}${slide.dataset.title}`; button.addEventListener("click", () => { render(index); tocDialog.close(); }); tocList.appendChild(button); }); } async function toggleFullscreen() { try { if (!document.fullscreenElement) { await document.documentElement.requestFullscreen(); } else { await document.exitFullscreen(); } } catch (error) { console.warn("Fullscreen is unavailable in this browser.", error); } } prevButton.addEventListener("click", () => move(-1)); nextButton.addEventListener("click", () => move(1)); tocButton.addEventListener("click", () => tocDialog.showModal()); tocCloseButton.addEventListener("click", () => tocDialog.close()); fullscreenButton.addEventListener("click", toggleFullscreen); printButton.addEventListener("click", () => window.print()); document.addEventListener("keydown", (event) => { if (tocDialog.open && event.key !== "Escape") return; if (["ArrowRight", "PageDown", " "].includes(event.key)) { event.preventDefault(); move(1); } else if (["ArrowLeft", "PageUp"].includes(event.key)) { event.preventDefault(); move(-1); } else if (event.key === "Home") { event.preventDefault(); render(0); } else if (event.key === "End") { event.preventDefault(); render(slides.length - 1); } }); document.addEventListener("touchstart", (event) => { touchStartX = event.changedTouches[0]?.clientX ?? null; }, { passive: true }); document.addEventListener("touchend", (event) => { if (touchStartX === null) return; const touchEndX = event.changedTouches[0]?.clientX ?? touchStartX; const distance = touchEndX - touchStartX; if (Math.abs(distance) > 60) move(distance < 0 ? 1 : -1); touchStartX = null; }, { passive: true }); window.addEventListener("hashchange", () => render(indexFromHash(), false)); buildToc(); render(indexFromHash(), false); })();