(function () { const CHARS = ['~', '-', '_', '·', '°', '*', 'o', '+', '×', '=', '≈', '∿', '◦']; function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; } class Swirl { constructor(container) { this.container = container; this.cx = container.offsetWidth / 2 || window.innerWidth / 2; this.cy = container.offsetHeight / 2 || window.innerHeight / 2; this.elements = []; this.frameCount = 0; this.angle = 0; this.radius = 2; this.hue = Math.random() * 360; this.hueSpeed = 0.25; this.angleStep = 0.18; this.radiusStep = 0.55; this.globalRotation = 0; this.loop = this.loop.bind(this); requestAnimationFrame(this.loop); window.addEventListener('resize', () => { this.cx = container.offsetWidth / 2 || window.innerWidth / 2; this.cy = container.offsetHeight / 2 || window.innerHeight / 2; }); } createChar(x, y, hue, scale) { const color = `hsl(${hue}, 60%, 68%)`; const el = document.createElement('span'); el.textContent = pick(CHARS); el.classList.add('swirl-char'); el.style.cssText = `position:absolute;` + `left:${x}px;top:${y}px;` + `font-family:"Courier New",Courier,monospace;` + `font-size:15px;line-height:1;` + `color:${color};` + `pointer-events:none;user-select:none;` + `opacity:0.85;`; el.style.transform = `scale(${scale}) rotate(${Math.random() * 360}deg)`; el.style.display = 'inline-block'; this.container.appendChild(el); this.elements.push({ el, born: this.frameCount, maxAge: 900 + Math.floor(Math.random() * 400), }); } ageElements() { if (this.frameCount % 12 !== 0) return; const now = this.frameCount; let pruneCount = 0; const maxPrune = 12; for (let i = 0; i < this.elements.length && pruneCount < maxPrune; i++) { const item = this.elements[i]; const age = now - item.born; if (age >= item.maxAge) { item.el.remove(); this.elements.splice(i, 1); i--; pruneCount++; } else if (age > item.maxAge - 150) { const fade = 1 - (age - (item.maxAge - 150)) / 150; item.el.style.opacity = Math.max(0, fade * 0.85); } } } update() { this.frameCount++; // Rotation globale du tourbillon this.globalRotation += 0.002; // Évolution de la couleur this.hue = (this.hue + this.hueSpeed) % 360; // Ajouter un nouveau caractère à la spirale if (this.frameCount % 2 === 0) { this.angle += this.angleStep; // L'espacement augmente avec le rayon (spirale qui s'écarte) const dynamicStep = this.radiusStep + (this.radius / 100) * 0.18; this.radius += dynamicStep; const effectiveAngle = this.angle + this.globalRotation; const x = this.cx + Math.cos(effectiveAngle) * this.radius; const y = this.cy + Math.sin(effectiveAngle) * this.radius * 0.6; // Scale qui grandit avec le rayon const scale = 0.6 + (this.radius / 600) * 0.6; this.createChar(x, y, this.hue, scale); } // Quand le rayon dépasse l'écran, on recommence plus petit const maxRadius = Math.max(this.cx, this.cy) + 80; if (this.radius > maxRadius) { this.radius = 8 + Math.random() * 12; this.hue = (this.hue + 60 + Math.random() * 120) % 360; } this.ageElements(); } loop() { this.update(); requestAnimationFrame(this.loop); } } document.addEventListener('DOMContentLoaded', () => { const container = document.getElementById('swirl-container'); if (container) { new Swirl(container); } }); })();