/* ========================= CONFIG ========================= */ const MIN_TIME = 10000; const startTime = performance.now(); /* ========================= HELPERS ========================= */ function getTimeStamp() { return new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } /* ========================= SEQUENTIAL TEXT + FROZEN TIME ========================= */ const steps = document.querySelectorAll(".step"); steps.forEach((el, i) => { setTimeout(() => { const timeEl = el.querySelector(".time"); if (timeEl) timeEl.textContent = getTimeStamp(); el.classList.add("visible"); }, i * 600); }); /* ========================= RANDOM GRID / PIXEL ART ========================= */ const grid = document.getElementById("grid"); let gridSquares = []; // Track for cleanup let pixelIntervals = []; // Track pixel intervals for cleanup if (grid) { const squareCount = Math.floor(Math.random() * 6) + 8; const occupiedCells = new Set(); // Track occupied grid positions for (let i = 0; i < squareCount; i++) { const square = document.createElement("div"); square.className = "square"; // Find an unoccupied position let col, row, posKey; let attempts = 0; do { col = Math.floor(Math.random() * 5) + 1; row = Math.floor(Math.random() * 4) + 1; posKey = `${col}-${row}`; attempts++; } while (occupiedCells.has(posKey) && attempts < 50); // Mark position as occupied occupiedCells.add(posKey); square.style.gridColumn = col; square.style.gridRow = row; // Make squares visible immediately without animation square.style.opacity = "1"; square.style.transform = "none"; // Draw pixels on hover (can be triggered multiple times) const drawHandler = () => { const canvas = square.querySelector("canvas"); if (!canvas || !square.hasPixels) { drawPixels(square); if (!square.pixelInterval) { startPixelCycle(square); // Start the 5-second cycle for this square } } }; square.addEventListener("pointerenter", drawHandler); gridSquares.push(square); grid.appendChild(square); } } function drawPixels(el) { const existingCanvas = el.querySelector("canvas"); if (existingCanvas) { existingCanvas.remove(); } const canvas = document.createElement("canvas"); canvas.width = el.clientWidth; canvas.height = el.clientHeight; canvas.style.position = "absolute"; canvas.style.top = "0"; canvas.style.left = "0"; el.appendChild(canvas); const ctx = canvas.getContext("2d"); const colors = ["#ff4fd8", "#9333ea", "#ef4444"]; const px = 6; // Fill background ctx.fillStyle = "#0a0a0c"; ctx.fillRect(0, 0, canvas.width, canvas.height); // Collect all pixel positions that will be drawn const pixels = []; for (let x = 0; x < canvas.width; x += px) { for (let y = 0; y < canvas.height; y += px) { if (Math.random() > 0.6) { pixels.push({ x: x, y: y, color: colors[Math.floor(Math.random() * colors.length)] }); } } } // Mark that this square has pixels el.hasPixels = true; // Create an offscreen canvas to draw all pixels at once const offscreenCanvas = document.createElement("canvas"); offscreenCanvas.width = canvas.width; offscreenCanvas.height = canvas.height; const offscreenCtx = offscreenCanvas.getContext("2d"); // Draw all pixels on offscreen canvas offscreenCtx.fillStyle = "#0a0a0c"; offscreenCtx.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height); pixels.forEach(pixel => { offscreenCtx.fillStyle = pixel.color; offscreenCtx.fillRect(pixel.x, pixel.y, px, px); }); // Animate the entire pixel pattern sliding in from left const duration = 400; // ms for animation const startTime = performance.now(); const startX = -canvas.width; // Start completely off-screen to the left const endX = 0; function animate(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Clear canvas ctx.fillStyle = "#0a0a0c"; ctx.fillRect(0, 0, canvas.width, canvas.height); // Calculate current X position const currentX = startX + (endX - startX) * progress; // Draw the entire offscreen canvas at the current position ctx.drawImage(offscreenCanvas, currentX, 0); if (progress < 1) { requestAnimationFrame(animate); } } requestAnimationFrame(animate); } function clearPixels(el) { const canvas = el.querySelector("canvas"); if (!canvas) return; const ctx = canvas.getContext("2d"); // Create an offscreen canvas with current pixel state const offscreenCanvas = document.createElement("canvas"); offscreenCanvas.width = canvas.width; offscreenCanvas.height = canvas.height; const offscreenCtx = offscreenCanvas.getContext("2d"); // Copy current canvas to offscreen offscreenCtx.drawImage(canvas, 0, 0); // Animate the entire pixel pattern sliding out to the right const duration = 400; // ms for animation const startTime = performance.now(); const startX = 0; const endX = canvas.width; // Slide completely off-screen to the right function animate(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Clear canvas ctx.fillStyle = "#0a0a0c"; ctx.fillRect(0, 0, canvas.width, canvas.height); // Calculate current X position const currentX = startX + (endX - startX) * progress; // Draw the entire offscreen canvas at the current position ctx.drawImage(offscreenCanvas, currentX, 0); if (progress < 1) { requestAnimationFrame(animate); } else { // Final clear ctx.fillStyle = "#0a0a0c"; ctx.fillRect(0, 0, canvas.width, canvas.height); } } requestAnimationFrame(animate); } function startPixelCycle(square) { const interval = setInterval(() => { clearPixels(square); square.hasPixels = false; // Mark that pixels are cleared }, 5000); pixelIntervals.push(interval); // Store interval on the element for later cleanup square.pixelInterval = interval; } /* ========================= LOADER CLEANUP ========================= */ function cleanupLoader() { // Clear all intervals pixelIntervals.forEach(interval => clearInterval(interval)); pixelIntervals = []; // Remove grid squares gridSquares.forEach(sq => sq.remove()); gridSquares = []; // Remove loader const loader = document.getElementById("loader"); if (loader) { loader.style.opacity = "0"; loader.style.pointerEvents = "none"; setTimeout(() => { loader.remove(); }, 800); } } /* ========================= LOADER HANDOFF ========================= */ function hideLoader() { const elapsed = performance.now() - startTime; const remaining = Math.max(0, MIN_TIME - elapsed); setTimeout(() => { document.body.classList.remove("loading"); cleanupLoader(); // Clean up properly }, remaining); } window.addEventListener("load", hideLoader); /* ========================= LOADING BAR - OPTIMIZED ========================= */ const loadingBar = document.getElementById('loading-bar'); let currentLoadingInterval = null; function showLoadingBar() { // Clear any existing interval first if (currentLoadingInterval) { clearInterval(currentLoadingInterval); } loadingBar.style.width = '0%'; loadingBar.style.display = 'block'; setTimeout(() => loadingBar.style.width = '30%', 50); let progress = 30; currentLoadingInterval = setInterval(() => { progress += Math.random() * 20; if (progress < 90) { loadingBar.style.width = progress + '%'; } else { clearInterval(currentLoadingInterval); currentLoadingInterval = null; } }, 300); return currentLoadingInterval; } function completeLoadingBar(interval) { if (interval) { clearInterval(interval); if (interval === currentLoadingInterval) { currentLoadingInterval = null; } } loadingBar.style.width = '100%'; setTimeout(() => { loadingBar.style.width = '0%'; }, 400); } // Initial page load let pageLoadInterval; if (document.readyState === 'loading') { pageLoadInterval = showLoadingBar(); } window.addEventListener('load', () => { completeLoadingBar(pageLoadInterval); });