diff --git a/src/components/islands/ParticleField.tsx b/src/components/islands/ParticleField.tsx index cc5c8f8..081f72d 100644 --- a/src/components/islands/ParticleField.tsx +++ b/src/components/islands/ParticleField.tsx @@ -43,38 +43,44 @@ function fbm(P: number[], x: number, y: number, octaves = 4): number { const PARTICLE_CONFIG = { count: 40, // number of particles - reach: 130, // max connection distance (px) - flowSpeed: 1.8, // field drift speed (1–10 scale) - fieldScale: 4, // noise zoom level - dimAtMin: 0.55, // opacity reduction at field minimum (0–1) + reach: 600, // max connection distance (px) + flowSpeed: 1.0, // field drift speed (1–10 scale) + fieldScale: 1, // noise zoom level + dimAtMin: 0.3, // opacity reduction at field minimum (0–1) - particleSpeed: 0.28, - particleMinR: 1.2, - particleMaxR: 2.4, + particleSpeed: 0.14, + particleMinR: 0.8, + particleMaxR: 1.6, fieldOffscreenW: 80, fieldOffscreenH: 52, fieldUpdateMs: 50, fieldOctaves: 4, - timeConstantX: 0.000014 * 60, - timeConstantY: 0.0000085 * 37, + timeConstantX: 0.000014 * 30, + timeConstantY: 0.0000085 * 20, // Warm neutral palette — fg-on-dark-2/3 register (#AE9B7F range) lineColor: "174,140,100", - lineColorMouse: "210,185,150", glowColorInner: "200,170,130", glowColorMid: "155,120,82", // Dot colour at fv=0 (dim) and per-unit boost at fv=1 (bright) dotBaseR: 174, dotBaseG: 145, dotBaseB: 110, dotBoostR: 30, dotBoostG: 35, dotBoostB: 40, + + // Glow: base radius multiplier (relative to particle r); field peaks expand by +1.5× + glowScale: 1.2, + + // Master opacity applied to the entire field (0–1); 0.5 = half brightness + masterOpacity: 0.4, + // Max brightness boost on particles near the cursor (0–1); 0.30 = 30% increase + cursorBoostMax: 1.0, }; // ---- Types ----------------------------------------------------------------- interface Particle { x: number; y: number; vx: number; vy: number; r: number; } -type DrawNode = { x: number; y: number; r: number; isMouse?: boolean }; // ---- Component ------------------------------------------------------------- @@ -152,11 +158,13 @@ export default function ParticleField() { } function draw() { - const { reach, lineColor, lineColorMouse, glowColorInner, glowColorMid, - dotBaseR, dotBaseG, dotBaseB, dotBoostR, dotBoostG, dotBoostB } = cfg; + const { reach, lineColor, glowColorInner, glowColorMid, + dotBaseR, dotBaseG, dotBaseB, dotBoostR, dotBoostG, dotBoostB, + glowScale, masterOpacity, cursorBoostMax } = cfg; const reach2 = reach * reach; c.clearRect(0, 0, W, H); + c.globalAlpha = masterOpacity; for (const p of particles) { p.x += p.vx; p.y += p.vy; @@ -164,34 +172,26 @@ export default function ParticleField() { if (p.y < 0 || p.y > H) p.vy *= -1; } - const all: DrawNode[] = [ - ...particles, - { x: mouse.x, y: mouse.y, r: 0, isMouse: true }, - ]; - const n = all.length; - const fv = all.map(p => sampleField(p.x, p.y)); + const fv = particles.map(p => sampleField(p.x, p.y)); const fo = fv.map(v => fieldToOpacity(v)); - for (let i = 0; i < n; i++) { - for (let j = i + 1; j < n; j++) { - const a = all[i], b = all[j]; + for (let i = 0; i < particles.length; i++) { + for (let j = i + 1; j < particles.length; j++) { + const a = particles[i], b = particles[j]; const dx = a.x - b.x, dy = a.y - b.y; const d2 = dx * dx + dy * dy; if (d2 >= reach2) continue; - const dist = Math.sqrt(d2); - const t01 = dist / reach; - const expFade = (1 - t01) ** 3; + const dist = Math.sqrt(d2); + const t01 = dist / reach; + const expFade = (1 - t01) ** 3; const lineMult = Math.sqrt(fo[i] * fo[j]); - const alpha = expFade * lineMult * 0.85; + const alpha = expFade * lineMult * 0.85; if (alpha < 0.005) continue; - const isMc = a.isMouse || b.isMouse; c.beginPath(); c.moveTo(a.x, a.y); c.lineTo(b.x, b.y); - c.strokeStyle = isMc - ? `rgba(${lineColorMouse},${alpha * 0.9})` - : `rgba(${lineColor},${alpha * 0.7})`; - c.lineWidth = isMc ? 0.9 : 0.5; + c.strokeStyle = `rgba(${lineColor},${alpha * 0.7})`; + c.lineWidth = 0.5; c.stroke(); } } @@ -202,9 +202,10 @@ export default function ParticleField() { const opMult = fo[i]; const dx = p.x - mouse.x, dy = p.y - mouse.y; const md = Math.sqrt(dx * dx + dy * dy); - const mb = md < reach ? 0.4 * (1 - md / reach) : 0; - const gA = Math.max(0.02, opMult * 0.85 + mb); - const glowR = p.r * (2.5 + f * 1.5); + const proximity = md < reach ? 1 - md / reach : 0; + const cursorBoost = cursorBoostMax * proximity ** 3; + const gA = Math.max(0.02, opMult * 0.85 * (1 + cursorBoost)); + const glowR = p.r * (glowScale + f * 1.5); const grd = c.createRadialGradient(p.x, p.y, 0, p.x, p.y, glowR * 3); grd.addColorStop(0, `rgba(${glowColorInner},${gA * 0.55})`); @@ -223,6 +224,8 @@ export default function ParticleField() { c.fillStyle = `rgba(${dR},${dG},${dB},${gA})`; c.fill(); } + + c.globalAlpha = 1; } function loop(ts: number) {