PageSourceSearch

https://summitvalley.app/js/sparkles.js

js summitvalley.app collected 2026-10-02 01:15:09 UTC 3,920 bytes, 138 lines download raw bytes

1// sparkles.js — Sparkle particles over gold pile and mushrooms
2
3'use strict';
4
5(function () {
6  const canvas = document.getElementById('nl-sparkles');
7  if (!canvas) return;
8
9  const ctx = canvas.getContext('2d');
10
11  // Three spawn zones with their own palettes
12  const ZONES = [
13    {
14      // Left — blue/teal mushrooms
15      cx: 0.16,
16      spread: 0.10,
17      cy: 0.72,
18      colors: ['#a8e8ff', '#70d4ff', '#c8f4ff', '#40c8f0', '#e0f8ff', '#88e0ff'],
19    },
20    {
21      // Center — gold coin pile
22      cx: 0.48,
23      spread: 0.14,
24      cy: 0.88,
25      colors: ['#ffe066', '#ffd700', '#e8c8ff', '#fff4a0', '#c8a8ff', '#fffad0', '#d4b8ff', '#ffeeaa'],
26    },
27    {
28      // Right — rainbow mushrooms (red + green)
29      cx: 0.82,
30      spread: 0.10,
31      cy: 0.72,
32      colors: ['#ff6060', '#ff3030', '#80ff80', '#40e840', '#ffaaaa', '#aaffaa', '#ff8080', '#60ff60'],
33    },
34  ];
35
36  const particles = [];
37
38  function resize() {
39    canvas.width  = canvas.offsetWidth;
40    canvas.height = canvas.offsetHeight;
41  }
42
43  function spawnFromZone(zone) {
44    const size = 1.2 + Math.random() * 2.6;
45    particles.push({
46      x:       canvas.width  * zone.cx + (Math.random() - 0.5) * canvas.width  * zone.spread,
47      y:       canvas.height * zone.cy + (Math.random() - 0.5) * canvas.height * 0.06,
48      vx:      (Math.random() - 0.5) * 0.18,
49      vy:      -(0.10 + Math.random() * 0.28),
50      size,
51      maxSize: size,
52      life:    0,
53      maxLife: 240 + Math.random() * 200,
54      color:   zone.colors[Math.floor(Math.random() * zone.colors.length)],
55      twinkle: Math.random() * Math.PI * 2,
56    });
57  }
58
59  function drawStar(x, y, r, opacity, color) {
60    ctx.save();
61    ctx.translate(x, y);
62
63    // Soft outer glow
64    const grd = ctx.createRadialGradient(0, 0, 0, 0, 0, r * 4);
65    grd.addColorStop(0,   hexToRgba(color, opacity * 0.5));
66    grd.addColorStop(0.4, hexToRgba(color, opacity * 0.15));
67    grd.addColorStop(1,   hexToRgba(color, 0));
68    ctx.beginPath();
69    ctx.arc(0, 0, r * 4, 0, Math.PI * 2);
70    ctx.fillStyle = grd;
71    ctx.fill();
72
73    // 4-point star
74    ctx.globalAlpha = opacity;
75    ctx.beginPath();
76    for (let i = 0; i < 8; i++) {
77      const angle  = (i * Math.PI) / 4 - Math.PI / 4;
78      const radius = i % 2 === 0 ? r : r * 0.35;
79      i === 0
80        ? ctx.moveTo(Math.cos(angle) * radius, Math.sin(angle) * radius)
81        : ctx.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
82    }
83    ctx.closePath();
84    ctx.fillStyle = '#ffffff';
85    ctx.shadowColor = color;
86    ctx.shadowBlur  = r * 3;
87    ctx.fill();
88
89    ctx.restore();
90  }
91
92  function hexToRgba(hex, alpha) {
93    const r = parseInt(hex.slice(1,3), 16);
94    const g = parseInt(hex.slice(3,5), 16);
95    const b = parseInt(hex.slice(5,7), 16);
96    return `rgba(${r},${g},${b},${alpha})`;
97  }
98
99  let frame = 0;
100
101  function tick() {
102    requestAnimationFrame(tick);
103    if (window._svScrolling) return; // yield GPU to scroll compositor
104    ctx.clearRect(0, 0, canvas.width, canvas.height);
105
106    frame++;
107    // Spawn one particle per zone every ~20 frames, staggered
108    ZONES.forEach((zone, i) => {
109      if (frame % 20 === i * 6 % 20) spawnFromZone(zone);
110    });
111
112    for (let i = particles.length - 1; i >= 0; i--) {
113      const p = particles[i];
114      p.life++;
115      p.x += p.vx;
116      p.y += p.vy;
117      p.vy -= 0.002;
118
119      const progress = p.life / p.maxLife;
120
121      let opacity;
122      if (progress < 0.15)      opacity = progress / 0.15;
123      else if (progress < 0.60) opacity = 1;
124      else                      opacity = 1 - (progress - 0.6) / 0.4;
125
126      const twinkle = 0.75 + 0.25 * Math.sin(p.twinkle + p.life * 0.06);
127      const r = p.maxSize * twinkle * (1 - progress * 0.3);
128
129      drawStar(p.x, p.y, r, opacity * 0.85, p.color);
130
131      if (p.life >= p.maxLife) particles.splice(i, 1);
132    }
133  }
134
135  window.addEventListener('resize', resize);
136  resize();
137  tick();
138})();

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.