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 ,
2026-05-26 10:23:50 -04:00
traceHue : params . traceHue ? ? 0 , diskHue : params . diskHue ? ? 0.06 , diskSat : params . diskSat ? ? 0.82 ,
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
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
2026-05-22 07:50:39 -04:00
/* ---------- Media & hand (réseau, splice, film furniture, grease pencil) ---------- */
let media = '' ;
if ( scene . media ) {
const M = scene . media ;
const nx = ( x ) => ( cx + x * scale ) , ny = ( y ) => ( cy + y * scale ) ;
const esc = ( t ) => String ( t ) . replace ( /[<&]/g , c => ( c === '<' ? '<' : '&' ) ) ;
if ( M . reseau ) {
let g = ` <g stroke=" ${ ink } " stroke-opacity=" ${ M . reseau . opacity . toFixed ( 3 ) } " stroke-width=" ${ 1 * u } "> ` ;
const ss = M . reseau . size * scale ;
for ( const m of M . reseau . marks ) {
const x = nx ( m . x ) , y = ny ( m . y ) ;
g += ` <line x1=" ${ ( x - ss ) . toFixed ( 1 ) } " y1=" ${ y . toFixed ( 1 ) } " x2=" ${ ( x + ss ) . toFixed ( 1 ) } " y2=" ${ y . toFixed ( 1 ) } "/><line x1=" ${ x . toFixed ( 1 ) } " y1=" ${ ( y - ss ) . toFixed ( 1 ) } " x2=" ${ x . toFixed ( 1 ) } " y2=" ${ ( y + ss ) . toFixed ( 1 ) } "/> ` ;
}
media += g + ` </g> ` ;
}
if ( M . splice ) {
const yc = ny ( M . splice . y ) , hh = M . splice . h * scale , deg = M . splice . tilt * 57.3 ;
media += ` <g transform="rotate( ${ deg . toFixed ( 2 ) } ${ ( w / 2 ) . toFixed ( 1 ) } ${ yc . toFixed ( 1 ) } )"> `
+ ` <rect x="0" y=" ${ ( yc - hh ) . toFixed ( 1 ) } " width=" ${ w } " height=" ${ ( hh * 2 ) . toFixed ( 1 ) } " fill=" ${ ink } " fill-opacity=" ${ M . splice . opacity . toFixed ( 3 ) } "/> `
+ ` <line x1="0" y1=" ${ ( yc - hh ) . toFixed ( 1 ) } " x2=" ${ w } " y2=" ${ ( yc - hh ) . toFixed ( 1 ) } " stroke=" ${ ink } " stroke-opacity=" ${ Math . min ( 0.6 , M . splice . opacity * 2 ) . toFixed ( 3 ) } " stroke-width=" ${ u } "/> `
+ ` <line x1="0" y1=" ${ ( yc + hh ) . toFixed ( 1 ) } " x2=" ${ w } " y2=" ${ ( yc + hh ) . toFixed ( 1 ) } " stroke=" ${ ink } " stroke-opacity=" ${ Math . min ( 0.6 , M . splice . opacity * 2 ) . toFixed ( 3 ) } " stroke-width=" ${ u } "/></g> ` ;
}
if ( M . film ) {
const f = M . film , m = 0.965 ;
media += ` <rect x=" ${ nx ( - m ) . toFixed ( 1 ) } " y=" ${ ny ( - m ) . toFixed ( 1 ) } " width=" ${ ( ( nx ( m ) - nx ( - m ) ) ) . toFixed ( 1 ) } " height=" ${ ( ( ny ( m ) - ny ( - m ) ) ) . toFixed ( 1 ) } " fill="none" stroke=" ${ ink } " stroke-opacity="0.5" stroke-width=" ${ 2 * u } "/> ` ;
const sw = 0.028 * scale , sh = 0.05 * scale , rx = 4 * u , holeFill = inv ? '#faf8f0' : '#12100d' ;
let g = '' ;
for ( const y of f . sprockets ) for ( const sx of [ nx ( - 0.985 ) , nx ( 0.985 ) ] ) {
g += ` <rect x=" ${ ( sx - sw / 2 ) . toFixed ( 1 ) } " y=" ${ ( ny ( y ) - sh / 2 ) . toFixed ( 1 ) } " width=" ${ sw . toFixed ( 1 ) } " height=" ${ sh . toFixed ( 1 ) } " rx=" ${ rx . toFixed ( 1 ) } " fill=" ${ holeFill } " fill-opacity="0.7" stroke=" ${ ink } " stroke-opacity="0.4" stroke-width=" ${ u } "/> ` ;
}
media += g ;
media += ` <g fill=" ${ ink } " font-family="'JetBrains Mono', monospace"> `
+ ` <text x=" ${ nx ( - 0.935 ) . toFixed ( 1 ) } " y=" ${ ny ( 0 ) . toFixed ( 1 ) } " font-size=" ${ ( 7 * u ) . toFixed ( 0 ) } " fill-opacity="0.45" text-anchor="middle" transform="rotate(-90 ${ nx ( - 0.935 ) . toFixed ( 1 ) } ${ ny ( 0 ) . toFixed ( 1 ) } )"> ${ esc ( f . edgeText ) } </text> ` ;
f . dataBox . forEach ( ( line , i ) => { media += ` <text x=" ${ nx ( - 0.9 ) . toFixed ( 1 ) } " y=" ${ ( ny ( 0.8 ) + i * 12 * u ) . toFixed ( 1 ) } " font-size=" ${ ( 9 * u ) . toFixed ( 0 ) } " fill-opacity="0.5"> ${ esc ( line ) } </text> ` ; } ) ;
media += ` </g> ` ;
}
if ( M . grease && M . grease . length ) {
const ch = inv ? '#9c1e1e' : '#f0e296' ;
let g = ` <g stroke=" ${ ch } " fill=" ${ ch } " stroke-linecap="round" stroke-linejoin="round"> ` ;
for ( const gm of M . grease ) {
if ( gm . kind === 'ring' || gm . kind === 'arrow' ) {
const pts = gm . pts || gm . shaft ;
let d = ` M ${ nx ( pts [ 0 ] . x ) . toFixed ( 1 ) } ${ ny ( pts [ 0 ] . y ) . toFixed ( 1 ) } ` ;
for ( let i = 1 ; i < pts . length ; i ++ ) d += ` L ${ nx ( pts [ i ] . x ) . toFixed ( 1 ) } ${ ny ( pts [ i ] . y ) . toFixed ( 1 ) } ` ;
g += ` <path d=" ${ d } " fill="none" stroke-opacity="0.85" stroke-width=" ${ ( gm . width * u ) . toFixed ( 2 ) } "/> ` ;
if ( gm . kind === 'arrow' ) {
const { x , y , ang } = gm . tip , hl = 0.03 * scale ;
g += ` <path d="M ${ nx ( x ) . toFixed ( 1 ) } ${ ny ( y ) . toFixed ( 1 ) } L ${ ( nx ( x ) - Math . cos ( ang - 0.4 ) * hl ) . toFixed ( 1 ) } ${ ( ny ( y ) - Math . sin ( ang - 0.4 ) * hl ) . toFixed ( 1 ) } M ${ nx ( x ) . toFixed ( 1 ) } ${ ny ( y ) . toFixed ( 1 ) } L ${ ( nx ( x ) - Math . cos ( ang + 0.4 ) * hl ) . toFixed ( 1 ) } ${ ( ny ( y ) - Math . sin ( ang + 0.4 ) * hl ) . toFixed ( 1 ) } " fill="none" stroke-opacity="0.85" stroke-width=" ${ ( gm . width * u ) . toFixed ( 2 ) } "/> ` ;
}
} else if ( gm . kind === 'arc' ) {
const x0 = nx ( gm . x ) + Math . cos ( gm . a0 ) * gm . r * scale , y0 = ny ( gm . y ) + Math . sin ( gm . a0 ) * gm . r * scale ;
const x1 = nx ( gm . x ) + Math . cos ( gm . a1 ) * gm . r * scale , y1 = ny ( gm . y ) + Math . sin ( gm . a1 ) * gm . r * scale ;
g += ` <path d="M ${ x0 . toFixed ( 1 ) } ${ y0 . toFixed ( 1 ) } A ${ ( gm . r * scale ) . toFixed ( 1 ) } ${ ( gm . r * scale ) . toFixed ( 1 ) } 0 0 1 ${ x1 . toFixed ( 1 ) } ${ y1 . toFixed ( 1 ) } " fill="none" stroke-opacity="0.85" stroke-width=" ${ ( gm . width * u ) . toFixed ( 2 ) } "/> ` ;
} else if ( gm . kind === 'tick' ) {
g += ` <line x1=" ${ nx ( gm . x1 ) . toFixed ( 1 ) } " y1=" ${ ny ( gm . y1 ) . toFixed ( 1 ) } " x2=" ${ nx ( gm . x2 ) . toFixed ( 1 ) } " y2=" ${ ny ( gm . y2 ) . toFixed ( 1 ) } " stroke-opacity="0.85" stroke-width=" ${ ( gm . width * u ) . toFixed ( 2 ) } "/> ` ;
} else if ( gm . kind === 'text' ) {
g += ` <text x=" ${ nx ( gm . x ) . toFixed ( 1 ) } " y=" ${ ny ( gm . y ) . toFixed ( 1 ) } " font-size=" ${ ( gm . size * scale ) . toFixed ( 0 ) } " font-family="'Bradley Hand','Segoe Script','Comic Sans MS',cursive" fill-opacity="0.9" transform="rotate( ${ ( ( gm . rot || 0 ) * 57.3 ) . toFixed ( 1 ) } ${ nx ( gm . x ) . toFixed ( 1 ) } ${ ny ( gm . y ) . toFixed ( 1 ) } )"> ${ esc ( gm . text ) } </text> ` ;
}
}
media += g + ` </g> ` ;
}
}
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-22 07:50:39 -04:00
s += layer ( 'media' , 'Media & hand' , media ) ;
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
< / r a d i a l G r a d i e n t >
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" / >
< / r a d i a l G r a d i e n t >
< 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" / >
< / r a d i a l G r a d i e n t >
< 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
< / r a d i a l G r a d i e n t >
< 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
< / r a d i a l G r a d i e n t >
< / d e f s > \ n ` ;
}