2026-05-29 17:17:06 -04:00
|
|
|
/* ============================================================
|
|
|
|
|
layering.mjs — the assembled piece: FILM/diffusion (back) + a QFT
|
|
|
|
|
vacuum-carpet DECK (middle, 3 spaced plate sheets) + a BUBBLE-CHAMBER
|
|
|
|
|
event (front), composited as one image (and the literal plexi stack).
|
|
|
|
|
Produces variations into output/layering/.
|
|
|
|
|
Usage: node tools/layering.mjs [size]
|
|
|
|
|
============================================================ */
|
|
|
|
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
|
|
|
import { carpetSVG } from '../src/qft/carpet.js';
|
2026-06-02 19:17:19 -04:00
|
|
|
import { perspectiveGridSVG } from '../src/qft/perspgrid.js';
|
2026-05-29 17:17:06 -04:00
|
|
|
import { generateScene } from '../src/scene/scene.js';
|
|
|
|
|
import { renderSVG } from '../src/render/svgVector.js';
|
|
|
|
|
import { paramsFromSeed as bcParams } from '../src/scene/params.js';
|
|
|
|
|
import { GROUPS, TOGGLES, FIXED } from '../src/ui/controls.js';
|
|
|
|
|
|
|
|
|
|
const SIZE = +(process.argv[2] || 1500);
|
|
|
|
|
const OUT = 'output/layering';
|
|
|
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
|
const u = SIZE / 1000;
|
|
|
|
|
const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
|
|
|
|
|
|
|
|
|
|
// ---- FILM / diffusion: milky clouds (light, soft) the light diffuses through ----
|
|
|
|
|
function filmSVG(o = {}) {
|
|
|
|
|
const { seed = 7, freq = 0.0016, octaves = 4, tone = [236, 228, 208], density = 0.55 } = o;
|
|
|
|
|
const t = tone.map(v => (v / 255).toFixed(3));
|
|
|
|
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
|
|
|
|
|
<defs><filter id="fog" x="0" y="0" width="100%" height="100%">
|
|
|
|
|
<feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="${octaves}" seed="${seed}" stitchTiles="stitch" result="n"/>
|
|
|
|
|
<feColorMatrix in="n" type="matrix" values="0 0 0 0 ${t[0]} 0 0 0 0 ${t[1]} 0 0 0 0 ${t[2]} 0 0 0 ${density} 0"/>
|
|
|
|
|
</filter></defs><rect width="${SIZE}" height="${SIZE}" filter="url(#fog)"/></svg>`;
|
|
|
|
|
}
|
|
|
|
|
// ---- film GRAIN veil (fine, dark, low opacity) on top ----
|
|
|
|
|
function grainSVG(o = {}) {
|
|
|
|
|
const { seed = 19, tone = [38, 32, 26], amount = 0.5 } = o;
|
|
|
|
|
const t = tone.map(v => (v / 255).toFixed(3));
|
|
|
|
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
|
|
|
|
|
<defs><filter id="g" x="0" y="0" width="100%" height="100%">
|
|
|
|
|
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="${seed}" stitchTiles="stitch" result="n"/>
|
|
|
|
|
<feColorMatrix in="n" type="matrix" values="0 0 0 0 ${t[0]} 0 0 0 0 ${t[1]} 0 0 0 0 ${t[2]} 0 0 0 ${amount} 0"/>
|
|
|
|
|
</filter></defs><rect width="${SIZE}" height="${SIZE}" filter="url(#g)"/></svg>`;
|
|
|
|
|
}
|
|
|
|
|
// ---- bubble-chamber event ----
|
2026-05-30 06:42:16 -04:00
|
|
|
function bcSVG(seed, over = {}, float = false) {
|
2026-05-29 17:17:06 -04:00
|
|
|
const p = { ...FIXED, ...bcParams(seed) };
|
|
|
|
|
for (const g of GROUPS) for (const c of g.controls) if (!(c.id in p)) p[c.id] = c.value;
|
|
|
|
|
for (const t of TOGGLES) if (!(t.id in p)) p[t.id] = t.value;
|
|
|
|
|
p.invert = true; p.showHeader = false;
|
2026-05-30 06:42:16 -04:00
|
|
|
if (float) p.transparentPaper = true; // no paper rect → event floats as an object
|
2026-05-29 17:17:06 -04:00
|
|
|
Object.assign(p, over);
|
2026-05-30 06:42:16 -04:00
|
|
|
const scene = generateScene(p);
|
|
|
|
|
const sh = scene.shock ? { x: scene.shock.x, y: scene.shock.y } : { x: 0, y: 0 };
|
|
|
|
|
return { svg: renderSVG(scene, p, SIZE), sh }; // sh = disk centre (normalized) for anchoring
|
2026-05-29 17:17:06 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-30 06:42:16 -04:00
|
|
|
// AGING layer: sparse scratches + dust speckle + foxing/stain blotches. Seeded.
|
|
|
|
|
function agingSVG(o = {}) {
|
|
|
|
|
const { seed = 5, scratches = 7, dust = 0.5, foxing = 0.5, tone = [60, 48, 34] } = o;
|
|
|
|
|
const t = tone.map(v => (v / 255).toFixed(3));
|
|
|
|
|
let lcg = (seed * 9301 + 49297) % 233280; const rnd = () => (lcg = (lcg * 9301 + 49297) % 233280) / 233280;
|
|
|
|
|
let lines = '';
|
|
|
|
|
for (let i = 0; i < scratches; i++) {
|
|
|
|
|
const x = rnd() * SIZE, y0 = rnd() * SIZE * 0.4, len = (0.3 + rnd() * 0.6) * SIZE;
|
|
|
|
|
const x2 = x + (rnd() - 0.5) * 40, y2 = y0 + len;
|
|
|
|
|
const op = (0.05 + rnd() * 0.12).toFixed(3), wsc = (0.4 + rnd() * 0.8).toFixed(2);
|
|
|
|
|
lines += `<line x1="${x.toFixed(0)}" y1="${y0.toFixed(0)}" x2="${x2.toFixed(0)}" y2="${y2.toFixed(0)}" stroke="rgb(${tone.join(',')})" stroke-opacity="${op}" stroke-width="${wsc}"/>`;
|
|
|
|
|
}
|
|
|
|
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
|
|
|
|
|
<defs>
|
|
|
|
|
<filter id="dust" x="0" y="0" width="100%" height="100%"><feTurbulence type="turbulence" baseFrequency="0.5" numOctaves="2" seed="${seed + 5}" stitchTiles="stitch" result="n"/><feColorMatrix in="n" type="matrix" values="0 0 0 0 ${t[0]} 0 0 0 0 ${t[1]} 0 0 0 0 ${t[2]} 0 0 0 ${(dust * 0.5).toFixed(3)} -0.18"/></filter>
|
|
|
|
|
<filter id="fox" x="0" y="0" width="100%" height="100%"><feTurbulence type="fractalNoise" baseFrequency="0.004" numOctaves="3" seed="${seed + 9}" stitchTiles="stitch" result="n"/><feColorMatrix in="n" type="matrix" values="0 0 0 0 0.42 0 0 0 0 0.30 0 0 0 0 0.16 0 0 0 ${(foxing * 0.32).toFixed(3)} -0.12"/></filter>
|
|
|
|
|
</defs>
|
|
|
|
|
<rect width="${SIZE}" height="${SIZE}" filter="url(#fox)"/>
|
|
|
|
|
<rect width="${SIZE}" height="${SIZE}" filter="url(#dust)"/>
|
|
|
|
|
${lines}</svg>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build a 3-sheet carpet deck for a variation.
|
|
|
|
|
// PROGRESSION back→front: thick lines (+ heavy blur in the composite) → fine, crisp.
|
2026-05-29 17:17:06 -04:00
|
|
|
function deck(c) {
|
|
|
|
|
const base = { mode: 'plate', rows: 46, horizon: c.horizon ?? 0.37, wFar: 0.58, wNear: 0.7,
|
|
|
|
|
overlap: c.overlap ?? 1.7, mound: c.mound ?? 0.35, sat: c.sat ?? 0.58, lightNear: 0.33, lightFar: 0.56, blips: c.blips ?? 1.0 };
|
|
|
|
|
const lerp = (a, b, t) => a + (b - a) * t;
|
2026-05-30 06:42:16 -04:00
|
|
|
// per-sheet stroke weights: back coarse, front fine
|
|
|
|
|
const strokes = [{ near: 2.6, far: 1.0 }, { near: 1.6, far: 0.6 }, { near: 1.0, far: 0.38 }];
|
2026-05-29 17:17:06 -04:00
|
|
|
return [0, 1, 2].map(i => {
|
|
|
|
|
const t = i / 2; // 0 back → 1 front
|
|
|
|
|
return carpetSVG(SIZE, { ...base, salt: 'field' + i,
|
|
|
|
|
hue: lerp(c.hueBack, c.hueFront, t), hue2: lerp(c.hueBack, c.hueFront, t) + 0.035,
|
2026-05-30 06:42:16 -04:00
|
|
|
chaos: lerp((c.chaos ?? 0.6) * 0.8, c.chaos ?? 0.6, t),
|
|
|
|
|
strokeNear: strokes[i].near, strokeFar: strokes[i].far });
|
2026-05-29 17:17:06 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compose(v) {
|
|
|
|
|
const film = dataUri(filmSVG(v.film));
|
|
|
|
|
const sheets = deck(v.carpet).map(dataUri);
|
2026-05-30 06:42:16 -04:00
|
|
|
const float = !!v.bcFloat;
|
|
|
|
|
const { svg: bcRaw, sh } = bcSVG(v.bcSeed, v.bcOver, float);
|
|
|
|
|
const bc = dataUri(bcRaw);
|
2026-05-29 17:17:06 -04:00
|
|
|
const grain = dataUri(grainSVG(v.grain));
|
2026-05-30 06:42:16 -04:00
|
|
|
const aging = v.aging ? dataUri(agingSVG(v.aging)) : null;
|
2026-05-29 17:17:06 -04:00
|
|
|
const base = v.base || 'rgb(226,219,199)';
|
2026-05-30 06:42:16 -04:00
|
|
|
const blend = float ? 'normal' : 'multiply'; // floating event = transparent paper, normal blend
|
|
|
|
|
|
|
|
|
|
// BC placement. sunAt:[sx,sy] (frame coords -1..1) anchors on the DISK centre and
|
|
|
|
|
// scales the whole event about it → the sun lands exactly there, never clipped.
|
|
|
|
|
let bcEl;
|
|
|
|
|
if (v.sunAt) {
|
|
|
|
|
const s = v.bcScale ?? 1, scB = (SIZE / 2) * (1 - 0.02);
|
|
|
|
|
const dpx = SIZE / 2 + sh.x * scB, dpy = SIZE / 2 + sh.y * scB; // disk px in BC canvas
|
|
|
|
|
const fx = SIZE / 2 + v.sunAt[0] * (SIZE / 2), fy = SIZE / 2 + v.sunAt[1] * (SIZE / 2);
|
|
|
|
|
bcEl = `<g transform="translate(${(fx - s * dpx).toFixed(1)} ${(fy - s * dpy).toFixed(1)}) scale(${s})" style="mix-blend-mode:${blend}"><image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${bc}"/></g>`;
|
|
|
|
|
} else {
|
|
|
|
|
const s = v.bcScale ?? 1, dx = (v.bcDx || 0) * SIZE, dy = (v.bcDy || 0) * SIZE;
|
|
|
|
|
const bcW = SIZE * s, bcX = (SIZE - bcW) / 2 + dx, bcY = (SIZE - bcW) / 2 + dy;
|
|
|
|
|
bcEl = `<image x="${bcX.toFixed(0)}" y="${bcY.toFixed(0)}" width="${bcW.toFixed(0)}" height="${bcW.toFixed(0)}" href="${bc}" style="mix-blend-mode:${blend}"/>`;
|
|
|
|
|
}
|
|
|
|
|
const dir = v.dir ? `${OUT}/${v.dir}` : OUT;
|
|
|
|
|
if (v.dir) mkdirSync(dir, { recursive: true });
|
2026-06-02 19:17:19 -04:00
|
|
|
|
|
|
|
|
const IMG = (href, attrs = '') => `<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${href}" ${attrs}/>`;
|
|
|
|
|
const carpetEls = [
|
|
|
|
|
IMG(sheets[0], `filter="url(#b3)" opacity="0.5"`),
|
|
|
|
|
IMG(sheets[1], `filter="url(#b2)" opacity="0.72"`),
|
|
|
|
|
IMG(sheets[2], `opacity="0.95"`),
|
|
|
|
|
].join('\n');
|
|
|
|
|
// optional secondary PERSPECTIVE-GRID plex layer (dual blur/fine deck)
|
|
|
|
|
let perspEls = '';
|
|
|
|
|
if (v.persp) {
|
|
|
|
|
const pb = { mode: 'plate', ...v.persp };
|
|
|
|
|
// BACK: ripple A, heavier stroke (blurred in composite). FRONT: a DISTINCT
|
|
|
|
|
// ripple (shifted freq + phase) and fine line → the two interfere (moiré).
|
|
|
|
|
const back = dataUri(perspectiveGridSVG(SIZE, { ...pb, salt: 'pA', stroke: (pb.stroke ?? 1.4) * 1.6, strokeFar: (pb.strokeFar ?? 0.5) * 1.6 }));
|
|
|
|
|
const front = dataUri(perspectiveGridSVG(SIZE, {
|
|
|
|
|
...pb, salt: 'pB', stroke: (pb.stroke ?? 1.4) * 0.8, strokeFar: (pb.strokeFar ?? 0.5) * 0.8,
|
|
|
|
|
rippleFreqR: (pb.rippleFreqR ?? 2.4) * 1.24, rippleFreqA: (pb.rippleFreqA ?? 5) + 1.5, ripplePhase: (pb.ripplePhase ?? 0) + 0.95,
|
|
|
|
|
}));
|
|
|
|
|
const op = v.perspOpacity ?? 0.42;
|
|
|
|
|
perspEls = IMG(back, `filter="url(#b2)" opacity="${op}"`) + '\n' + IMG(front, `opacity="${(op + 0.18).toFixed(2)}"`);
|
|
|
|
|
}
|
|
|
|
|
const mid = v.perspPos === 'behind' ? (perspEls + '\n' + carpetEls) : (carpetEls + '\n' + perspEls);
|
2026-05-29 17:17:06 -04:00
|
|
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
|
|
|
|
|
<defs>
|
2026-05-30 06:42:16 -04:00
|
|
|
<filter id="b3" x="-8%" y="-8%" width="116%" height="116%"><feGaussianBlur stdDeviation="${(2.6 * u).toFixed(2)}"/></filter>
|
|
|
|
|
<filter id="b2" x="-6%" y="-6%" width="112%" height="112%"><feGaussianBlur stdDeviation="${(1.1 * u).toFixed(2)}"/></filter>
|
2026-05-29 17:17:06 -04:00
|
|
|
</defs>
|
|
|
|
|
<rect width="${SIZE}" height="${SIZE}" fill="${base}"/>
|
|
|
|
|
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${film}" opacity="0.6"/>
|
2026-06-02 19:17:19 -04:00
|
|
|
${mid}
|
2026-05-30 06:42:16 -04:00
|
|
|
${bcEl}
|
|
|
|
|
${aging ? `<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${aging}" opacity="${v.agingOpacity ?? 0.6}" style="mix-blend-mode:multiply"/>` : ''}
|
2026-05-29 17:17:06 -04:00
|
|
|
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${grain}" opacity="${v.grainOpacity ?? 0.45}" style="mix-blend-mode:multiply"/>
|
|
|
|
|
</svg>`;
|
2026-05-30 06:42:16 -04:00
|
|
|
writeFileSync(`${dir}/${v.name}.svg`, svg);
|
|
|
|
|
console.log(` ${v.dir ? v.dir + '/' : ''}${v.name} (bc=${v.bcSeed})`);
|
2026-05-29 17:17:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Variations — film + carpet + event, ranging mood & "loudness".
|
|
|
|
|
// Guiding thesis: calm sea, loud event.
|
|
|
|
|
// ============================================================
|
|
|
|
|
const VARIATIONS = [
|
|
|
|
|
{ name: '01_mono-calm-sea', base: 'rgb(228,221,201)',
|
|
|
|
|
film: { seed: 3, density: 0.5, tone: [236, 228, 208] }, grain: { amount: 0.4 },
|
|
|
|
|
carpet: { hueBack: 0.58, hueFront: 0.50, chaos: 0.35, blips: 0.7, mound: 0.32 },
|
|
|
|
|
bcSeed: 'LAMBDA-2648', bcOver: { palette: 'mono' } },
|
|
|
|
|
|
|
|
|
|
{ name: '02_magenta-over-teal', base: 'rgb(228,221,201)',
|
|
|
|
|
film: { seed: 8, density: 0.5 }, grain: { amount: 0.4 },
|
|
|
|
|
carpet: { hueBack: 0.54, hueFront: 0.47, chaos: 0.45, blips: 1.0, mound: 0.35 },
|
|
|
|
|
bcSeed: 'MESON-5113', bcOver: { palette: 'magentarise', saturation: 1.05 } },
|
|
|
|
|
|
|
|
|
|
{ name: '03_quiet-vast', base: 'rgb(230,224,206)',
|
|
|
|
|
film: { seed: 12, density: 0.42, tone: [238, 231, 212] }, grain: { amount: 0.32 },
|
|
|
|
|
carpet: { hueBack: 0.57, hueFront: 0.52, chaos: 0.22, blips: 0.5, mound: 0.28, overlap: 1.5 },
|
|
|
|
|
bcSeed: 'NUCLEON-2131', bcOver: { palette: 'mono', primaries: 7, burst: 0.4, cosmics: 2, deltaRate: 0.45 } },
|
|
|
|
|
|
|
|
|
|
{ name: '04_kind-verdigris', base: 'rgb(226,221,205)',
|
|
|
|
|
film: { seed: 21, density: 0.5 }, grain: { amount: 0.4 },
|
|
|
|
|
carpet: { hueBack: 0.45, hueFront: 0.40, chaos: 0.5, blips: 1.1, sat: 0.42, mound: 0.35 },
|
|
|
|
|
bcSeed: 'HYPERON-8444', bcOver: { palette: 'kind', saturation: 1.0 } },
|
|
|
|
|
|
|
|
|
|
{ name: '05_ember-warm', base: 'rgb(230,222,202)',
|
|
|
|
|
film: { seed: 30, density: 0.52, tone: [238, 226, 204] }, grain: { amount: 0.42 },
|
|
|
|
|
carpet: { hueBack: 0.10, hueFront: 0.07, chaos: 0.5, blips: 1.0, sat: 0.5, mound: 0.34 },
|
|
|
|
|
bcSeed: 'CASCADE-2755', bcOver: { palette: 'kindrise', saturation: 1.0 } },
|
|
|
|
|
|
|
|
|
|
{ name: '06_seethe-bold', base: 'rgb(227,220,200)',
|
|
|
|
|
film: { seed: 41, density: 0.55 }, grain: { amount: 0.46 },
|
|
|
|
|
carpet: { hueBack: 0.55, hueFront: 0.47, chaos: 0.8, blips: 1.4, mound: 0.4, overlap: 1.9 },
|
|
|
|
|
bcSeed: 'MESON-5113', bcOver: { palette: 'magentarise', saturation: 1.08 } },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
console.log(`Layered piece — ${VARIATIONS.length} variations → ${OUT}/`);
|
|
|
|
|
for (const v of VARIATIONS) compose(v);
|
|
|
|
|
|
2026-05-30 06:42:16 -04:00
|
|
|
// ============================================================
|
|
|
|
|
// COMPOSITION study — fixed magenta/teal deck (the 02 look), only the
|
|
|
|
|
// BC event's COMPOSITION changes: vertex placement (eventX/Y), disk height
|
|
|
|
|
// (shockY), and placement in frame (bcScale/bcDy). Same palette / density /
|
|
|
|
|
// arc / disk style throughout. Chasing "sunrise over a vast ocean".
|
|
|
|
|
// ============================================================
|
|
|
|
|
const TEAL = { hueBack: 0.54, hueFront: 0.47, chaos: 0.42, blips: 1.0, mound: 0.35 };
|
|
|
|
|
const tealFilm = { seed: 8, density: 0.5 }, tealGrain = { amount: 0.4 };
|
|
|
|
|
// place the SUN (disk) and the event VERTEX together so tracks radiate from it
|
|
|
|
|
const sun = (name, px, py) => ({
|
|
|
|
|
name, dir: 'composition', base: 'rgb(228,221,201)', film: tealFilm, grain: tealGrain, carpet: TEAL,
|
|
|
|
|
bcSeed: 'MESON-5113',
|
|
|
|
|
bcOver: { palette: 'magentarise', saturation: 1.05, shockX: px, shockY: py, eventX: px, eventY: py * 0.9 },
|
|
|
|
|
});
|
|
|
|
|
const COMPOSITION = [
|
|
|
|
|
sun('01_sunrise-on-horizon', 0, -0.26), // sun resting on the horizon, rays up
|
|
|
|
|
sun('02_sun-low-in-sea', 0, 0.46), // low in the water — reflected/setting
|
|
|
|
|
sun('03_high-sun-vast', 0, -0.5), // small high sun, vast sea below
|
|
|
|
|
sun('04_offset-left', -0.46, -0.12),
|
|
|
|
|
sun('05_offset-right-rise', 0.44, -0.06),
|
|
|
|
|
sun('06_centered', 0, 0.0),
|
|
|
|
|
sun('07_low-corner-sun', 0.42, 0.34), // rising in the lower-right
|
|
|
|
|
sun('08_corner-dawn-left', -0.5, 0.28), // rising in the lower-left
|
|
|
|
|
];
|
|
|
|
|
console.log(`BC composition study — ${COMPOSITION.length} → ${OUT}/composition/`);
|
|
|
|
|
for (const v of COMPOSITION) compose(v);
|
|
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/composition/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${COMPOSITION.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VAST — the floating event (transparent paper) shrunk + placed into a huge sea,
|
|
|
|
|
// so the sun is small and the ocean is vast. The event stays CENTRED in its own
|
|
|
|
|
// canvas (never clipped); (sx,sy) is its target position in the FINAL frame
|
|
|
|
|
// (-1..1), reached by translating the scaled image: bcD = s/2.
|
|
|
|
|
const vast = (name, sx, sy, scale) => ({
|
|
|
|
|
name, dir: 'vast', base: 'rgb(229,222,203)', film: tealFilm, grain: tealGrain, carpet: TEAL,
|
|
|
|
|
bcSeed: 'MESON-5113', bcFloat: true, bcScale: scale, bcDx: sx / 2, bcDy: sy / 2,
|
|
|
|
|
bcOver: { palette: 'magentarise', saturation: 1.05, shockX: 0, shockY: 0, eventX: 0, eventY: 0 },
|
|
|
|
|
});
|
|
|
|
|
const VAST = [
|
|
|
|
|
vast('01_distant-dawn', 0, -0.26, 0.6), // sun on the horizon
|
|
|
|
|
vast('02_small-sun-horizon', 0, -0.26, 0.5),
|
|
|
|
|
vast('03_corner-rise-left', -0.42, 0.02, 0.55),
|
|
|
|
|
vast('04_high-lonely', 0.14, -0.5, 0.42), // tiny sun high, immense sea
|
|
|
|
|
vast('05_mid-sea-jewel', 0, -0.04, 0.7),
|
|
|
|
|
vast('06_low-wide-sea', 0, 0.22, 0.62),
|
|
|
|
|
];
|
|
|
|
|
console.log(`VAST (floating sun) — ${VAST.length} → ${OUT}/vast/`);
|
|
|
|
|
for (const v of VAST) compose(v);
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// VAST02 — small sun (≈04 disk size) but LARGER bubble traces; ripple-forward
|
|
|
|
|
// calm sea; now WITH film grain + an aging layer (scratches/dust/foxing).
|
|
|
|
|
// Disk anchored via sunAt (never clips). shockSize small + bcScale big → small
|
|
|
|
|
// disk, big traces. Each iteration varies the trace character.
|
|
|
|
|
// ============================================================
|
|
|
|
|
const RIPPLE = { hueBack: 0.54, hueFront: 0.47, chaos: 0.32, blips: 0.7, mound: 0.3, overlap: 1.55 };
|
|
|
|
|
const v2 = (name, sunAt, bcOver, scale = 0.78) => ({
|
|
|
|
|
name, dir: 'vast02', base: 'rgb(229,222,203)', film: { seed: 8, density: 0.5 },
|
|
|
|
|
grain: { amount: 0.42 }, aging: { seed: (name.charCodeAt(3) * 7) % 97, scratches: 6, dust: 0.5, foxing: 0.55 }, agingOpacity: 0.6,
|
|
|
|
|
carpet: RIPPLE, bcSeed: 'MESON-5113', bcFloat: true, bcScale: scale, sunAt,
|
|
|
|
|
bcOver: { palette: 'magentarise', saturation: 1.05, shockX: 0, shockY: 0, eventX: 0, eventY: 0, shockSize: 0.16, ...bcOver },
|
|
|
|
|
});
|
|
|
|
|
const VAST02 = [
|
|
|
|
|
v2('01_dawn-sweeping-arcs', [0, -0.26], { sweepers: 6, primaries: 18, eloss: 0.34, pspread: 0.8 }),
|
|
|
|
|
v2('02_high-lonely-long', [0.1, -0.5], { bfield: 0.7, eloss: 0.3, primaries: 16, cosmics: 7 }, 0.72),
|
|
|
|
|
v2('03_corner-left-bigarcs', [-0.4, -0.04], { sweepers: 7, primaries: 17, bfield: 0.9 }),
|
|
|
|
|
v2('04_low-wide-spirals', [0, 0.2], { deltaRate: 0.85, deltaTight: 0.5, primaries: 20, eloss: 0.4 }),
|
|
|
|
|
v2('05_mid-jewel-dense', [0, -0.05], { primaries: 24, burst: 0.82, pspread: 0.9 }, 0.74),
|
|
|
|
|
v2('06_right-rise-curl', [0.4, -0.06], { bfield: 1.7, primaries: 18, deltaRate: 0.7 }),
|
|
|
|
|
v2('07_centered-long-reach', [0, 0.0], { eloss: 0.28, pspread: 0.95, primaries: 16, shockSize: 0.15 }),
|
|
|
|
|
v2('08_distant-tiny-vast', [0.06, -0.42], { primaries: 18, sweepers: 5, eloss: 0.34 }, 0.6),
|
|
|
|
|
v2('09_dawn-cosmic-streaks', [-0.14, -0.2], { cosmics: 9, primaries: 15, bfield: 0.8, eloss: 0.3 }),
|
|
|
|
|
v2('10_horizon-symmetry', [0, -0.24], { sweepers: 4, primaries: 19, deltaRate: 0.7, shockSize: 0.17 }),
|
|
|
|
|
];
|
|
|
|
|
console.log(`VAST02 (small sun, big traces, grain+aging) — ${VAST02.length} → ${OUT}/vast02/`);
|
|
|
|
|
for (const v of VAST02) compose(v);
|
|
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/vast02/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${VAST02.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// VAST03 — an "archive of dawns": vary SUN COLOUR (diskHue/diskSat) + slight
|
|
|
|
|
// SIZE, keep larger bubble traces, and bring back the ANNOTATING HAND
|
|
|
|
|
// (grease-pencil ring/№/angle via `annotate`; a faint réseau on a couple).
|
|
|
|
|
// Trace family stays magenta → a two-colour relationship against each sun.
|
|
|
|
|
// ============================================================
|
|
|
|
|
const v3 = (name, sunAt, sun, traces, ann, scale = 0.78) => ({
|
|
|
|
|
name, dir: 'vast03', base: 'rgb(229,222,203)', film: { seed: 8, density: 0.5 },
|
|
|
|
|
grain: { amount: 0.4 }, aging: { seed: (name.charCodeAt(2) * 7) % 97, scratches: 6, dust: 0.5, foxing: 0.55 }, agingOpacity: 0.58,
|
|
|
|
|
carpet: RIPPLE, bcSeed: 'MESON-5113', bcFloat: true, bcScale: scale, sunAt,
|
|
|
|
|
bcOver: {
|
|
|
|
|
palette: 'magentarise', saturation: 1.05, shockX: 0, shockY: 0, eventX: 0, eventY: 0,
|
|
|
|
|
diskHue: sun.h, diskSat: sun.s, shockSize: sun.size,
|
|
|
|
|
annotate: ann.a, reseau: ann.r || 0,
|
|
|
|
|
...traces,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const VAST03 = [
|
|
|
|
|
v3('01_burnt-orange-studied', [0, -0.25], { h: 0.06, s: 0.82, size: 0.16 }, { sweepers: 6, primaries: 18, eloss: 0.34 }, { a: 0.9 }),
|
|
|
|
|
v3('02_gold-sun-light-ring', [0.08, -0.42],{ h: 0.11, s: 0.85, size: 0.15 }, { primaries: 17, eloss: 0.3, cosmics: 6 }, { a: 0.6 }, 0.74),
|
|
|
|
|
v3('03_amber-reseau', [0, -0.1], { h: 0.085, s: 0.9, size: 0.17 }, { primaries: 20, sweepers: 5 }, { a: 0.7, r: 0.32 }),
|
|
|
|
|
v3('04_coral-red-spirals', [-0.1, 0.12], { h: 0.02, s: 0.85, size: 0.16 }, { deltaRate: 0.85, deltaTight: 0.5, primaries: 20 }, { a: 0.8 }),
|
|
|
|
|
v3('05_rose-pink-dawn', [0.36, -0.06],{ h: 0.95, s: 0.7, size: 0.15 }, { bfield: 1.4, primaries: 18 }, { a: 0.7 }),
|
|
|
|
|
v3('06_copper-long-reach', [0, 0.0], { h: 0.05, s: 0.72, size: 0.18 }, { eloss: 0.28, pspread: 0.95, primaries: 16 }, { a: 0.65 }),
|
|
|
|
|
v3('07_pale-white-hot', [0.05, -0.5], { h: 0.1, s: 0.22, size: 0.14 }, { primaries: 16, sweepers: 5, eloss: 0.32 }, { a: 0.55 }, 0.7),
|
|
|
|
|
v3('08_cool-teal-sun', [-0.4, -0.04],{ h: 0.5, s: 0.6, size: 0.16 }, { sweepers: 7, primaries: 17 }, { a: 0.7 }),
|
|
|
|
|
v3('09_magenta-monochrome', [0, -0.24], { h: 0.9, s: 0.78, size: 0.17 }, { primaries: 19, deltaRate: 0.7, sweepers: 4 }, { a: 0.8, r: 0.28 }),
|
|
|
|
|
v3('10_deep-ember-studied', [0.12, 0.18], { h: 0.0, s: 0.9, size: 0.19 }, { primaries: 22, burst: 0.82, eloss: 0.36 }, { a: 1.0 }, 0.76),
|
|
|
|
|
];
|
|
|
|
|
console.log(`VAST03 (archive of dawns: sun colour + annotation) — ${VAST03.length} → ${OUT}/vast03/`);
|
|
|
|
|
for (const v of VAST03) compose(v);
|
|
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/vast03/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${VAST03.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// VAST04 — refined annotation: ONLY "No 001" + an arrow (no ring, no scrawl),
|
|
|
|
|
// THIN white pencil; the particle-collision VERTEX is OFFSET from the disk
|
|
|
|
|
// (eventX/Y ≠ shock); the disk gets colour + PRESSURE variation (dark dense
|
|
|
|
|
// core). Larger traces continue. Two graphite-pencil controls for comparison.
|
|
|
|
|
// ============================================================
|
|
|
|
|
const v4 = (name, sunAt, sun, ev, traces, pencil = '#f6f2ea', scale = 0.78) => ({
|
|
|
|
|
name, dir: 'vast04', base: 'rgb(229,222,203)', film: { seed: 8, density: 0.5 },
|
|
|
|
|
grain: { amount: 0.4 }, aging: { seed: (name.charCodeAt(2) * 7) % 97, scratches: 5, dust: 0.45, foxing: 0.5 }, agingOpacity: 0.55,
|
|
|
|
|
carpet: RIPPLE, bcSeed: 'MESON-5113', bcFloat: true, bcScale: scale, sunAt,
|
|
|
|
|
bcOver: {
|
|
|
|
|
palette: 'magentarise', saturation: 1.05,
|
|
|
|
|
shockX: 0, shockY: 0, eventX: ev[0], eventY: ev[1], // collision offset from the disk
|
|
|
|
|
diskHue: sun.h, diskSat: sun.s, shockSize: sun.size, diskPressure: sun.p,
|
|
|
|
|
annotate: 0.5, annoRing: false, annoExtras: false, annoArrow: true, annoNum: true,
|
|
|
|
|
annoLabel: 'No 001', annoWidth: 0.4, annotateInk: pencil,
|
|
|
|
|
...traces,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const VAST04 = [
|
|
|
|
|
v4('01_burnt-orange-pressure', [0, -0.25], { h: 0.06, s: 0.82, size: 0.16, p: 0.85 }, [0.26, -0.14], { sweepers: 6, primaries: 18, eloss: 0.34 }),
|
|
|
|
|
v4('02_gold-mid-pressure', [0.08, -0.42], { h: 0.11, s: 0.85, size: 0.15, p: 0.5 }, [-0.2, 0.16], { primaries: 17, eloss: 0.3, cosmics: 6 }, '#f6f2ea', 0.74),
|
|
|
|
|
v4('03_amber-soft-core', [0, -0.1], { h: 0.085, s: 0.9, size: 0.17, p: 0.25 }, [0.28, 0.12], { primaries: 20, sweepers: 5 }),
|
|
|
|
|
v4('04_coral-deep-core', [-0.1, 0.12], { h: 0.02, s: 0.85, size: 0.16, p: 0.8 }, [0.22, -0.18], { deltaRate: 0.85, deltaTight: 0.5, primaries: 20 }),
|
|
|
|
|
v4('05_copper-pressure', [0.36, -0.06], { h: 0.05, s: 0.72, size: 0.16, p: 0.55 }, [-0.24, -0.1], { bfield: 1.4, primaries: 18 }),
|
|
|
|
|
v4('06_deep-ember-densecore', [0, -0.24], { h: 0.0, s: 0.9, size: 0.18, p: 0.95 }, [0.2, 0.18], { primaries: 19, sweepers: 4 }),
|
|
|
|
|
v4('07_rose-mid', [0.05, -0.5], { h: 0.95, s: 0.7, size: 0.15, p: 0.5 }, [-0.18, 0.2], { primaries: 16, eloss: 0.3, cosmics: 5 }, '#f6f2ea', 0.7),
|
|
|
|
|
v4('08_magenta-pressure', [0, 0.0], { h: 0.9, s: 0.78, size: 0.17, p: 0.7 }, [0.3, -0.1], { eloss: 0.28, pspread: 0.95, primaries: 16 }),
|
|
|
|
|
v4('09_graphite-on-orange', [-0.4, -0.04], { h: 0.06, s: 0.82, size: 0.16, p: 0.8 }, [0.24, 0.14], { sweepers: 7, primaries: 17 }, '#39312a'),
|
|
|
|
|
v4('10_graphite-on-ember', [0.12, 0.18], { h: 0.0, s: 0.9, size: 0.19, p: 0.9 }, [-0.22, -0.16],{ primaries: 22, burst: 0.82 }, '#39312a', 0.76),
|
|
|
|
|
];
|
|
|
|
|
console.log(`VAST04 (No001 + arrow, white pencil, vertex offset, disk pressure) — ${VAST04.length} → ${OUT}/vast04/`);
|
|
|
|
|
for (const v of VAST04) compose(v);
|
|
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/vast04/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${VAST04.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
2026-06-02 19:17:19 -04:00
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// PERSPECTIVE — add the secondary depth-grid plex layer (VP off-horizon) to a
|
|
|
|
|
// strong vast04-style frame. Cool draughtsman's-pencil grid, dual blur/fine.
|
|
|
|
|
// Vary VP position + over/behind the sea.
|
|
|
|
|
// ============================================================
|
|
|
|
|
const DEG = Math.PI / 180;
|
|
|
|
|
const PG = (vp, dir, spread, extra = {}) => ({ vp, dir, spread, rays: 26, depthLines: 15, hue: 0.56, hue2: 0.5, sat: 0.4, ...extra });
|
|
|
|
|
const v5 = (name, sunAt, sun, ev, persp, perspPos = 'over', pencil = '#39312a') => ({
|
|
|
|
|
name, dir: 'perspective', base: 'rgb(229,222,203)', film: { seed: 8, density: 0.5 },
|
|
|
|
|
grain: { amount: 0.4 }, aging: { seed: (name.charCodeAt(2) * 7) % 97, scratches: 5, dust: 0.45, foxing: 0.5 }, agingOpacity: 0.55,
|
|
|
|
|
carpet: RIPPLE, persp, perspPos, perspOpacity: 0.48,
|
|
|
|
|
bcSeed: 'MESON-5113', bcFloat: true, bcScale: 0.78, sunAt,
|
|
|
|
|
bcOver: {
|
|
|
|
|
palette: 'magentarise', saturation: 1.05, shockX: 0, shockY: 0, eventX: ev[0], eventY: ev[1],
|
|
|
|
|
diskHue: sun.h, diskSat: sun.s, shockSize: sun.size, diskPressure: sun.p,
|
|
|
|
|
annotate: 0.5, annoRing: false, annoExtras: false, annoArrow: true, annoNum: true, annoLabel: 'No 001', annoWidth: 0.4, annotateInk: pencil,
|
|
|
|
|
sweepers: 5, primaries: 18, eloss: 0.34,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const PERSP = [
|
|
|
|
|
v5('01_center-funnel-over', [0, -0.25], { h: 0.06, s: 0.82, size: 0.16, p: 0.85 }, [0.26, -0.14], PG([0, 0.02], 0, Math.PI * 2, { rays: 30, depthLines: 16 }), 'over'),
|
|
|
|
|
v5('02_corner-left-over', [0.1, -0.3], { h: 0.0, s: 0.9, size: 0.17, p: 0.9 }, [-0.22, 0.14], PG([-1.3, -1.05], 35 * DEG, 60 * DEG, { rMax: 3.4, rays: 22 }), 'over'),
|
|
|
|
|
v5('03_high-vp-behind', [0, -0.24], { h: 0.08, s: 0.85, size: 0.16, p: 0.6 }, [0.24, 0.12], PG([0, -0.62], 95 * DEG, 150 * DEG, { rays: 26 }), 'behind'),
|
|
|
|
|
v5('04_low-into-sea-over', [0.05, -0.42], { h: 0.06, s: 0.82, size: 0.15, p: 0.8 },[-0.2, -0.14], PG([0, 0.5], -90 * DEG, 150 * DEG, { rays: 26 }), 'over'),
|
|
|
|
|
v5('05_center-funnel-behind',[0, -0.25], { h: 0.05, s: 0.78, size: 0.16, p: 0.7 }, [0.2, 0.16], PG([0.05, -0.05], 0, Math.PI * 2, { rays: 32, depthLines: 18 }), 'behind'),
|
|
|
|
|
v5('06_offright-corridor', [-0.35, -0.05], { h: 0.02, s: 0.85, size: 0.16, p: 0.8 },[0.26, 0.12], PG([1.35, -0.1], 178 * DEG, 70 * DEG, { rMax: 3.2, rays: 22 }), 'over'),
|
|
|
|
|
];
|
|
|
|
|
console.log(`PERSPECTIVE (secondary depth-grid layer) — ${PERSP.length} → ${OUT}/perspective/`);
|
|
|
|
|
for (const v of PERSP) compose(v);
|
|
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/perspective/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${PERSP.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// PERSPECTIVE2 — 20 iterations of the RIPPLED depth grid (two interfering
|
|
|
|
|
// layers, faint/near-paper), to know the unknowable: edge-filling lattices,
|
|
|
|
|
// vacuum-drains, ghost corridors, two infinities, dissolving nets.
|
|
|
|
|
// ============================================================
|
|
|
|
|
// grid colour presets (faint, near the ground)
|
|
|
|
|
const GC = {
|
|
|
|
|
cool: { hue: 0.56, hue2: 0.5, sat: 0.26, lightNear: 0.44, lightFar: 0.66 },
|
|
|
|
|
warm: { hue: 0.1, hue2: 0.08, sat: 0.18, lightNear: 0.5, lightFar: 0.68 }, // near cream
|
|
|
|
|
teal: { hue: 0.5, hue2: 0.55, sat: 0.34, lightNear: 0.42, lightFar: 0.64 },
|
|
|
|
|
violet:{ hue: 0.7, hue2: 0.66, sat: 0.3, lightNear: 0.44, lightFar: 0.66 },
|
|
|
|
|
ash: { hue: 0.6, hue2: 0.6, sat: 0.08, lightNear: 0.46, lightFar: 0.66 },
|
|
|
|
|
};
|
|
|
|
|
const SUN = {
|
|
|
|
|
ember: { h: 0.0, s: 0.9, size: 0.16, p: 0.9 }, orange: { h: 0.06, s: 0.82, size: 0.16, p: 0.85 },
|
|
|
|
|
gold: { h: 0.11, s: 0.85, size: 0.15, p: 0.5 }, rose: { h: 0.95, s: 0.7, size: 0.15, p: 0.5 },
|
|
|
|
|
teal: { h: 0.5, s: 0.6, size: 0.16, p: 0.6 }, magenta: { h: 0.9, s: 0.78, size: 0.17, p: 0.7 },
|
|
|
|
|
copper: { h: 0.05, s: 0.72, size: 0.16, p: 0.55 }, amber: { h: 0.085, s: 0.9, size: 0.17, p: 0.3 },
|
|
|
|
|
};
|
|
|
|
|
// rip(ampPerp, ampRad, freqR, freqA, phase)
|
|
|
|
|
const rip = (a, ar, fr, fa, ph = 0) => ({ rippleAmp: a, rippleAmpRad: ar, rippleFreqR: fr, rippleFreqA: fa, ripplePhase: ph });
|
|
|
|
|
// pg(vp, dir, spread, color, ripple, extra)
|
|
|
|
|
const pg = (vp, dir, spread, color, ripple, extra = {}) => ({ vp, dir, spread, rays: 30, depthLines: 17, depthPow: 2.3, rMin: 0.04, rMax: 2.8, ...GC[color], ...ripple, ...extra });
|
|
|
|
|
const EDGE = { rMax: 3.7, rMin: 0.03 }; // push rings past the frame → fills to edges
|
|
|
|
|
const p2 = (name, sun, ev, persp, o = {}) => ({
|
|
|
|
|
name, dir: 'perspective2', base: 'rgb(229,222,203)', film: { seed: 8, density: 0.5 },
|
|
|
|
|
grain: { amount: 0.4 }, aging: { seed: (name.charCodeAt(3) * 7) % 97, scratches: 5, dust: 0.45, foxing: 0.5 }, agingOpacity: 0.52,
|
|
|
|
|
carpet: RIPPLE, persp, perspPos: o.pos || 'over', perspOpacity: o.op ?? 0.4,
|
|
|
|
|
bcSeed: 'MESON-5113', bcFloat: true, bcScale: o.scale || 0.78, sunAt: o.sunAt || [0, -0.25],
|
|
|
|
|
bcOver: {
|
|
|
|
|
palette: 'magentarise', saturation: 1.05, shockX: 0, shockY: 0, eventX: ev[0], eventY: ev[1],
|
|
|
|
|
diskHue: SUN[sun].h, diskSat: SUN[sun].s, shockSize: SUN[sun].size, diskPressure: SUN[sun].p,
|
|
|
|
|
annotate: 0.5, annoRing: false, annoExtras: false, annoArrow: true, annoNum: true, annoLabel: o.label || 'No 001', annoWidth: 0.4, annotateInk: o.pencil || '#39312a',
|
|
|
|
|
sweepers: o.sweepers ?? 5, primaries: o.primaries ?? 18, eloss: o.eloss ?? 0.34,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const F = Math.PI * 2; // full radial
|
|
|
|
|
const PERSP2 = [
|
|
|
|
|
p2('01_lattice-behind-things', 'orange', [0.26, -0.14], pg([0, 0.0], 0, F, 'cool', rip(0.06, 0.05, 2.2, 5), EDGE), { op: 0.38 }),
|
|
|
|
|
p2('02_a-guessed-geometry', 'ember', [-0.2, 0.16], pg([0.1, -0.05], 0, F, 'warm', rip(0.08, 0.06, 2.6, 6, 0.6), EDGE), { pos: 'behind', op: 0.5 }),
|
|
|
|
|
p2('03_interference-of-readings','teal', [0.3, -0.1], pg([0, -0.02], 0, F, 'teal', rip(0.1, 0.08, 2.8, 7, 1.1), { rays: 34, depthLines: 20 }), { op: 0.42 }),
|
|
|
|
|
p2('04_drain-of-the-vacuum', 'ember', [0.22, -0.16], pg([0.05, -0.05], 0, F, 'cool', rip(0.07, 0.06, 3.0, 6), { rMax: 2.5, rays: 36, depthLines: 22, depthPow: 3.0 }), { op: 0.44 }),
|
|
|
|
|
p2('05_scaffolding-of-space', 'copper', [-0.24, -0.1], pg([-1.3, -1.05], 35 * DEG, 60 * DEG, 'ash', rip(0.05, 0.04, 2.0, 4), { rMax: 3.6, rays: 24 }), { op: 0.46 }),
|
|
|
|
|
p2('06_what-the-field-rests-on', 'gold', [0.24, 0.12], pg([0, -0.6], 95 * DEG, 160 * DEG, 'cool', rip(0.07, 0.06, 2.4, 5), EDGE), { pos: 'behind', op: 0.5 }),
|
|
|
|
|
p2('07_rumor-of-order', 'rose', [-0.18, 0.18], pg([-1.2, -1.0], 38 * DEG, 70 * DEG, 'violet', rip(0.06, 0.05, 2.2, 5, 0.4), { rMax: 3.4 }), { op: 0.36 }),
|
|
|
|
|
p2('08_the-unseen-floor', 'orange', [-0.2, -0.14], pg([0, 0.5], -90 * DEG, 170 * DEG, 'warm', rip(0.08, 0.07, 2.6, 6), EDGE), { op: 0.42 }),
|
|
|
|
|
p2('09_two-infinities', 'amber', [0.2, 0.16], pg([0, -0.03], 0, F, 'cool', rip(0.05, 0.05, 2.0, 5), EDGE), { op: 0.36 }),
|
|
|
|
|
p2('10_net-over-the-deep', 'teal', [0.26, -0.12], pg([0.05, -0.05], 0, F, 'teal', rip(0.11, 0.09, 2.9, 7, 0.8), EDGE), { op: 0.4 }),
|
|
|
|
|
p2('11_measured-and-the-wild', 'ember', [0.24, 0.14], pg([1.35, -0.1], 178 * DEG, 75 * DEG, 'ash', rip(0.05, 0.04, 2.1, 4), { rMax: 3.4, rays: 24 }), { op: 0.46 }),
|
|
|
|
|
p2('12_standing-wave-cathedral', 'gold', [-0.18, -0.12], pg([0, -0.68], 95 * DEG, 150 * DEG, 'violet', rip(0.12, 0.1, 3.0, 8, 1.3), { rays: 28 }), { pos: 'behind', op: 0.5 }),
|
|
|
|
|
p2('13_a-point-that-isnt-there', 'copper', [0.26, -0.1], pg([1.6, -1.3], 215 * DEG, 55 * DEG, 'cool', rip(0.06, 0.05, 2.3, 5), { rMax: 4.2 }), { op: 0.34 }),
|
|
|
|
|
p2('14_architecture-of-absence', 'orange', [0.24, 0.12], pg([0, -0.02], 0, F, 'warm', rip(0.05, 0.05, 2.2, 5), EDGE), { op: 0.28 }),
|
|
|
|
|
p2('15_ripple-vortex-rose', 'rose', [0.22, -0.14], pg([0.04, -0.04], 0, F, 'teal', rip(0.1, 0.09, 3.1, 7, 0.5), { rMax: 2.6, rays: 34, depthPow: 2.8 }), { op: 0.42 }),
|
|
|
|
|
p2('16_cold-sun-cold-grid', 'teal', [-0.22, -0.1], pg([0, -0.04], 0, F, 'teal', rip(0.07, 0.06, 2.5, 6), EDGE), { op: 0.44 }),
|
|
|
|
|
p2('17_ghost-corridor', 'amber', [0.26, 0.12], pg([-1.25, -0.2], 20 * DEG, 70 * DEG, 'cool', rip(0.06, 0.05, 2.2, 5), { rMax: 3.4 }), { pos: 'behind', op: 0.5 }),
|
|
|
|
|
p2('18_mandala-of-depth', 'magenta', [0.2, -0.12], pg([0, -0.03], 0, F, 'ash', rip(0.09, 0.08, 2.7, 8, 0.9), { rays: 36, depthLines: 20 }), { op: 0.4 }),
|
|
|
|
|
p2('19_the-grid-dissolving', 'ember', [-0.2, -0.12], pg([0.05, -0.05], 0, F, 'warm', rip(0.13, 0.11, 3.2, 7, 1.0), EDGE), { op: 0.26 }),
|
|
|
|
|
p2('20_quiet-coordinates', 'gold', [0.24, 0.12], pg([0.4, -0.5], 100 * DEG, 110 * DEG, 'ash', rip(0.05, 0.04, 2.0, 5), {}), { op: 0.42 }),
|
|
|
|
|
];
|
|
|
|
|
console.log(`PERSPECTIVE2 (20 rippled-grid iterations) — ${PERSP2.length} → ${OUT}/perspective2/`);
|
|
|
|
|
for (const v of PERSP2) compose(v);
|
|
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/perspective2/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>perspective2 · rippled depth grid · 20</title>
|
|
|
|
|
<style>html,body{margin:0;background:#1a1a1a}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${PERSP2.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
2026-05-30 06:42:16 -04:00
|
|
|
{
|
|
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/vast/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${VAST.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 17:17:06 -04:00
|
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
|
|
|
|
writeFileSync(`${OUT}/index.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layered piece · film + QFT carpet + bubble chamber</title>
|
|
|
|
|
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
|
|
|
|
|
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#a5d4c9}
|
|
|
|
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
|
|
|
|
|
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
|
|
|
|
|
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
|
|
|
|
|
.notes{color:#999;background:#0e1816;padding:14px 18px;border-left:3px solid #a5d4c9;margin:18px 0}</style></head><body>
|
|
|
|
|
<h1>Layered piece — film · QFT vacuum carpet · bubble-chamber event</h1>
|
|
|
|
|
<div class="notes">back→front: cream ground · milky film/diffusion · 3 spaced QFT carpet sheets (back two blurred = air-gap depth of field) · bubble-chamber event (multiply) · film grain. The literal plexi stack, previewed as one image.</div>
|
|
|
|
|
<div class=grid>${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}<small>${v.bcSeed} · ${v.bcOver?.palette || 'mono'}</small></figcaption></figure>`).join('\n')}</div></body></html>`);
|
|
|
|
|
writeFileSync(`${OUT}/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
|
|
|
|
<div class="grid">${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
|
|
|
|
console.log(`contact sheets -> ${OUT}/index.html , m.html`);
|