(() => {
"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);
})();