Created
April 1, 2026 00:34
-
-
Save asong56/5429fd8c0a0d8ca8169c2007a600aaca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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