Fireworks Effects
CSS-only animated fireworks for celebrations and special moments
burst-effect
Expanding particle burst animation using multiple radial gradients
<div class="demo-box mb-4" style="background: linear-gradient(to bottom, #000428, #004e92); min-height: 400px; position: relative; overflow: hidden;">
<div id="firework-demo-1" style="position: absolute; inset: 0;">
<!-- Particles will be generated here -->
</div>
<div style="position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 10; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;">
<button type="button" class="btn btn-warning btn-sm" data-action="1">
Celebration
</button>
<button type="button" class="btn btn-light btn-sm" data-action="2">Special</button>
<button type="button" class="btn btn-primary btn-sm" data-action="3">Birthday
</button>
<button type="button" class="btn btn-danger btn-sm" data-action="4">Romance</button>
<button type="button" class="btn btn-success btn-sm" data-action="5">Success</button>
</div>
</div>/**
* burst-effect - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Restart animations on button click for demo
// Add click to restart for demo boxes
$('.demo-box').css('cursor', 'pointer').on('click', function () {
$(this).find('.firework').each((i, fw) => {
// If it has the new structure
const launch = fw.querySelector('.firework-launch');
const particles = fw.querySelector('.firework-particles');
if (launch && particles) {
// Clone and replace to restart animations
const newLaunch = launch.cloneNode(true);
const newParticles = particles.cloneNode(true);
launch.parentNode.replaceChild(newLaunch, launch);
particles.parentNode.replaceChild(newParticles, particles);
} else {
// Old structure - just clone the whole thing
const newFw = fw.cloneNode(true);
fw.parentNode.replaceChild(newFw, fw);
}
});
});
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
launchFirework('firework-demo-1', 'celebration');
});
$(root).find('[data-action="2"]').on('click', function (event) {
launchFirework('firework-demo-1', 'special');
});
$(root).find('[data-action="3"]').on('click', function (event) {
launchFirework('firework-demo-1', 'birthday');
});
$(root).find('[data-action="4"]').on('click', function (event) {
launchFirework('firework-demo-1', 'romance');
});
$(root).find('[data-action="5"]').on('click', function (event) {
launchFirework('firework-demo-1', 'success');
});
}<div class="firework firework-burst">
<div class="firework-launch"></div>
<div class="firework-particles"></div>
</div>
theme-variants
Different colour themes for various occasions
<div class="demo-box mb-4" style="background: linear-gradient(to bottom, #1a1a2e, #0f0f1e); min-height: 400px; position: relative; overflow: hidden;">
<div id="firework-demo-2" style="position: absolute; inset: 0;">
<!-- Particles will be generated here -->
</div>
<div style="position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 10; display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; max-width: 500px;">
<button type="button" class="btn btn-info btn-sm" data-action="1">Winter</button>
<button type="button" class="btn btn-danger btn-sm" data-action="2">Christmas
</button>
<button type="button" class="btn btn-warning btn-sm" data-action="3">New Year
</button>
<button type="button" class="btn btn-primary btn-sm" data-action="4">Patriotic
</button>
<button type="button" class="btn btn-warning btn-sm" data-action="5">Sunset</button>
<button type="button" class="btn btn-info btn-sm" data-action="6">Ocean</button>
<button type="button" class="btn btn-secondary btn-sm" data-action="7">Purple</button>
<button type="button" class="btn btn-light btn-sm" data-action="8">Special</button>
</div>
</div>/**
* theme-variants - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
launchFirework('firework-demo-2', 'winter');
});
$(root).find('[data-action="2"]').on('click', function (event) {
launchFirework('firework-demo-2', 'christmas');
});
$(root).find('[data-action="3"]').on('click', function (event) {
launchFirework('firework-demo-2', 'newyear');
});
$(root).find('[data-action="4"]').on('click', function (event) {
launchFirework('firework-demo-2', 'patriotic');
});
$(root).find('[data-action="5"]').on('click', function (event) {
launchFirework('firework-demo-2', 'sunset');
});
$(root).find('[data-action="6"]').on('click', function (event) {
launchFirework('firework-demo-2', 'ocean');
});
$(root).find('[data-action="7"]').on('click', function (event) {
launchFirework('firework-demo-2', 'purple');
});
$(root).find('[data-action="8"]').on('click', function (event) {
launchFirework('firework-demo-2', 'special');
});
}multiple-display
Launch multiple fireworks with different themes simultaneously
<div class="demo-box mb-4" style="background: linear-gradient(to bottom, #000428, #004e92); min-height: 500px; position: relative; overflow: hidden;">
<div id="firework-demo-3" style="position: absolute; inset: 0;">
<!-- Multiple firework containers -->
</div>
<div style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 10; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;">
<button type="button" class="btn btn-primary btn-lg" data-action="1">
🎆 Grand Display (8)
</button>
<button type="button" class="btn btn-warning" data-action="2">
🎊 Celebration (5)
</button>
<button type="button" class="btn btn-light" data-action="3">
✨ New Year (10)
</button>
</div>
</div>/**
* multiple-display - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Launch multiple fireworks function
function launchMultipleFireworks(containerId = 'firework-demo-3', theme = 'mixed', count = 5) {
const container = $('#' + containerId)[0];
if (!container) return;
// Clear any existing fireworks for a fresh display
container.innerHTML = '';
// Calculate optimal particle count based on total fireworks
// Formula: More fireworks = fewer particles each for better performance
const particleCount = Math.max(50, Math.min(200, Math.floor(300 / count)));
// If mixed theme, use random themes
const themes = ['celebration', 'special', 'birthday', 'newyear', 'christmas', 'valentine', 'ocean', 'purple', 'patriotic', 'sunset', 'romance', 'success', 'winter'];
for (let i = 0; i < count; i++) {
setTimeout(() => {
const currentTheme = theme === 'mixed' ? themes[Math.floor(Math.random() * themes.length)] : theme;
const position = 20 + Math.random() * 60; // Random position between 20% and 80%
launchFirework(containerId, currentTheme, position, particleCount);
}, i * 300 + Math.random() * 200);
}
}
// Continuous display functionality
let continuousInterval = null;
function startContinuousDisplay(containerId = 'continuous-container') {
if (continuousInterval) return;
// Launch immediately with optimized count
launchMultipleFireworks(containerId, 'mixed', 3);
// Then launch every 2 seconds with smaller displays for performance
continuousInterval = setInterval(() => {
const count = Math.floor(Math.random() * 2) + 2; // 2-3 fireworks for continuous display
launchMultipleFireworks(containerId, 'mixed', count);
}, 2000);
}
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
launchMultipleFireworks('firework-demo-3', 'mixed', 8);
});
$(root).find('[data-action="2"]').on('click', function (event) {
launchMultipleFireworks('firework-demo-3', 'celebration', 5);
});
$(root).find('[data-action="3"]').on('click', function (event) {
launchMultipleFireworks('firework-demo-3', 'newyear', 10);
});
return () => clearInterval(continuousInterval);
}// Launch multiple fireworks
function launchMultipleFireworks(containerId, theme, count) {
const themes = ['celebration', 'special', 'birthday', 'newyear'];
for (let i = 0; i < count; i++) {
setTimeout(() => {
const currentTheme = theme === 'mixed' ?
themes[Math.floor(Math.random() * themes.length)] : theme;
const position = 20 + Math.random() * 60;
launchFirework(containerId, currentTheme, position);
}, i * 300 + Math.random() * 200);
}
}
color-variants
Click each card to launch themed fireworks
🌈 Rainbow Spectacular
Multiple themes in one grand display
Click
for spectacular display
<div class="card bg-gradient-to-br from-gray-900 to-gray-800" style="min-height: 300px; position: relative; cursor: pointer; overflow: hidden;" data-action="1">
<div id="color-primary" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-end" style="position: relative; z-index: 10; pointer-events: none;">
<h4 class="text-white mb-2">Patriotic</h4>
<p class="text-white-50 small mb-0">Red, White & Blue</p>
<code class="mt-2" style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.5rem; border-radius: 4px; display: inline-block;">Click
to launch</code>
</div>
</div>
<div class="card bg-gradient-to-br from-purple-900 to-purple-800" style="min-height: 300px; position: relative; cursor: pointer; overflow: hidden;" data-action="2">
<div id="color-secondary" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-end" style="position: relative; z-index: 10; pointer-events: none;">
<h4 class="text-white mb-2">Purple Magic</h4>
<p class="text-white-50 small mb-0">Purple & White</p>
<code class="mt-2" style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.5rem; border-radius: 4px; display: inline-block;">Click
to launch</code>
</div>
</div>
<div class="card bg-gradient-to-br from-green-900 to-green-800" style="min-height: 300px; position: relative; cursor: pointer; overflow: hidden;" data-action="3">
<div id="color-success" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-end" style="position: relative; z-index: 10; pointer-events: none;">
<h4 class="text-white mb-2">Success</h4>
<p class="text-white-50 small mb-0">Green & Gold</p>
<code class="mt-2" style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.5rem; border-radius: 4px; display: inline-block;">Click
to launch</code>
</div>
</div>
<div class="card bg-gradient-to-br from-yellow-900 to-yellow-800" style="min-height: 300px; position: relative; cursor: pointer; overflow: hidden;" data-action="4">
<div id="color-warning" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-end" style="position: relative; z-index: 10; pointer-events: none;">
<h4 class="text-white mb-2">Celebration</h4>
<p class="text-white-50 small mb-0">Gold & Yellow</p>
<code class="mt-2" style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.5rem; border-radius: 4px; display: inline-block;">Click
to launch</code>
</div>
</div>
<div class="card bg-gradient-to-br from-red-900 to-pink-900" style="min-height: 300px; position: relative; cursor: pointer; overflow: hidden;" data-action="5">
<div id="color-danger" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-end" style="position: relative; z-index: 10; pointer-events: none;">
<h4 class="text-white mb-2">Romance</h4>
<p class="text-white-50 small mb-0">Red & Pink</p>
<code class="mt-2" style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.5rem; border-radius: 4px; display: inline-block;">Click
to launch</code>
</div>
</div>
<div class="card bg-gradient-to-br from-blue-900 to-cyan-900" style="min-height: 300px; position: relative; cursor: pointer; overflow: hidden;" data-action="6">
<div id="color-info" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-end" style="position: relative; z-index: 10; pointer-events: none;">
<h4 class="text-white mb-2">Ocean</h4>
<p class="text-white-50 small mb-0">Blue & Cyan</p>
<code class="mt-2" style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.5rem; border-radius: 4px; display: inline-block;">Click
to launch</code>
</div>
</div>
<div id="color-rainbow" style="position: absolute; inset: 0;"></div>
<div class="card-body d-flex flex-column justify-content-center align-items-center" style="position: relative; z-index: 10; pointer-events: none;">
<h3 class="text-white mb-3">🌈 Rainbow Spectacular</h3>
<p class="text-white-50 mb-3">Multiple themes in one grand display</p>
<code style="background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.9); padding: 0.75rem 1.5rem; border-radius: 4px; font-size: 1.1rem;">Click
for spectacular display</code>
</div>/**
* color-variants - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Launch multiple fireworks function
function launchMultipleFireworks(containerId = 'firework-demo-3', theme = 'mixed', count = 5) {
const container = $('#' + containerId)[0];
if (!container) return;
// Clear any existing fireworks for a fresh display
container.innerHTML = '';
// Calculate optimal particle count based on total fireworks
// Formula: More fireworks = fewer particles each for better performance
const particleCount = Math.max(50, Math.min(200, Math.floor(300 / count)));
// If mixed theme, use random themes
const themes = ['celebration', 'special', 'birthday', 'newyear', 'christmas', 'valentine', 'ocean', 'purple', 'patriotic', 'sunset', 'romance', 'success', 'winter'];
for (let i = 0; i < count; i++) {
setTimeout(() => {
const currentTheme = theme === 'mixed' ? themes[Math.floor(Math.random() * themes.length)] : theme;
const position = 20 + Math.random() * 60; // Random position between 20% and 80%
launchFirework(containerId, currentTheme, position, particleCount);
}, i * 300 + Math.random() * 200);
}
}
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
launchFirework('color-primary', 'patriotic', 50);
});
$(root).find('[data-action="2"]').on('click', function (event) {
launchFirework('color-secondary', 'purple', 50);
});
$(root).find('[data-action="3"]').on('click', function (event) {
launchFirework('color-success', 'success', 50);
});
$(root).find('[data-action="4"]').on('click', function (event) {
launchFirework('color-warning', 'celebration', 50);
});
$(root).find('[data-action="5"]').on('click', function (event) {
launchFirework('color-danger', 'romance', 50);
});
$(root).find('[data-action="6"]').on('click', function (event) {
launchFirework('color-info', 'ocean', 50);
});
$(root).find('[data-action="7"]').on('click', function (event) {
launchMultipleFireworks('color-rainbow', 'mixed', 5);
});
}continuous-animation
Loop the fireworks animation infinitely for ongoing celebrations
<div class="demo-box dark-demo-box mb-4" style="min-height: 400px; position: relative;">
<div id="continuous-demo" style="position: absolute; inset: 0;"></div>
<div style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 10; display: flex; gap: 10px;">
<button type="button" class="btn btn-primary btn-sm" data-action="1">Start Display
</button>
<button type="button" class="btn btn-secondary btn-sm" data-action="2">Stop Display</button>
</div>
</div>/**
* continuous-animation - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Launch multiple fireworks function
function launchMultipleFireworks(containerId = 'firework-demo-3', theme = 'mixed', count = 5) {
const container = $('#' + containerId)[0];
if (!container) return;
// Clear any existing fireworks for a fresh display
container.innerHTML = '';
// Calculate optimal particle count based on total fireworks
// Formula: More fireworks = fewer particles each for better performance
const particleCount = Math.max(50, Math.min(200, Math.floor(300 / count)));
// If mixed theme, use random themes
const themes = ['celebration', 'special', 'birthday', 'newyear', 'christmas', 'valentine', 'ocean', 'purple', 'patriotic', 'sunset', 'romance', 'success', 'winter'];
for (let i = 0; i < count; i++) {
setTimeout(() => {
const currentTheme = theme === 'mixed' ? themes[Math.floor(Math.random() * themes.length)] : theme;
const position = 20 + Math.random() * 60; // Random position between 20% and 80%
launchFirework(containerId, currentTheme, position, particleCount);
}, i * 300 + Math.random() * 200);
}
}
// Continuous display functionality
let continuousInterval = null;
function startContinuousDisplay(containerId = 'continuous-container') {
if (continuousInterval) return;
// Launch immediately with optimized count
launchMultipleFireworks(containerId, 'mixed', 3);
// Then launch every 2 seconds with smaller displays for performance
continuousInterval = setInterval(() => {
const count = Math.floor(Math.random() * 2) + 2; // 2-3 fireworks for continuous display
launchMultipleFireworks(containerId, 'mixed', count);
}, 2000);
}
function stopContinuousDisplay() {
if (continuousInterval) {
clearInterval(continuousInterval);
continuousInterval = null;
}
}
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
startContinuousDisplay('continuous-demo');
});
$(root).find('[data-action="2"]').on('click', function (event) {
stopContinuousDisplay();
});
return () => clearInterval(continuousInterval);
}// Start continuous fireworks display
startContinuousDisplay('container-id');
// Stop the display
stopContinuousDisplay();
hover-trigger
Trigger fireworks animation on hover for interactive elements
<div class="demo-box mb-4" style="min-height: 400px; position: relative;">
<div id="hover-demo" style="position: absolute; inset: 0;"></div>
<button type="button" id="hover-btn" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10;" class="btn btn-primary btn-lg celebration-btn">
🎉 Hover to Celebrate!
</button>
</div>/**
* hover-trigger - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Add hover trigger
$(root).find('#hover-btn').on('mouseenter', () => {
launchFirework('hover-demo', 'celebration', 50);
});
}// Add hover trigger to button
button.addEventListener('mouseenter', () => {
launchFirework('container-id', 'celebration');
});
click-to-launch
Click anywhere on the canvas to launch fireworks at that position
Click anywhere to launch fireworks
<div class="demo-box dark-demo-box mb-4" style="min-height: 400px; position: relative; cursor: crosshair;">
<div id="click-demo" style="position: absolute; inset: 0;"></div>
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; pointer-events: none; z-index: 5;">
<p style="color: rgba(255,255,255,0.5); font-size: 1.2rem;">Click anywhere to launch fireworks</p>
</div>
</div>/**
* click-to-launch - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Add click to launch
const clickDemo = $(root).find('#click-demo')[0];
if (clickDemo && clickDemo.parentElement) {
$(clickDemo.parentElement).on('click', (e) => {
if (e.target.tagName !== 'BUTTON') {
const rect = clickDemo.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const themes = ['celebration', 'birthday', 'newyear', 'patriotic', 'christmas', 'valentine', 'special', 'ocean'];
const theme = themes[Math.floor(Math.random() * themes.length)];
launchFirework('click-demo', theme, x);
}
});
}
}// Click to launch at mouse position
container.addEventListener('click', (e) => {
const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
launchFirework('container-id', 'celebration', x);
});
usage-examples
Real-world applications of fireworks effects
Success Message
Congratulations! Your form was submitted successfully. Celebrate
Achievement Badge
🏆 Achievement Unlocked! (Click me!)
<div id="achievement-demo" style="position: absolute; inset: 0;"></div>
<div class="badge badge-lg badge-warning p-4" style="position: relative; font-size: 1.2rem; z-index: 10; cursor: pointer;" data-action="1">
🏆 Achievement Unlocked! (Click me!)
</div>/**
* Achievement Badge - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Launch multiple fireworks function
function launchMultipleFireworks(containerId = 'firework-demo-3', theme = 'mixed', count = 5) {
const container = $('#' + containerId)[0];
if (!container) return;
// Clear any existing fireworks for a fresh display
container.innerHTML = '';
// Calculate optimal particle count based on total fireworks
// Formula: More fireworks = fewer particles each for better performance
const particleCount = Math.max(50, Math.min(200, Math.floor(300 / count)));
// If mixed theme, use random themes
const themes = ['celebration', 'special', 'birthday', 'newyear', 'christmas', 'valentine', 'ocean', 'purple', 'patriotic', 'sunset', 'romance', 'success', 'winter'];
for (let i = 0; i < count; i++) {
setTimeout(() => {
const currentTheme = theme === 'mixed' ? themes[Math.floor(Math.random() * themes.length)] : theme;
const position = 20 + Math.random() * 60; // Random position between 20% and 80%
launchFirework(containerId, currentTheme, position, particleCount);
}, i * 300 + Math.random() * 200);
}
}
// Handlers the old page had inline (onclick=…), wired here.
$(root).find('[data-action="1"]').on('click', function (event) {
launchMultipleFireworks('achievement-demo', 'celebration', 2);
});
}Celebration Button
<div id="celebrate-demo" style="position: absolute; inset: 0;"></div>
<button type="button" class="btn btn-primary btn-lg" style="position: relative; z-index: 10;" id="celebrate-btn">
Complete Purchase
</button>/**
* Celebration Button - from public/showcase/effects/fireworks.
*/
export function mount(root) {
function launchFirework(containerId, theme = 'celebration', position = 50, particleCount = 200) {
const container = $('#' + containerId)[0];
if (!container) return;
// DON'T clear container - allow multiple fireworks
// Create individual container for this firework
const fireworkContainer = document.createElement('div');
fireworkContainer.style.cssText = `
position: absolute;
bottom: 0;
left: ${position}%;
width: 10px;
height: 100%;
transform: translateX(-50%);
`;
container.appendChild(fireworkContainer);
// Create rocket trail going up
const rocket = document.createElement('div');
rocket.style.cssText = `
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 3px;
height: 10px;
background: linear-gradient(to top, transparent, #ffa500, #fff);
box-shadow: 0 0 10px #ffa500;
animation: rocket-launch 1s ease-out forwards;
z-index: 100;
`;
fireworkContainer.appendChild(rocket);
// After rocket reaches top, create explosion
setTimeout(() => {
rocket.remove();
createExplosion(fireworkContainer, theme, particleCount);
// Clean up the entire firework container after animation completes
// Shorter cleanup for multiple fireworks to improve performance
const containerCleanup = particleCount > 100 ? 3000 : 2500;
setTimeout(() => {
fireworkContainer.remove();
}, containerCleanup);
}, 1000);
}
function createExplosion(container, theme = 'celebration', particleCount = 200) {
// Dynamic particle count based on context
// Themed color palettes
const themes = {
celebration: ['#ffd700', '#ffed4e', '#fff59d', '#ffecb3'], // Gold/yellow
special: ['#ffd700', '#ffed4e', '#c0c0c0', '#e0e0e0', '#ffffff'], // Gold & silver
birthday: ['#ff1744', '#ff5252', '#2196f3', '#64b5f6', '#ffffff'], // Red, blue, white
romance: ['#ff1744', '#ff69b4', '#ff8a95', '#ffc1cc', '#ffffff'], // Reds & pinks
success: ['#4caf50', '#8bc34a', '#ffd700', '#ffeb3b'], // Green & gold
winter: ['#2196f3', '#64b5f6', '#bbdefb', '#ffffff', '#e1f5fe'], // Blues & white
christmas: ['#ff1744', '#f44336', '#4caf50', '#66bb6a', '#ffd700'], // Red, green, gold
newyear: ['#ffd700', '#ffed4e', '#ffffff', '#c0c0c0', '#e0e0e0'], // Gold, silver, white
patriotic: ['#ff1744', '#ffffff', '#2196f3', '#1976d2'], // Red, white, blue
sunset: ['#ff6b35', '#ff8c42', '#ffd23f', '#ffaa00', '#ff7043'], // Orange/yellow
ocean: ['#006994', '#2196f3', '#40e0d0', '#00bcd4', '#ffffff'], // Ocean blues
purple: ['#9c27b0', '#ba68c8', '#ce93d8', '#e1bee7', '#ffffff'] // Purple monochrome
};
const colors = themes[theme] || themes.celebration;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
// Random angle for more natural distribution
const angle = Math.random() * Math.PI * 2;
const velocity = 80 + Math.random() * 150; // Increased explosion speeds
const color = colors[Math.floor(Math.random() * colors.length)];
const particleSize = 3 + Math.random() * 4; // Larger particles
particle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: ${particleSize}px;
height: ${particleSize}px;
background: ${color};
border-radius: 50%;
box-shadow: 0 0 ${particleSize * 3}px ${color}, 0 0 ${particleSize * 6}px ${color};
pointer-events: none;
z-index: 50;
`;
// Simple explosion outward - no complex falling
const endX = Math.cos(angle) * velocity;
const endY = Math.sin(angle) * velocity;
particle.animate([
{
transform: 'translate(-50%, 0) scale(0)',
opacity: 0
},
{
// Quick burst
transform: 'translate(-50%, 0) scale(1)',
opacity: 1,
offset: 0.1
},
{
// Explode outward
transform: `translate(calc(-50% + ${endX}px), ${endY}px) scale(1.2)`,
opacity: 1,
offset: 0.3
},
{
// Continue outward and start fading
transform: `translate(calc(-50% + ${endX * 1.5}px), ${endY * 1.5}px) scale(1)`,
opacity: 0.8,
offset: 0.5
},
{
// Fade out in place
transform: `translate(calc(-50% + ${endX * 1.8}px), ${endY * 1.8}px) scale(0.8)`,
opacity: 0.4,
offset: 0.7
},
{
// Almost gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.5)`,
opacity: 0.1,
offset: 0.9
},
{
// Gone
transform: `translate(calc(-50% + ${endX * 2}px), ${endY * 2}px) scale(0.3)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 2500 : 2000, // Shorter duration for multiple fireworks
easing: 'cubic-bezier(0, 0.5, 0.5, 1)',
fill: 'forwards'
});
container.appendChild(particle);
// Remove particle after animation - shorter cleanup for multiple fireworks
const cleanupDelay = particleCount > 100 ? 2500 : 2000;
setTimeout(() => particle.remove(), cleanupDelay);
}
// Add sparkle trails - scale with particle count
const sparkleCount = Math.max(5, Math.floor(particleCount / 20));
for (let i = 0; i < sparkleCount; i++) {
setTimeout(() => {
const sparkle = document.createElement('div');
const angle = Math.random() * Math.PI * 2;
const distance = 50 + Math.random() * 100;
sparkle.style.cssText = `
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 4px #fff;
pointer-events: none;
z-index: 50;
`;
sparkle.animate([
{
transform: 'translate(-50%, 0) scale(1)',
opacity: 1
},
{
transform: `translate(calc(-50% + ${Math.cos(angle) * distance}px), ${Math.sin(angle) * distance}px) scale(0)`,
opacity: 0
}
], {
duration: particleCount > 100 ? 1000 : 800,
easing: 'ease-out',
fill: 'forwards'
});
container.appendChild(sparkle);
const sparkleCleanup = particleCount > 100 ? 1000 : 800;
setTimeout(() => sparkle.remove(), sparkleCleanup);
}, i * 50);
}
}
// Launch multiple fireworks function
function launchMultipleFireworks(containerId = 'firework-demo-3', theme = 'mixed', count = 5) {
const container = $('#' + containerId)[0];
if (!container) return;
// Clear any existing fireworks for a fresh display
container.innerHTML = '';
// Calculate optimal particle count based on total fireworks
// Formula: More fireworks = fewer particles each for better performance
const particleCount = Math.max(50, Math.min(200, Math.floor(300 / count)));
// If mixed theme, use random themes
const themes = ['celebration', 'special', 'birthday', 'newyear', 'christmas', 'valentine', 'ocean', 'purple', 'patriotic', 'sunset', 'romance', 'success', 'winter'];
for (let i = 0; i < count; i++) {
setTimeout(() => {
const currentTheme = theme === 'mixed' ? themes[Math.floor(Math.random() * themes.length)] : theme;
const position = 20 + Math.random() * 60; // Random position between 20% and 80%
launchFirework(containerId, currentTheme, position, particleCount);
}, i * 300 + Math.random() * 200);
}
}
// Celebrate button example
const $celebrate = $(root).find('#celebrate-btn');
if ($celebrate.length) {
$celebrate.on('click', () => {
// Launch multiple celebration fireworks
launchMultipleFireworks('celebrate-demo', 'celebration', 5);
// Change button text temporarily
const originalText = $celebrate.text();
$celebrate.text('🎉 Purchase Complete!').prop('disabled', true);
setTimeout(() => {
$celebrate.text(originalText).prop('disabled', false);
}, 3000);
});
}
}css-reference
Base Classes
.firework- Base firework container.firework-burst- Expanding particle burst effect.firework-sparkle- Twinkling stars effect.firework-trail- Falling particle trail effect
Size Modifiers
.firework-sm- Small size (100px).firework-md- Medium size (300px).firework-lg- Large size (400px).firework-xl- Extra large size (500px)
Colour Modifiers
.firework-primary- Primary theme colours.firework-success- Success theme colours.firework-rainbow- Rainbow colours
Behavior Modifiers
.firework-continuous- Loop animation infinitely.firework-on-hover- Trigger on hover
CSS Variables
--dm-fw-duration- Animation duration (default: 1.5s)--dm-fw-delay- Animation delay (default: 0s)--dm-fw-color-1through--dm-fw-color-8- Custom colours