web interface
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
============================================================ */
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { carpetSVG } from '../src/qft/carpet.js';
|
||||
import { perspectiveGridSVG } from '../src/qft/perspgrid.js';
|
||||
import { generateScene } from '../src/scene/scene.js';
|
||||
import { renderSVG } from '../src/render/svgVector.js';
|
||||
import { paramsFromSeed as bcParams } from '../src/scene/params.js';
|
||||
@@ -116,6 +117,28 @@ function compose(v) {
|
||||
}
|
||||
const dir = v.dir ? `${OUT}/${v.dir}` : OUT;
|
||||
if (v.dir) mkdirSync(dir, { recursive: true });
|
||||
|
||||
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);
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
|
||||
<defs>
|
||||
<filter id="b3" x="-8%" y="-8%" width="116%" height="116%"><feGaussianBlur stdDeviation="${(2.6 * u).toFixed(2)}"/></filter>
|
||||
@@ -123,9 +146,7 @@ function compose(v) {
|
||||
</defs>
|
||||
<rect width="${SIZE}" height="${SIZE}" fill="${base}"/>
|
||||
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${film}" opacity="0.6"/>
|
||||
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${sheets[0]}" filter="url(#b3)" opacity="0.5"/>
|
||||
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${sheets[1]}" filter="url(#b2)" opacity="0.72"/>
|
||||
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${sheets[2]}" opacity="0.95"/>
|
||||
${mid}
|
||||
${bcEl}
|
||||
${aging ? `<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${aging}" opacity="${v.agingOpacity ?? 0.6}" style="mix-blend-mode:multiply"/>` : ''}
|
||||
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${grain}" opacity="${v.grainOpacity ?? 0.45}" style="mix-blend-mode:multiply"/>
|
||||
@@ -337,6 +358,110 @@ for (const v of VAST04) compose(v);
|
||||
<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>`);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 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>`);
|
||||
}
|
||||
{
|
||||
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
||||
writeFileSync(`${OUT}/vast/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
|
||||
Reference in New Issue
Block a user