From 02ae132a7a399280b5cc6f032d7e1b09f6a1a672 Mon Sep 17 00:00:00 2001 From: Tony M Date: Sat, 4 Jul 2026 11:33:33 -0700 Subject: [PATCH] Add fireworks effect on ticket CTA clicks Canvas-based particle system that triggers when clicking any href=#tickets button. Uses theme colors, auto-clears when done. --- js/fireworks.js | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 js/fireworks.js diff --git a/js/fireworks.js b/js/fireworks.js new file mode 100644 index 0000000..a7ef969 --- /dev/null +++ b/js/fireworks.js @@ -0,0 +1,95 @@ +(function () { + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + canvas.style.cssText = + "position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999;"; + document.body.appendChild(canvas); + + var W = (canvas.width = window.innerWidth); + var H = (canvas.height = window.innerHeight); + + window.addEventListener("resize", function () { + W = canvas.width = window.innerWidth; + H = canvas.height = window.innerHeight; + }); + + var particles = []; + var raf = null; + var colors = ["#3a6b5e", "#c8a24a", "#e0c068", "#5a9384", "#f5e4cf", "#2d5448"]; + + function Particle(x, y, color) { + this.x = x; + this.y = y; + this.color = color; + var angle = Math.random() * Math.PI * 2; + var speed = Math.random() * 6 + 2; + this.vx = Math.cos(angle) * speed; + this.vy = Math.sin(angle) * speed; + this.alpha = 1; + this.decay = Math.random() * 0.015 + 0.01; + this.size = Math.random() * 3 + 1; + } + + Particle.prototype.update = function () { + this.x += this.vx; + this.y += this.vy; + this.vy += 0.08; + this.vx *= 0.99; + this.vy *= 0.99; + this.alpha -= this.decay; + }; + + Particle.prototype.draw = function () { + ctx.globalAlpha = this.alpha; + ctx.fillStyle = this.color; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + }; + + function explode(x, y) { + for (var i = 0; i < 80; i++) { + particles.push(new Particle(x, y, colors[Math.floor(Math.random() * colors.length)])); + } + if (!raf) loop(); + } + + function loop() { + raf = requestAnimationFrame(loop); + ctx.clearRect(0, 0, W, H); + for (var i = particles.length - 1; i >= 0; i--) { + var p = particles[i]; + p.update(); + p.draw(); + if (p.alpha <= 0) particles.splice(i, 1); + } + if (particles.length === 0) { + cancelAnimationFrame(raf); + raf = null; + ctx.clearRect(0, 0, W, H); + } + } + + function handleClick(e) { + var x = e.clientX; + var y = e.clientY; + + explode(x, y); + setTimeout(function () { explode(x - 80, y - 60); }, 120); + setTimeout(function () { explode(x + 80, y - 40); }, 240); + setTimeout(function () { explode(x, y - 100); }, 360); + } + + function isTicketsCTA(el) { + if (!el) return false; + do { + if (el.tagName === "A" && el.getAttribute("href") === "#tickets") return true; + el = el.parentElement; + } while (el); + return false; + } + + document.addEventListener("click", function (e) { + if (isTicketsCTA(e.target)) handleClick(e); + }); +})(); \ No newline at end of file