document.addEventListener("DOMContentLoaded", () => { // init const introduction = document.querySelector("#introduction"); if (introduction) playIntroduction(); initNav(); initNotes(); styleCallouts(); linkLayersScrolls(); let currentUrl = location.href; window.onpopstate = function () { let target = null; const currentLayerId = document.querySelector(".current").id; if (currentLayerId === "first-level") { window.location.href = window.location.href; } else if (currentLayerId === "second-level") { history.pushState(null, document.title, currentUrl); target = document.querySelector("#first-level .layer-btn"); const event = { preventDefault: () => {}, stopPropagation: () => {}, target: target, }; go(event); } else if (currentLayerId === "third-level") { history.pushState(null, document.title, currentUrl); target = document.querySelector("#second-level .layer-btn"); const event = { preventDefault: () => {}, stopPropagation: () => {}, target: target, }; go(event); } }; const collapsableSectionBtns = document.querySelectorAll( ".collapsable__button" ); collapsableSectionBtns.forEach((btn) => { btn.addEventListener("click", () => { const section = btn.parentNode; section.classList.toggle("open"); }); }); animateCursor(); // functions let ticking = false; function linkLayersScrolls() { const thirdLayer = document.querySelector("#third-level"); let secondLayer = document.querySelector("#second-level"); thirdLayer.addEventListener("scroll", () => { if (!ticking) { window.requestAnimationFrame(() => { scrollLayer(secondLayer, thirdLayer.scrollTop); ticking = false; }); ticking = true; } }); } function scrollLayer(secondLayer, scrollTop) { if (window.innerWidth > 650) return; const parallaxFactor = 0.5; if (secondLayer.scrollTop <= 40) { let newScrollTop = scrollTop * parallaxFactor; newScrollTop = newScrollTop > 40 ? 40 : newScrollTop; secondLayer.scrollTop = newScrollTop; } } function playIntroduction() { const parts = introduction.querySelectorAll("span"); const step = 800; parts.forEach((part, index) => { setTimeout(() => { part.classList.remove("hide"); }, step + step * index); }); setTimeout(() => { introduction.classList.add("hide"); }, parts.length + 5 * step); } function formatSize(size) { if (size < 1_000) { return { size: size, unit: " o", }; } else if (size < 1_000_000) { return { size: size / 1_000, unit: " Ko", }; } else if (size < 1_000_000_000) { return { size: size / 1_000_000, unit: " Mo", }; } else { return { size: size / 1_000_000_000, unit: " Go", }; } } function initNotes() { const notes = document.querySelectorAll(".note"); notes.forEach((note) => { note.addEventListener("click", () => { note.classList.toggle("open"); note.querySelector(".details").classList.toggle("hide"); }); }); } function animateCursor() { const cursor = document.getElementById("cursor"); let animationFrameId; document.addEventListener("mousemove", (e) => { if (window.innerWidth < 640) return; const hoveredLayer = e.target.closest(".layer"); if (hoveredLayer.id === "first-level") { cursor.classList.add("light"); } else if (!e.target.closest(".collapsable__button")) { cursor.classList.remove("light"); } isCursorVisible = true; cursor.style.display = "flex"; document.body.style.cursor = "none"; const x = e.clientX - 16; const y = e.clientY - 16 + window.scrollY; cancelAnimationFrame(animationFrameId); animationFrameId = requestAnimationFrame(() => { cursor.style.left = x + "px"; cursor.style.top = y + "px"; }); }); } function initNav() { listenLinks(); showTargetPageSize(); listenCollapsableSections(); } function listenLinks() { const internalLinks = document.querySelectorAll("a.internal-link"); internalLinks.forEach((btn) => { btn.addEventListener("click", go); }); const imageLinks = document.querySelectorAll("figure a"); imageLinks.forEach((link) => { const cursorText = document.querySelector("#cursor #text"); link.addEventListener("mouseenter", () => { const href = new URL(link.href); cursorText.style.display = "flex"; cursorText.textContent = "ouvrir " + href.host; }); link.addEventListener("mouseleave", () => { cursorText.style.display = ""; cursorText.textContent = ""; }); }); } function removeListeners() { const internalLinks = document.querySelectorAll("a.internal-link"); internalLinks.forEach((btn) => { btn.removeEventListener("click", go); }); } function removeTargetPageListeners() { const internalLinks = document.querySelectorAll("a.internal-link"); internalLinks.forEach((link) => { link.removeEventListener("mouseenter", handleMouseEnter); link.removeEventListener("mouseleave", handleMouseLeave); }); } function handleMouseEnter() { const cursorText = document.querySelector("#cursor #text"); if (this.href === window.location.href) return; cursorText.style.display = "flex"; cursorText.textContent = this.dataset.size; } function handleMouseLeave() { const cursorText = document.querySelector("#cursor #text"); cursorText.style.display = ""; cursorText.textContent = ""; } function toggleVisibility(element, condition) { element.classList.remove("unvisible", condition); } function moveBackward(wrapper, page) { page.content .querySelector(".current .layer-btn") .classList.remove("unvisible"); wrapper.innerHTML = page.content.innerHTML; wrapper.classList.add("current"); setTimeout(() => { wrapper.querySelector(".current .layer-btn").classList.add("unvisible"); }, 50); } function moveForward(originWrapper, page) { const isThirdLevel = originWrapper.id === "third-level"; const targetWrapper = isThirdLevel ? document.querySelector("#second-level") : originWrapper.nextElementSibling; originWrapper.classList.remove("current"); targetWrapper.classList.add("current"); const emptys = page.content.querySelectorAll(".empty"); const current = document.querySelector(".current"); if (current.id === "second-level" && emptys.length === 3) { const firstEmpty = page.content.querySelector(".empty"); firstEmpty.parentNode.removeChild(firstEmpty); } targetWrapper.innerHTML = page.content.innerHTML; targetWrapper.classList.remove("out-screen"); } function go(event) { event.stopPropagation(); event.preventDefault(); const btn = event.target.closest("a"); const wrapper = btn.closest(".layer"); const targetUrl = btn.href; if (targetUrl === window.location.href) return; removeListeners(); removeTargetPageListeners(); fetch(targetUrl) .then((res) => res.text()) .then((htmlString) => { const page = getPage(htmlString); history.pushState(null, page.title, targetUrl); let timer = 0; const currentLevel = document.querySelector(".current"); if (currentLevel.scrollTop > 0) { document.querySelector(".current").scrollTo({ top: 0, behavior: "smooth", }); timer = 500; } setTimeout(() => { if (wrapper.classList.contains("current")) { moveForward(wrapper, page); } else { moveBackward(wrapper, page); } document.querySelector("title").textContent = `${page.title}`; document.querySelectorAll(`.current ~ .layer`).forEach((layer) => { layer.classList.add("out-screen"); }); const layerBtnsToShow = document.querySelectorAll( ".layer-btn:not(.current .layer-btn)" ); layerBtnsToShow.forEach((layerBtn) => { toggleVisibility(layerBtn, window.innerWidth < 640); }); listenLinks(); showTargetPageSize(); styleCallouts(); initNotes(); linkLayersScrolls(); currentUrl = window.location.href; }, timer); }) .catch((err) => { console.error("Fetch Error:", err); }); } function showTargetPageSize() { const internalLinks = document.querySelectorAll("a.internal-link"); internalLinks.forEach((link) => { link.addEventListener("mouseenter", handleMouseEnter); link.addEventListener("mouseleave", handleMouseLeave); }); } function listenCollapsableSections() { const collapsableSections = document.querySelectorAll( ".collapsable__button" ); const cursorText = document.querySelector("#cursor #text"); collapsableSections.forEach((section) => { section.addEventListener("mouseenter", () => { cursorText.style.display = "flex"; cursorText.textContent = "ouvrir/fermer"; }); section.addEventListener("mouseleave", () => { cursorText.style.display = ""; cursorText.textContent = ""; }); }); } }); function getPage(htmlString) { const parser = new DOMParser(); const fullPage = parser.parseFromString(htmlString, "text/html"); const title = fullPage.querySelector("title").textContent; const content = fullPage.querySelector(".layer.current"); return { title: title, content: content, }; } function getUrlLastPart(url) { const parts = url.split("/"); const lastPart = parts[parts.length - 1]; return lastPart; } function styleCallouts() { const callouts = document.querySelectorAll(".callout"); callouts.forEach((callout) => { if (!callout.closest("p")) return; callout.closest("p").classList.add("callout"); }); }