// Geometric family: orthogonal traces growing outward from the centre, with a
// pad in the track's signature form at every junction.
//
// Distinct from Moiré Grid (two interfering line grids) and Prism Bloom (folded
// radial geometry): the structure here is Manhattan — everything runs at right
// angles, and the only curves are the pads. That right-angle language is the
// thing the library was missing, and it cuts hard against every radial scene.
//
// Traces light up in travelling pulses rather than all at once, so the frame is
// busy without ever changing brightness as a whole.

export const circuitBloom = {
    name: 'Circuit Bloom',
    family: 'geometric',
    kind: 'fragment',
    // Takes the track's surface grain, but lightly — this is drawn, not filmed.
    texture: 0.4,
    consumes: ['cast', 'ink'],
    traits: ['shape', 'camera', 'style'],

    params: {
        cells:    { type: 'float', range: [2, 14],     default: 6,    uniform: 'u_cells', bias: 'density'  },
        trace:    { type: 'float', range: [0.01, 0.1], default: 0.035,uniform: 'u_trace' },
        pads:     { type: 'float', range: [0, 1],      default: 0.55, uniform: 'u_pads' },
        padSize:  { type: 'float', range: [0.04, 0.22],default: 0.1,  uniform: 'u_padSize' },
        pulse:    { type: 'float', range: [0, 1.5],    default: 0.6,  uniform: 'u_pulse', bias: 'energy' },
        speed:    { type: 'float', range: [0.05, 1.2], default: 0.35, uniform: 'u_speed', bias: 'motion', rate: true },
        fill:     { type: 'float', range: [0.2, 0.95], default: 0.6,  uniform: 'u_fill', bias: 'density' },
        // How far the board has been routed out from its centre. This is the
        // slow axis: the board BLOOMS across the track, which is what the scene
        // is named for and what it never actually did.
        // Range stops at 1.6 on purpose: the frame's far corner is about 1.4
        // units out, so anything past that is a board that already covers
        // everything, and axis travel spent up there changes nothing.
        // Floor raised from 0.25: below about half, the growth front has not
        // reached far enough for any pad to be drawn and the frame is empty.
        grown:    { type: 'float', range: [0.55, 1.6], default: 1.0,  uniform: 'u_grown', slowAxis: true },
        edge:     { type: 'float', range: [0.2, 1],    default: 0.5,  uniform: 'u_edge' },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        pulse: { feature: 'bandHigh', amount: 0.4, response: 'smooth' },
        pads:  { feature: 'beat',     amount: 0.2, response: 'spike' },
    },

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

    vec2 g = p * u_cells;
    vec2 cell = floor(g);
    vec2 f = fract(g) - 0.5;

    float rnd = hash12(cell + u_seed);
    float rnd2 = hash12(cell * 1.7 + 11.3 + u_seed);

    vec3 col = pal(0) * 0.05;

    // Each cell carries a horizontal trace, a vertical one, or both — the L
    // junctions are what make it read as routing rather than as a grid.
    float horizontal = step(1.0 - u_fill, rnd);
    float vertical   = step(1.0 - u_fill, rnd2);

    float width = u_trace * (0.6 + u_sigLine);
    float dH = abs(f.y);
    float dV = abs(f.x);

    // How far the board has been routed. This used to be a fixed vignette
    // (a plain falloff on length(p)), which meant the layout was identical at
    // every moment of the song — the packets ran, the pads blinked, and the
    // BOARD never changed. Growing it is the scene's one piece of large-scale
    // structure, and the thing its name was always promising.
    //
    // Floored rather than closed: an unrouted region keeps a trace of copper so
    // a small board is sparse rather than an empty frame.
    float reach = mix(0.04, 1.0, sat((u_grown - length(p)) * (0.6 + u_edge * 3.0)));

    float traceMask = 0.0;
    if (horizontal > 0.5) traceMask += smoothstep(width, width * 0.35, dH);
    if (vertical > 0.5)   traceMask += smoothstep(width, width * 0.35, dV);
    traceMask = sat(traceMask) * reach;

    vec3 traceColor = palRamp(rnd * 0.4 + 0.15);
    col += traceColor * traceMask * 0.5;

    // Travelling pulse: a bright packet running along the trace, its position a
    // function of the cell's own hash so packets are out of step with each other.
    float along = horizontal > 0.5 ? f.x : f.y;
    float packet = fract(rnd * 3.1 + t * (0.4 + rnd2 * 0.8));
    float dPacket = abs(along - (packet - 0.5));
    col += pal(4) * traceMask * exp(-dPacket * dPacket * 260.0) * u_pulse;

    // Pads sit where both traces meet, stamped in the signature form.
    if (horizontal > 0.5 && vertical > 0.5 && rnd2 > 1.0 - u_pads) {
        float d = castMain(f / max(u_padSize, 1e-3)) * u_padSize;
        col += pal(2) * smoothstep(0.01, -0.01, d) * reach * 0.7;
        // Filled as well as stroked. At the bottom of the 'grown' range the pads
        // were a hairline and nothing else, and the ink's stroke can be thinner
        // than the edge it replaced — which left the frame empty at grown=0.25.
        col = mix(col, pal(3), inkMask(d, uv) * reach * 0.6);
        col += pal(3) * inkStroke(d) * reach * (0.4 + u_pulse * 0.4);
    }

    return vec4(inkValue(col), 1.0);
}
`,
};

export default circuitBloom;
