Skip to content

Instantly share code, notes, and snippets.

@asong56
Created April 1, 2026 00:34
Show Gist options
  • Select an option

  • Save asong56/5429fd8c0a0d8ca8169c2007a600aaca to your computer and use it in GitHub Desktop.

Select an option

Save asong56/5429fd8c0a0d8ca8169c2007a600aaca to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shard Flow — Refined</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #f5f2ed;
cursor: none;
}
canvas { display: block; }
#cursor {
position: fixed;
width: 6px;
height: 6px;
background: #c8b89a;
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1),
opacity 0.4s ease;
z-index: 100;
}
#cursor.pressed {
transform: translate(-50%, -50%) scale(2.5);
opacity: 0.5;
}
</style>
</head>
<body>
<div id="cursor"></div>
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/simplex-noise@2.4.0/simplex-noise.min.js"></script>
<script>
const S = {
pCount: 220,
bgColor: '#f5f2ed',
stiffnessIdle: 0.0000050,
stiffnessHover: 0.0000178,
stiffnessPress: 0.0000305,
friction: 0.9980,
repelRadius: 200,
repelStrength: 0.030,
easing: 0.00155,
minLen: 8,
maxLen: 22,
baseWidth: 1.2,
colorPalette: [
{ h: 35, s: 22, l: 62 },
{ h: 33, s: 18, l: 55 },
{ h: 30, s: 25, l: 48 },
{ h: 38, s: 15, l: 68 },
],
alphaMin: 0.18,
alphaMax: 0.62,
noiseSpeed: 0.00000155,
noiseScale: 90,
flowAngle: Math.PI * 0.08,
flowStrength: 0.6,
};
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const cursorEl = document.getElementById('cursor');
const simplex = new SimplexNoise();
let W, H, dpr, particles;
let isPressed = false;
let mouse = { x: -9999, y: -9999, active: false };
let time = 0;
class Shard {
constructor() {
this.ox = Math.random() * W;
this.oy = Math.random() * H;
this.x = this.ox;
this.y = this.oy;
this.vx = 0;
this.vy = 0;
this.seed = Math.random() * 30000;
const pick = S.colorPalette[
Math.floor(Math.pow(Math.random(), 1.2) * S.colorPalette.length)
];
this.h = pick.h + (Math.random() - 0.5) * 6;
this.s = pick.s;
this.l = pick.l + (Math.random() - 0.5) * 8;
this.dispLen = S.minLen;
this.dispAlpha = S.alphaMin + Math.random() * 0.15;
this.dispAngle = S.flowAngle + (Math.random() - 0.5) * 0.8;
}
update() {
const t = time * S.noiseSpeed;
const nx = simplex.noise2D(this.seed, t) * S.noiseScale;
const ny = simplex.noise2D(this.seed + 5000, t) * S.noiseScale;
let tx, ty;
let k;
if (isPressed && mouse.active) {
tx = mouse.x + nx * 0.4;
ty = mouse.y + ny * 0.4;
k = S.stiffnessPress;
} else if (mouse.active) {
tx = this.ox + nx;
ty = this.oy + ny;
k = S.stiffnessHover;
const dx = this.x - mouse.x;
const dy = this.y - mouse.y;
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist < S.repelRadius && dist > 0) {
const force = Math.pow((S.repelRadius - dist) / S.repelRadius, 1.8);
this.vx += (dx / dist) * force * S.repelStrength;
this.vy += (dy / dist) * force * S.repelStrength;
}
} else {
tx = this.ox + nx;
ty = this.oy + ny;
k = S.stiffnessIdle;
}
this.vx += (tx - this.x) * k;
this.vy += (ty - this.y) * k;
this.vx *= S.friction;
this.vy *= S.friction;
this.x += this.vx;
this.y += this.vy;
const speed = Math.sqrt(this.vx*this.vx + this.vy*this.vy);
const targetLen = S.minLen + speed * 2.8;
this.dispLen += (Math.min(targetLen, S.maxLen) - this.dispLen) * S.easing;
const targetAlpha = S.alphaMin + Math.min(speed * 0.22, S.alphaMax - S.alphaMin);
this.dispAlpha += (targetAlpha - this.dispAlpha) * S.easing;
if (speed > 0.04) {
const motionAngle = Math.atan2(this.vy, this.vx);
const blend = Math.min(speed * 0.6, 1.0);
const targetAngle = motionAngle * blend + S.flowAngle * (1 - blend);
let da = targetAngle - this.dispAngle;
while (da > Math.PI) da -= 2*Math.PI;
while (da < -Math.PI) da += 2*Math.PI;
this.dispAngle += da * S.easing * 1.5;
}
}
draw() {
const len = this.dispLen * dpr;
const ex = this.x + Math.cos(this.dispAngle) * len;
const ey = this.y + Math.sin(this.dispAngle) * len;
ctx.save();
ctx.beginPath();
ctx.strokeStyle = `hsla(${this.h}, ${this.s}%, ${this.l}%, ${this.dispAlpha})`;
ctx.lineWidth = S.baseWidth * dpr;
ctx.lineCap = 'round';
ctx.moveTo(this.x, this.y);
ctx.lineTo(ex, ey);
ctx.stroke();
ctx.restore();
}
}
function resize() {
dpr = window.devicePixelRatio || 1;
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.scale(dpr, dpr);
particles = Array.from({ length: S.pCount }, () => new Shard());
}
function animate(ts) {
time = ts;
ctx.fillStyle = 'rgba(245, 242, 237, 0.82)';
ctx.fillRect(0, 0, W, H);
particles.forEach(p => { p.update(); p.draw(); });
requestAnimationFrame(animate);
}
window.addEventListener('mousemove', e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
mouse.active = true;
cursorEl.style.left = e.clientX + 'px';
cursorEl.style.top = e.clientY + 'px';
});
window.addEventListener('mouseenter', () => { mouse.active = true; });
window.addEventListener('mouseleave', () => {
mouse.active = false;
mouse.x = -9999;
mouse.y = -9999;
});
window.addEventListener('mousedown', () => {
isPressed = true;
cursorEl.classList.add('pressed');
});
window.addEventListener('mouseup', () => {
isPressed = false;
cursorEl.classList.remove('pressed');
});
window.addEventListener('resize', resize);
resize();
requestAnimationFrame(animate);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment