existence
← All posts
EngineeringNovember 2026 · 5 min read

Change it without stopping it: a particle system you can restyle in real time.

The ambient motion behind our apps used to play one way forever. Here is how we made it something you can recolour and re-pace mid-flight, without ever restarting the scene.

S
SiyabongaAuthor
XLinkedIn
restyle a running scene, no reset

Open almost any Existence app and something is moving behind the content. A drift of soft bubbles, each one stroked in its own colour, rising at its own pace. It is meant to be felt more than noticed, the kind of ambient motion that makes a screen feel alive without asking for attention. For a long time, though, that motion was also completely deaf. It played the way we configured it on the very first frame, and it kept playing exactly that way no matter what happened in the app around it.

This is the story of how we taught that background to listen. The interesting part turned out not to be adding new effects, but being able to change the ones already on screen without starting over.

The reset you can feel

The naive way to change a running animation is to throw it away and build a new one. Want a different colour? Tear down every particle, read the new settings, spawn a fresh batch. It works, and for a splash screen nobody ever interacts with, it is perfectly fine.

It stops being fine the moment the change is meant to carry some meaning. If a bubble field is supposed to drift from a calm blue to a warm amber because the app has moved into a new state, a full rebuild reads as a stutter. The old bubbles vanish, there is a blank beat, and a new set fades in from nowhere. The eye catches it every single time. Instead of a mood shifting, you get a thing restarting. The feeling you were reaching for, continuity, is the exact thing the reset destroys.

A system, not a clip

The shift in thinking was small to say and large to build. We stopped treating the animation as a clip we replay, and started treating it as a system we can reach into while it is still running. Every particle on screen is an object with its own position, velocity, and style, and every one of those properties stays addressable after the particle is already alive.

An animation is not a clip you replay. It is a system you can reach into while it is still moving.

Once that is true, changing the look of the field is no longer a rebuild. It is a message. The particles keep their positions and their momentum, and only the property you asked about changes. A bubble that was climbing in blue is still the same bubble, in the same place, a frame later, now climbing in amber. Nothing blinked.

What you can change while it runs

The package now exposes a small set of controls that all obey one rule: they never recreate what is already there. In practice that means we can, at any moment and in response to anything the app wants to signal:

  • change the stroke or border colour of live particles, so the field recolours in place rather than from scratch;
  • change the fill behind them, so the background and the motion shift together instead of fighting each other;
  • change speed, so the whole field can calm down or pick up without losing the shape it already has;
  • pause or resume a single element rather than the entire scene, so one bubble can hold while the rest keep drifting past it.

Each of these can be driven by a live event. A state change, an incoming message, a moment worth marking: the app sends a signal, and the motion answers by shifting, not by restarting. The background stops being decoration bolted onto the app and becomes a quiet instrument the app can actually play.

One package, every app

We could have solved this once, inside a single app, and moved on. We have learned the hard way where that road ends: maintaining the same animation five slightly different times. So the particle system lives where the rest of our shared foundations live, as one tested package in our core toolkit, with a single interface and the look swappable underneath it.

0particles recreated on a restyle
60frames per second, held under change
1shared package, every app

A new app does not inherit a copy of the code. It inherits the behaviour, every real-time control included, and the refinements we make later flow to all of them at once. One app learning a new trick becomes every app learning it, which is how a small team keeps this many surfaces feeling like one family.

Making it hold up under change

The honest test of a real-time system is not whether it works when you change one thing. It is whether it stays smooth when the changes come fast. An ambient field might take dozens of updates in a few seconds as the app reacts to a burst of activity, and every one of them has to land without dropping a frame.

Mutating in place is what makes that possible. Because we are not allocating a fresh set of particles on every change, there is no churn to absorb, no garbage waiting to be collected at the worst possible moment, no spike while a new batch spins up. We are editing a system that is already running, which is cheap, instead of rebuilding one, which is not. Smoothness under frequent change stopped being something we crossed our fingers about and became a property of the design itself.

Where this goes

None of this is the flashiest thing we shipped this quarter. It is, after all, a background. But it captures a habit we keep returning to: the difference between replaying something and being able to change it while it lives. Most of the hard, satisfying engineering we do sits right on that line. It is easy to make a thing happen once. It is real work to make it something you can steer.

The bubbles will keep drifting behind our apps, mostly unnoticed, which is exactly the point. The difference now is that when the moment calls for it, they can change their mind without missing a beat.

S
SiyabongaWriting for Existence

Notes, as they happen.

One email when we publish. No digest, no drip, just the post.