// Minimal family: four or five of the track's signature form stacked one on
// another on an empty plain, leaning further than they should, with a low sun
// throwing the whole stack across the ground.
//
// Salt Flat is the other minimal with a horizon and an object, and its object is
// one, tiny and miles away: the subject there is the emptiness. Here the subject
// is close, it is the only thing in the frame, and it is a structure with a
// problem — it leans, and the low sun throws it across the whole plain. Nearly
// still, and the stillness is tense rather than calm.
//
// No declared slow axis. The sun crossing is the one that should qualify — it
// regrades the entire sky — and it measures 0.93x against the noise floor,
// because in a frame this empty the camera's drift moves the average about as
// much as anything in the scene can. Left undeclared rather than overclaimed.

export const balanceStack = {
    name: 'Balance Stack',
    family: 'minimal',
    kind: 'fragment',
    traits: ['shape', 'camera', 'space', 'style'],

    params: {
        count:   { type: 'int',   range: [2, 6],       default: 4,    uniform: 'u_count', bias: 'density' },
        lean:    { type: 'float', range: [-0.5, 0.5],  default: 0.12, uniform: 'u_lean' },
        block:   { type: 'float', range: [0.08, 0.4],  default: 0.2,  uniform: 'u_block' },
        taper:   { type: 'float', range: [0.5, 1.0],   default: 0.82, uniform: 'u_taper' },
        sun:     { type: 'float', range: [-1.0, 1.0],  default: -0.6, uniform: 'u_sun' },
        shadow:  { type: 'float', range: [0, 1],       default: 0.6,  uniform: 'u_shadow' },
        rim:     { type: 'float', range: [0, 1.5],     default: 0.7,  uniform: 'u_rim', bias: 'energy' },
        sway:    { type: 'float', range: [0.01, 0.2],  default: 0.04, uniform: 'u_sway', bias: 'motion', rate: true },
        palette: { type: 'palette', count: 5 },
    },

    reactive: {
        rim:    { feature: 'loudness', amount: 0.3,  response: 'smooth' },
        shadow: { feature: 'bandLow',  amount: 0.2,  response: 'smooth' },
    },

    shader: `
vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_sway + u_seed;
    p = sigCamera(p);

    float ground = sigHorizonY() * 0.4 - 0.35;
    float above = p.y - ground;

    // Sky and plain. Two flat tones and a line: everything else in the frame is
    // the stack.
    // The sun's position lights the whole frame, not just the stack: the glow
    // sits where it is on the horizon and the plain takes its colour from it.
    // That is what makes the sun crossing the sky the scene's long journey.
    float sunX = u_sun * 1.2;
    float toSun = abs(p.x - sunX);

    // The sky is graded ACROSS the frame toward the sun as well as up it, so
    // where the sun stands recolours everything rather than adding a spot.
    vec3 sky = mix(pal(1) * 0.22, pal(0) * 0.07, sat(above * 0.5 + 0.1));
    vec3 warm = mix(pal(3) * 0.3, pal(2) * 0.22, sat(above * 0.6 + 0.2));
    vec3 col = mix(sky, warm, sat(1.0 - toSun * 0.55));
    col += pal(3) * exp(-abs(above) * 8.0) * exp(-toSun * 0.8) * 0.9;
    col += pal(3) * exp(-abs(above) * 2.0) * exp(-toSun * 1.6) * 0.35;
    if (above < 0.0) {
        col = mix(pal(2) * 0.16, pal(0) * 0.09, sat(-above * 1.6));
        col += pal(3) * exp(above * 4.0) * exp(-toSun * 1.2) * 0.5;
    }

    // The sun is low and to one side; everything about the light comes from it.
    vec2 lightDir = normalize(vec2(u_sun, 0.55));

    // The shadow first, so the stack sits on top of its own.
    float shade = 0.0;
    float y = ground;
    float x = 0.0;
    // lint: fixed-cost — at most six blocks in the stack
    for (int i = 0; i < 6; i++) {
        if (i >= u_count) break;
        float fi = float(i);
        float size = u_block * pow(u_taper, fi);
        // Stacked by accumulation, so each block actually rests on the one
        // below whatever the taper is doing.
        y += size;
        x += u_lean * size * 1.6 + sin(t * 1.3 + fi * 0.7) * 0.012 * fi;
        float h = y;

        // Cast along the ground, stretched by how high the block sits.
        vec2 at = vec2(x - lightDir.x * (h - ground) * 2.2, ground - 0.02);
        vec2 q = (p - at) / vec2(size * 2.4, size * 0.32);
        shade = max(shade, exp(-dot(q, q) * 1.6));
        y += size;
    }
    col = mix(col, pal(0) * 0.05, shade * u_shadow * smoothstep(0.02, -0.02, above));

    // The stack. Drawn back to front, which for a vertical pile is bottom up.
    y = ground;
    x = 0.0;
    // lint: fixed-cost — the same six blocks
    for (int i = 0; i < 6; i++) {
        if (i >= u_count) break;
        float fi = float(i);
        float size = u_block * pow(u_taper, fi);
        y += size;
        x += u_lean * size * 1.6 + sin(t * 1.3 + fi * 0.7) * 0.012 * fi;

        vec2 at = vec2(x, y);
        float d = sigShape((p - at) / size) * size;

        // Body: shaded across the form, so it has a lit side and a dark side.
        float lit = sat(dot(normalize(p - at + 1e-4), lightDir) * 0.5 + 0.5);
        vec3 body = mix(pal(0) * 0.08, pal(2) * 0.4, lit) * (0.65 + 0.35 * (1.0 - fi * 0.12));
        col = mix(col, body, smoothstep(0.004, -0.004, d));

        // Rim: the sun catching the edge, in the track's line weight.
        col += pal(4) * sigEdge(d) * (0.3 + u_rim * 0.9) * (0.4 + lit * 0.8);
        y += size;
    }

    col = sigAir(col, p, smoothstep(0.1, 1.5, abs(p.x) * 0.7 + sat(above * 0.5)));
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default balanceStack;
