// Programmatic spark generation for homepage logo (function() { const config = { numSparks: 9, minSize: 1.5, maxSize: 3.5, minRadius: 180, maxRadius: 350, minDuration: 23, maxDuration: 49, pathComplexity: 17, behindLogoFraction: 0.75, minOpacity: 0.5, maxOpacity: 0.8, staggerAnimations: true }; function getViewportScale() { const width = window.innerWidth; if (width < 768) { return width < 480 ? 0.25 : 0.4; } return width < 1024 ? 0.7 : 1.0; } function random(min, max) { return Math.random() * (max - min) + min; } function generatePath(radius, complexity, scale) { const points = []; const jitter = 50 * scale; for (let i = 0; i <= complexity; i++) { const angle = (i / complexity) * Math.PI * 2; const r = random(radius * 0.8, radius * 1.2); const x = Math.cos(angle) * r + random(-jitter, jitter); const y = Math.sin(angle) * r + random(-jitter, jitter); points.push({ x, y, scale: random(0.7, 1.3) }); } return points; } function createKeyframes(id, path, isBehind) { const steps = path.length; let keyframes = '@keyframes spark' + id + ' {\n'; path.forEach((point, i) => { const percent = (i / (steps - 1)) * 100; const opacity = (i > 1 && i < steps - 2) ? random(config.minOpacity, config.maxOpacity) : 0; let opacityValue = opacity; if (isBehind) { const hiddenStart = random(30, 45); const hiddenEnd = random(55, 70); if (percent > hiddenStart && percent < hiddenEnd) { opacityValue = 0; } } keyframes += ` ${percent.toFixed(1)}% {\n`; keyframes += ` transform: translate(-50%, -50%) translateX(${point.x.toFixed(1)}px) translateY(${point.y.toFixed(1)}px) scale(${point.scale.toFixed(2)});\n`; keyframes += ` opacity: ${opacityValue.toFixed(2)};\n`; keyframes += ` }\n`; }); keyframes += '}\n'; return keyframes; } function initSparks() { const logoFigure = document.querySelector('body:has(.background-container) article.glass figure:first-of-type'); if (!logoFigure) return; const scale = getViewportScale(); if (scale < 0.3 && window.innerWidth < 400) return; let allKeyframes = ''; const sparks = []; for (let i = 0; i < config.numSparks; i++) { const spark = document.createElement('span'); spark.className = 'logo-spark'; spark.dataset.sparkId = i; const size = random(config.minSize, config.maxSize) * scale; const duration = random(config.minDuration, config.maxDuration); const radius = random(config.minRadius, config.maxRadius) * scale; const isBehind = Math.random() < config.behindLogoFraction; const delay = config.staggerAnimations ? random(0, config.maxDuration) : 0; spark.style.cssText = ` position: absolute; width: ${size}px; height: ${size}px; border-radius: 50%; background: radial-gradient(circle, rgba(255, 255, 255, 0.9), rgba(74, 158, 255, 0.6), transparent); box-shadow: 0 0 ${8 * scale}px rgba(74, 158, 255, 0.8), 0 0 ${4 * scale}px rgba(255, 255, 255, 0.6); top: 50%; left: 50%; pointer-events: none; z-index: ${isBehind ? -1 : 1}; animation: spark${i} ${duration}s linear infinite; animation-delay: -${delay.toFixed(2)}s; `; const path = generatePath(radius, config.pathComplexity, scale); allKeyframes += createKeyframes(i, path, isBehind); sparks.push(spark); } // Inject keyframes const style = document.createElement('style'); style.textContent = allKeyframes; style.id = 'logo-sparks-keyframes'; document.head.appendChild(style); // Inject sparks sparks.forEach(spark => logoFigure.appendChild(spark)); } function cleanupSparks() { const existingStyle = document.getElementById('logo-sparks-keyframes'); if (existingStyle) existingStyle.remove(); const sparks = document.querySelectorAll('.logo-spark'); sparks.forEach(spark => spark.remove()); } let resizeTimeout; function handleResize() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { cleanupSparks(); initSparks(); }, 250); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initSparks); } else { initSparks(); } window.addEventListener('resize', handleResize); })();