/* ============================================================ qft/palette.js — per-field hue gradients + substrate variants. Each FIELD (one lattice) is parameterised as a slider: hueStart, hueEnd, saturation, lightness, opacity The renderer asks for the colour at a t∈[0,1] along an edge (or at an (x,y) position when the gradient mode is radial/linear). "Hue gradient over time" = per-edge progression, the QFT analog of the bubble-chamber lifecycle palette. Self-contained: no dependency on the bubble-chamber palette registry. Reuses only the generic hslToRgb util. ============================================================ */ import { hslToRgb } from '../render/palette.js'; const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v)); /* Substrate variants — the "chemistry" the lattices sit on. */ export const SUBSTRATES = { vintage: { paper: { flat: [42, 32, 22], glowIn: [62, 48, 32], glowOut: [22, 16, 10] }, vign: [10, 7, 4], feature: [178, 138, 78] }, void: { paper: { flat: [10, 10, 14], glowIn: [22, 22, 30], glowOut: [3, 3, 6] }, vign: [0, 0, 0], feature: [150, 150, 180] }, cream: { paper: { flat: [207, 200, 180], glowIn: [226, 219, 199], glowOut: [179, 170, 146] }, vign: [56, 46, 32], feature: [42, 30, 20] }, selenium: { paper: { flat: [190, 181, 197], glowIn: [210, 201, 217], glowOut: [156, 146, 166] }, vign: [42, 38, 54], feature: [58, 50, 72] }, cyanotype:{ paper: { flat: [22, 48, 82], glowIn: [38, 70, 110], glowOut: [10, 22, 44] }, vign: [6, 12, 22], feature: [180, 210, 235] }, }; export function resolveSubstrate(id = 'vintage') { return SUBSTRATES[id] || SUBSTRATES.vintage; } /* Sample the gradient parameter s ∈ [0, 1] for a point on an edge. - along-edge: s = t (the per-edge t the renderer passes in) - radial: s = min(1, |(x, y)|) (centre → 0, corners → 1) - linear-x: s = (x + 1) / 2 (left → 0, right → 1) - linear-y: s = (y + 1) / 2 (top → 0, bottom → 1) */ export function gradientS(t, x, y, mode = 'along-edge') { switch (mode) { case 'radial': return clamp(Math.hypot(x, y), 0, 1); case 'linear-x': return clamp((x + 1) / 2, 0, 1); case 'linear-y': return clamp((y + 1) / 2, 0, 1); case 'along-edge': default: return clamp(t, 0, 1); } } /* Compute the colour for a field at gradient parameter s. Hue rotates from hueStart → hueEnd by the SHORT way around the circle (so a small Δ stays small even when crossing 1.0). */ export function fieldColor(field, s) { let dh = field.hueEnd - field.hueStart; // wrap to shortest-path delta (-0.5 .. +0.5) if (dh > 0.5) dh -= 1; if (dh < -0.5) dh += 1; const h = field.hueStart + s * dh; return hslToRgb(h, field.saturation, field.lightness); }