Files
GameList/wwwroot/js/effects.js

122 lines
3.6 KiB
JavaScript

// Effects & helpers --------------------------------------------------
// Screenshot hover ---------------------------------------------------
export function setupCardVisualHover(el, url) {
if (!el || !url) return;
el.addEventListener("mouseenter", () => {
el.classList.add("hovering");
});
["mouseleave", "blur"].forEach((evt) =>
el.addEventListener(evt, () => {
el.classList.remove("hovering");
}),
);
}
// Celebration FX -----------------------------------------------------
let fxCanvas;
let fxCtx;
let fxParticles = [];
let fxAnimating = false;
function ensureFxCanvas() {
if (fxCanvas) return;
fxCanvas = document.createElement("canvas");
fxCanvas.className = "fx-canvas";
fxCanvas.width = window.innerWidth;
fxCanvas.height = window.innerHeight;
fxCtx = fxCanvas.getContext("2d");
document.body.appendChild(fxCanvas);
window.addEventListener("resize", () => {
fxCanvas.width = window.innerWidth;
fxCanvas.height = window.innerHeight;
});
}
export function triggerCelebration(button) {
ensureFxCanvas();
const rect = (button || document.body).getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
spawnConfetti(x, y, 80);
spawnFirework(x, y, 40);
if (!fxAnimating) {
fxAnimating = true;
requestAnimationFrame(fxStep);
}
}
function spawnConfetti(x, y, count) {
for (let i = 0; i < count; i++) {
fxParticles.push({
x: x + (Math.random() - 0.5) * 20,
y: y + (Math.random() - 0.5) * 200,
vx: (Math.random() - 0.5) * 6,
vy: Math.random() * -6 - 2,
size: 6 + Math.random() * 4,
life: 600 + Math.random() * 200,
color: randomColor(),
type: "confetti",
wobble: Math.random() * Math.PI * 2,
});
}
}
function spawnFirework(x, y, count) {
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count + Math.random() * 0.3;
const speed = (3 + Math.random() * 3) * 0.1;
fxParticles.push({
x,
y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 3,
size: 4 + Math.random() * 3,
life: 500 + Math.random() * 200,
color: randomColor(),
type: "spark",
});
}
}
function fxStep() {
if (!fxCtx || !fxCanvas) return;
fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
fxParticles = fxParticles.filter((p) => p.life > 0);
for (const p of fxParticles) {
if (p.type === "confetti") {
p.vy += 0.05;
p.vx *= 0.999;
p.wobble += 0.2;
p.x += p.vx + Math.cos(p.wobble) * 0.8;
p.y += p.vy;
} else {
p.vy += 0.02;
p.vx *= 0.9995;
p.x += p.vx;
p.y += p.vy;
}
p.life -= 1;
fxCtx.fillStyle = p.color;
fxCtx.beginPath();
if (p.type === "confetti") {
fxCtx.fillRect(p.x, p.y, p.size, p.size * 0.6);
} else {
fxCtx.arc(p.x, p.y, p.size * 0.5, 0, Math.PI * 2);
fxCtx.fill();
}
}
if (fxParticles.length > 0) {
requestAnimationFrame(fxStep);
} else {
fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
fxAnimating = false;
}
}
function randomColor() {
const palette = ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"];
return palette[Math.floor(Math.random() * palette.length)];
}