chore(particle-field): visual tuning — tighter glow, dimmer field, cursor brightness boost
This commit is contained in:
parent
993cedf0ed
commit
f533e9933b
1 changed files with 37 additions and 34 deletions
|
|
@ -43,38 +43,44 @@ function fbm(P: number[], x: number, y: number, octaves = 4): number {
|
||||||
|
|
||||||
const PARTICLE_CONFIG = {
|
const PARTICLE_CONFIG = {
|
||||||
count: 40, // number of particles
|
count: 40, // number of particles
|
||||||
reach: 130, // max connection distance (px)
|
reach: 600, // max connection distance (px)
|
||||||
flowSpeed: 1.8, // field drift speed (1–10 scale)
|
flowSpeed: 1.0, // field drift speed (1–10 scale)
|
||||||
fieldScale: 4, // noise zoom level
|
fieldScale: 1, // noise zoom level
|
||||||
dimAtMin: 0.55, // opacity reduction at field minimum (0–1)
|
dimAtMin: 0.3, // opacity reduction at field minimum (0–1)
|
||||||
|
|
||||||
particleSpeed: 0.28,
|
particleSpeed: 0.14,
|
||||||
particleMinR: 1.2,
|
particleMinR: 0.8,
|
||||||
particleMaxR: 2.4,
|
particleMaxR: 1.6,
|
||||||
|
|
||||||
fieldOffscreenW: 80,
|
fieldOffscreenW: 80,
|
||||||
fieldOffscreenH: 52,
|
fieldOffscreenH: 52,
|
||||||
fieldUpdateMs: 50,
|
fieldUpdateMs: 50,
|
||||||
fieldOctaves: 4,
|
fieldOctaves: 4,
|
||||||
|
|
||||||
timeConstantX: 0.000014 * 60,
|
timeConstantX: 0.000014 * 30,
|
||||||
timeConstantY: 0.0000085 * 37,
|
timeConstantY: 0.0000085 * 20,
|
||||||
|
|
||||||
// Warm neutral palette — fg-on-dark-2/3 register (#AE9B7F range)
|
// Warm neutral palette — fg-on-dark-2/3 register (#AE9B7F range)
|
||||||
lineColor: "174,140,100",
|
lineColor: "174,140,100",
|
||||||
lineColorMouse: "210,185,150",
|
|
||||||
glowColorInner: "200,170,130",
|
glowColorInner: "200,170,130",
|
||||||
glowColorMid: "155,120,82",
|
glowColorMid: "155,120,82",
|
||||||
|
|
||||||
// Dot colour at fv=0 (dim) and per-unit boost at fv=1 (bright)
|
// Dot colour at fv=0 (dim) and per-unit boost at fv=1 (bright)
|
||||||
dotBaseR: 174, dotBaseG: 145, dotBaseB: 110,
|
dotBaseR: 174, dotBaseG: 145, dotBaseB: 110,
|
||||||
dotBoostR: 30, dotBoostG: 35, dotBoostB: 40,
|
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 -----------------------------------------------------------------
|
// ---- Types -----------------------------------------------------------------
|
||||||
|
|
||||||
interface Particle { x: number; y: number; vx: number; vy: number; r: number; }
|
interface Particle { x: number; y: number; vx: number; vy: number; r: number; }
|
||||||
type DrawNode = { x: number; y: number; r: number; isMouse?: boolean };
|
|
||||||
|
|
||||||
// ---- Component -------------------------------------------------------------
|
// ---- Component -------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -152,11 +158,13 @@ export default function ParticleField() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
const { reach, lineColor, lineColorMouse, glowColorInner, glowColorMid,
|
const { reach, lineColor, glowColorInner, glowColorMid,
|
||||||
dotBaseR, dotBaseG, dotBaseB, dotBoostR, dotBoostG, dotBoostB } = cfg;
|
dotBaseR, dotBaseG, dotBaseB, dotBoostR, dotBoostG, dotBoostB,
|
||||||
|
glowScale, masterOpacity, cursorBoostMax } = cfg;
|
||||||
const reach2 = reach * reach;
|
const reach2 = reach * reach;
|
||||||
|
|
||||||
c.clearRect(0, 0, W, H);
|
c.clearRect(0, 0, W, H);
|
||||||
|
c.globalAlpha = masterOpacity;
|
||||||
|
|
||||||
for (const p of particles) {
|
for (const p of particles) {
|
||||||
p.x += p.vx; p.y += p.vy;
|
p.x += p.vx; p.y += p.vy;
|
||||||
|
|
@ -164,17 +172,12 @@ export default function ParticleField() {
|
||||||
if (p.y < 0 || p.y > H) p.vy *= -1;
|
if (p.y < 0 || p.y > H) p.vy *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const all: DrawNode[] = [
|
const fv = particles.map(p => sampleField(p.x, p.y));
|
||||||
...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 fo = fv.map(v => fieldToOpacity(v));
|
const fo = fv.map(v => fieldToOpacity(v));
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < particles.length; i++) {
|
||||||
for (let j = i + 1; j < n; j++) {
|
for (let j = i + 1; j < particles.length; j++) {
|
||||||
const a = all[i], b = all[j];
|
const a = particles[i], b = particles[j];
|
||||||
const dx = a.x - b.x, dy = a.y - b.y;
|
const dx = a.x - b.x, dy = a.y - b.y;
|
||||||
const d2 = dx * dx + dy * dy;
|
const d2 = dx * dx + dy * dy;
|
||||||
if (d2 >= reach2) continue;
|
if (d2 >= reach2) continue;
|
||||||
|
|
@ -184,14 +187,11 @@ export default function ParticleField() {
|
||||||
const lineMult = Math.sqrt(fo[i] * fo[j]);
|
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;
|
if (alpha < 0.005) continue;
|
||||||
const isMc = a.isMouse || b.isMouse;
|
|
||||||
c.beginPath();
|
c.beginPath();
|
||||||
c.moveTo(a.x, a.y);
|
c.moveTo(a.x, a.y);
|
||||||
c.lineTo(b.x, b.y);
|
c.lineTo(b.x, b.y);
|
||||||
c.strokeStyle = isMc
|
c.strokeStyle = `rgba(${lineColor},${alpha * 0.7})`;
|
||||||
? `rgba(${lineColorMouse},${alpha * 0.9})`
|
c.lineWidth = 0.5;
|
||||||
: `rgba(${lineColor},${alpha * 0.7})`;
|
|
||||||
c.lineWidth = isMc ? 0.9 : 0.5;
|
|
||||||
c.stroke();
|
c.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,9 +202,10 @@ export default function ParticleField() {
|
||||||
const opMult = fo[i];
|
const opMult = fo[i];
|
||||||
const dx = p.x - mouse.x, dy = p.y - mouse.y;
|
const dx = p.x - mouse.x, dy = p.y - mouse.y;
|
||||||
const md = Math.sqrt(dx * dx + dy * dy);
|
const md = Math.sqrt(dx * dx + dy * dy);
|
||||||
const mb = md < reach ? 0.4 * (1 - md / reach) : 0;
|
const proximity = md < reach ? 1 - md / reach : 0;
|
||||||
const gA = Math.max(0.02, opMult * 0.85 + mb);
|
const cursorBoost = cursorBoostMax * proximity ** 3;
|
||||||
const glowR = p.r * (2.5 + f * 1.5);
|
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);
|
const grd = c.createRadialGradient(p.x, p.y, 0, p.x, p.y, glowR * 3);
|
||||||
grd.addColorStop(0, `rgba(${glowColorInner},${gA * 0.55})`);
|
grd.addColorStop(0, `rgba(${glowColorInner},${gA * 0.55})`);
|
||||||
|
|
@ -223,6 +224,8 @@ export default function ParticleField() {
|
||||||
c.fillStyle = `rgba(${dR},${dG},${dB},${gA})`;
|
c.fillStyle = `rgba(${dR},${dG},${dB},${gA})`;
|
||||||
c.fill();
|
c.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.globalAlpha = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loop(ts: number) {
|
function loop(ts: number) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue