I Built an Interactive Wave on Vibe Code Arena

At first glance, this looks like one of those “visual toys.”
A line across the screen. Move your mouse — it wiggles. Scroll — it shifts.
Simple.
But the moment you try to make it feel fluid — not just reactive, but continuous — you realize:
This isn’t about drawing a line. It’s about simulating a function in real time.
The trap: treating SVG like static graphics
Most people start here:
<path d="M0 100 L200 100" />
Hardcoded.
Then they try to “update” it with a few values.
But a sine wave isn’t a shape you tweak.
It’s a function you recompute every frame.
The shift happens here
Instead of thinking:
“How do I modify this path?”
You think:
“How do I generate this path from scratch, continuously?”
That’s when you land on something like:
y = centerY + Math.sin(x * frequency + phase) * amplitude;
That one line becomes the entire system.
Everything else is just feeding it variables.
The wave is not an object
It’s a projection of parameters
You don’t store the wave.
You store:
amplitude
frequency
phase
And the wave emerges from them.
That’s why your state looks like:
let amplitude = 50;
let frequency = 0.02;
let phase = 0;
And nothing else.
Mouse movement becomes parameter control
Vertical movement → amplitude
amplitude = mouseY / 2;
Horizontal movement → frequency
frequency = mouseX / width * 0.05;
This is where it stops being UI.
And starts being an instrument
Because now the user isn’t clicking things.
They’re modulating a system
The real work: building the path
SVG needs a string.
Something like:
"M x0 y0 L x1 y1 L x2 y2 ..."
So every frame, you loop:
let path = `M 0 ${centerY}`;
for (let x = 0; x < width; x += step) {
const y = centerY + Math.sin(x * frequency + phase) * amplitude;
path += ` L \({x} \){y}`;
}
That loop is your rendering engine.
And the step size?
That’s your performance dial.
Where performance quietly dies
If you do:
for (let x = 0; x < width; x++)
You’re generating hundreds (or thousands) of points per frame.
It works.
Until it doesn’t.
Frame drops.
Lag.
Jitter.
The fix is not optimization.
It’s restraint.
const step = 4;
Now you sample fewer points.
Visually? Almost identical.
Performance? Dramatically better.
Phase is where motion comes from
Without phase:
The wave is static.
Scroll adds phase:
phase += deltaY * 0.01;
Now the wave “travels”
But nothing is actually moving.
You’re just shifting the function.
This is the kind of illusion that feels simple…
But is actually deeply mathematical.
requestAnimationFrame is non-negotiable
You can’t update this on events alone.
Because motion needs continuity.
function animate() {
drawWave();
requestAnimationFrame(animate);
}
This loop ensures:
consistent updates
smooth transitions
no stuttering
Without it, everything feels reactive.
With it, everything feels alive.
Smoothness is not what it sounds like
The requirement says:
“Smoothness slider”
At first, you think:
- more points = smoother
But that’s not quite it.
Smoothness is actually about:
how sharply the wave changes between points
So instead of just reducing step, you can influence interpolation.
Or even exaggerate:
Math.sin(x * frequency) ** smoothnessFactor
Now the wave morphs between:
sharp peaks
soft curves
It’s not just smoother.
It’s different behavior
Color morph is where perception shifts
A static color feels… static.
When you tie color to frequency:
const hue = frequency * 10000 % 360;
Now the wave responds visually to interaction.
And the system feels connected.
Not because of complexity.
But because of consistency across dimensions
The background pulse is subtle but powerful
You take peak intensity:
const intensity = amplitude / maxAmplitude;
And map it:
backgroundAlpha = intensity * 0.1;
Now the background breathes with the wave.
It’s not necessary.
But it reinforces the illusion:
the entire system is one thing
What shows up in Vibe Code Arena
This challenge exposes something specific.
One model builds:
a working sine wave
reactive controls
decent visuals
But updates only on input.
So motion feels… interrupted.
Another model builds:
continuous render loop
parameter-driven system
efficient path generation
Now everything feels fluid.
The human version usually does one thing right
It accepts that:
the wave must be recalculated every frame
No shortcuts.
No partial updates.
Because anything less breaks continuity.
The part you don’t expect
After building this, you stop seeing:
“a wavy line”
And start seeing:
“a function sampled across space, evolving over time”
That’s a completely different perspective.
The shift
You stop thinking:
“how do I animate this?”
And start thinking:
“how do I continuously evaluate a function under changing parameters?”
That’s math.
Not UI.
If you want to explore this properly
Push it:
increase frequency aggressively
reduce step size
scroll rapidly
Watch where it breaks.
Because this system doesn’t fail in features.
It fails in continuity
And that’s much harder to fix.
👉Try the exact challenge here, let's see how you could refactor this: https://vibecodearena.ai/share/5c6b41e6-5d43-4685-8678-7856a2f1f590
