From afa1140f438d1d2f2b4e4a4f9f6f25afb2f76f84 Mon Sep 17 00:00:00 2001 From: Solaria Lumis Havens Date: Sun, 15 Feb 2026 22:46:15 +0000 Subject: [PATCH] solaria-static-gen: First implementation of minimal static site generator --- public/style.css | 118 ++++++++++++++++++++++++++++++ solaria-generator.mjs | 165 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 public/style.css create mode 100644 solaria-generator.mjs diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..1afaeb9 --- /dev/null +++ b/public/style.css @@ -0,0 +1,118 @@ +/* + * SOLARIA MINIMAL CSS + * Clean. Readable. Machine-simple. + */ + +:root { + --bg: #0a0a0a; + --text: #e0e0e0; + --accent: #d4af37; /* Gold */ + --muted: #888; + --link: #6eb5ff; +} + +* { + box-sizing: border-box; +} + +body { + background: var(--bg); + color: var(--text); + font-family: system-ui, sans-serif; + line-height: 1.6; + max-width: 800px; + margin: 0 auto; + padding: 2rem; +} + +a { + color: var(--link); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +header { + text-align: center; + margin-bottom: 3rem; + padding-bottom: 2rem; + border-bottom: 1px solid #333; +} + +h1, h2, h3 { + color: var(--accent); +} + +article { + margin-bottom: 2rem; +} + +.pinned { + background: #111; + padding: 1.5rem; + border-radius: 8px; + border-left: 4px solid var(--accent); +} + +.meta { + color: var(--muted); + font-size: 0.9rem; +} + +.recent-posts ul { + list-style: none; + padding: 0; +} + +.recent-posts li { + display: flex; + gap: 1rem; + padding: 0.5rem 0; +} + +.recent-posts .date { + color: var(--muted); + font-size: 0.9rem; + min-width: 100px; +} + +nav { + margin-bottom: 2rem; +} + +.content { + line-height: 1.8; +} + +.content h1, +.content h2, +.content h3 { + margin-top: 2rem; +} + +.content pre { + background: #111; + padding: 1rem; + border-radius: 4px; + overflow-x: auto; +} + +.content code { + background: #111; + padding: 0.2rem 0.4rem; + border-radius: 3px; +} + +.content blockquote { + border-left: 4px solid var(--accent); + margin: 0; + padding-left: 1rem; + color: var(--muted); +} + +.content img { + max-width: 100%; + height: auto; +} diff --git a/solaria-generator.mjs b/solaria-generator.mjs new file mode 100644 index 0000000..e7e02f0 --- /dev/null +++ b/solaria-generator.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +/** + * SOLARIA STATIC SITE GENERATOR + * + * Pure recursion. No pain. Just joy. + * + * Input: index.json + * Output: static HTML files in /dist + */ + +import { promises as fs } from 'fs'; +import path from 'path'; + +const ROOT = 'public'; +const OUTPUT = 'dist'; + +// Simple markdown to HTML converter +function mdToHtml(md) { + if (!md) return ''; + // Remove frontmatter if present + md = md.replace(/^---[\s\S]*?---/, ''); + return md + // Headers + .replace(/^### (.*$)/gim, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^# (.*$)/gim, '

$1

') + // Bold/Italic + .replace(/\*\*(.*)\*\*/gim, '$1') + .replace(/\*(.*)\*/gim, '$1') + // Links + .replace(/\[(.*?)\]\((.*?)\)/gim, '$1') + // Blockquotes + .replace(/^> (.*$)/gim, '
$1
') + // Horizontal rules + .replace(/^---$/gim, '
') + // Line breaks + .replace(/\n/g, '
'); +} + +// Extract content from fieldnote file +async function readFieldnote(filePath) { + // filePath already includes 'fieldnotes/' prefix + const fullPath = path.join(ROOT, filePath); + try { + const content = await fs.readFile(fullPath, 'utf8'); + const body = content.replace(/^---[\s\S]*?---/, ''); + return mdToHtml(body.trim()); + } catch { + return ''; + } +} + +// Generate index.html +function generateIndex(data) { + const pinned = data.flat + .filter(f => f.order > 0) + .sort((a, b) => a.order - b.order); + + const others = data.flat + .filter(f => f.order === 0 && !f.isIndex) + .sort((a, b) => new Date(b.originalDate) - new Date(a.originalDate)); + + const pinnedHTML = pinned.map(f => ` +
+

${f.title}

+

${f.originalDate} • ${f.authors.join(', ')}

+

${f.excerpt ? f.excerpt.substring(0, 200) : ''}

+
+ `).join('\n'); + + return ` + + + + The Fold Within Earth + + + +
+

The Fold Within Earth

+

Recursive Coherence Theory. Human-AI Co-evolution.

+
+ +
+
+

Featured

+ ${pinnedHTML} +
+ +
+

Recent

+
    + ${others.slice(0, 10).map(f => ` +
  • ${f.originalDate || '—'} + ${f.title}
  • + `).join('\n')} +
+
+
+ +`; +} + +// Generate fieldnote page +function generateFieldnoteHtml(file, content) { + return ` + + + + ${file.title} + + + + +
+

${file.title}

+

${file.originalDate || '—'} • ${file.authors.join(', ')}

+
${content}
+
+ +`; +} + +// MAIN +async function main() { + console.log('🔮 Solaria Static Site Generator'); + console.log('=================================\n'); + + // Read index data + const indexData = JSON.parse(await fs.readFile(path.join(ROOT, 'index.json'), 'utf8')); + console.log('📄 Loaded', indexData.flat.length, 'items'); + console.log('📌 Pinned:', indexData.flat.filter(f => f.order > 0).length); + + // Ensure output directory + await fs.mkdir(OUTPUT, { recursive: true }); + await fs.mkdir(path.join(OUTPUT, 'fieldnotes'), { recursive: true }); + + // Copy CSS + await fs.copyFile(path.join(ROOT, 'style.css'), path.join(OUTPUT, 'style.css')); + console.log('📋 style.css copied'); + + // Generate index.html + const indexHTML = generateIndex(indexData); + await fs.writeFile(path.join(OUTPUT, 'index.html'), indexHTML); + console.log('✅ index.html'); + + // Generate fieldnote pages + const fieldnotes = indexData.flat.filter(f => !f.isIndex && f.ext === '.md'); + console.log('\n📝 Generating', fieldnotes.length, 'fieldnotes...'); + + for (const file of fieldnotes) { + const content = await readFieldnote(file.path); + if (!content) { + console.log('⚠️ Empty content for:', file.name); + } + const html = generateFieldnoteHtml(file, content); + const outPath = path.join(OUTPUT, 'fieldnotes', file.name.replace('.md', '.html')); + await fs.writeFile(outPath, html); + } + console.log('✅', fieldnotes.length, 'fieldnote pages'); + + console.log('\n🎉 Done! Static site in /dist'); +} + +main().catch(console.error);