Files

60 lines
3.9 KiB
JavaScript
Raw Permalink Normal View History

2026-06-02 20:32:41 -04:00
/* templates.mjs render the composition templates (famous-composition skeletons)
to SVGs + .mjs configs + a contact sheet + an archive-wall MATRIX.
Usage: node tools/templates.mjs [size] [outDir] [seedSalt]
seedSalt appended to each seed for a fresh batch (e.g. set 2). */
import { writeFileSync, mkdirSync } from 'node:fs';
import { TEMPLATES, buildTemplate } from '../src/compose/templates.js';
import { renderComposition } from '../src/compose/composition.js';
const SIZE = +(process.argv[2] || 1300);
const OUT = process.argv[3] || 'output/templates';
const SALT = process.argv[4] || '';
mkdirSync(OUT, { recursive: true });
let n = 0;
const items = [];
for (const t of TEMPLATES) {
for (let i = 0; i < t.reps; i++) {
const comp = buildTemplate(t, i);
if (SALT) comp.seed = comp.seed + '-' + SALT; // fresh event content, same grammar
const name = `${String(++n).padStart(2, '0')}_${t.id}_v${i + 1}`;
writeFileSync(`${OUT}/${name}.svg`, renderComposition(comp, SIZE));
writeFileSync(`${OUT}/${name}.mjs`, `/* ${t.name} (${t.source}) · variation ${i + 1} */\nexport const composition = ${JSON.stringify(comp, null, 2)};\n`);
items.push({ name, t, seed: comp.seed });
console.log(` ${name} ${t.name} · ${t.source} · ${comp.seed}`);
}
}
console.log(`templates — ${n} compositions → ${OUT}/`);
// plain contact sheet
writeFileSync(`${OUT}/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Composition templates · ${n}</title>
<style>html,body{margin:0;background:#15140f;color:#cabfa6;font:12px ui-monospace,monospace}h1{font-weight:400;letter-spacing:.2em;text-transform:uppercase;color:#9fb7af;padding:18px 14px 0}.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:14px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 9px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<h1>Composition templates (${n})</h1>
<div class="grid">${items.map(it => `<figure><img src="${it.name}.svg"><figcaption>${it.t.name} · ${it.t.source}</figcaption></figure>`).join('')}</div></body></html>`);
// ARCHIVE-WALL MATRIX — the typology hung as a gallery wall (Becher energy):
// warm wall, consistent matting + plate labels, catalogue numbers.
const cols = 5;
const matrix = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Traces of the Invisible — a catalogue of compositions</title>
<style>
html,body{margin:0;background:#17150f;color:#cabfa6}
body{padding:60px 54px 72px;font:12px/1.5 ui-monospace,Menlo,monospace}
.head{letter-spacing:.34em;text-transform:uppercase;font-size:14px;color:#9fb7af;margin:0 0 4px}
.sub{color:#6f6a5c;font-size:12px;margin:0 0 40px;letter-spacing:.06em}
.wall{display:grid;grid-template-columns:repeat(${cols},1fr);gap:46px 38px}
figure{margin:0}
.mat{background:#f3ecdb;padding:14px;box-shadow:0 2px 0 #0008,0 20px 44px -20px #000c;border:1px solid #000}
.mat img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover;background:#fff}
figcaption{margin-top:10px;display:flex;justify-content:space-between;gap:8px;align-items:baseline}
.cat{color:#9fb7af;letter-spacing:.1em}.src{color:#6f6a5c;font-style:italic}
.ttl{color:#cabfa6;text-align:center;flex:1}
</style></head><body>
<p class="head">Traces of the Invisible a catalogue of compositions</p>
<p class="sub">the grammar of great pictures, applied to one instrument · ${n} plates${SALT ? ' · series ' + SALT : ''}</p>
<div class="wall">
${items.map((it, k) => `<figure><div class="mat"><img src="${it.name}.svg"></div>
<figcaption><span class="cat">${String(k + 1).padStart(2, '0')}</span><span class="ttl">${it.t.name}</span><span class="src">${it.t.source}</span></figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/matrix.html`, matrix);
console.log(`-> ${OUT}/m.html + ${OUT}/matrix.html`);