118 lines
3.6 KiB
JavaScript
118 lines
3.6 KiB
JavaScript
// 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 random(min, max) {
|
|
return Math.random() * (max - min) + min;
|
|
}
|
|
|
|
function generatePath(radius, complexity) {
|
|
const points = [];
|
|
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(-50, 50);
|
|
const y = Math.sin(angle) * r + random(-50, 50);
|
|
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;
|
|
|
|
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);
|
|
const duration = random(config.minDuration, config.maxDuration);
|
|
const radius = random(config.minRadius, config.maxRadius);
|
|
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 8px rgba(74, 158, 255, 0.8), 0 0 4px 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);
|
|
allKeyframes += createKeyframes(i, path, isBehind);
|
|
|
|
sparks.push(spark);
|
|
}
|
|
|
|
// Inject keyframes
|
|
const style = document.createElement('style');
|
|
style.textContent = allKeyframes;
|
|
document.head.appendChild(style);
|
|
|
|
// Inject sparks
|
|
sparks.forEach(spark => logoFigure.appendChild(spark));
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initSparks);
|
|
} else {
|
|
initSparks();
|
|
}
|
|
})();
|
|
|