🐐
This commit is contained in:
+126
@@ -0,0 +1,126 @@
|
||||
// ── Sprinkle background ────────────────────────────
|
||||
(function () {
|
||||
const canvas = document.getElementById('bgCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const COLORS = ['#ffd84d', '#8cbcff', '#ff9fca', '#a9e76c', '#b99cff', '#ff9d57'];
|
||||
const INK = '#171717';
|
||||
const COUNT = 1000; // number of sprinkles
|
||||
const MIN_LEN = 12; // px
|
||||
const MAX_LEN = 18; // px
|
||||
const MIN_W = 4;
|
||||
const MAX_W = 6;
|
||||
|
||||
let W, H, sprinkles;
|
||||
|
||||
function rand(a, b) { return a + Math.random() * (b - a); }
|
||||
|
||||
function resize() {
|
||||
W = canvas.width = window.innerWidth;
|
||||
H = canvas.height = window.innerHeight;
|
||||
if (!sprinkles) init();
|
||||
// Redistribute any sprinkles that landed outside new bounds
|
||||
sprinkles.forEach(s => {
|
||||
if (s.x > W) s.x = rand(0, W);
|
||||
if (s.y > H) s.y = rand(0, H);
|
||||
});
|
||||
// Resizing clears the canvas; with no animation loop running, repaint it
|
||||
if (reduceMotion.matches) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = requestAnimationFrame(draw);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
sprinkles = [];
|
||||
for (let i = 0; i < COUNT; i++) {
|
||||
sprinkles.push(makeSprinkle());
|
||||
}
|
||||
}
|
||||
|
||||
function makeSprinkle() {
|
||||
return {
|
||||
x: rand(0, W),
|
||||
y: rand(0, H),
|
||||
len: rand(MIN_LEN, MAX_LEN),
|
||||
width: rand(MIN_W, MAX_W),
|
||||
color: COLORS[Math.floor(Math.random() * COLORS.length)],
|
||||
angle: rand(0, Math.PI * 2),
|
||||
// very slow drift
|
||||
vx: rand(-0.1, 0.1),
|
||||
vy: rand(-0.1, 0.1),
|
||||
va: rand(-0.005, 0.005), // angular velocity
|
||||
};
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.lineCap = 'round';
|
||||
|
||||
sprinkles.forEach(s => {
|
||||
// Endpoints computed directly; no canvas transform per sprinkle
|
||||
const ex = Math.sin(s.angle) * s.len / 2;
|
||||
const ey = -Math.cos(s.angle) * s.len / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(s.x + ex, s.y + ey);
|
||||
ctx.lineTo(s.x - ex, s.y - ey);
|
||||
// Ink outline under the colored fill (sticker look): same path, two strokes
|
||||
ctx.strokeStyle = INK;
|
||||
ctx.lineWidth = s.width + 3;
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = s.color;
|
||||
ctx.lineWidth = s.width;
|
||||
ctx.stroke();
|
||||
|
||||
// Reduced motion: sprinkles stay where they are
|
||||
if (reduceMotion.matches) return;
|
||||
|
||||
// Drift
|
||||
s.x += s.vx;
|
||||
s.y += s.vy;
|
||||
if (s.vx > 0.1 || s.vx < -0.1) {
|
||||
s.vx *= 0.98;
|
||||
}
|
||||
if (s.vy > 0.1 || s.vy < -0.1) {
|
||||
s.vy *= 0.98;
|
||||
}
|
||||
s.angle += s.va;
|
||||
|
||||
// Repel from cursor
|
||||
const dx = s.x - mouseX;
|
||||
const dy = s.y - mouseY;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
const repelRadius = 80;
|
||||
if (dist < repelRadius && dist > 0) {
|
||||
const force = (repelRadius - dist) / repelRadius;
|
||||
s.vx += (dx / dist) * force * 1.0;
|
||||
s.vy += (dy / dist) * force * 1.0;
|
||||
}
|
||||
|
||||
// Wrap: when a sprinkle drifts off the screen, reappear on the other side
|
||||
if (s.y < -MAX_LEN) s.y = H + MAX_LEN;
|
||||
if (s.y > H + MAX_LEN) s.y = -MAX_LEN
|
||||
if (s.x < -MAX_LEN) s.x = W + MAX_LEN;
|
||||
if (s.x > W + MAX_LEN) s.x = -MAX_LEN;
|
||||
});
|
||||
|
||||
if (!reduceMotion.matches) rafId = requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
// With reduced motion the sprinkles render once as a static backdrop
|
||||
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
let rafId;
|
||||
reduceMotion.addEventListener('change', () => {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = requestAnimationFrame(draw);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', resize, { passive: true });
|
||||
resize();
|
||||
let mouseX = -9999, mouseY = -9999;
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
mouseX = e.clientX;
|
||||
mouseY = e.clientY;
|
||||
}, { passive: true });
|
||||
rafId = requestAnimationFrame(draw);
|
||||
})();
|
||||
Reference in New Issue
Block a user