diff --git a/scripts/generate-pages.mjs b/scripts/generate-pages.mjs index 78a0af0..4b115b8 100644 --- a/scripts/generate-pages.mjs +++ b/scripts/generate-pages.mjs @@ -6,7 +6,9 @@ import { marked } from "marked"; const ROOT = process.cwd(); const SRC = path.join(ROOT, "src"); +const PUBLIC = path.join(ROOT, "public"); const ARTICLES_GLOB = "articles/**/index.md"; +const SITE_URL = (process.env.SITE_URL || "https://apophenia.news").replace(/\/+$/, ""); marked.setOptions({ breaks: true, gfm: true }); @@ -27,7 +29,7 @@ const logo = ` `; -const shellHead = ({ title, desc, image }) => ` +const shellHead = ({ title, desc, image, url, type = "website" }) => ` @@ -36,9 +38,12 @@ const shellHead = ({ title, desc, image }) => ` - + + ${url ? `` : ""} ${image ? `` : ""} + ${url ? `` : ""} + @@ -59,11 +64,13 @@ const nav = ` @@ -85,6 +92,9 @@ const fmtDate = (date) => const escapeHtml = (s = "") => s.replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch])); +const escapeXml = (s = "") => + s.replace(/[<>&'"]/g, (ch) => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ }[ch])); + const fixInternalLinks = (html) => html .replace(/href="\.\//g, 'href="/') @@ -98,7 +108,9 @@ const renderHome = (articles) => ` ${shellHead({ title: "apophenia.news — The news outlet for pattern seekers", desc: "Signals, anomalies, civilization trajectories, and deep pattern analysis.", - image: "https://direct-img.link/constellation+data+points+minimal+white+background" + image: "https://direct-img.link/constellation+data+points+minimal+white+background", + url: `${SITE_URL}/`, + type: "website" })} ${nav}
@@ -108,7 +120,7 @@ ${nav}

High-agency analysis at the intersection of AGI, consciousness, geopolitics, and first-contact logic.

-
+

Latest Articles

${articles.length} published @@ -143,7 +155,13 @@ ${footer} `; const renderArticle = (article) => ` -${shellHead({ title: `${article.title} — apophenia.news`, desc: article.description, image: article.header_image })} +${shellHead({ + title: `${article.title} — apophenia.news`, + desc: article.description, + image: article.header_image, + url: `${SITE_URL}/${article.slug}/`, + type: "article" +})} ${nav}
@@ -169,6 +187,58 @@ ${nav} ${footer} `; +const renderWritePage = () => ` +${shellHead({ + title: "Become a writer for apophenia.news", + desc: "Pitch your pattern analysis. Email your story as a Markdown file for review and publication.", + image: "https://direct-img.link/writer+typing+cosmic+newsroom+editorial", + url: `${SITE_URL}/write/`, + type: "website" +})} +${nav} +
+
+

Contributor Program

+

Become a writer for apophenia.news

+

+ Have a strong pattern-based story, analysis, or investigation? Send it to us. +

+ +
+

How to submit

+
    +
  • Write your article in a .md (Markdown) file.
  • +
  • Email it to planetrenox@pm.me.
  • +
  • If approved, your story will be published on apophenia.news.
  • +
  • Your byline can use your real name or an alias.
  • +
+ +

Submission tips

+
    +
  • Lead with a clear thesis and strong evidence.
  • +
  • Use links/citations when making factual claims.
  • +
  • Include a short author bio line if you want one shown.
  • +
  • Add a suggested title, slug, description, and tags at the top (frontmatter preferred).
  • +
+ +

Frontmatter template (optional)

+
---
+title: "Your headline"
+slug: your-slug
+date: 2026-03-01
+author: Your Name or Alias
+description: "1-2 sentence summary"
+header_image: https://direct-img.link/your+image+query
+tags:
+  - your-tag
+  - another-tag
+---
+
+
+
+${footer} +`; + const ensureCleanGenerated = async () => { await fs.mkdir(SRC, { recursive: true }); const children = await fs.readdir(SRC, { withFileTypes: true }); @@ -180,6 +250,74 @@ const ensureCleanGenerated = async () => { ); }; +const toISODate = (d) => new Date(d).toISOString().split("T")[0]; + +const renderRss = (articles) => { + const lastBuildDate = new Date().toUTCString(); + const items = articles + .map((a) => { + const link = `${SITE_URL}/${a.slug}/`; + return ` + ${escapeXml(a.title || "")} + ${escapeXml(link)} + ${escapeXml(link)} + ${new Date(a.date).toUTCString()} + ${escapeXml(a.description || "")} +`; + }) + .join("\n"); + + return ` + + + apophenia.news + ${SITE_URL}/ + Signals, anomalies, civilization trajectories, and deep pattern analysis. + en-us + ${lastBuildDate} + ${items} + + +`; +}; + +const renderSitemap = (articles) => { + const now = toISODate(new Date()); + const urls = [ + { loc: `${SITE_URL}/`, lastmod: now }, + { loc: `${SITE_URL}/write/`, lastmod: now }, + ...articles.map((a) => ({ loc: `${SITE_URL}/${a.slug}/`, lastmod: toISODate(a.date || new Date()) })) + ]; + + return ` + +${urls + .map( + (u) => ` + ${escapeXml(u.loc)} + ${u.lastmod} + ` + ) + .join("\n")} + +`; +}; + +const renderRobots = () => `User-agent: * +Allow: / + +Sitemap: ${SITE_URL}/sitemap.xml +`; + +const writeDiscoveryFiles = async (articles) => { + await fs.mkdir(PUBLIC, { recursive: true }); + await Promise.all([ + fs.writeFile(path.join(PUBLIC, "rss.xml"), renderRss(articles), "utf8"), + fs.writeFile(path.join(PUBLIC, "sitemap.xml"), renderSitemap(articles), "utf8"), + fs.writeFile(path.join(PUBLIC, "robots.txt"), renderRobots(), "utf8") + ]); +}; + const run = async () => { const files = await fg(ARTICLES_GLOB, { cwd: ROOT, absolute: true }); const articles = []; @@ -201,13 +339,19 @@ const run = async () => { await ensureCleanGenerated(); await fs.writeFile(path.join(SRC, "index.html"), renderHome(articles), "utf8"); + const writeDir = path.join(SRC, "write"); + await fs.mkdir(writeDir, { recursive: true }); + await fs.writeFile(path.join(writeDir, "index.html"), renderWritePage(), "utf8"); + for (const article of articles) { const dir = path.join(SRC, article.slug); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(path.join(dir, "index.html"), renderArticle(article), "utf8"); } - console.log(`Generated ${articles.length} article pages + home.`); + await writeDiscoveryFiles(articles); + + console.log(`Generated ${articles.length} article pages + home + write + rss/sitemap/robots.`); }; run().catch((err) => {