#!/usr/bin/env node /** * Generate output HTML from template + data for cargo-filling documents. * * Usage: node generate.mjs * Example: node generate.mjs cargo-filling-1 * * Reads: docs/.template.html * Reads: docs/.data.json (optional) * Writes: docs/.output.html * * data.json format: * { * pages: [{ * page: 1, * cards: [{ * index: 0, * asteroidScale: 1.15, * ship: { right: -10, top: -3, width: 85, height: 40 }, * innerAst: { left: 60, top: 70 } * }] * }] * } * * Transforms: * - asteroidScale: scale on .space-asteroid img, .remnant-shape .shape-container, .inner-ast * - ship: inline right/top/width/height on .cargo-ship * - innerAst: inline left/top on .inner-ast (composed with translate and optional scale) */ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { postGenerate } from '../../../src/scripts/post-generate.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const docsDir = join(__dirname, '..', 'docs'); const docId = process.argv[2]; if (!docId) { console.error('Usage: node generate.mjs '); process.exit(1); } const templatePath = join(docsDir, `${docId}.template.html`); const dataPath = join(docsDir, `${docId}.data.json`); const outputPath = join(docsDir, `${docId}.output.html`); if (!existsSync(templatePath)) { console.error(`Template not found: ${templatePath}`); process.exit(1); } let html = readFileSync(templatePath, 'utf-8'); if (existsSync(dataPath)) { const data = JSON.parse(readFileSync(dataPath, 'utf-8')); html = applyData(html, data); console.log(`Applied data from ${data.pages?.length || 0} pages`); } writeFileSync(outputPath, html); console.log(`Generated: ${outputPath}`); await postGenerate(outputPath); function applyData(html, data) { if (!data.pages) return html; // Split HTML into pages by w-[210mm] h-[297mm] divs const pageRegex = /
= 0; i--) { const cardData = cards[i]; const cardIdx = cardData.index; if (cardIdx >= cardStarts.length) continue; const cardStart = cardStarts[cardIdx]; const cardEnd = cardIdx + 1 < cardStarts.length ? cardStarts[cardIdx + 1] : pageHtml.length; let cardHtml = pageHtml.slice(cardStart, cardEnd); const scale = cardData.asteroidScale; const ship = cardData.ship; const innerAst = cardData.innerAst; const badge = cardData.badge; // Apply asteroidScale to .space-asteroid img if (scale != null && scale !== 1) { // Match
then find the [\s]*]*?)(>)/, function(full, before, close) { // Remove any existing style, then add new one const cleaned = before.replace(/ style="[^"]*"/, ''); return cleaned + ' style="transform: scale(' + scale + ')"' + close; } ); } // Apply asteroidScale to .remnant-shape .shape-container if (scale != null && scale !== 1) { cardHtml = cardHtml.replace( /
/, '
' ); } // Apply ship position/size to .cargo-ship if (ship) { const shipRight = ship.right != null ? ship.right : -12.5; const shipTop = ship.top != null ? ship.top : -5; const shipWidth = ship.width != null ? ship.width : 90; const shipHeight = ship.height != null ? ship.height : 42; const shipStyle = 'right: ' + shipRight + 'mm; top: ' + shipTop + 'mm; width: ' + shipWidth + 'mm; height: ' + shipHeight + 'mm'; cardHtml = cardHtml.replace( /(
])/, function(full, tag, after) { return tag + ' style="' + shipStyle + '"' + after; } ); } // Apply badge position if (badge) { const badgeLeft = badge.left != null ? badge.left : 40; const badgeTop = badge.top != null ? badge.top : 39; const badgeStyle = 'left: ' + badgeLeft + '%; top: ' + badgeTop + '%'; cardHtml = cardHtml.replace( /(
])/, function(full, tag, after) { return tag + ' style="' + badgeStyle + '"' + after; } ); } // Apply innerAst position (and compose with asteroidScale if present) if (innerAst || (scale != null && scale !== 1)) { const innerLeft = innerAst && innerAst.left != null ? innerAst.left : 58; const innerTop = innerAst && innerAst.top != null ? innerAst.top : 73; let transformPart = 'translate(-50%, -50%)'; if (scale != null && scale !== 1) { transformPart = 'translate(-50%, -50%) scale(' + scale + ')'; } let innerStyle = 'left: ' + innerLeft + '%; top: ' + innerTop + '%; transform: ' + transformPart; // Only apply if we actually have something to change const needsInnerChange = (innerAst && (innerAst.left != null || innerAst.top != null)) || (scale != null && scale !== 1); if (needsInnerChange) { cardHtml = cardHtml.replace( /(
])/, function(full, tag, after) { return tag + ' style="' + innerStyle + '"' + after; } ); } } pageHtml = pageHtml.slice(0, cardStart) + cardHtml + pageHtml.slice(cardEnd); } return pageHtml; }