// Minimal family: a large occluder covering a bright source, with the light // escaping past its edge. // // Distinct from Slow Orb (a small soft body drifting through empty space): the // object here nearly fills the frame, its edge is hard, and it is not the // subject — the subject is the corona around it. That inversion is the whole // idea, and it gives the library its one genuinely high-contrast minimal scene: // almost black, with a thin ring of the brightest thing in the video. // // The occluder is the track's signature form, so this is where a hexagonal // track's hexagon is most legible. // // Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md. export const eclipseField = { name: 'Eclipse Field', family: 'minimal', kind: 'fragment', traits: ['shape', 'camera', 'space', 'style'], params: { size: { type: 'float', range: [0.2, 0.85], default: 0.45, uniform: 'u_size' }, corona: { type: 'float', range: [0.05, 1.2], default: 0.45, uniform: 'u_corona', bias: 'energy' }, rays: { type: 'float', range: [0, 1], default: 0.35, uniform: 'u_rays' }, rayCount: { type: 'float', range: [3, 24], default: 9, uniform: 'u_rayCount', bias: 'density' }, offset: { type: 'float', range: [0, 0.5], default: 0.12, uniform: 'u_offset' }, drift: { type: 'float', range: [0.01, 0.3], default: 0.05, uniform: 'u_drift', bias: 'motion', rate: true }, sky: { type: 'float', range: [0, 0.6], default: 0.15, uniform: 'u_sky' }, palette: { type: 'palette', count: 5 }, }, reactive: { corona: { feature: 'loudness', amount: 0.3, response: 'smooth' }, rays: { feature: 'bandAir', amount: 0.25, response: 'smooth' }, }, shader: ` vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_drift + u_seed; p = sigCamera(p); // The light sits a little off centre from the occluder, so the corona is // brighter on one side — a perfectly concentric eclipse looks like a decal. vec2 lightAt = vec2(sin(t * 0.8), cos(t * 0.6)) * u_offset; vec2 discAt = vec2(0.0, sigHorizonY() * 0.25); // Distance to the occluder's edge, in the track's form. float d = sigShape((p - discAt) / max(u_size, 1e-3)) * u_size; // The source behind it. float toLight = length(p - lightAt); float source = exp(-toLight * toLight * 2.2); vec3 col = pal(0) * u_sky * (1.0 - smoothstep(0.0, 1.6, length(p))); col += pal(1) * source * 0.5; // Corona: brightest exactly at the edge, falling off outward only. Inside // the disc it must be zero, or the occluder stops occluding. float outside = smoothstep(0.0, 0.02, d); float ring = exp(-max(d, 0.0) / max(u_corona * 0.25, 0.01)); col += palRamp(0.35 + d * 0.5) * ring * outside * (0.8 + u_corona); // Rays: angular streaks in the corona, rotating slowly. Confined to the // corona by the same falloff, so they never light the whole frame. if (u_rays > 0.01) { float ang = atan(p.y - discAt.y, p.x - discAt.x); float spokes = 0.5 + 0.5 * sin(ang * u_rayCount + t * 2.0); col += pal(4) * pow(spokes, 3.0) * ring * outside * u_rays; } // The occluder: not pure black, so it reads as an object rather than a hole. col = mix(col, pal(0) * 0.12, smoothstep(0.004, -0.004, d)); col += pal(3) * sigEdge(d) * (0.4 + u_corona * 0.5); col = sigAir(col, p, smoothstep(0.0, 1.7, length(p))); col += sigGrain(uv); return vec4(col, 1.0); } `, }; export default eclipseField;