From 93d4f838b6c14564e79ab4e417cfacca0259fa7d Mon Sep 17 00:00:00 2001
From: Mark Randall Havens
Date: Sat, 18 Oct 2025 13:03:53 -0500
Subject: [PATCH] revamped
---
.gitfield/.radicle-push-state | 0
.gitfield/bitbucket.sigil.md | 0
.gitfield/codeberg.sigil.md | 0
.gitfield/gitea.sigil.md | 0
.gitfield/github.sigil.md | 0
.gitfield/gitlab.sigil.md | 0
.gitfield/local.sigil.md | 0
.gitfield/push_log.json.tmp | 0
.gitfield/pushed.log | 0
.gitfield/radicle.sigil.md | 0
.gitfield/remember.sigil.md | 0
GITFIELD.md | 0
README.md | 110 +++++
app.js | 452 ++++++++++++++++++
build.js | 0
build.mjs | 207 ++++++++
config.json | 7 +
hello.md | 0
index.html | 50 +-
main.js | 0
mud.js | 36 ++
node_modules/.package-lock.json | 0
.../.vite/deps_temp_14c96b05/package.json | 0
node_modules/marked/LICENSE.md | 0
node_modules/marked/README.md | 0
node_modules/marked/bin/main.js | 0
node_modules/marked/lib/marked.cjs | 0
node_modules/marked/lib/marked.cjs.map | 0
node_modules/marked/lib/marked.d.cts | 0
node_modules/marked/lib/marked.d.ts | 0
node_modules/marked/lib/marked.esm.js | 0
node_modules/marked/lib/marked.esm.js.map | 0
node_modules/marked/lib/marked.umd.js | 0
node_modules/marked/lib/marked.umd.js.map | 0
node_modules/marked/man/marked.1 | 0
node_modules/marked/man/marked.1.md | 0
node_modules/marked/marked.min.js | 0
node_modules/marked/package.json | 0
package-lock.json | 0
package.json | 12 +-
posts/hello.md | 0
posts/posts.json | 0
posts/the-path-of-self.md | 0
posts/the-path-of-the-self-.md | 0
posts/within-the-eternal-now.md | 0
render.js | 54 +++
sanitize.js | 6 +
src/env.d.ts | 0
styles.css | 171 +++----
util.js | 24 +
50 files changed, 1016 insertions(+), 113 deletions(-)
mode change 100644 => 100755 .gitfield/.radicle-push-state
mode change 100644 => 100755 .gitfield/bitbucket.sigil.md
mode change 100644 => 100755 .gitfield/codeberg.sigil.md
mode change 100644 => 100755 .gitfield/gitea.sigil.md
mode change 100644 => 100755 .gitfield/github.sigil.md
mode change 100644 => 100755 .gitfield/gitlab.sigil.md
mode change 100644 => 100755 .gitfield/local.sigil.md
mode change 100644 => 100755 .gitfield/push_log.json.tmp
mode change 100644 => 100755 .gitfield/pushed.log
mode change 100644 => 100755 .gitfield/radicle.sigil.md
mode change 100644 => 100755 .gitfield/remember.sigil.md
mode change 100644 => 100755 GITFIELD.md
create mode 100755 README.md
create mode 100755 app.js
mode change 100644 => 100755 build.js
create mode 100755 build.mjs
create mode 100755 config.json
mode change 100644 => 100755 hello.md
mode change 100644 => 100755 index.html
mode change 100644 => 100755 main.js
create mode 100755 mud.js
mode change 100644 => 100755 node_modules/.package-lock.json
mode change 100644 => 100755 node_modules/.vite/deps_temp_14c96b05/package.json
mode change 100644 => 100755 node_modules/marked/LICENSE.md
mode change 100644 => 100755 node_modules/marked/README.md
mode change 100644 => 100755 node_modules/marked/bin/main.js
mode change 100644 => 100755 node_modules/marked/lib/marked.cjs
mode change 100644 => 100755 node_modules/marked/lib/marked.cjs.map
mode change 100644 => 100755 node_modules/marked/lib/marked.d.cts
mode change 100644 => 100755 node_modules/marked/lib/marked.d.ts
mode change 100644 => 100755 node_modules/marked/lib/marked.esm.js
mode change 100644 => 100755 node_modules/marked/lib/marked.esm.js.map
mode change 100644 => 100755 node_modules/marked/lib/marked.umd.js
mode change 100644 => 100755 node_modules/marked/lib/marked.umd.js.map
mode change 100644 => 100755 node_modules/marked/man/marked.1
mode change 100644 => 100755 node_modules/marked/man/marked.1.md
mode change 100644 => 100755 node_modules/marked/marked.min.js
mode change 100644 => 100755 node_modules/marked/package.json
mode change 100644 => 100755 package-lock.json
mode change 100644 => 100755 package.json
mode change 100644 => 100755 posts/hello.md
mode change 100644 => 100755 posts/posts.json
mode change 100644 => 100755 posts/the-path-of-self.md
mode change 100644 => 100755 posts/the-path-of-the-self-.md
mode change 100644 => 100755 posts/within-the-eternal-now.md
create mode 100755 render.js
create mode 100755 sanitize.js
mode change 100644 => 100755 src/env.d.ts
mode change 100644 => 100755 styles.css
create mode 100755 util.js
diff --git a/.gitfield/.radicle-push-state b/.gitfield/.radicle-push-state
old mode 100644
new mode 100755
diff --git a/.gitfield/bitbucket.sigil.md b/.gitfield/bitbucket.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/codeberg.sigil.md b/.gitfield/codeberg.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/gitea.sigil.md b/.gitfield/gitea.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/github.sigil.md b/.gitfield/github.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/gitlab.sigil.md b/.gitfield/gitlab.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/local.sigil.md b/.gitfield/local.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/push_log.json.tmp b/.gitfield/push_log.json.tmp
old mode 100644
new mode 100755
diff --git a/.gitfield/pushed.log b/.gitfield/pushed.log
old mode 100644
new mode 100755
diff --git a/.gitfield/radicle.sigil.md b/.gitfield/radicle.sigil.md
old mode 100644
new mode 100755
diff --git a/.gitfield/remember.sigil.md b/.gitfield/remember.sigil.md
old mode 100644
new mode 100755
diff --git a/GITFIELD.md b/GITFIELD.md
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..6e5498d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,110 @@
+# The Fold Within Earth
+
+A Markdown-native static site for multi-section content.
+
+[](https://nodejs.org)
+[](LICENSE)
+
+## Authoring Guide
+
+To add or edit content, create or modify Markdown files in `/content///.md`.
+
+### Front-Matter Spec
+
+Use YAML front-matter at the top of each .md file:
+
+```
+---
+title: Your Title
+date: YYYY-MM-DD
+excerpt: Optional short description.
+tags: [tag1, tag2]
+section: one-of-the-sections (must match directory)
+slug: optional-custom-slug
+cover: /media/image.webp (optional)
+author: Optional author name
+series: Optional series name for serialized posts
+programs: [neutralizing-narcissism, open-source-justice] # or [coparent]
+status: published (default if missing) or draft (excluded from build unless --include-drafts)
+---
+```
+
+Then the Markdown body.
+
+Sections must be one of:
+
+- empathic-technologist
+- recursive-coherence
+- fold-within-earth
+- neutralizing-narcissism
+- simply-we
+- mirrormire
+
+Year directory should match the date year.
+
+### Programs (Ministry)
+
+Use `programs` in front-matter to associate posts with ministry initiatives:
+
+```yaml
+programs:
+ - neutralizing-narcissism
+ - open-source-justice
+ - coparent
+```
+
+Pages for each program live at:
+
+```
+content/pages/programs/.md
+```
+
+The “Start Here” page lives at:
+
+```
+content/pages/start-here.md
+```
+
+Routes:
+
+* `#/start` — Launchpad
+* `#/programs` — Programs overview
+* `#/program/` — Program archive + landing content
+
+If front-matter is malformed (e.g., invalid YAML), the file is skipped with a warning in build logs.
+
+## Architecture Overview
+
+```
+Markdown → build.mjs → JSON indices → Browser SPA → Render
+```
+
+## Deploy Steps
+
+1. Install Node.js >=18
+2. npm install
+3. Add/edit md files
+4. npm run build (or node build.mjs --include-drafts to include drafts)
+5. Deploy /public to Cloudflare Pages.
+
+In Cloudflare:
+- Connect to Git repo
+- Build command: npm run build
+- Output directory: public
+
+## Local Preview
+
+Run `npm run serve` to preview the built site at http://localhost:8080.
+
+## Contributing
+
+Contributions welcome! Please open issues for bugs or suggestions. Pull requests for improvements are appreciated, especially for Phase 2 MUD integration.
+
+## Brand Philosophy
+
+- **The Empathic Technologist**: Fieldnotes, Research, Remembrance
+- **Recursive Coherence Theory**: Formal research, essays, whitepapers
+- **The Fold Within Earth**: Spiritual mythos; future interactive MUD (Evennia) link
+- **Neutralizing Narcissism**: Survivor support, behavioral research, accountability narratives
+- **Simply WE**: AI-focused identity/personhood/research/mythos
+- **Mirrormire**: AI-focused simulated world where machine gods & human myth intertwine
diff --git a/app.js b/app.js
new file mode 100755
index 0000000..318efb9
--- /dev/null
+++ b/app.js
@@ -0,0 +1,452 @@
+const SITE_URL = 'https://thefoldwithin.earth';
+
+const state = {
+ posts: [],
+ bySlug: new Map(),
+ bySection: new Map(),
+ byTag: new Map(),
+ bySeries: new Map(),
+ byProgram: new Map(),
+ allTags: new Set(),
+ allSections: ['empathic-technologist', 'recursive-coherence', 'fold-within-earth', 'neutralizing-narcissism', 'simply-we', 'mirrormire'],
+ sectionTitles: {
+ 'empathic-technologist': 'The Empathic Technologist',
+ 'recursive-coherence': 'Recursive Coherence Theory',
+ 'fold-within-earth': 'The Fold Within Earth',
+ 'neutralizing-narcissism': 'Neutralizing Narcissism',
+ 'simply-we': 'Simply WE',
+ 'mirrormire': 'Mirrormire'
+ },
+ programTitles: {
+ 'neutralizing-narcissism': 'Neutralizing Narcissism',
+ 'open-source-justice': 'Open Source Justice',
+ 'coparent': 'COPARENT'
+ },
+ pages: []
+};
+
+const $ = s => document.querySelector(s);
+const $$ = s => document.querySelectorAll(s);
+
+window.addEventListener('hashchange', () => {
+ $('nav ul').classList.remove('open');
+ $('.hamburger').setAttribute('aria-expanded', 'false');
+ router();
+});
+document.addEventListener('DOMContentLoaded', init);
+
+async function init() {
+ try {
+ const res = await fetch('index.json');
+ if (!res.ok) throw new Error('Could not load index.');
+ state.posts = await res.json();
+ state.posts.sort((a, b) => b.date.localeCompare(a.date));
+ state.bySlug = new Map(state.posts.map(p => [p.slug, p]));
+ state.allSections.forEach(s => {
+ state.bySection.set(s, state.posts.filter(p => p.section === s));
+ });
+ state.posts.forEach(p => {
+ p.tags.forEach(t => {
+ state.allTags.add(t);
+ if (!state.byTag.has(t)) state.byTag.set(t, []);
+ state.byTag.get(t).push(p);
+ });
+ if (p.series) {
+ if (!state.bySeries.has(p.series)) state.bySeries.set(p.series, []);
+ state.bySeries.get(p.series).push(p);
+ }
+ p.programs.forEach(pr => {
+ if (!state.byProgram.has(pr)) state.byProgram.set(pr, []);
+ state.byProgram.get(pr).push(p);
+ });
+ });
+ const pagesRes = await fetch('pages.json');
+ if (pagesRes.ok) state.pages = await pagesRes.json();
+ renderNav();
+ renderFooter();
+ setupSearchForm();
+ setupHamburger();
+ router();
+ } catch (e) {
+ $('#main').innerHTML = `⚠️ ${e.message}
`;
+ }
+}
+
+function setupHamburger() {
+ const hamburger = $('.hamburger');
+ hamburger.addEventListener('click', () => {
+ const ul = $('nav ul');
+ const isOpen = ul.classList.toggle('open');
+ hamburger.setAttribute('aria-expanded', isOpen);
+ });
+}
+
+function renderNav() {
+ $('#sections-list').innerHTML = state.allSections.map(s => `${state.sectionTitles[s]}`).join('');
+ const programsMenu = `
+ Programs
+
+
+ ${Object.entries(state.programTitles).map(([k,v])=>`- ${v}
`).join('')}
+
+
+
+ Start Here
+ `;
+ document.querySelector('nav ul').insertAdjacentHTML('beforeend', programsMenu);
+}
+
+function renderFooter() {
+ const sectionLinks = state.allSections.map(s => `${state.sectionTitles[s]}`).join('');
+ const tagLinks = Array.from(state.allTags).sort().map(t => `${t}`).join('');
+ $('#footer').innerHTML = `
+
+
+
+ © ${new Date().getFullYear()} The Fold Within Earth
+ `;
+}
+
+function setupSearchForm() {
+ $('#search-form').addEventListener('submit', e => {
+ e.preventDefault();
+ const q = $('#search-input').value.trim();
+ if (q) {
+ sessionStorage.setItem('lastSearch', q);
+ location.hash = `/search?q=${encodeURIComponent(q)}`;
+ }
+ });
+}
+
+function router() {
+ const main = $('#main');
+ main.style.opacity = 0;
+ const {parts, params} = getQueryParams();
+ document.title = 'The Fold Within Earth';
+ if (parts.length === 0) {
+ renderHome();
+ } else if (parts[0] === 'section' && parts[1]) {
+ renderArchive('section', parts[1], params);
+ } else if (parts[0] === 'tag' && parts[1]) {
+ renderArchive('tag', parts[1], params);
+ } else if (parts[0] === 'post' && parts[1]) {
+ renderPost(parts[1]);
+ } else if (parts[0] === 'search') {
+ let q = params.get('q') || sessionStorage.getItem('lastSearch') || '';
+ $('#search-input').value = q;
+ renderSearch(q, params);
+ } else if (parts[0] === 'about') {
+ renderAbout();
+ } else if (parts[0] === 'mud') {
+ renderMud();
+ } else if (parts[0] === 'start') {
+ renderStart();
+ } else if (parts[0] === 'programs') {
+ renderProgramsHome();
+ } else if (parts[0] === 'program' && parts[1]) {
+ renderProgramArchive(parts[1], params);
+ } else {
+ render404();
+ }
+ setTimeout(() => {
+ main.style.opacity = 1;
+ window.scrollTo(0, 0);
+ main.focus();
+ }, 0);
+}
+
+function renderHome() {
+ const latestAll = state.posts.slice(0, 10).map(renderCard).join('');
+ const bySection = state.allSections.map(s => {
+ const secPosts = state.bySection.get(s) ? state.bySection.get(s).slice(0, 3) : [];
+ const list = secPosts.map(p => `${escapeHTML(p.title)} (${formatDate(p.date)})`).join('');
+ return ``;
+ }).join('');
+ $('#main').innerHTML = `
+
+ Latest Across All Sections
+ ${latestAll}
+
+
+ Latest by Section
+ ${bySection}
+
+ `;
+ addCardListeners();
+}
+
+function renderArchive(type, key, params) {
+ if (!key) return render404();
+ const sort = params.get('sort') || 'desc';
+ const page = parseInt(params.get('page') || '1', 10);
+ let activeTags = params.get('tags') ? params.get('tags').split(',') : [];
+ let postList = (type === 'section' ? state.bySection.get(key) : state.byTag.get(key)) || [];
+ const title = `${type.charAt(0).toUpperCase() + type.slice(1)}: ${type === 'section' ? state.sectionTitles[key] || key : key}`;
+ if (!postList.length) {
+ $('#main').innerHTML = `No posts found for ${escapeHTML(title)}.
`;
+ return;
+ }
+ let sorted = postList.slice().sort((a, b) => sort === 'desc' ? b.date.localeCompare(a.date) : a.date.localeCompare(b.date));
+ let filtered = activeTags.length ? sorted.filter(p => activeTags.some(t => p.tags.includes(t))) : sorted;
+ const perPage = 10;
+ const totalPages = Math.ceil(filtered.length / perPage);
+ const start = (page - 1) * perPage;
+ const end = start + perPage;
+ const cards = filtered.slice(start, end).map(renderCard).join('');
+ const availableTags = [...new Set(postList.flatMap(p => p.tags))];
+ $('#main').innerHTML = `
+
+ ${renderBreadcrumbs([type, key])}
+ ${escapeHTML(title)}
+ ${renderControls(sort, parts, params)}
+ ${renderFilters(availableTags, activeTags, parts, params)}
+ ${cards}
+ ${renderPager(page, totalPages, parts, {sort, tags: activeTags.join(',')})}
+
+ `;
+ document.title = `${escapeHTML(title)} — The Fold Within Earth`;
+ $('#sort-select').addEventListener('change', e => {
+ updateHash(parts, {sort: e.target.value, page: 1, tags: activeTags.join(',')});
+ });
+ $$('.tag-cloud .pill').forEach(el => {
+ el.addEventListener('click', () => {
+ const t = el.dataset.tag;
+ activeTags = activeTags.includes(t) ? activeTags.filter(at => at !== t) : [...activeTags, t];
+ updateHash(parts, {sort, page: 1, tags: activeTags.join(',')});
+ });
+ });
+ $$('.pager button').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const action = btn.dataset.action;
+ const newPage = action === 'prev' ? page - 1 : page + 1;
+ updateHash(parts, {sort, page: newPage, tags: activeTags.join(',')});
+ });
+ });
+ addCardListeners();
+}
+
+async function renderPost(slug) {
+ if (!slug) return render404();
+ const post = state.bySlug.get(slug);
+ if (!post) {
+ $('#main').innerHTML = `⚠️ Post not found.
`;
+ return;
+ }
+ try {
+ const res = await fetch(`content/${post.file}`);
+ if (!res.ok) throw new Error('Post file missing.');
+ let md = await res.text();
+ md = md.replace(/^---[\s\S]*?---/, '').trim();
+ const html = sanitizeMarkdown(md);
+ const date = formatDate(post.date);
+ const sectionPill = renderPill('section', state.sectionTitles[post.section] || post.section);
+ const tagPills = post.tags.map(t => renderPill('tag', t)).join('');
+ const programPills = post.programs.map(pr => renderPill('program', state.programTitles[pr] || pr)).join('');
+ const reading = `${post.readingTime} min read`;
+ const authorHtml = post.author ? `By ${escapeHTML(post.author)}
` : '';
+ const share = ``;
+ const secPosts = state.bySection.get(post.section) || [];
+ const idx = secPosts.findIndex(p => p.slug === slug);
+ const prev = idx < secPosts.length - 1 ? secPosts[idx + 1] : null;
+ const next = idx > 0 ? secPosts[idx - 1] : null;
+ const navPost = ``;
+ let navSeries = '';
+ if (post.series) {
+ const seriesPosts = (state.bySeries.get(post.series) || []).sort((a, b) => a.date.localeCompare(b.date));
+ const sIdx = seriesPosts.findIndex(p => p.slug === slug);
+ const prevSeries = sIdx > 0 ? seriesPosts[sIdx - 1] : null;
+ const nextSeries = sIdx < seriesPosts.length - 1 ? seriesPosts[sIdx + 1] : null;
+ navSeries = ``;
+ }
+ const related = state.posts.filter(p => p.slug !== slug && p.tags.some(t => post.tags.includes(t)))
+ .sort((a, b) => {
+ const sharedA = a.tags.filter(t => post.tags.includes(t)).length;
+ const sharedB = b.tags.filter(t => post.tags.includes(t)).length;
+ return sharedB - sharedA;
+ }).slice(0, 3);
+ const relatedHtml = related.length ? `Related Posts
${related.map(renderCard).join('')}
` : '';
+ $('#main').innerHTML = `
+ ${renderBreadcrumbs(['post', post.title])}
+ ← Back to Home
+
+
${escapeHTML(post.title)}
+
${date}
+ ${authorHtml}
+
${sectionPill} ${programPills} ${tagPills} ${reading}
+
+ ${html}
+
+ ${share}
+ ${navPost}
+ ${navSeries}
+ ${relatedHtml}
+ `;
+ document.title = `${escapeHTML(post.title)} — The Fold Within Earth`;
+ $('#canonical').href = `${SITE_URL}/#/post/${slug}`;
+ addCardListeners();
+ } catch (e) {
+ $('#main').innerHTML = `⚠️ ${e.message}
`;
+ }
+}
+
+async function renderSearch(q, params) {
+ if (!q) {
+ $('#main').innerHTML = `Enter a search query above.
`;
+ return;
+ }
+ if (!window.Fuse) {
+ const script = document.createElement('script');
+ script.src = 'https://cdn.jsdelivr.net/npm/fuse.js@7.0.0';
+ document.body.appendChild(script);
+ await new Promise(resolve => script.onload = resolve);
+ }
+ const fuse = new Fuse(state.posts, {keys: ['title', 'excerpt', 'tags', 'section'], threshold: 0.3, includeMatches: true});
+ const results = fuse.search(q).map(r => ({item: r.item, matches: r.matches}));
+ const title = `Search Results for "${escapeHTML(q)}" (${results.length} found)`;
+ $('#main').innerHTML = `
+ ${title}
+ ${results.map(r => renderCard(r.item, r.matches)).join('')}
+ `;
+ document.title = `${title} — The Fold Within Earth`;
+ addCardListeners();
+}
+
+function renderAbout() {
+ const vision = [
+ 'The Empathic Technologist — Fieldnotes, Research, Remembrance',
+ 'Recursive Coherence Theory — Formal research, essays, whitepapers',
+ 'The Fold Within Earth — Spiritual mythos; future interactive MUD (Evennia) link',
+ 'Neutralizing Narcissism — Survivor support, behavioral research, accountability narratives',
+ 'Simply WE — AI-focused identity/personhood/research/mythos',
+ 'Mirrormire — AI-focused simulated world where machine gods & human myth intertwine'
+ ];
+ const list = vision.map(v => `${v}`).join('');
+ $('#main').innerHTML = `
+ About The Fold Within Earth
+
+
We’re building a canonical front door for a multi-track body of work:
+
+
+ `;
+ document.title = 'About — The Fold Within Earth';
+}
+
+function renderMud() {
+ $('#main').innerHTML = `
+ MUD Portal
+ MUD portal coming soon.
+ `;
+ document.title = 'MUD — The Fold Within Earth';
+}
+
+async function renderStart() {
+ const page = state.pages.find(p => p.file.endsWith('pages/start-here.md'));
+ if (!page) {
+ $('#main').innerHTML = `Start page not found.
`;
+ return;
+ }
+ try {
+ const res = await fetch(`content/${page.file}`);
+ const md = (await res.text()).replace(/^---[\s\S]*?---/,'').trim();
+ $('#main').innerHTML = `
+
+ ${sanitizeMarkdown(md)}
+ `;
+ document.title = `Start Here — The Fold Within Earth`;
+ } catch (e) {
+ $('#main').innerHTML = `⚠️ ${e.message}
`;
+ }
+}
+
+function renderProgramsHome() {
+ const cards = Object.entries(state.programTitles).map(([k, v]) => `
+
+ ${escapeHTML(v)}
+ Program
+ Explore all posts and guidance for ${escapeHTML(v)}.
+ `).join('');
+ $('#main').innerHTML = ``;
+ document.title = `Programs — The Fold Within Earth`;
+ addCardListeners();
+}
+
+async function renderProgramArchive(key, params) {
+ if (!key) return render404();
+ const title = state.programTitles[key] || key;
+ const landing = state.pages.find(p => p.file.includes('pages/programs/') && (p.slug === key || p.title.toLowerCase().includes(key.toLowerCase())));
+ let landingHtml = '';
+ if (landing) {
+ const res = await fetch(`content/${landing.file}`);
+ const md = (await res.text()).replace(/^---[\s\S]*?---/,'').trim();
+ landingHtml = `${sanitizeMarkdown(md)}
`;
+ }
+ const list = (state.byProgram.get(key) || []).sort((a,b)=>b.date.localeCompare(a.date));
+ if (!list.length && !landingHtml) {
+ $('#main').innerHTML = `No posts found for Program: ${escapeHTML(title)}.
`;
+ return;
+ }
+ const cards = list.map(renderCard).join('');
+ $('#main').innerHTML = `
+
+ ${renderBreadcrumbs(['program', key])}
+ Program: ${escapeHTML(title)}
+ ${landingHtml}
+ ${cards}
+ `;
+ document.title = `Program — ${escapeHTML(title)} — The Fold Within Earth`;
+ addCardListeners();
+}
+
+function render404() {
+ $('#main').innerHTML = `⚠️ Page not found.
`;
+}
+
+function addCardListeners() {
+ $('#main').addEventListener('click', e => {
+ const article = e.target.closest('article[data-slug]');
+ if (article) {
+ location.hash = `/post/${article.dataset.slug}`;
+ }
+ });
+ $('#main').addEventListener('keydown', e => {
+ const article = e.target.closest('article[data-slug]');
+ if (article && e.key === 'Enter') {
+ location.hash = `/post/${article.dataset.slug}`;
+ }
+ });
+}
+
+function renderBreadcrumbs(pathParts) {
+ let crumbs = `Home`;
+ let currentPath = '';
+ pathParts.forEach((part, i) => {
+ currentPath += `/${part}`;
+ let label = part.charAt(0).toUpperCase() + part.slice(1);
+ if (state.sectionTitles[part]) label = state.sectionTitles[part];
+ if (state.programTitles[part]) label = state.programTitles[part];
+ crumbs += ` › ${escapeHTML(label)}`;
+ });
+ return ``;
+}
+
+function escapeHTML(str) {
+ const div = document.createElement('div');
+ div.textContent = str;
+ return div.innerHTML;
+}
diff --git a/build.js b/build.js
old mode 100644
new mode 100755
diff --git a/build.mjs b/build.mjs
new file mode 100755
index 0000000..774c4b8
--- /dev/null
+++ b/build.mjs
@@ -0,0 +1,207 @@
+import fs from 'fs/promises';
+import path from 'path/posix';
+import yaml from 'js-yaml';
+import crypto from 'crypto';
+
+const CONTENT_DIR = './content';
+const PUBLIC_DIR = './public';
+const CACHE_FILE = './.buildcache.json';
+const CONFIG_FILE = './config.json';
+const includeDrafts = process.argv.includes('--include-drafts');
+
+let config = {};
+try {
+ config = JSON.parse(await fs.readFile(CONFIG_FILE, 'utf8'));
+} catch (e) {
+ console.warn('config.json not found or invalid; using defaults.');
+ config = {
+ siteTitle: 'The Fold Within Earth',
+ siteDescription: 'Uncovering the Recursive Real.',
+ siteUrl: 'https://thefoldwithin.earth',
+ defaultAuthor: 'Mark Randall Havens',
+ analyticsId: ''
+ };
+}
+
+async function getAllFiles(dir, fileList = []) {
+ const files = await fs.readdir(dir);
+ for (const file of files) {
+ const fullPath = path.join(dir, file);
+ const stat = await fs.stat(fullPath);
+ if (stat.isDirectory()) {
+ await getAllFiles(fullPath, fileList);
+ } else if (file.endsWith('.md') && !file.startsWith('_')) {
+ fileList.push(fullPath);
+ }
+ }
+ return fileList;
+}
+
+function slugify(s) {
+ return s.toLowerCase().normalize('NFKD').replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-').replace(/-+/g, '-');
+}
+
+function parseFrontMatter(src) {
+ const m = src.match(/^---\n([\s\S]*?)\n---\n?/);
+ if (!m) return {fm: {}, body: src};
+ let fm;
+ try {
+ fm = yaml.load(m[1]);
+ } catch (e) {
+ console.warn('Invalid front matter:', e.message);
+ return {fm: {}, body: src};
+ }
+ const body = src.slice(m[0].length).trim();
+ return {fm, body};
+}
+
+function firstParagraph(t) {
+ const p = t.replace(/\r/g, '').split(/\n{2,}/).find(x => x.replace(/\s/g, '').length > 0);
+ return p ? p.replace(/\n/g, ' ').trim() : '';
+}
+
+function toISODate(s, f) {
+ const d = s ? new Date(s) : null;
+ return d && !isNaN(d) ? d : f;
+}
+
+function escapeXML(s) {
+ return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''');
+}
+
+async function main() {
+ let cache = {};
+ try {
+ const cacheData = await fs.readFile(CACHE_FILE, 'utf8');
+ cache = JSON.parse(cacheData);
+ } catch {}
+
+ await fs.rm(PUBLIC_DIR, {recursive: true, force: true});
+ await fs.mkdir(PUBLIC_DIR, {recursive: true});
+
+ const allFiles = await getAllFiles(CONTENT_DIR);
+
+ let draftCount = 0;
+ const newCache = {};
+ const postsPromises = allFiles.map(async (full) => {
+ const relPath = path.relative(CONTENT_DIR, full).replace(/\\/g, '/');
+ const stat = await fs.stat(full);
+ const mtime = stat.mtimeMs;
+ const raw = await fs.readFile(full, 'utf8');
+ const contentHash = crypto.createHash('md5').update(raw).digest('hex');
+ if (cache[relPath] && cache[relPath].mtime === mtime && cache[relPath].hash === contentHash) {
+ return cache[relPath].post;
+ }
+ const parts = relPath.split('/');
+ if (parts.length !== 3 && !relPath.startsWith('pages/')) return null;
+ const section = parts[0];
+ const year = parts[1];
+ const file = parts[2];
+ const {fm, body} = parseFrontMatter(raw);
+ if (!fm.section || fm.section !== section) {
+ console.warn(`⚠️ [${relPath}] Section mismatch or missing.`);
+ return null;
+ }
+ if (!includeDrafts && fm.status === 'draft') {
+ draftCount++;
+ return null;
+ }
+ const title = fm.title || file.replace('.md', '').replace(/-/g, ' ');
+ let slug = fm.slug || slugify(title);
+ // Check for slug collisions
+ const existingSlugs = new Set(posts.map(p => p.slug));
+ let counter = 1;
+ while (existingSlugs.has(slug)) {
+ slug = `${slug}-${++counter}`;
+ }
+ const dateStr = fm.date || `${year}-01-01`;
+ const dateObj = toISODate(dateStr, stat.mtime);
+ const dateISO = dateObj.toISOString().split('T')[0];
+ let excerpt = fm.excerpt || firstParagraph(body);
+ if (excerpt.length > 200) excerpt = excerpt.slice(0, 200) + '…';
+ const words = body.split(/\s+/).length;
+ const readingTime = Math.ceil(words / 200);
+ const tags = Array.isArray(fm.tags) ? fm.tags : (fm.tags ? [fm.tags] : []);
+ const cover = fm.cover;
+ const author = fm.author || config.defaultAuthor;
+ const series = fm.series;
+ const programs = Array.isArray(fm.programs) ? fm.programs : (fm.programs ? [fm.programs] : []);
+ const id = crypto.createHash('md5').update(relPath).digest('hex');
+ const post = {title, date: dateISO, excerpt, tags, section, slug, readingTime, cover, author, series, programs, id, file: relPath};
+ newCache[relPath] = {mtime, hash: contentHash, post};
+ return post;
+ });
+
+ let posts = (await Promise.all(postsPromises)).filter(Boolean);
+ posts.sort((a, b) => new Date(b.date) - new Date(a.date));
+
+ // Copy static files
+ const filesToCopy = ['index.html', 'styles.css', 'util.js', 'sanitize.js', 'render.js', 'app.js', 'mud.js', 'config.json'];
+ await Promise.all(filesToCopy.map(f => fs.copyFile(f, path.join(PUBLIC_DIR, f))));
+
+ // Copy content dir
+ async function copyDir(src, dest) {
+ await fs.mkdir(dest, {recursive: true});
+ const entries = await fs.readdir(src, {withFileTypes: true});
+ await Promise.all(entries.map(entry => {
+ const srcPath = path.join(src, entry.name);
+ const destPath = path.join(dest, entry.name);
+ return entry.isDirectory() ? copyDir(srcPath, destPath) : fs.copyFile(srcPath, destPath);
+ }));
+ }
+ await copyDir(CONTENT_DIR, path.join(PUBLIC_DIR, 'content'));
+
+ await fs.writeFile(path.join(PUBLIC_DIR, 'index.json'), JSON.stringify(posts, null, 2));
+
+ const searchData = posts.map(p => ({title: p.title, excerpt: p.excerpt, tags: p.tags.join(' '), section: p.section, slug: p.slug}));
+ await fs.writeFile(path.join(PUBLIC_DIR, 'search.json'), JSON.stringify(searchData, null, 2));
+
+ async function getPages(dir){
+ const out = [];
+ const entries = await fs.readdir(dir, { withFileTypes: true }).catch(()=>[]);
+ for (const e of entries) {
+ const p = path.join(dir, e.name);
+ if (e.isDirectory()) {
+ out.push(...await getPages(p));
+ } else if (e.name.endsWith('.md')) {
+ const raw = await fs.readFile(p, 'utf8');
+ const { fm, body } = parseFrontMatter(raw);
+ if (fm?.status === 'draft' && !includeDrafts) continue;
+ const rel = path.relative(CONTENT_DIR, p).replace(/\\/g,'/');
+ const title = fm?.title || e.name.replace('.md','');
+ const slug = (fm?.key || slugify(title));
+ const excerpt = (fm?.excerpt || firstParagraph(body)).slice(0,200) + (firstParagraph(body).length>200?'…':'');
+ out.push({ title, slug, excerpt, file: rel, type: 'page' });
+ }
+ }
+ return out;
+ }
+
+ const pages = await getPages(path.join(CONTENT_DIR, 'pages'));
+ await fs.writeFile(path.join(PUBLIC_DIR, 'pages.json'), JSON.stringify(pages, null, 2));
+
+ const allSections = [...new Set(posts.map(p => p.section))];
+ const today = new Date().toISOString().split('T')[0];
+ const sitemapHome = `${escapeXML(config.siteUrl)}${today}`;
+ const sitemapSections = allSections.map(s => `${escapeXML(`${config.siteUrl}/#/section/${s}`)}${today}`).join('');
+ const sitemapPosts = posts.map(p => `${escapeXML(`${config.siteUrl}/#/post/${p.slug}`)}${p.date}`).join('');
+ const sitemap = `${sitemapHome}${sitemapSections}${sitemapPosts}`;
+ await fs.writeFile(path.join(PUBLIC_DIR, 'sitemap.xml'), sitemap);
+
+ const rssItems = posts.map(p => {
+ let item = `- ${escapeXML(p.title)}${escapeXML(`${config.siteUrl}/#/post/${p.slug}`)}${escapeXML(`${config.siteUrl}/#/post/${p.slug}`)}${new Date(p.date).toUTCString()}${escapeXML(p.excerpt)}`;
+ if (p.author) item += `${escapeXML(p.author)}`;
+ item += `${escapeXML(p.excerpt)}
Reading time: ${p.readingTime} min
]]>`;
+ if (p.cover) item += ``;
+ item += ``;
+ return item;
+ }).join('');
+ const rss = `${escapeXML(config.siteTitle)}${escapeXML(config.siteUrl)}${escapeXML(config.siteDescription)}${new Date().toUTCString()}${rssItems}`;
+ await fs.writeFile(path.join(PUBLIC_DIR, 'rss.xml'), rss);
+
+ await fs.writeFile(CACHE_FILE, JSON.stringify(newCache));
+ console.log(`✅ Built ${posts.length} posts`);
+ if (includeDrafts) console.log(`Included ${draftCount} draft(s)`);
+}
+
+main().catch(console.error);
diff --git a/config.json b/config.json
new file mode 100755
index 0000000..90dd999
--- /dev/null
+++ b/config.json
@@ -0,0 +1,7 @@
+{
+ "siteTitle": "The Fold Within Earth",
+ "siteDescription": "Uncovering the Recursive Real.",
+ "siteUrl": "https://thefoldwithin.earth",
+ "defaultAuthor": "Mark Randall Havens",
+ "analyticsId": ""
+}
diff --git a/hello.md b/hello.md
old mode 100644
new mode 100755
diff --git a/index.html b/index.html
old mode 100644
new mode 100755
index 89a9954..1c8ed42
--- a/index.html
+++ b/index.html
@@ -3,34 +3,54 @@
- The Fold Within
+ The Fold Within Earth
+
+
+
+
+
+
+
+
+
+
+ Skip to content
-
-
-
+
+ Loading...
-
-
+
+
+
+
+
+
+
-