168 lines
4.6 KiB
JavaScript
168 lines
4.6 KiB
JavaScript
import { ButtonEffect } from "../effect-base.js";
|
|
|
|
/**
|
|
* Sparkle/Twinkle background effect
|
|
* Random twinkling stars overlay - classic 88x31 button aesthetic
|
|
*/
|
|
export class SparkleEffect extends ButtonEffect {
|
|
constructor() {
|
|
super({
|
|
id: "bg-sparkle",
|
|
name: "Sparkle",
|
|
type: "background-animation",
|
|
category: "Background Animations",
|
|
renderOrder: 15,
|
|
});
|
|
|
|
this.sparkles = [];
|
|
this.initialized = false;
|
|
}
|
|
|
|
defineControls() {
|
|
return [
|
|
{
|
|
id: "animate-sparkle",
|
|
type: "checkbox",
|
|
label: "Sparkle Effect",
|
|
defaultValue: false,
|
|
},
|
|
{
|
|
id: "sparkle-density",
|
|
type: "range",
|
|
label: "Sparkle Count",
|
|
defaultValue: 20,
|
|
min: 5,
|
|
max: 50,
|
|
step: 1,
|
|
showWhen: "animate-sparkle",
|
|
description: "Number of sparkles",
|
|
},
|
|
{
|
|
id: "sparkle-size",
|
|
type: "range",
|
|
label: "Max Size",
|
|
defaultValue: 3,
|
|
min: 1,
|
|
max: 5,
|
|
step: 0.5,
|
|
showWhen: "animate-sparkle",
|
|
description: "Maximum sparkle size",
|
|
},
|
|
{
|
|
id: "sparkle-speed",
|
|
type: "range",
|
|
label: "Twinkle Speed",
|
|
defaultValue: 1.5,
|
|
min: 0.5,
|
|
max: 3,
|
|
step: 0.1,
|
|
showWhen: "animate-sparkle",
|
|
description: "How fast sparkles twinkle",
|
|
},
|
|
{
|
|
id: "sparkle-color",
|
|
type: "color",
|
|
label: "Sparkle Color",
|
|
defaultValue: "#ffffff",
|
|
showWhen: "animate-sparkle",
|
|
description: "Color of sparkles",
|
|
},
|
|
];
|
|
}
|
|
|
|
isEnabled(controlValues) {
|
|
return controlValues["animate-sparkle"] === true;
|
|
}
|
|
|
|
apply(context, controlValues, animState, renderData) {
|
|
if (!animState) return;
|
|
|
|
const density = controlValues["sparkle-density"] || 20;
|
|
const maxSize = controlValues["sparkle-size"] || 3;
|
|
const speed = controlValues["sparkle-speed"] || 1.5;
|
|
const sparkleColor = controlValues["sparkle-color"] || "#ffffff";
|
|
|
|
// Initialize sparkles on first frame or density change
|
|
if (!this.initialized || this.sparkles.length !== density) {
|
|
this.sparkles = [];
|
|
for (let i = 0; i < density; i++) {
|
|
this.sparkles.push({
|
|
x: Math.random() * renderData.width,
|
|
y: Math.random() * renderData.height,
|
|
phase: Math.random() * Math.PI * 2,
|
|
size: 1 + Math.random() * (maxSize - 1),
|
|
speedMult: 0.7 + Math.random() * 0.6,
|
|
});
|
|
}
|
|
this.initialized = true;
|
|
}
|
|
|
|
// Parse color
|
|
const hexToRgb = (hex) => {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result
|
|
? {
|
|
r: parseInt(result[1], 16),
|
|
g: parseInt(result[2], 16),
|
|
b: parseInt(result[3], 16),
|
|
}
|
|
: { r: 255, g: 255, b: 255 };
|
|
};
|
|
|
|
const rgb = hexToRgb(sparkleColor);
|
|
|
|
// Draw sparkles
|
|
this.sparkles.forEach((sparkle) => {
|
|
// Calculate twinkle phase - each sparkle has its own timing
|
|
const phase = animState.getPhase(speed * sparkle.speedMult) + sparkle.phase;
|
|
|
|
// Use sin² for smooth fade in/out, with some sparkles fully off
|
|
const rawAlpha = Math.sin(phase);
|
|
const alpha = rawAlpha > 0 ? rawAlpha * rawAlpha : 0;
|
|
|
|
if (alpha < 0.05) return; // Skip nearly invisible sparkles
|
|
|
|
const size = sparkle.size * (0.5 + alpha * 0.5);
|
|
|
|
// Draw 4-point star shape
|
|
context.save();
|
|
context.translate(sparkle.x, sparkle.y);
|
|
context.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
|
|
|
|
// Draw diamond/star shape
|
|
context.beginPath();
|
|
|
|
// Horizontal points
|
|
context.moveTo(-size, 0);
|
|
context.lineTo(0, -size * 0.3);
|
|
context.lineTo(size, 0);
|
|
context.lineTo(0, size * 0.3);
|
|
context.closePath();
|
|
context.fill();
|
|
|
|
// Vertical points
|
|
context.beginPath();
|
|
context.moveTo(0, -size);
|
|
context.lineTo(size * 0.3, 0);
|
|
context.lineTo(0, size);
|
|
context.lineTo(-size * 0.3, 0);
|
|
context.closePath();
|
|
context.fill();
|
|
|
|
// Center glow
|
|
const gradient = context.createRadialGradient(0, 0, 0, 0, 0, size * 0.5);
|
|
gradient.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`);
|
|
gradient.addColorStop(1, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0)`);
|
|
context.fillStyle = gradient;
|
|
context.beginPath();
|
|
context.arc(0, 0, size * 0.5, 0, Math.PI * 2);
|
|
context.fill();
|
|
|
|
context.restore();
|
|
});
|
|
}
|
|
}
|
|
|
|
export function register(generator) {
|
|
generator.registerEffect(new SparkleEffect());
|
|
}
|