Files
zeekay 91fbe784d0 film/models: each model's concept, drawn by a shader and cut into a loop
Nine concepts to the owner's mockup, grey on true black, the subject right
of centre and the left 40% kept dark for a card's words: Enso a brushed
ensō shedding particles over water and mist, Kai three panes of glass
threaded by falling light, Zen a sphere in raked sand, Iro mountains and
cloud spilling out of a frame, Koma film frames running into depth, Oto a
mirrored waveform, Koe twisting ribbons, Uta a fan rising over strings,
Mori a wireframe peak.

Each is one fragment shader over film/models/gl.js, a pure function of the
loop's phase, so the frame after the last is the first. film.mjs steps it
on the GPU, refuses a seam, and cuts public/models/<name>.{webp,webm,mp4}:
1200x1500 for every model and a 1500x1500 Enso, 30 fps, each loop under
700 KB. Frames stay in film/models/frames (ignored).
2026-10-30 07:49:48 -07:00

64 lines
2.4 KiB
HTML

<!doctype html>
<html data-seconds="6">
<meta charset="utf-8" />
<title>Uta</title>
<style>html,body{margin:0;background:#000}canvas{display:block}</style>
<body>
<canvas></canvas>
<!--
Uta: a crescendo. A fan of fine lines leaves one point low on the left and
rises, curving ever more steeply, to the right, spreading as it climbs;
under it stand the strings, each reaching up to the lowest line. In the
loop the fan sways and a light runs up the strings, left to right.
-->
<script type="x-shader/x-fragment">
const int N = 46;
float scene(vec2 f){
float H = R.y;
vec2 o = vec2(.42 * R.x, .76 * H);
float span = R.x * .98 - o.x;
float u = (f.x - o.x) / span;
float L = 0.;
float lowest = 1e9;
if (u > -.02) {
float uc = max(u, 0.);
for (int i = 0; i < N; i++) {
float t = float(i) / float(N - 1);
float sway = .035 * sin(TAU * (P + t * .6));
float a = mix(.07, .8, pow(t, 1.1)) + sway;
float pw = mix(2.3, 1.35, t);
float yi = o.y - H * .64 * a * pow(uc, pw);
float dy = H * .64 * a * pw * pow(max(uc, 1e-4), pw - 1.) / span;
float d = abs(f.y - yi) / sqrt(1. + dy * dy);
lowest = min(lowest, yi);
// Dense at the root, so dimmer there; each line brightest as it climbs.
float bright = (.28 + .5 * pow(t, 1.5)) * smoothstep(.0, .25, uc) * (1. - smoothstep(1., 1.03, uc));
L += bright * (1. - smoothstep(.25, 1.05, d)) + exp(-d / 4.) * .02 * bright;
}
// The root, where every line leaves from.
L += exp(-length(f - o) / (H * .012)) * .5;
}
// The strings, each standing up to the lowest line, a light running up them in turn.
float pitch = H * .011;
float sx = f.x - (o.x + span * .22);
if (sx > 0. && f.x < R.x * .985) {
float n = floor(sx / pitch + .5);
float dx = abs(sx - n * pitch);
float uu = (n * pitch + span * .22) / span;
float sway = .035 * sin(TAU * P);
float top = o.y - H * .64 * (.07 + sway) * pow(uu, 2.3) + 6.;
float bottom = o.y + H * .06;
float on = smoothstep(top, top + 10., f.y) * (1. - smoothstep(bottom - 30., bottom, f.y));
float run = exp(-pow(fract(uu * 1.5 - P * 2.) - .5, 2.) * 40.);
L += (1. - smoothstep(.3, .9, dx)) * on * (.22 + .5 * run) * smoothstep(.0, .2, uu);
}
// The left of the frame is the words': keep it dark.
L *= mix(.1, 1., smoothstep(.22, .45, f.x / R.x));
return pow(tone(L * 1.2), .9);
}
</script>
<script src="./gl.js"></script>
</body>
</html>