karsttech.com/assets/js/logo-sparks.js

151 lines
4.5 KiB
JavaScript
Raw Permalink Normal View History

2026-01-06 22:50:10 -05:00
// 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;
}
2026-01-06 22:50:10 -05:00
function random(min, max) {
return Math.random() * (max - min) + min;
}
function generatePath(radius, complexity, scale) {
2026-01-06 22:50:10 -05:00
const points = [];
const jitter = 50 * scale;
2026-01-06 22:50:10 -05:00
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);
2026-01-06 22:50:10 -05:00
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;
2026-01-06 22:50:10 -05:00
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;
2026-01-06 22:50:10 -05:00
const duration = random(config.minDuration, config.maxDuration);
const radius = random(config.minRadius, config.maxRadius) * scale;
2026-01-06 22:50:10 -05:00
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);
2026-01-06 22:50:10 -05:00
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);
2026-01-06 22:50:10 -05:00
allKeyframes += createKeyframes(i, path, isBehind);
sparks.push(spark);
}
// Inject keyframes
const style = document.createElement('style');
style.textContent = allKeyframes;
style.id = 'logo-sparks-keyframes';
2026-01-06 22:50:10 -05:00
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);
}
2026-01-06 22:50:10 -05:00
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSparks);
} else {
initSparks();
}
window.addEventListener('resize', handleResize);
2026-01-06 22:50:10 -05:00
})();