website/assets/js/pages/404.js

209 lines
No EOL
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

feather.replace();
// Particles
const particlesContainer = document.getElementById('particles');
for (let i = 0; i < 30; i++) {
const particle = document.createElement('div');
particle.classList.add('particle');
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 10 + 's';
particle.style.animationDuration = (Math.random() * 5 + 8) + 's';
particle.style.width = (Math.random() * 4 + 2) + 'px';
particle.style.height = particle.style.width;
particlesContainer.appendChild(particle);
}
// Easter Egg: Konami Code (desktop) ou 10 taps (mobile)
const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
let konamiIndex = 0;
let tapCount = 0;
let tapTimer = null;
const REQUIRED_TAPS = 10;
const errorCodeEl = document.querySelector('.error-code');
// Desktop: Konami Code
document.addEventListener('keydown', (e) => {
if (e.key === konamiCode[konamiIndex]) {
konamiIndex++;
if (konamiIndex === konamiCode.length) {
startPortalTransition();
konamiIndex = 0;
}
} else {
konamiIndex = 0;
}
});
// Mobile: 10 taps
function handleTap(e) {
if (e.type === 'touchend') e.preventDefault();
tapCount++;
errorCodeEl.style.transform = 'scale(0.95)';
setTimeout(() => errorCodeEl.style.transform = '', 100);
if (tapCount >= 3 && tapCount < REQUIRED_TAPS) {
showTapCounter(tapCount, REQUIRED_TAPS);
}
if (tapTimer) clearTimeout(tapTimer);
tapTimer = setTimeout(() => { tapCount = 0; hideTapCounter(); }, 500);
if (tapCount >= REQUIRED_TAPS) {
tapCount = 0;
hideTapCounter();
startPortalTransition();
}
}
function showTapCounter(current, total) {
let counter = document.getElementById('tap-counter');
if (!counter) {
counter = document.createElement('div');
counter.id = 'tap-counter';
counter.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:rgba(139,58,98,0.9);color:white;padding:0.5rem 1rem;border-radius:20px;font-size:0.85rem;font-weight:600;z-index:1000;';
document.body.appendChild(counter);
}
counter.textContent = current + ' / ' + total + ' 🔓';
}
function hideTapCounter() {
const counter = document.getElementById('tap-counter');
if (counter) counter.remove();
}
errorCodeEl.addEventListener('click', handleTap);
errorCodeEl.addEventListener('touchend', handleTap);
// Portal Transition
function startPortalTransition() {
const transition = document.createElement('div');
transition.id = 'portal-transition';
transition.innerHTML = '<div class="warp-container"><div class="warp-tunnel"></div><div class="warp-stars"></div><div class="warp-center"><div class="warp-ring"></div><div class="warp-ring"></div><div class="warp-ring"></div></div><div class="warp-flash"></div></div><div class="warp-text">ENTRÉE DANS LA MATRICE</div>';
document.body.appendChild(transition);
const style = document.createElement('style');
style.id = 'transition-styles';
style.textContent = '#portal-transition{position:fixed;inset:0;z-index:10000;background:#000;display:flex;align-items:center;justify-content:center;overflow:hidden}.warp-container{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;perspective:500px}.warp-tunnel{position:absolute;width:200vmax;height:200vmax;background:repeating-conic-gradient(from 0deg,transparent 0deg 10deg,rgba(139,58,98,0.1) 10deg 20deg);animation:tunnelSpin 2s linear infinite,tunnelZoom 3s ease-in forwards}@keyframes tunnelSpin{to{transform:rotate(360deg)}}@keyframes tunnelZoom{0%{transform:rotate(0)scale(1);opacity:0}20%{opacity:1}100%{transform:rotate(720deg)scale(20);opacity:0}}.warp-stars{position:absolute;inset:0;background-image:radial-gradient(2px 2px at 20px 30px,#fff,transparent),radial-gradient(2px 2px at 40px 70px,rgba(255,255,255,0.8),transparent),radial-gradient(1px 1px at 90px 40px,#fff,transparent);background-size:350px 200px;animation:starsWarp 3s ease-in forwards}@keyframes starsWarp{0%{transform:scale(1);opacity:0}30%{opacity:1}100%{transform:scale(50);opacity:0}}.warp-center{position:relative;width:150px;height:150px;animation:centerPulse 0.5s ease-in-out infinite,centerGrow 2.5s ease-in forwards}@keyframes centerPulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.5)}}@keyframes centerGrow{0%{transform:scale(0);opacity:0}20%{transform:scale(1);opacity:1}80%{transform:scale(1);opacity:1}100%{transform:scale(100);opacity:1}}.warp-ring{position:absolute;inset:0;border:4px solid #8b3a62;border-radius:50%;animation:ringPulse 1s ease-in-out infinite;box-shadow:0 0 30px #8b3a62,inset 0 0 30px #8b3a62}.warp-ring:nth-child(2){inset:20px;border-color:#5e2a8b;animation-delay:0.2s;box-shadow:0 0 30px #5e2a8b,inset 0 0 30px #5e2a8b}.warp-ring:nth-child(3){inset:40px;border-color:#d4a5c9;animation-delay:0.4s;box-shadow:0 0 30px #d4a5c9,inset 0 0 30px #d4a5c9}@keyframes ringPulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.1);opacity:0.8}}.warp-flash{position:absolute;inset:0;background:#fff;opacity:0;animation:flashBang 0.5s ease-out 2.5s forwards}@keyframes flashBang{0%{opacity:0}50%{opacity:1}100%{opacity:0}}.warp-text{position:absolute;font-size:1.5rem;font-weight:700;color:#fff;letter-spacing:0.3rem;z-index:10;opacity:0;animation:textAppear 0.5s ease 0.5s forwards,textFade 0.5s ease 2s forwards}@keyframes textAppear{to{opacity:1}}@keyframes textFade{to{opacity:0;transform:scale(2)}}';
document.head.appendChild(style);
setTimeout(() => {
transition.remove();
style.remove();
launchGameOfLife();
}, 3000);
}
// Game of Life
function launchGameOfLife() {
const gameContainer = document.createElement('div');
gameContainer.id = 'game-of-life';
gameContainer.innerHTML = '<canvas id="gol-canvas"></canvas><div class="gol-ui"><div class="gol-header"><div class="gol-header-text"><h1>🧬 Dimension Secrète</h1><p>Le Jeu de la Vie de Conway</p></div><button id="gol-minimize" class="gol-minimize" title="Réduire"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="icon-min"><line x1="5" y1="12" x2="19" y2="12"/></svg><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="icon-max"><rect x="3" y="3" width="18" height="18" rx="2"/></svg></button></div><div class="gol-body"><div class="gol-description"><p><strong>Règles :</strong> Cellule vivante avec 2-3 voisins survit. Cellule morte avec 3 voisins naît.</p></div><div class="gol-controls"><button id="gol-play" class="gol-btn active" title="Play/Pause"><svg viewBox="0 0 24 24" fill="currentColor" class="icon-pause"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg><svg viewBox="0 0 24 24" fill="currentColor" class="icon-play"><polygon points="5,3 19,12 5,21"/></svg></button><button id="gol-step" class="gol-btn" title="Étape"><svg viewBox="0 0 24 24" fill="currentColor"><polygon points="5,3 15,12 5,21"/><rect x="15" y="3" width="4" height="18"/></svg></button><button id="gol-random" class="gol-btn" title="Aléatoire"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg></button><button id="gol-clear" class="gol-btn" title="Effacer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button><button id="gol-gliders" class="gol-btn" title="Planeurs"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><polygon points="22,2 15,22 11,13 2,9"/></svg></button></div><div class="gol-zoom-row"><button id="gol-zoom-out" class="gol-btn gol-btn-sm">−</button><span id="gol-zoom-val">30px</span><button id="gol-zoom-in" class="gol-btn gol-btn-sm">+</button></div><div class="gol-speed-row"><label>Vitesse: <span id="gol-speed-val">15</span> fps</label><input type="range" id="gol-speed" min="1" max="60" value="15"></div><div class="gol-stats"><div class="gol-stat"><span id="gol-gen">0</span><label>Génération</label></div><div class="gol-stat"><span id="gol-pop">0</span><label>Population</label></div><div class="gol-stat"><span id="gol-dim">0×0</span><label>Grille</label></div></div></div><button id="gol-exit" class="gol-exit">✕ Quitter</button></div>';
document.body.appendChild(gameContainer);
const gameStyles = document.createElement('style');
gameStyles.id = 'gol-styles';
gameStyles.textContent = '#game-of-life{position:fixed;inset:0;z-index:10000;background:#050508;animation:golIn 0.5s ease}@keyframes golIn{from{opacity:0}to{opacity:1}}#gol-canvas{position:absolute;inset:0;width:100%;height:100%;cursor:crosshair}.gol-ui{position:absolute;top:20px;left:20px;background:rgba(10,10,15,0.95);backdrop-filter:blur(10px);border:1px solid rgba(139,58,98,0.3);border-radius:16px;padding:1rem;min-width:280px;box-shadow:0 20px 60px rgba(0,0,0,0.5)}.gol-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem}.gol-header-text h1{font-size:1.1rem;margin-bottom:0.1rem;background:linear-gradient(135deg,#d4a5c9,#8b3a62);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.gol-header-text p{font-size:0.7rem;color:#6b6b7b}.gol-minimize{width:32px;height:32px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05);border-radius:8px;color:#b8b8c8;cursor:pointer;display:flex;align-items:center;justify-content:center}.gol-minimize:hover{background:rgba(139,58,98,0.3);border-color:#8b3a62;color:#fff}.gol-minimize svg{width:16px;height:16px}.gol-minimize .icon-max{display:none}.gol-ui.minimized .gol-body,.gol-ui.minimized .gol-exit{display:none}.gol-ui.minimized .gol-minimize .icon-min{display:none}.gol-ui.minimized .gol-minimize .icon-max{display:block}.gol-ui.minimized{min-width:auto}.gol-body{display:flex;flex-direction:column;gap:0.75rem}.gol-description{background:rgba(139,58,98,0.1);border:1px solid rgba(139,58,98,0.2);border-radius:8px;padding:0.5rem}.gol-description p{font-size:0.7rem;color:#b8b8c8;margin:0}.gol-description strong{color:#d4a5c9}.gol-controls{display:flex;gap:0.4rem;flex-wrap:wrap}.gol-btn{width:40px;height:40px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05);border-radius:8px;color:#b8b8c8;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.2s}.gol-btn:hover{background:rgba(139,58,98,0.3);border-color:#8b3a62;color:#fff;transform:translateY(-2px)}.gol-btn svg{width:18px;height:18px}.gol-btn.active{background:linear-gradient(135deg,#8b3a62,#5e2a8b);border-color:transparent;color:#fff}.gol-btn .icon-play{display:none}.gol-btn .icon-pause{display:block}.gol-btn:not(.active) .icon-play{display:block}.gol-btn:not(.active) .icon-pause{display:none}.gol-btn-sm{width:32px;height:32px;font-size:1.2rem;font-weight:bold}.gol-zoom-row,.gol-speed-row{display:flex;align-items:center;gap:0.5rem}.gol-zoom-row span{color:#d4a5c9;font-family:monospace;font-size:0.85rem;min-width:40px;text-align:center}.gol-speed-row{flex-direction:column;align-items:stretch}.gol-speed-row label{font-size:0.75rem;color:#b8b8c8}.gol-speed-row label span{color:#d4a5c9;font-family:monospace}.gol-speed-row input{width:100%;height:6px;-webkit-appearance:none;background:rgba(255,255,255,0.1);border-radius:3px;cursor:pointer}.gol-speed-row input::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;background:linear-gradient(135deg,#8b3a62,#5e2a8b);border-radius:50%;cursor:pointer}.gol-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:0.5rem}.gol-stat{text-align:center;padding:0.4rem;background:rgba(255,255,255,0.03);border-radius:6px}.gol-stat span{font-size:1rem;font-weight:700;color:#d4a5c9;font-family:monospace;display:block}.gol-stat label{font-size:0.6rem;color:#6b6b7b;text-transform:uppercase}.gol-exit{width:100%;margin-top:0.5rem;padding:0.6rem;background:transparent;border:1px solid rgba(255,255,255,0.1);border-radius:8px;color:#6b6b7b;font-size:0.8rem;cursor:pointer;transition:all 0.2s}.gol-exit:hover{background:rgba(255,50,50,0.2);border-color:rgba(255,50,50,0.5);color:#ff6b6b}@media(max-width:768px){.gol-ui{top:auto;bottom:0;left:0;right:0;border-radius:16px 16px 0 0;max-height:50vh;overflow-y:auto;min-width:auto}.gol-header-text p,.gol-description{display:none}.gol-ui.minimized{max-height:none}}@media(max-width:480px){.gol-ui{padding:0.75rem}.gol-btn{width:36px;height:36px}.gol-btn-sm{width:28px;height:28px}}';
document.head.appendChild(gameStyles);
const canvas = document.getElementById('gol-canvas');
const ctx = canvas.getContext('2d');
const MAX_CELLS = 150, MIN_SIZE = 4, MAX_SIZE = 30;
let cellSize = MAX_SIZE, minCellSize = MIN_SIZE, cols, rows, grid, nextGrid;
let isRunning = true, generation = 0, speed = 15, lastTime = 0, isDrawing = false, drawValue = 1;
function initCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
minCellSize = Math.max(MIN_SIZE, Math.ceil(canvas.width / MAX_CELLS), Math.ceil(canvas.height / MAX_CELLS));
cellSize = Math.max(minCellSize, Math.min(MAX_SIZE, cellSize));
cols = Math.ceil(canvas.width / cellSize);
rows = Math.ceil(canvas.height / cellSize);
grid = Array(cols).fill(null).map(() => Array(rows).fill(0));
nextGrid = Array(cols).fill(null).map(() => Array(rows).fill(0));
updateUI();
}
function addGlider(sx, sy, dir) {
const p = [[[0,1],[1,2],[2,0],[2,1],[2,2]],[[0,1],[1,0],[2,0],[2,1],[2,2]],[[0,0],[0,1],[0,2],[1,2],[2,1]],[[0,0],[0,1],[0,2],[1,0],[2,1]]];
p[dir % 4].forEach(([dx, dy]) => { const x = (sx + dx) % cols, y = (sy + dy) % rows; if (x >= 0 && x < cols && y >= 0 && y < rows) grid[x][y] = 1; });
}
function addGliders(n) {
grid = Array(cols).fill(null).map(() => Array(rows).fill(0));
for (let i = 0; i < n; i++) addGlider(Math.floor(Math.random() * (cols - 10)) + 5, Math.floor(Math.random() * (rows - 10)) + 5, Math.floor(Math.random() * 4));
generation = 0; updateUI();
}
function randomize() { for (let x = 0; x < cols; x++) for (let y = 0; y < rows; y++) grid[x][y] = Math.random() > 0.7 ? 1 : 0; generation = 0; updateUI(); }
function clear() { for (let x = 0; x < cols; x++) for (let y = 0; y < rows; y++) grid[x][y] = 0; generation = 0; updateUI(); }
function step() {
for (let x = 0; x < cols; x++) for (let y = 0; y < rows; y++) {
let n = 0;
for (let i = -1; i <= 1; i++) for (let j = -1; j <= 1; j++) if (i || j) n += grid[(x + i + cols) % cols][(y + j + rows) % rows];
nextGrid[x][y] = grid[x][y] ? (n === 2 || n === 3 ? 1 : 0) : (n === 3 ? 1 : 0);
}
[grid, nextGrid] = [nextGrid, grid]; generation++; updateUI();
}
function draw() {
ctx.fillStyle = '#0a0a0f'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'rgba(139,58,98,0.1)'; ctx.lineWidth = 0.5;
for (let x = 0; x <= cols * cellSize; x += cellSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, rows * cellSize); ctx.stroke(); }
for (let y = 0; y <= rows * cellSize; y += cellSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(cols * cellSize, y); ctx.stroke(); }
ctx.shadowColor = 'rgba(139,58,98,0.5)'; ctx.shadowBlur = 8;
for (let x = 0; x < cols; x++) for (let y = 0; y < rows; y++) if (grid[x][y]) {
const g = ctx.createRadialGradient(x * cellSize + cellSize / 2, y * cellSize + cellSize / 2, 0, x * cellSize + cellSize / 2, y * cellSize + cellSize / 2, cellSize);
g.addColorStop(0, '#d4a5c9'); g.addColorStop(0.5, '#8b3a62'); g.addColorStop(1, '#5e2a8b');
ctx.fillStyle = g; ctx.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 2, cellSize - 2);
}
ctx.shadowBlur = 0;
}
function updateUI() {
let pop = 0; for (let x = 0; x < cols; x++) for (let y = 0; y < rows; y++) pop += grid[x][y];
document.getElementById('gol-gen').textContent = generation;
document.getElementById('gol-pop').textContent = pop;
document.getElementById('gol-dim').textContent = cols + '×' + rows;
document.getElementById('gol-zoom-val').textContent = cellSize + 'px';
document.getElementById('gol-speed-val').textContent = speed;
}
function handleZoom(d) {
const old = cellSize; cellSize = Math.max(minCellSize, Math.min(MAX_SIZE, cellSize + d));
if (cellSize !== old) { const og = grid, oc = cols, or = rows; initCanvas(); for (let x = 0; x < Math.min(oc, cols); x++) for (let y = 0; y < Math.min(or, rows); y++) grid[x][y] = og[x][y]; updateUI(); }
}
function getCell(e) {
const r = canvas.getBoundingClientRect(), cx = e.touches ? e.touches[0].clientX : e.clientX, cy = e.touches ? e.touches[0].clientY : e.clientY;
return { x: Math.max(0, Math.min(cols - 1, Math.floor((cx - r.left) / cellSize))), y: Math.max(0, Math.min(rows - 1, Math.floor((cy - r.top) / cellSize))) };
}
function gameLoop(t) { if (isRunning && t - lastTime > 1000 / speed) { step(); lastTime = t; } draw(); requestAnimationFrame(gameLoop); }
canvas.addEventListener('mousedown', e => { isDrawing = true; const c = getCell(e); drawValue = grid[c.x][c.y] ? 0 : 1; grid[c.x][c.y] = drawValue; });
canvas.addEventListener('mousemove', e => { if (isDrawing) { const c = getCell(e); grid[c.x][c.y] = drawValue; } });
canvas.addEventListener('mouseup', () => isDrawing = false);
canvas.addEventListener('mouseleave', () => isDrawing = false);
canvas.addEventListener('touchstart', e => { e.preventDefault(); isDrawing = true; const c = getCell(e); drawValue = grid[c.x][c.y] ? 0 : 1; grid[c.x][c.y] = drawValue; }, { passive: false });
canvas.addEventListener('touchmove', e => { e.preventDefault(); if (isDrawing) { const c = getCell(e); grid[c.x][c.y] = drawValue; } }, { passive: false });
canvas.addEventListener('touchend', () => isDrawing = false);
canvas.addEventListener('wheel', e => { e.preventDefault(); handleZoom(e.deltaY > 0 ? -1 : 1); });
document.getElementById('gol-play').addEventListener('click', function() { isRunning = !isRunning; this.classList.toggle('active', isRunning); });
document.getElementById('gol-step').addEventListener('click', () => { if (!isRunning) step(); });
document.getElementById('gol-random').addEventListener('click', randomize);
document.getElementById('gol-clear').addEventListener('click', clear);
document.getElementById('gol-gliders').addEventListener('click', () => addGliders(3));
document.getElementById('gol-zoom-in').addEventListener('click', () => handleZoom(2));
document.getElementById('gol-zoom-out').addEventListener('click', () => handleZoom(-2));
document.getElementById('gol-speed').addEventListener('input', function() { speed = parseInt(this.value); updateUI(); });
document.getElementById('gol-minimize').addEventListener('click', () => document.querySelector('.gol-ui').classList.toggle('minimized'));
document.getElementById('gol-exit').addEventListener('click', () => { document.getElementById('game-of-life').remove(); document.getElementById('gol-styles').remove(); });
window.addEventListener('resize', () => { const og = grid, oc = cols, or = rows; initCanvas(); for (let x = 0; x < Math.min(oc, cols); x++) for (let y = 0; y < Math.min(or, rows); y++) grid[x][y] = og[x][y]; updateUI(); });
initCanvas();
addGliders(3);
requestAnimationFrame(gameLoop);
}