Toss
The cloud from the homepage in a room of its own, to pick up and throw. Shake it and it gets dizzy; throw it too hard and it sulks.
Floating.
Drag it and let go to throw it, or click to pet it.Drag it and let go to throw it, or tap to pet it. With the keyboard, the arrow keys push it and Enter pets it.
// A frame of flight. No gravity: it floats, pulled back to its height by a
// weak spring, slowed by the air, and given back 60% of its speed by a wall.
export function step(body: Body, dt: number, room: Box, size: Box) {
const rest = restOf(room);
const air = Math.exp(-DRAG * dt);
let vx = body.vx * air;
let vy = (body.vy + (rest.y - body.y) * LIFT * dt) * air;
let x = body.x + vx * dt;
let y = body.y + vy * dt;
const hits: Hit[] = [];
const inside = confine({ x, y }, room, size);
if (inside.x !== x) {
hits.push({ wall: x < inside.x ? "left" : "right", speed: Math.abs(vx) });
vx = -vx * RESTITUTION;
x = inside.x;
}
if (inside.y !== y) {
hits.push({ wall: y < inside.y ? "top" : "bottom", speed: Math.abs(vy) });
vy = -vy * RESTITUTION;
y = inside.y;
}
return { body: { x, y, vx, vy }, hits };
}
// The throw: the last 80 ms before letting go, so the flick counts and the
// slow wind-up doesn't. Held still before release, it's dropped.
export function releaseVelocity(samples: Sample[], now: number): Vec {
const last = samples.filter((sample) => now - sample.t <= 80);
if (last.length < 2) return { x: 0, y: 0 };
const seconds = Math.max(0.016, (last.at(-1)!.t - last[0].t) / 1000);
return capped({ x: (last.at(-1)!.x - last[0].x) / seconds, y: (last.at(-1)!.y - last[0].y) / seconds });
}A cloud, not a ball
There's no gravity: it floats. A weak spring pulls it back to its height, the air takes about two thirds of its speed every second, and each wall gives back 60% of what hits it. Together they make a throw glide, bounce a few times and drift home, rising and sinking a little before it settles, more like a balloon than a ball.
The throw is the movement over the last 80 milliseconds before letting go, so the flick counts and the slow wind-up before it doesn't, and a cloud held still before release is dropped rather than thrown. A wall flattens it along the axis it hit and swells it along the other, on a spring that overshoots once on its way back to round.
How it feels about it
Its moods come from the same numbers. Four sharp changes of direction within a second in the hand, or four bounces within a second and a half, and its eyes turn to x_x for three seconds. A throw faster than 2,400 pixels a second, or a wall hit faster than 2,000, and it sulks, eyes shut and frowning, under its own rain for six seconds, unless it's petted first. A sulk outlasts everything but a pet; held or flying fast, it's simply happy.
Its faces are the homepage cloud's, in its own font; where that font has nothing, as for the dizzy eyes, they're plain ASCII. The flight runs outside React, written straight to the page each frame, and only while something moves, so a settled cloud costs nothing. With reduced motion it goes where it's dragged or pushed and stays there.
Tilt
On a phone, Tilt hands the cloud to gravity. The phone reports how it's tipped, front to back and side to side; that becomes which way is down across the screen, turned to match the screen in landscape, and the float that holds the cloud at its height gives way to a pull toward the low edge, up to full gravity with the phone on its side. Level is however the phone was held when Tilt went on, so it doesn't drop the moment you look at it. Lying against a wall it's tipped to, it doesn't squash or count bounces, so it doesn't get dizzy just resting there. Safari asks before a page may read the tilt, so the switch asks too; under reduced motion it stays off.