2026-05-20 16:53:23 -04:00
|
|
|
/* ============================================================
|
|
|
|
|
svgVector.js — clean vector renderer for print.
|
|
|
|
|
Same scene model as the photographic renderer, emitted as
|
|
|
|
|
resolution-independent SVG. Soft bubbles are approximated
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
with radial-gradient fills; tracks get a faint continuity
|
|
|
|
|
under-stroke. Grain/mottle are intentionally omitted
|
|
|
|
|
(raster-only effects); this is the graphic version.
|
2026-05-20 17:10:32 -04:00
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
Output is organised into named LAYERS (Inkscape convention),
|
|
|
|
|
and coloured through the shared palette abstraction — track
|
|
|
|
|
ink can vary per-trail (charge, β, …); a gradient is emitted
|
|
|
|
|
per distinct ink colour.
|
2026-05-20 16:53:23 -04:00
|
|
|
============================================================ */
|
|
|
|
|
import { makeRng, cyrb53 } from '../rng.js';
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
import { sampleBubbles, trackInkWeight, depthFactors } from '../scene/bubbles.js';
|
|
|
|
|
import { resolvePalette, paperTone, rgbHex, rgbKey, bubbleStops, hslToRgb, mix } from './palette.js';
|
2026-05-20 16:53:23 -04:00
|
|
|
|
|
|
|
|
const MARGIN = 0.02;
|
|
|
|
|
|
2026-05-20 17:10:32 -04:00
|
|
|
const TRACK_LAYERS = [
|
|
|
|
|
{ id: 'tracks-primary', label: 'Tracks · primary', kinds: ['primary'] },
|
|
|
|
|
{ id: 'tracks-cosmic', label: 'Tracks · cosmic & sweepers', kinds: ['cosmic', 'sweep'] },
|
|
|
|
|
{ id: 'tracks-vdecay', label: 'Tracks · V-decays', kinds: ['vdecay'] },
|
|
|
|
|
{ id: 'tracks-delta', label: 'Tracks · δ-rays (curls)', kinds: ['delta'] },
|
|
|
|
|
];
|
|
|
|
|
|
2026-05-20 16:53:23 -04:00
|
|
|
export function renderSVG(scene, params, sizePx = 4800) {
|
|
|
|
|
const w = sizePx, h = sizePx;
|
|
|
|
|
const scale = (w / 2) * (1 - MARGIN);
|
|
|
|
|
const cx = w / 2, cy = h / 2;
|
|
|
|
|
const tx = (x) => (cx + x * scale).toFixed(2);
|
|
|
|
|
const ty = (y) => (cy + y * scale).toFixed(2);
|
|
|
|
|
const u = w / 1000;
|
|
|
|
|
|
|
|
|
|
const inv = params.invert;
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
// base ink (mono), toned paper (independent of ink palette), then resolve feel
|
|
|
|
|
const baseInk = inv ? [28, 24, 20] : [233, 228, 214];
|
|
|
|
|
const pt = paperTone(params, inv);
|
|
|
|
|
const pal = resolvePalette(params.palette, {
|
|
|
|
|
inv, sat: params.saturation ?? 1, hue: (params.hueShift ?? 0) * 360, cycles: params.hueCycles ?? 3,
|
|
|
|
|
baseInk, basePaper: { flat: pt.flat, glowIn: pt.glowIn, glowOut: pt.glowOut }, baseVign: pt.vign,
|
|
|
|
|
});
|
|
|
|
|
const paperRGB = pal.paper(); // {flat,glowIn,glowOut} rgb arrays
|
|
|
|
|
const paperC = { flat: rgbHex(paperRGB.flat), glowIn: rgbHex(paperRGB.glowIn), glowOut: rgbHex(paperRGB.glowOut) };
|
|
|
|
|
const ink = rgbHex(pal.feature()); // non-particle marks (optics, disk, damage, header)
|
|
|
|
|
const featKey = rgbKey(pal.feature());
|
|
|
|
|
const colorMap = new Map([[featKey, pal.feature()]]); // distinct bubble colours → gradients
|
2026-05-20 16:53:23 -04:00
|
|
|
|
2026-05-21 05:59:44 -04:00
|
|
|
const attr = (t) => String(t).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
|
|
|
const layer = (id, label, content, extra = '') =>
|
|
|
|
|
content ? `<g id="${id}" inkscape:groupmode="layer" inkscape:label="${attr(label)}" style="display:inline"${extra ? ' ' + extra : ''}>\n${content}\n</g>\n` : '';
|
2026-05-20 16:53:23 -04:00
|
|
|
|
2026-05-20 17:10:32 -04:00
|
|
|
/* ---------- Background ---------- */
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
const bg = `<rect width="${w}" height="${h}" fill="${paperC.flat}"/>\n<rect width="${w}" height="${h}" fill="url(#paper)"/>`;
|
2026-05-20 17:10:32 -04:00
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
/* ---------- Chamber optics ---------- */
|
2026-05-20 17:10:32 -04:00
|
|
|
let optics = '';
|
2026-05-20 16:53:23 -04:00
|
|
|
if (params.showBoundary) {
|
|
|
|
|
const bcx = cx, bcy = cy + h * 0.35, br = w * 0.45;
|
|
|
|
|
const a1 = Math.PI * 0.15, a2 = Math.PI - Math.PI * 0.15;
|
|
|
|
|
const x1 = bcx + Math.cos(Math.PI + a1) * br, y1 = bcy + Math.sin(Math.PI + a1) * br;
|
|
|
|
|
const x2 = bcx + Math.cos(Math.PI + a2) * br, y2 = bcy + Math.sin(Math.PI + a2) * br;
|
2026-05-20 17:10:32 -04:00
|
|
|
optics += `<path d="M ${x1.toFixed(1)} ${y1.toFixed(1)} A ${br} ${br} 0 0 1 ${x2.toFixed(1)} ${y2.toFixed(1)}" fill="none" stroke="${ink}" stroke-opacity="0.4" stroke-width="${2 * u}"/>\n`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
if (scene.instrument) {
|
|
|
|
|
const inst = scene.instrument;
|
2026-05-20 17:10:32 -04:00
|
|
|
let g = `<g fill="none" stroke="${ink}" stroke-linecap="round">`;
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const l of inst.lines)
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<line x1="${tx(l.x1)}" y1="${ty(l.y1)}" x2="${tx(l.x2)}" y2="${ty(l.y2)}" stroke-opacity="${l.opacity.toFixed(3)}" stroke-width="${(l.width * u).toFixed(2)}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const a of inst.arcs) {
|
|
|
|
|
let d = `M ${tx(a.pts[0].x)} ${ty(a.pts[0].y)}`;
|
|
|
|
|
for (let i = 1; i < a.pts.length; i++) d += ` L ${tx(a.pts[i].x)} ${ty(a.pts[i].y)}`;
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<path d="${d}" stroke-opacity="${a.opacity.toFixed(3)}" stroke-width="${(a.width * u).toFixed(2)}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
2026-05-20 17:10:32 -04:00
|
|
|
optics += g + `</g>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
/* ---------- Shock disk (feature ink) ---------- */
|
2026-05-20 17:10:32 -04:00
|
|
|
let shock = '';
|
2026-05-20 16:53:23 -04:00
|
|
|
if (scene.shock) {
|
|
|
|
|
const sh = scene.shock;
|
|
|
|
|
const px = +tx(sh.x), py = +ty(sh.y), R = sh.r * scale;
|
2026-05-20 17:10:32 -04:00
|
|
|
const bodyOpacity = (params.diskBubbles !== false) ? 0.6 : 1;
|
|
|
|
|
shock += `<circle cx="${px}" cy="${py}" r="${R.toFixed(1)}" fill="url(#shockcore)" fill-opacity="${bodyOpacity}"/>\n`;
|
|
|
|
|
if (params.diskBubbles !== false && sh.bubbleStrokes) {
|
|
|
|
|
const dRng = makeRng(params.seed, 'diskbubbles');
|
|
|
|
|
const dbk = new Map();
|
|
|
|
|
for (const stroke of sh.bubbleStrokes) {
|
|
|
|
|
const key = Math.round(Math.min(1, 0.45 + stroke.weight * 0.5) * 20) / 20;
|
|
|
|
|
if (!dbk.has(key)) dbk.set(key, []);
|
|
|
|
|
dbk.get(key).push(...sampleBubbles(stroke, params, dRng));
|
|
|
|
|
}
|
|
|
|
|
for (const [alpha, bubs] of dbk) {
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
shock += `<g fill="url(#bub-${featKey})" fill-opacity="${alpha}">`;
|
2026-05-20 17:10:32 -04:00
|
|
|
for (const b of bubs) shock += `<circle cx="${tx(b.x)}" cy="${ty(b.y)}" r="${Math.max(b.r * scale * 1.15, 0.5).toFixed(2)}"/>`;
|
|
|
|
|
shock += `</g>\n`;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
// optionally iridescent: hue across the sunburst (per-element stroke colour)
|
|
|
|
|
const spec = params.diskSpectrum || 0;
|
|
|
|
|
const featRGB = pal.feature();
|
|
|
|
|
const TWO_PI = Math.PI * 2;
|
|
|
|
|
const sCol = (frac) => spec <= 0 ? ink
|
|
|
|
|
: rgbHex(mix(featRGB, hslToRgb((((frac + (params.hueShift || 0)) % 1) + 1) % 1, 0.85 * (params.saturation ?? 1), 0.52), spec));
|
|
|
|
|
let g = `<g stroke-linecap="round" fill="none">`;
|
2026-05-20 17:10:32 -04:00
|
|
|
for (const st of sh.striations) {
|
|
|
|
|
const ix = px + Math.cos(st.a) * st.inner * scale, iy = py + Math.sin(st.a) * st.inner * scale;
|
|
|
|
|
const ox = px + Math.cos(st.a) * st.outer * scale, oy = py + Math.sin(st.a) * st.outer * scale;
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
g += `<line x1="${ix.toFixed(1)}" y1="${iy.toFixed(1)}" x2="${ox.toFixed(1)}" y2="${oy.toFixed(1)}" stroke="${sCol(st.a / TWO_PI)}" stroke-opacity="${st.opacity.toFixed(3)}" stroke-width="${(st.width * u).toFixed(2)}"/>`;
|
2026-05-20 17:10:32 -04:00
|
|
|
}
|
|
|
|
|
for (const ring of sh.rings)
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
g += `<circle cx="${px}" cy="${py}" r="${(ring.rr * scale).toFixed(1)}" stroke="${sCol(ring.rr / sh.r)}" stroke-opacity="${ring.opacity.toFixed(3)}" stroke-width="${(ring.width * u).toFixed(2)}"/>`;
|
2026-05-20 17:10:32 -04:00
|
|
|
for (const seg of (sh.rimSegs || [])) {
|
|
|
|
|
const x0 = px + Math.cos(seg.a0) * R, y0 = py + Math.sin(seg.a0) * R;
|
|
|
|
|
const x1a = px + Math.cos(seg.a1) * R, y1a = py + Math.sin(seg.a1) * R;
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
g += `<path d="M ${x0.toFixed(1)} ${y0.toFixed(1)} A ${R.toFixed(1)} ${R.toFixed(1)} 0 0 1 ${x1a.toFixed(1)} ${y1a.toFixed(1)}" stroke="${sCol(seg.a0 / TWO_PI)}" stroke-opacity="${seg.opacity.toFixed(3)}" stroke-width="${(seg.width * u).toFixed(2)}"/>`;
|
|
|
|
|
}
|
|
|
|
|
for (const k of sh.core) {
|
|
|
|
|
const ca = Math.atan2(k.y1 - sh.y, k.x1 - sh.x);
|
|
|
|
|
g += `<line x1="${tx(k.x1)}" y1="${ty(k.y1)}" x2="${tx(k.x2)}" y2="${ty(k.y2)}" stroke="${sCol(ca / TWO_PI)}" stroke-opacity="${k.opacity.toFixed(3)}" stroke-width="${(k.width * u).toFixed(2)}"/>`;
|
2026-05-20 17:10:32 -04:00
|
|
|
}
|
|
|
|
|
shock += g + `</g>\n`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
for (const st of (sh.stains || [])) {
|
|
|
|
|
const sr = (st.r * scale).toFixed(1);
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
shock += `<circle cx="${tx(st.x)}" cy="${ty(st.y)}" r="${sr}" fill="url(#${st.dark ? 'shockstain' : 'shockclean'})" fill-opacity="${st.opacity.toFixed(3)}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
/* ---------- Tracks (per-trail colour via palette; positions unchanged) ---------- */
|
2026-05-20 17:10:32 -04:00
|
|
|
const labelFor = {};
|
|
|
|
|
for (const L of TRACK_LAYERS) for (const k of L.kinds) labelFor[k] = L.id;
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
const under = new Map(); // layerId -> understroke string (per-path stroke colour)
|
|
|
|
|
const buckets = new Map(); // layerId -> Map(colorKey|alpha -> {key, alpha, arr})
|
2026-05-20 17:10:32 -04:00
|
|
|
const ensure = (id) => { if (!under.has(id)) { under.set(id, ''); buckets.set(id, new Map()); } };
|
|
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
const bubbleRng = makeRng(params.seed, 'bubbles'); // scene.tracks order → deterministic
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const track of scene.tracks) {
|
2026-05-20 17:10:32 -04:00
|
|
|
if (track.pts.length < 2) continue;
|
|
|
|
|
const id = labelFor[track.kind] || 'tracks-primary';
|
|
|
|
|
ensure(id);
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
const df = depthFactors(track, params);
|
|
|
|
|
const repCol = pal.ink(track); // representative ink (under-stroke)
|
2026-05-20 17:10:32 -04:00
|
|
|
// continuity under-stroke
|
|
|
|
|
const iw = trackInkWeight(track);
|
|
|
|
|
const lw = Math.min(2.6, 0.25 + Math.sqrt(iw) * 0.12) * u * params.size * track.weight;
|
|
|
|
|
if (lw >= 0.2 * u) {
|
|
|
|
|
let d = `M ${tx(track.pts[0].x)} ${ty(track.pts[0].y)}`;
|
|
|
|
|
for (let i = 1; i < track.pts.length; i++) d += ` L ${tx(track.pts[i].x)} ${ty(track.pts[i].y)}`;
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
under.set(id, under.get(id) + `<path d="${d}" stroke="${rgbHex(repCol)}" stroke-opacity="${(0.14 * df.tone).toFixed(3)}" stroke-width="${lw.toFixed(2)}"/>`);
|
2026-05-20 17:10:32 -04:00
|
|
|
}
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
// bubbles — opacity from depth/age tone, colour per-bubble from palette
|
|
|
|
|
const alpha = Math.round(Math.min(1, 0.36 + df.tone * 0.58) * 20) / 20;
|
2026-05-20 17:10:32 -04:00
|
|
|
const m = buckets.get(id);
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
for (const b of sampleBubbles({ ...track, sizeScale: df.sizeScale }, params, bubbleRng)) {
|
|
|
|
|
const bcol = pal.bubbleInk(track, b.life, b.beta), bkc = rgbKey(bcol);
|
|
|
|
|
if (!colorMap.has(bkc)) colorMap.set(bkc, bcol);
|
|
|
|
|
const bkey = bkc + '|' + alpha;
|
|
|
|
|
if (!m.has(bkey)) m.set(bkey, { key: bkc, alpha, arr: [] });
|
|
|
|
|
m.get(bkey).arr.push(`<circle cx="${tx(b.x)}" cy="${ty(b.y)}" r="${Math.max(b.r * scale * 1.15, 0.5).toFixed(2)}"/>`);
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-20 17:10:32 -04:00
|
|
|
const trackLayers = TRACK_LAYERS.map(L => {
|
|
|
|
|
if (!under.has(L.id)) return '';
|
|
|
|
|
let content = '';
|
|
|
|
|
const us = under.get(L.id);
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
if (us) content += `<g fill="none" stroke-linecap="round" stroke-linejoin="round">${us}</g>\n`;
|
|
|
|
|
for (const { key, alpha, arr } of buckets.get(L.id).values()) {
|
|
|
|
|
if (arr.length) content += `<g fill="url(#bub-${key})" fill-opacity="${alpha}">${arr.join('')}</g>\n`;
|
2026-05-20 17:10:32 -04:00
|
|
|
}
|
|
|
|
|
return layer(L.id, L.label, content);
|
|
|
|
|
}).join('');
|
2026-05-20 16:53:23 -04:00
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
/* ---------- Plate damage (feature ink) ---------- */
|
2026-05-20 17:10:32 -04:00
|
|
|
let damage = '';
|
2026-05-20 16:53:23 -04:00
|
|
|
const A = scene.artifacts;
|
|
|
|
|
if (A) {
|
2026-05-20 17:10:32 -04:00
|
|
|
let g = `<g stroke="${ink}" fill="none" stroke-linecap="round">`;
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const ring of A.rings)
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<circle cx="${tx(ring.x)}" cy="${ty(ring.y)}" r="${(ring.r * scale).toFixed(1)}" stroke-opacity="${ring.opacity.toFixed(3)}" stroke-width="${(ring.width * u).toFixed(2)}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const sc of A.scratches)
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<line x1="${tx(sc.x1)}" y1="${ty(sc.y1)}" x2="${tx(sc.x2)}" y2="${ty(sc.y2)}" stroke-opacity="${sc.opacity.toFixed(3)}" stroke-width="${(sc.width * u).toFixed(2)}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const hair of A.hairs) {
|
|
|
|
|
let d = `M ${tx(hair.pts[0].x)} ${ty(hair.pts[0].y)}`;
|
|
|
|
|
for (let i = 1; i < hair.pts.length; i++) d += ` L ${tx(hair.pts[i].x)} ${ty(hair.pts[i].y)}`;
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<path d="${d}" stroke-opacity="${hair.opacity.toFixed(3)}" stroke-width="${(hair.width * u).toFixed(2)}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `</g><g fill="${ink}">`;
|
2026-05-20 16:53:23 -04:00
|
|
|
for (const sp of A.specks)
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<circle cx="${tx(sp.x)}" cy="${ty(sp.y)}" r="${Math.max(sp.r * scale, 0.5).toFixed(2)}" fill-opacity="${sp.opacity.toFixed(3)}"/>`;
|
|
|
|
|
damage = g + `</g>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 17:10:32 -04:00
|
|
|
/* ---------- Fiducials ---------- */
|
|
|
|
|
let fids = '';
|
2026-05-20 16:53:23 -04:00
|
|
|
if (params.showFiducials) {
|
2026-05-20 17:10:32 -04:00
|
|
|
let g = `<g stroke="${ink}" stroke-opacity="0.55" stroke-width="${1.2 * u}">`;
|
|
|
|
|
const F = [[-0.85, -0.85], [0.85, -0.85], [-0.85, 0.85], [0.85, 0.85], [0, -0.85], [-0.85, 0], [0.85, 0]];
|
2026-05-20 16:53:23 -04:00
|
|
|
const sz = 9 * u;
|
2026-05-20 17:10:32 -04:00
|
|
|
for (const [fx, fy] of F) {
|
2026-05-20 16:53:23 -04:00
|
|
|
const px = +tx(fx), py = +ty(fy);
|
2026-05-20 17:10:32 -04:00
|
|
|
g += `<line x1="${px - sz}" y1="${py}" x2="${px + sz}" y2="${py}"/><line x1="${px}" y1="${py - sz}" x2="${px}" y2="${py + sz}"/>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
2026-05-20 17:10:32 -04:00
|
|
|
fids = g + `</g>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 17:10:32 -04:00
|
|
|
/* ---------- Vignette ---------- */
|
|
|
|
|
const vign = params.vign > 0 ? `<rect width="${w}" height="${h}" fill="url(#vign)"/>` : '';
|
2026-05-20 16:53:23 -04:00
|
|
|
|
2026-05-20 17:10:32 -04:00
|
|
|
/* ---------- Header ---------- */
|
|
|
|
|
let header = '';
|
2026-05-20 16:53:23 -04:00
|
|
|
if (params.showHeader) {
|
|
|
|
|
const pad = 26 * u;
|
|
|
|
|
const esc = (t) => String(t).replace(/[<&]/g, c => (c === '<' ? '<' : '&'));
|
2026-05-20 17:10:32 -04:00
|
|
|
header = `<g fill="${ink}" font-family="'JetBrains Mono', monospace">`
|
|
|
|
|
+ `<text x="${pad.toFixed(0)}" y="${(pad + 11 * u).toFixed(0)}" font-size="${(11 * u).toFixed(0)}" fill-opacity="0.62">${esc(scene.lab.toUpperCase())}</text>`
|
|
|
|
|
+ `<text x="${pad.toFixed(0)}" y="${(pad + 27 * u).toFixed(0)}" font-size="${(9 * u).toFixed(0)}" fill-opacity="0.5">SEED ${esc(params.seed)}</text>`
|
|
|
|
|
+ `<text x="${(w - pad).toFixed(0)}" y="${(h - pad - 13 * u).toFixed(0)}" font-size="${(10 * u).toFixed(0)}" text-anchor="end" fill-opacity="0.58">PLATE ${scene.plate}</text>`
|
|
|
|
|
+ `<text x="${(w - pad).toFixed(0)}" y="${(h - pad).toFixed(0)}" font-size="${(10 * u).toFixed(0)}" text-anchor="end" fill-opacity="0.58">EXPOSED ${scene.exposure}</text>`
|
|
|
|
|
+ `</g>`;
|
2026-05-20 16:53:23 -04:00
|
|
|
}
|
2026-05-20 17:10:32 -04:00
|
|
|
|
|
|
|
|
/* ---------- Assemble ---------- */
|
|
|
|
|
let s = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
|
|
|
|
s += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">\n`;
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
s += `<metadata>Bubble Chamber · seed=${params.seed} · hash=${cyrb53(params.seed)} · palette=${params.palette || 'mono'}</metadata>\n`;
|
|
|
|
|
s += defs({ paperC, ink, baseVign: pal.vign(), params, u, colorMap });
|
2026-05-20 17:10:32 -04:00
|
|
|
s += layer('background', 'Background', bg);
|
|
|
|
|
s += layer('optics', 'Chamber optics', optics);
|
2026-05-21 05:59:44 -04:00
|
|
|
s += layer('shock', 'Shock disk', shock, params.diskSoften > 0 ? 'filter="url(#soften)"' : '');
|
2026-05-20 17:10:32 -04:00
|
|
|
s += trackLayers;
|
|
|
|
|
s += layer('damage', 'Plate damage', damage);
|
|
|
|
|
s += layer('fiducials', 'Fiducials', fids);
|
|
|
|
|
s += layer('vignette', 'Vignette', vign);
|
|
|
|
|
s += layer('header', 'Archival header', header);
|
2026-05-20 16:53:23 -04:00
|
|
|
s += `</svg>\n`;
|
|
|
|
|
return s;
|
|
|
|
|
}
|
2026-05-20 17:10:32 -04:00
|
|
|
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
function defs({ paperC, ink, baseVign, params, u, colorMap }) {
|
2026-05-21 05:59:44 -04:00
|
|
|
const soften = params.diskSoften > 0
|
|
|
|
|
? `<filter id="soften" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="${(params.diskSoften * u).toFixed(2)}"/></filter>`
|
|
|
|
|
: '';
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
// one soft-bubble gradient per distinct ink colour used (edge profile shared
|
|
|
|
|
// with the raster sprite via bubbleStops)
|
|
|
|
|
const stops = bubbleStops(params.bubbleSoft ?? 0.3);
|
|
|
|
|
let bubGrads = '';
|
|
|
|
|
for (const [key, col] of colorMap) {
|
|
|
|
|
const c = rgbHex(col);
|
|
|
|
|
let st = '';
|
|
|
|
|
for (const [off, a] of stops) st += `<stop offset="${(off * 100).toFixed(0)}%" stop-color="${c}" stop-opacity="${a.toFixed(3)}"/>`;
|
|
|
|
|
bubGrads += `<radialGradient id="bub-${key}" cx="50%" cy="50%" r="50%">${st}</radialGradient>`;
|
|
|
|
|
}
|
|
|
|
|
const vignHex = rgbHex(baseVign);
|
2026-05-20 17:10:32 -04:00
|
|
|
return `<defs>
|
2026-05-21 05:59:44 -04:00
|
|
|
${soften}
|
2026-05-20 17:10:32 -04:00
|
|
|
<radialGradient id="paper" cx="50%" cy="42%" r="72%">
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
<stop offset="0%" stop-color="${paperC.glowIn}"/><stop offset="100%" stop-color="${paperC.glowOut}"/>
|
2026-05-20 17:10:32 -04:00
|
|
|
</radialGradient>
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
${bubGrads}
|
2026-05-20 17:10:32 -04:00
|
|
|
<radialGradient id="shockcore" cx="50%" cy="50%" r="50%">
|
|
|
|
|
<stop offset="0%" stop-color="${ink}" stop-opacity="0.5"/>
|
|
|
|
|
<stop offset="60%" stop-color="${ink}" stop-opacity="0.28"/>
|
|
|
|
|
<stop offset="100%" stop-color="${ink}" stop-opacity="0"/>
|
|
|
|
|
</radialGradient>
|
|
|
|
|
<radialGradient id="shockstain" cx="50%" cy="50%" r="50%">
|
|
|
|
|
<stop offset="0%" stop-color="${ink}" stop-opacity="0.9"/>
|
|
|
|
|
<stop offset="100%" stop-color="${ink}" stop-opacity="0"/>
|
|
|
|
|
</radialGradient>
|
|
|
|
|
<radialGradient id="shockclean" cx="50%" cy="50%" r="50%">
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
<stop offset="0%" stop-color="${paperC.flat}" stop-opacity="0.9"/>
|
|
|
|
|
<stop offset="100%" stop-color="${paperC.flat}" stop-opacity="0"/>
|
2026-05-20 17:10:32 -04:00
|
|
|
</radialGradient>
|
|
|
|
|
<radialGradient id="vign" cx="50%" cy="50%" r="72%">
|
Colour palettes, depth/exposure, disk & halo effects; stop tracking rasters
Generator
- Depth & exposure dynamics: per-track chamber depth (z) + event age drive
opacity, bubble size and defocus (depthFactors); depth/aging dials.
- Palette abstraction (src/render/palette.js) — one registry entry per "feel":
mono, charge, beta, kind, kindlife, kindrise, lifecycle, psychedelic,
cyanotype, magentarise. Per-track ink + per-bubble bubbleInk hooks.
- Global colour controls: saturation, hue shift; paper toning (cream/sepia/
selenium/cool/olive/neutral + brightness + gas-glow), bubble edge softness,
iridescent disk (spectral sunburst), chromatic halo. Ink blend chosen by
ground luminance so light-on-dark chemistries composite correctly.
- Tracks carry charge q; bubbles carry lifecycle position + local beta.
- All effects in raster + layered SVG + CMYK/OCG PDF; B&W remains the default.
Tooling & art
- tools/find-semicircle.mjs; render-svg/pdf --seed mode + k=v overrides.
- Curated vector SVG sets under output/ with browsable index.html.
Repo hygiene
- .gitignore: stop tracking generated rasters/PDFs (reproducible from seeds),
the reference image, and a stray db; keep curated SVGs + code + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:55:03 -04:00
|
|
|
<stop offset="30%" stop-color="${vignHex}" stop-opacity="0"/>
|
|
|
|
|
<stop offset="100%" stop-color="${vignHex}" stop-opacity="${(params.invert ? 0.5 : 0.85) * params.vign}"/>
|
2026-05-20 17:10:32 -04:00
|
|
|
</radialGradient>
|
|
|
|
|
</defs>\n`;
|
|
|
|
|
}
|