feat: 全员一致时增加 confetti 粒子特效、庆祝音效和默契度文案
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import confetti from "canvas-confetti";
|
||||
|
||||
type ConfettiFn = confetti.CreateTypes;
|
||||
|
||||
let _cannon: ConfettiFn | null = null;
|
||||
|
||||
function getCannon(): ConfettiFn {
|
||||
if (_cannon) return _cannon;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.cssText =
|
||||
"position:fixed;inset:0;width:100vw;height:100vh;z-index:2147483647;pointer-events:none";
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
canvas.width = canvas.offsetWidth * (window.devicePixelRatio || 1);
|
||||
canvas.height = canvas.offsetHeight * (window.devicePixelRatio || 1);
|
||||
|
||||
_cannon = confetti.create(canvas, { resize: true });
|
||||
return _cannon;
|
||||
}
|
||||
|
||||
export function fireCelebration() {
|
||||
const fire = getCannon();
|
||||
const colors = ["#10b981", "#f59e0b", "#ef4444", "#6366f1", "#ec4899"];
|
||||
|
||||
fire({
|
||||
particleCount: 80,
|
||||
spread: 100,
|
||||
origin: { y: 0.35 },
|
||||
colors,
|
||||
startVelocity: 45,
|
||||
ticks: 200,
|
||||
});
|
||||
|
||||
const end = Date.now() + 2500;
|
||||
|
||||
const frame = () => {
|
||||
if (Date.now() > end) return;
|
||||
|
||||
fire({
|
||||
particleCount: 3,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
origin: { x: 0, y: 0.6 },
|
||||
colors,
|
||||
startVelocity: 35,
|
||||
ticks: 150,
|
||||
});
|
||||
fire({
|
||||
particleCount: 3,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
origin: { x: 1, y: 0.6 },
|
||||
colors,
|
||||
startVelocity: 35,
|
||||
ticks: 150,
|
||||
});
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
};
|
||||
|
||||
setTimeout(frame, 300);
|
||||
}
|
||||
|
||||
export function playChime() {
|
||||
try {
|
||||
const ctx = new AudioContext();
|
||||
const gain = ctx.createGain();
|
||||
gain.connect(ctx.destination);
|
||||
gain.gain.setValueAtTime(0.15, ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 1.2);
|
||||
|
||||
const notes = [523.25, 659.25, 783.99, 1046.5]; // C5, E5, G5, C6
|
||||
notes.forEach((freq, i) => {
|
||||
const osc = ctx.createOscillator();
|
||||
osc.type = "sine";
|
||||
osc.frequency.value = freq;
|
||||
|
||||
const noteGain = ctx.createGain();
|
||||
noteGain.connect(gain);
|
||||
|
||||
const start = ctx.currentTime + i * 0.12;
|
||||
noteGain.gain.setValueAtTime(0, start);
|
||||
noteGain.gain.linearRampToValueAtTime(0.3, start + 0.03);
|
||||
noteGain.gain.exponentialRampToValueAtTime(0.001, start + 0.6);
|
||||
|
||||
osc.connect(noteGain);
|
||||
osc.start(start);
|
||||
osc.stop(start + 0.6);
|
||||
});
|
||||
|
||||
setTimeout(() => ctx.close(), 2000);
|
||||
} catch {
|
||||
// Audio not available — silent fallback
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user