// Minimal family: a single ribbon of light draping across an almost empty // frame. The curve is a closed-form travelling wave, so it is seek-exact; it is // drawn by sampling the polyline and taking the minimum distance, which reads as // one fluid strand rather than a repeated motif. Most of the frame is negative // space — what an intro or a breakdown needs. export const silkRibbon = { name: 'Silk Ribbon', family: 'minimal', kind: 'fragment', // Personality: see look/Personality.js. // A soft drape of light. Grain furs the one thing it is made of. texture: 0, traits: ['camera', 'style'], params: { thickness: { type: 'float', range: [0.008, 0.08], default: 0.03, uniform: 'u_thickness' }, wave: { type: 'float', range: [0.1, 2.5], default: 1.1, uniform: 'u_wave' }, sway: { type: 'float', range: [0.05, 0.6], default: 0.25, uniform: 'u_sway', bias: 'motion' }, glow: { type: 'float', range: [0, 1.5], default: 0.5, uniform: 'u_glow', bias: 'energy' }, speed: { type: 'float', range: [0.02, 0.4], default: 0.12, uniform: 'u_speed', bias: 'motion', rate: true }, spread: { type: 'float', range: [0.5, 1.2], default: 0.9, uniform: 'u_spread' }, palette: { type: 'palette', count: 4 }, }, reactive: { glow: { feature: 'beat', amount: 0.3, response: 'smooth' }, wave: { feature: 'bandLow', amount: 0.25, response: 'smooth' }, }, shader: ` // Nearest distance from p to a fixed travelling strand. vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_speed + u_seed; p = sigCamera(p); // Bare wash so the frame is never black. vec3 col = pal(0) * 0.04; // Two nested strands, offset in phase so the ribbon reads as a drape with a // folded edge rather than a single hairline. for (int k = 0; k < 2; k++) { float fk = float(k); float ph = fk * 2.2 + t * 0.3; float best = 1e9; // lint: fixed-cost — this samples the curve at a fixed resolution, so // there is no param to break on. Cost is governed by the 4K budget gate. for (int n = 0; n < 48; n++) { float uu = (float(n) + 0.5) / 48.0; float x = (uu - 0.5) * 2.0 * u_spread; float y = sin(uu * 6.28318530718 * u_wave + ph) * 0.28 + sin(uu * 13.1 - t * 0.8 + fk) * 0.06 + u_sway * 0.4 * sin(t * 0.6 + fk); float d = length(p - vec2(x, y)); best = min(best, d); } // TRAIT style: the ribbon is drawn in the track's hand. u_sigLine sets // how wide the strand is, u_sigSoft how fast it falls off — a sharp // track gets a filament with a defined edge, a soft one gets a haze. // This is the scene's whole art direction, so it is not scaled by any // of its own params: there is no setting at which the track stops // showing. float w = u_thickness * mix(0.55, 1.7, sat(u_sigLine)); float falloff = mix(2.6, 0.9, sat(u_sigSoft)); vec3 hc = pal(int(fk) % 3); col += hc * exp(-pow(best / w, falloff * 2.0)); col += pal(3) * exp(-best * best * 60.0) * u_glow * 0.6; } // Knot the ribbon just below frame centre so it has somewhere to pull from. float knot = length(p - vec2(0.0, 0.1)); col = mix(col, pal(2), exp(-knot * knot * 30.0) * u_glow * 0.5); col += sigGrain(uv); return vec4(col, 1.0); } `, };