PageSourceSearch

https://fireworkchampions.co.uk/assets/hero-bursts.js?v=3

js fireworkchampions.co.uk collected 2026-10-08 14:52:32 UTC 4,487 bytes, 90 lines download raw bytes

1/* Overlapping decorative bursts, behind the homepage text and controls. */
2(() => {
3  const hero = document.querySelector('.home-hero');
4  if (!hero || hero.querySelector('.hero-bursts')) return;
5  const reduced = matchMedia('(prefers-reduced-motion: reduce)');
6  const canvas = document.createElement('canvas');
7  canvas.className = 'hero-bursts';
8  canvas.setAttribute('aria-hidden', 'true');
9  canvas.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;z-index:-2;pointer-events:none;';
10  const ctx = canvas.getContext('2d');
11  if (!ctx) return;
12  hero.insertBefore(canvas, hero.querySelector('.hero-shade'));
13  const toggle = document.createElement('button');
14  toggle.type = 'button';
15  toggle.style.cssText = 'position:absolute;bottom:12px;left:24px;z-index:3;background:#04112599;border:1px solid #ffffff30;border-radius:4px;color:#d8e2ed;padding:6px 10px;font:12px Arial,sans-serif;';
16  hero.append(toggle);
17  let paused = false, visible = false, frame = 0, timer = 0, width = 0, height = 0, turn = 0;
18  let bursts = [], nextBurst = 0;
19  const enabled = () => !paused && !reduced.matches && visible && !document.hidden;
20  const clear = () => { cancelAnimationFrame(frame); clearTimeout(timer); frame = timer = 0; bursts = []; nextBurst = 0; ctx.clearRect(0, 0, width, height); };
21  const resize = () => {
22    const bounds = hero.getBoundingClientRect();
23    width = bounds.width; height = bounds.height;
24    const ratio = Math.min(devicePixelRatio || 1, 1.5);
25    canvas.width = Math.round(width * ratio); canvas.height = Math.round(height * ratio);
26    ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
27  };
28  const spawnBurst = now => {
29    const mobile = width < 600;
30    const positions = mobile ? [[.75,.13],[.22,.3],[.65,.42]] : [[.57,.18],[.84,.3],[.28,.12],[.7,.43],[.14,.33]];
31    const [px, py] = positions[turn++ % positions.length];
32    const count = mobile ? 36 : 64;
33    bursts.push({
34      x: width * (px + (Math.random()-.5)*.06), y: height * py,
35      radius: Math.min(width * .26, 230) * (.75+Math.random()*.25),
36      start: now, duration: 3200 + Math.random()*600,
37      sparks: Array.from({length: count}, (_, i) => ({
38        angle: i * Math.PI * 2 / count + Math.random()*.05,
39        length: .55 + Math.random()*.45,
40        warmth: i % 4 ? '255,196,125' : '255,239,210'
41      }))
42    });
43  };
44  const burst = () => {
45    if (!enabled() || !width || !height) return;
46    const draw = now => {
47      if (!enabled()) { clear(); return; }
48      if (now >= nextBurst) {
49        spawnBurst(now);
50        nextBurst = now + (width < 600 ? 1050 : 650) + Math.random()*300;
51      }
52      ctx.clearRect(0, 0, width, height);
53      bursts = bursts.filter(b => now - b.start < b.duration);
54      ctx.lineCap = 'round';
55      for (const b of bursts) {
56        const t = (now-b.start)/b.duration;
57        const travel = 1-Math.pow(1-t,3);
58        const opacity = Math.min(1,t/.1)*Math.pow(1-t,1.25);
59        for (const spark of b.sparks) {
60          const r = b.radius*spark.length*travel;
61          const tail = Math.max(0,r-b.radius*.18*(1-t));
62          const fall = 40*t*t;
63          const x1=b.x+Math.cos(spark.angle)*tail, y1=b.y+Math.sin(spark.angle)*tail+fall;
64          const x2=b.x+Math.cos(spark.angle)*r, y2=b.y+Math.sin(spark.angle)*r+fall;
65          const gradient=ctx.createLinearGradient(x1,y1,x2,y2);
66          gradient.addColorStop(0,'rgba('+spark.warmth+',0)');
67          gradient.addColorStop(1,'rgba('+spark.warmth+','+opacity+')');
68          ctx.strokeStyle=gradient;ctx.lineWidth=1.7;
69          ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();
70        }
71      }
72      frame=requestAnimationFrame(draw);
73    };
74    frame=requestAnimationFrame(draw);
75  };
76  const sync = () => {
77    clear(); canvas.hidden = reduced.matches; toggle.hidden = reduced.matches;
78    toggle.textContent = paused ? 'Play effects' : 'Pause effects';
79    toggle.setAttribute('aria-label', paused ? 'Play decorative fireworks' : 'Pause decorative fireworks');
80    if (enabled()) timer = setTimeout(burst, 350);
81  };
82  toggle.addEventListener('click', () => { paused = !paused; sync(); });
83  reduced.addEventListener('change', sync);
84  document.addEventListener('visibilitychange', sync);
85  new IntersectionObserver(entries => { visible = entries[0].isIntersecting; sync(); }, {threshold: .08}).observe(hero);
86  new ResizeObserver(() => { resize(); sync(); }).observe(hero);
87  window.addEventListener('pagehide', clear);
88  resize();sync();
89})();
90

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.