Fireworks Effects

CSS-only animated fireworks for celebrations and special moments

burst-effect

Expanding particle burst animation using multiple radial gradients

burst-effect
<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

theme-variants
<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

multiple-display
<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

color-variants

Patriotic

Red, White & Blue

Click to launch

Purple Magic

Purple & White

Click to launch

Success

Green & Gold

Click to launch

Celebration

Gold & Yellow

Click to launch

Romance

Red & Pink

Click to launch

Ocean

Blue & Cyan

Click to launch

🌈 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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

continuous-animation
<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

hover-trigger
<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-to-launch

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 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

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-1 through --dm-fw-color-8 - Custom colours