diff --git a/apps/web/src/widgets/wave/draw.ts b/apps/web/src/widgets/wave/draw.ts index 47bc8cc..d88deed 100644 --- a/apps/web/src/widgets/wave/draw.ts +++ b/apps/web/src/widgets/wave/draw.ts @@ -65,16 +65,9 @@ export function drawOrb(ctx: CanvasRenderingContext2D, cx: number, cy: number, r export function drawLiquid(ctx: CanvasRenderingContext2D, cx: number, cy: number, r: number, s: WaveState, t: number) { const cols = s.colors.map(rgb) as [Rgb, Rgb, Rgb]; - const glow = ctx.createRadialGradient(cx, cy, r * 0.4, cx, cy, r * 1.6); - glow.addColorStop(0, rgba(cols[0], 0.3 + s.pulse * 0.3)); - glow.addColorStop(1, rgba(cols[0], 0)); - ctx.fillStyle = glow; - ctx.beginPath(); - ctx.arc(cx, cy, r * 1.6, 0, Math.PI * 2); - ctx.fill(); - ctx.globalCompositeOperation = s.dark ? 'lighter' : 'source-over'; - for (let L = 0; L < 3; L++) { - ctx.beginPath(); + // Reuse the animated silhouettes for the halo so it follows every deformation. + const shapes = Array.from({ length: 3 }, (_, L) => { + const path = new Path2D(); const P = 140; for (let j = 0; j <= P; j++) { const th = (j / P) * Math.PI * 2; @@ -88,9 +81,24 @@ export function drawLiquid(ctx: CanvasRenderingContext2D, cx: number, cy: number const rr = r * (0.86 - L * 0.07) * w; const x = cx + Math.cos(th) * rr; const y = cy + Math.sin(th) * rr; - if (j) ctx.lineTo(x, y); - else ctx.moveTo(x, y); + if (j) path.lineTo(x, y); + else path.moveTo(x, y); } + path.closePath(); + return path; + }); + + ctx.save(); + // Two soft falloffs avoid a visible circular boundary, even at peak pulse. + for (const [blur, opacity] of [[0.2, 0.14], [0.09, 0.18]] as const) { + ctx.filter = `blur(${r * blur}px)`; + ctx.fillStyle = rgba(cols[0], opacity * (0.8 + s.pulse * 0.6)); + ctx.fill(shapes[0]!); + } + ctx.restore(); + + ctx.globalCompositeOperation = s.dark ? 'lighter' : 'source-over'; + for (let L = 0; L < shapes.length; L++) { const ox = Math.cos(t * 0.4 + L * 2) * r * 0.3; const oy = Math.sin(t * 0.5 + L) * r * 0.3; const g = ctx.createRadialGradient(cx + ox, cy + oy, 0, cx, cy, r * 1.1); @@ -98,7 +106,7 @@ export function drawLiquid(ctx: CanvasRenderingContext2D, cx: number, cy: number g.addColorStop(1, rgba(cols[L]!, 0.25)); ctx.fillStyle = g; ctx.globalAlpha = s.dark ? 0.6 : 0.75; - ctx.fill(); + ctx.fill(shapes[L]!); } ctx.globalAlpha = 1; ctx.globalCompositeOperation = 'source-over';