// Structural family: looking straight down a stairwell. Floor after floor of // landings and balustrades, each one turned a little from the one above, going // down forever. // // Gate Corridor also travels through repeated structure, but it travels // horizontally past gates that arrive and leave: the frame has a middle and two // sides. Here the vanishing point is dead centre, every floor is a closed ring // around it, and the motion is entirely radial. The turn per floor is what makes // it a stairwell rather than a tunnel — the flights spiral even though nothing // in the image rotates. export const stairwellDescent = { name: 'Stairwell Descent', family: 'structural', kind: 'fragment', texture: 0.7, consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { floors: { type: 'float', range: [0.18, 0.6], default: 0.32, uniform: 'u_floors', bias: 'density' }, turn: { type: 'float', range: [-0.5, 0.5], default: 0.22, uniform: 'u_turn' }, well: { type: 'float', range: [0.1, 0.6], default: 0.3, uniform: 'u_well' }, rails: { type: 'int', range: [6, 40], default: 18, uniform: 'u_rails', bias: 'density' }, landing: { type: 'float', range: [0.1, 0.45], default: 0.25, uniform: 'u_landing' }, lit: { type: 'float', range: [0, 1.4], default: 0.6, uniform: 'u_lit', bias: 'energy' }, depth: { type: 'float', range: [0.2, 1], default: 0.65, uniform: 'u_deep', slowAxis: true }, fall: { type: 'float', range: [0.01, 0.35], default: 0.08, uniform: 'u_fall', bias: 'motion', rate: true }, palette: { type: 'palette', count: 5 }, }, reactive: { lit: { feature: 'bandHigh', amount: 0.3, response: 'smooth' }, rails: { feature: 'bandMid', amount: 0.15, response: 'smooth' }, }, shader: ` vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_fall + u_seed; p = sigCamera(p); float r = length(p) + 0.001; float th = atan(p.y, p.x); // Log-radial: one unit is one floor. Descending is a constant slide down // this axis, which is what makes the fall never end and never repeat a cut. float z = log(r / u_well) / u_floors + t * 3.0; float floorId = floor(z); float f = fract(z); // Each flight is turned from the one above it. float a = th + floorId * u_turn * 6.28318530718; // The balustrade: uprights around the well, and the handrail on top of them. float posts = abs(fract(a / 6.28318530718 * float(u_rails)) - 0.5) * 2.0; float w = 0.25 + u_sigLine * 0.5; float upright = smoothstep(w, w * 0.25, posts) * smoothstep(0.75, 0.35, f); float rail = smoothstep(0.12, 0.02, abs(f - 0.72)); // The landing slab: a solid quarter of every floor, so there is somewhere to // stand and the eye can count the floors. float quarter = fract(a / 6.28318530718 + 0.5); float slab = smoothstep(0.5, 0.44, abs(quarter - 0.5)) * smoothstep(0.08, 0.2, f) * smoothstep(u_landing + 0.25, u_landing, f); // Everything dims toward the middle. This is the only cue that the well has // a bottom, so it carries the whole read of the image — and it is measured // off the radius rather than off the floor index, because the floors below // us occupy a few pixels at the centre while the darkness has to fill half // the frame to read as depth at all. float fade = mix(1.0, sat(r * 0.7), u_deep); float sky = smoothstep(0.85, 1.6, r); // the top of the well, above us vec3 col = pal(0) * 0.07; col += palRamp(0.25 + f * 0.2) * upright * 0.45 * fade; col += pal(4) * rail * (0.4 + u_lit * 0.7) * fade; col += mix(pal(2) * 0.3, pal(1) * 0.22, fract(floorId * 0.37)) * slab * fade; // A light on each landing, catching the rail below it. float lamp = exp(-abs(f - 0.45) * 6.0) * smoothstep(0.55, 0.2, abs(quarter - 0.25)); col += pal(3) * lamp * u_lit * 0.5 * fade; col = mix(col, pal(1) * 0.3, sky * 0.5); col = sigAir(col, p, sat(1.0 - fade)); return vec4(inkValue(col), 1.0); } `, }; export default stairwellDescent;