// Igneum site build: renders the engineering log from the repository's markdown into a branded
// page and merges the log's dated entries into the journey feed. The FUD ledger is internal and
// is never rendered here.
// Runs on every deploy (Vercel build command) and locally with `node build.mjs`.
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { scrubBench } from './scrub.mjs';
import { MARKS, VENDORS, tokensCss, markHtml } from './lib/marks.mjs';
import { osHtml } from './lib/os-marks.mjs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const repo = join(here, '..');
const docs = join(repo, 'docs');
function esc(s) { return s.replace(/&/g, '&').replace(//g, '>'); }
function inline(s) {
s = esc(s);
s = s.replace(/`([^`]+)`/g, '$1');
s = s.replace(/\*\*([^*]+)\*\*/g, '$1');
s = s.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, '$1');
return s;
}
function md(src) {
const lines = src.split('\n'); const out = []; let i = 0; let toc = [];
const slug = t => t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
while (i < lines.length) {
const l = lines[i];
if (/^```/.test(l)) { const buf = []; i++; while (i < lines.length && !/^```/.test(lines[i])) buf.push(lines[i++]); i++; out.push('
' + esc(buf.join('\n')) + ''); continue; }
const h = /^(#{1,4})\s+(.*)$/.exec(l);
if (h) { const lvl = h[1].length; const t = h[2].trim(); const id = slug(t); if (lvl <= 2) toc.push({ lvl, t, id }); out.push(`| ${inline(c)} | `).join('') + '
|---|
| ${inline(c)} | `).join('') + '
' + inline(buf.join(' ')) + '
'); } return { html: out.join('\n'), toc }; } // Shared chrome: fonts and tokens (head), the nav bar and the footer live once in site/partials/ and are injected into every // page between HTML comment markers ( ... , nav, footer; journey on the homepage). // The committed pages carry the injected copy, so they are right with or without a build; the build keeps them in step. const partial = name => readFileSync(join(here, 'partials', name), 'utf8').trim(); const HEAD = partial('head.html'), NAV = partial('nav.html'), FOOT = partial('footer.html'); function navFor(active) { // the slim bar (site-ui-5): the route's link carries aria-current in the panel and in the sheet, its group button data-active; // a page outside the panels (the explorer's block and address views, the 404, the scenes lab) marks nothing if (!active) return NAV; const re = new RegExp(`(\s*<\/div>/g)) { if (new RegExp(`data-nav="${active}"`).test(m[0])) { out = out.replace(`id="nav-btn-${m[1]}" data-group="${m[1]}" aria-expanded`, `id="nav-btn-${m[1]}" data-group="${m[1]}" data-active aria-expanded`); break; } } return out; } // Marks (site-ui-5): becomes the OS glyph in its well, // the vendor badge, and a table cell that names a card // ("NVIDIA RTX 5090", "AMD RX 9070 XT", "Apple M5 Max", "Intel Arc B580") gets the vendor's mini badge in front of the name. // Pages with a card picker ([data-yourcard]) get the five badges as JSON for yourcard.js. function vendorOfName(n) { if (/^nvidia|geforce|\bRTX\b|\bGTX\b/i.test(n)) return 'nvidia'; if (/^amd\b|radeon|\bgfx\d/i.test(n)) return 'amd'; if (/^intel|\barc\b|iris|\buhd\b/i.test(n)) return 'intel'; if (/^apple|\bM\d (max|pro|ultra)\b/i.test(n)) return 'apple'; return null; } function stampMarks(html) { html = html.replace(/<\/span>/g, (m, k, size) => osHtml(k, size)); html = html.replace(/<\/span>/g, (m, v, size) => markHtml(v, size)); html = html.replace(/