Skip to main content

Command Palette

Search for a command to run...

I Built an Interactive Wave on Vibe Code Arena

Updated
5 min read
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