Live demo: https://mbanslow.github.io/CloudWeatherSim/
A 3D simulation of a breath-driven cloud installation. Four people recline on bean bags facing each other under one big shared "cloud." Each person's breath lights their own part of that cloud (the individual layer), while the shared rhythm of all four breaths drives the room's "sky" weather from stormy twilight to warm daylight (the relationship layer).
- One shared cloud, lit in zones. The single cloud overhead has a matte body made of many packed puffs. Embedded in its underside are four LED lamp clusters, one per person, each sitting in that person's own sector and tinted with their colour. A local point light at each cluster spills that person's colour onto only the nearby puffs, so different regions of the one cloud are lit by different people rather than the whole mass glowing as one. Each cluster follows its person's breath - brighter / cooler on the inhale, dimmer / warmer on the exhale - but only once the breathing phase begins.
- Lightning lives in the strips. During the storm every LED strip flickers electric blue with the lightning forks and the cloud flashes; the body itself has no uniform self-glow, only this fork flash plus the per-person lights.
- Relationship = synchrony. A single synchrony value (the Kuramoto order parameter of all four breath phases) drives storminess, the storm-calming and daylight progress, and the sun/moon.
- Sun = reward. A warm "sun" fades in only when synchrony is sustained and high.
- Lighting is in-room only. There is no ambient or sky fill: the only light comes from the lamp clusters inside the cloud, the moon, the sun, and lightning. The walls are white so they catch and reflect those sources; the ceiling is sky blue so it reads as a daytime sky when the sun comes up. (With no ambient bounce, surfaces facing away from every light go dark - that is intentional.)
- Tunable lights. A Lights panel on the left exposes colour, intensity, radius and dropoff for each light group; edits ride live on top of the scripted breath/storm behaviour.
No build step. Serve the folder and open it:
python3 serve.pyThen visit http://localhost:8000.
serve.py is a tiny static server that sends no-cache headers, so each reload
always picks up your latest edits. It also adds a small save endpoint so the
Lights panel can persist its values to lights.yaml (GET/POST /lights.yaml). (Plain python3 -m http.server also works, but browsers cache
ES modules aggressively - you may need a hard refresh Cmd/Ctrl+Shift+R to see
code changes, and the Lights panel can read lights.yaml but cannot save back
to it.)
Three.js is loaded from a CDN via an import map, so an internet connection is needed on first load.
On the hosted GitHub Pages build there is no save endpoint, so Lights-panel edits apply live but are not persisted between reloads.
- Orbit camera: drag to rotate, scroll to zoom, right-drag to pan.
- Phases: top-left HUD. Step through Entry, Lightning storm, Breathing, and Daylight, or toggle Auto to advance automatically.
- Left panel (Lights): per light group (Moon, Night accents, Sun, Lightning
flash, Cloud forks, Breath bulbs), adjust colour, intensity, radius
and dropoff (and emission for the moon orb). Edits apply immediately on
top of the scripted behaviour and, on the dev server, persist to
lights.yaml. - Right panel: one lane per person.
- Drag a waveform left/right to shift that person's breathing phase (alignment).
- Rate and Depth sliders shape each breath.
- counts toggles whether a person feeds the room synchrony (their lamp cluster in the cloud keeps breathing either way).
- The room synchrony meter and phase ring show how aligned everyone is: each dot is a person's breath phase, the arrow is the combined rhythm.
- Lightning storm sliders (Frequency, Intensity) tune the storm.
- Entry - a moon lights the room and the cloud from above. The cloud is matte and dark inside; breathing does not affect it yet. People settle onto the bean bags.
- Lightning storm - the moon sets and a tunable storm rolls in. Lightning flashes the cloud and its LED strips crackle with forks; the whole cloud flickers. Still no breath lighting.
- Breathing - now the breath begins to count. Each person's LED lamp cluster lights its own part of the cloud with their breath, and a Storm calming bar appears at the bottom of the screen. Sustained synchrony fills it gradually (about 30s to fully calm the storm); drifting apart lets it drain and the storm builds back up (about 60s to return). As it fills, both the cloud's forks and the global storm overhead settle down.
- Daylight rising - the storm is gone and the room sits in the black of night. A Daylight completion bar appears at the bottom of the screen. Staying synchronized charges it up gradually (about 35s of sustained sync to fill); drifting apart lets it sink back down slowly (about 90s to empty). As it climbs, a slow sunrise unfolds: deep blue, then a violet and orange dawn, up to a full daytime sky as the (fixed) sun warms from a dim orange ember to a bright white daytime sun. The sky-blue ceiling lights up so the room reads as a real daytime sky overhead, the cloud turns white, and each person's lamp cluster breathes with them.
index.html- markup, import map, Lights/synchrony panel DOM.styles.css- layout and panel styling.serve.py- no-cache dev server with thelights.yamlsave endpoint.lights.yaml- persisted Lights-panel state (loaded on startup).src/main.js- bootstrap, render loop, module wiring.src/breathing.js- breath oscillator model + Kuramoto synchrony.src/phases.js- phase state machine and target lighting params.src/panel.js- waveform lanes, drag-to-phase, sliders, synchrony meter.src/lightsPanel.js- left Lights panel + load/save oflights.yaml.src/scene/room.js- room, reclining figures, base lighting.src/scene/clouds.js- the shared cloud body + per-person LED lamp clusters.src/scene/weather.js- synchrony/phase to sky, sun, moon, and lightning.