Title sequence
“cmotion” assembled from one extrude(text.glyph(...)) per letter, so
each turns on its own y axis. The spins are staggered and settle by
about four seconds; then the word sits quiet while two coloured lights
sweep the hue wheel and wobble their direction. A 2D background
composes behind the 3D scene.
Because the letters are placed by hand with translate(x:), the scene
sets camera(distance: 2166) so the native renderer’s 3D view is
exactly 1080 world-px tall — the same pixel-honest mapping the web
viewer uses — and the per-letter advances line up in both. Every motion
is tuned to loop seamlessly over the 12-second duration.
runner "0.0.1";
use std.shapes.*;
use std.mesh3d.*;
use std.text;
use std.lighting.*;
use std.scene3d.*;
use std.anim.*;
scene title(duration: Duration = 12s) -> Frame {
let bg = rect(width: 1920px, height: 1080px, fill: oklch(0.97, 0.012, 95));
// Per-letter Y spin, staggered by 0.3s, each 360° over 2s then held.
let r0 = animate { 0s => 0deg, 2.0s => 360deg, 12s => 360deg } with { easing: easing.in_out_cubic };
let r1 = animate { 0s => 0deg, 0.3s => 0deg, 2.3s => 360deg, 12s => 360deg } with { easing: easing.in_out_cubic };
let r2 = animate { 0s => 0deg, 0.6s => 0deg, 2.6s => 360deg, 12s => 360deg } with { easing: easing.in_out_cubic };
let r3 = animate { 0s => 0deg, 0.9s => 0deg, 2.9s => 360deg, 12s => 360deg } with { easing: easing.in_out_cubic };
let r5 = animate { 0s => 0deg, 1.5s => 0deg, 3.5s => 360deg, 12s => 360deg } with { easing: easing.in_out_cubic };
let r6 = animate { 0s => 0deg, 1.8s => 0deg, 3.8s => 360deg, 12s => 360deg } with { easing: easing.in_out_cubic };
// The "i" hops up and down instead of spinning, and rests slightly raised.
let ijump = animate {
0s => 30px,
1.3s => 250px,
2.1s => 30px,
2.8s => 165px,
3.5s => 30px,
12s => 30px,
} with { easing: easing.in_out_cubic };
// Bright neutral lighting so each letter shows its own colour; a slow
// wobble on the key keeps the highlights alive.
let key = directional(from: vec3(wave(amplitude: 3, period: 8s), 4, 6), intensity: 1.7);
let fill = directional(from: vec3(-4, -2, 4), intensity: 0.8);
// A subtle spotlight pool drifts across the word (most visible at rest).
let sweepx = animate { 0s => -1100px, 6s => 1100px, 12s => -1100px } with { easing: easing.in_out_cubic };
let spot = spotlight(at: vec3(sweepx, 120px, 360px), intensity: 4.0, range: 820px, color: oklch(0.98, 0.01, 95));
let lights = [ ambient(0.40), key, fill, spot ];
// Each letter a different bright colour, scattered (not a smooth ramp).
let c0 = extrude(text.glyph("c", size: 430px), depth: 43px).material(fill: oklch(0.70, 0.20, 25), metalness: 0.0, roughness: 0.45).rotate(y: r0).translate(x: -567px);
let c1 = extrude(text.glyph("m", size: 430px), depth: 43px).material(fill: oklch(0.83, 0.16, 92), metalness: 0.0, roughness: 0.45).rotate(y: r1).translate(x: -313px);
let c2 = extrude(text.glyph("o", size: 430px), depth: 43px).material(fill: oklch(0.70, 0.19, 150), metalness: 0.0, roughness: 0.45).rotate(y: r2).translate(x: -59px);
let c3 = extrude(text.glyph("t", size: 430px), depth: 43px).material(fill: oklch(0.66, 0.16, 245), metalness: 0.0, roughness: 0.45).rotate(y: r3).translate(x: 108px);
let c4 = extrude(text.glyph("i", size: 430px), depth: 43px).material(fill: oklch(0.70, 0.23, 350), metalness: 0.0, roughness: 0.45).translate(x: 225px, y: ijump);
let c5 = extrude(text.glyph("o", size: 430px), depth: 43px).material(fill: oklch(0.74, 0.20, 55), metalness: 0.0, roughness: 0.45).rotate(y: r5).translate(x: 368px);
let c6 = extrude(text.glyph("n", size: 430px), depth: 43px).material(fill: oklch(0.72, 0.17, 195), metalness: 0.0, roughness: 0.45).rotate(y: r6).translate(x: 570px);
compose [
bg,
render3d(compose [c0, c1, c2, c3, c4, c5, c6], lights: lights, camera: camera(distance: 2166)),
]
}