// Minimal family: one soft body drifting through a mostly empty frame. // // The quietest scene in the library, and the one an ambient intro or a long // breakdown should usually land on. Nothing here reacts sharply — the beat // mapping is deliberately weak, because a scene whose job is stillness should // not twitch. export const slowOrb = { name: 'Slow Orb', family: 'minimal', kind: 'fragment', params: { size: { type: 'float', range: [0.15, 0.8], default: 0.38, uniform: 'u_size', bias: 'energy' }, softness: { type: 'float', range: [0.2, 1.0], default: 0.7, uniform: 'u_softness' }, drift: { type: 'float', range: [0.01, 0.2], default: 0.05, uniform: 'u_drift', bias: 'motion', rate: true }, wobble: { type: 'float', range: [0, 0.5], default: 0.15, uniform: 'u_wobble' }, halo: { type: 'float', range: [0, 1.2], default: 0.4, uniform: 'u_halo' }, grain: { type: 'float', range: [0, 0.5], default: 0.12, uniform: 'u_grain' }, palette: { type: 'palette', count: 4 }, }, reactive: { size: { feature: 'loudness', amount: 0.12, response: 'smooth' }, halo: { feature: 'beat', amount: 0.15, response: 'spike' }, }, shader: ` vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_drift + u_seed; vec2 centre = vec2(sin(t * 1.7) * 0.28, cos(t * 1.3) * 0.18); vec2 q = p - centre; // Break the silhouette so it never reads as a hard circle. float wobble = fbm(q * 2.4 + t, 4) * u_wobble; float d = length(q) * (1.0 + wobble) - u_size; float body = smoothstep(u_softness * 0.5, -u_softness * 0.5, d); float glow = exp(-max(d, 0.0) * (5.0 / max(u_halo, 0.05))) * u_halo; vec3 col = pal(0) * 0.05; col = mix(col, pal(1), body * 0.85); col += pal(2) * body * body * 0.5; col += pal(3) * glow * 0.35; // Fine grain keeps large flat areas from banding. col += (hash12(uv * 640.0 + floor(u_frame)) - 0.5) * u_grain * 0.08; return vec4(col, 1.0); } `, }; export default slowOrb;