Files
kaleidoskop/scripts/build.js
T
valknar 6b61cf206a Fix page parity: images on even (left) pages, text on odd (right) pages
Add a section-title page ("Die Geschichten") as page 3 so all stories
begin on page 4. Image and text for each scene now land on the same
facing spread (even=left image, odd=right text). Page count: 99 → 100.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 19:14:43 +02:00

83 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readdir, readFile, writeFile, access } from 'fs/promises';
import { join, resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import matter from 'gray-matter';
import { marked } from 'marked';
import nunjucks from 'nunjucks';
const __dir = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dir, '..');
// Nunjucks: load templates relative to project root
const env = nunjucks.configure(join(root, 'templates'), { autoescape: true });
async function fileExists(path) {
try { await access(path); return true; } catch { return false; }
}
async function loadStories() {
const contentDir = join(root, 'content');
const files = (await readdir(contentDir)).filter(f => f.endsWith('.md')).sort();
const stories = [];
let finale = null;
for (const file of files) {
const raw = await readFile(join(contentDir, file), 'utf-8');
const { data, content } = matter(raw);
if (data.type === 'front-matter') continue;
if (data.type === 'finale') {
finale = data;
continue;
}
// Split body by --- into individual scene texts
const sceneTexts = content.split(/\n---\n/).map(t => t.trim()).filter(Boolean);
const scenes = await Promise.all(
(data.scenes || []).map(async (scene, i) => {
const imagePath = join(root, scene.image);
const imageExists = await fileExists(imagePath);
return {
...scene,
imageExists,
// Relative path from output/book.html back to project root
image: `../${scene.image}`,
html: marked.parse(sceneTexts[i] || ''),
};
})
);
stories.push({ ...data, scenes });
}
return { stories, finale: finale || {} };
}
async function loadFrontMatter() {
const raw = await readFile(join(root, 'content', '00-front-matter.md'), 'utf-8');
return matter(raw).data;
}
async function build() {
const [{ stories, finale }, frontMatter] = await Promise.all([loadStories(), loadFrontMatter()]);
const html = env.render('book.html', { stories, finale, frontMatter });
const outPath = join(root, 'output', 'book.html');
await writeFile(outPath, html, 'utf-8');
// imprint + TOC + section-title + (4 scenes × 2 pages per story) + finale
const pageCount = 3 + stories.reduce((acc, s) => acc + s.scenes.length * 2, 0) + 1;
await writeFile(
join(root, 'output', 'book-meta.json'),
JSON.stringify({ pageCount, storyCount: stories.length, builtAt: new Date().toISOString() }, null, 2),
'utf-8'
);
console.log(`Built output/book.html — ${stories.length} stories, ${pageCount} pages`);
}
build().catch(err => { console.error(err); process.exit(1); });