import { SITE_URL, TURNSTILE_SITE_KEY } from "./constants.mjs"; import { footer, merch, nav, shellHead } from "./templates.mjs"; import { escapeHtml, escapeXml, fmtDate, renderAuthorInline, toISO, toISODate } from "./utils.mjs"; // Structured data (JSON-LD) so Google reads the headline, image, dates and authors directly instead of guessing const absUrl = (u = "") => (/^https?:\/\//.test(u) ? u : `${SITE_URL}/${u.replace(/^\/+/, "")}`); const publisher = { "@type": "Organization", name: "apophenia.news", url: `${SITE_URL}/`, logo: `${SITE_URL}/favicon.svg` }; // Linked names in the byline (people with an author page) become Person authors. Unlinked co-authors are AI models, // which schema.org has no type for, so they stay in the visible byline only. const authorsLd = (author = "") => { const people = [...String(author).matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)].map(([, name, href]) => ({ "@type": "Person", name: name.trim(), url: absUrl(href.trim()) })); return people.length ? people : [publisher]; }; // Published is the frontmatter date. Modified is the article folder's last commit (see generate-pages.mjs), never // earlier than published. const articleDates = (a) => { const published = toISO(a.date); return { published, modified: a.modified > published ? a.modified : published }; }; const articleLd = (a, { published, modified }) => ({ "@context": "https://schema.org", "@type": "NewsArticle", headline: a.title, description: a.description, image: [absUrl(a.header_image)], datePublished: published, dateModified: modified, author: authorsLd(a.author), publisher, mainEntityOfPage: `${SITE_URL}/${a.slug}/` }); const homeLd = { "@context": "https://schema.org", "@graph": [ { "@type": "WebSite", name: "apophenia.news", url: `${SITE_URL}/` }, { ...publisher, sameAs: ["https://youtube.com/@ApopheniaLoop"] } ] }; export const renderHome = (articles) => ` ${shellHead({ title: "apophenia.news — The news outlet for pattern seekers", desc: "Signals, anomalies, civilization trajectories, and deep pattern analysis.", image: "https://free.direct-img.link/celestial+map?i=1", url: `${SITE_URL}/`, type: "website", jsonLd: homeLd })} ${nav}

Pattern Intelligence Journalism

The news outlet for pattern seekers

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

Latest Articles

${articles.length} published
${articles .map( (a) => ` ` ) .join("")}
${merch}
${footer} `; export const renderArticle = (article, dates = articleDates(article)) => ` ${shellHead({ title: `${article.title} — apophenia.news`, desc: article.description, image: article.header_image, url: `${SITE_URL}/${article.slug}/`, type: "article", ...dates, jsonLd: articleLd(article, dates) })} ${nav}
${escapeHtml(article.title)}

${fmtDate(article.date)} • ${renderAuthorInline(article.author)}

${escapeHtml(article.title)}

${escapeHtml(article.description || "")}

${(article.tags || []).map((t) => `#${escapeHtml(t)}`).join("")}

${article.html}
${merch}
Back to Home
${footer} `; // Served by Cloudflare Pages with a 404 status for any path that doesn't exist (instead of falling back to the homepage) export const renderNotFound = () => ` ${shellHead({ title: "Page not found — apophenia.news", desc: "This page doesn't exist.", type: "website", noindex: true })} ${nav}
Detective searching for clues with a magnifying glass

404

No pattern here

This page doesn't exist or has moved. Even pattern seekers hit a dead end sometimes.

Back to Home
${footer} `; export const renderNewsletterPage = () => ` ${shellHead({ title: "Newsletter — apophenia.news", desc: "Subscribe to the apophenia.news newsletter. We won't spam you with everything — only the good stuff.", image: "https://free.direct-img.link/radio+telescope?i=1", url: `${SITE_URL}/newsletter/`, type: "website" })} ${nav}

Newsletter

Get the signal, skip the noise

The sharpest pattern analysis, delivered straight to your inbox. We won't spam you with everything — only the good stuff. No filler, no daily blasts, just the pieces worth your attention.

You're in. Welcome to the pattern.

Unsubscribe anytime. We never share your email.

${merch}
Back to Home
${footer} `; export const renderUnsubscribePage = () => ` ${shellHead({ title: "Unsubscribe — apophenia.news", desc: "Unsubscribe from the apophenia.news newsletter.", url: `${SITE_URL}/newsletter/unsubscribe` })} ${nav}

You're unsubscribed

won't receive Apophenia emails anymore.

Changed your mind? Resubscribe
Unsubscribing …

Unsubscribe

Enter your email to stop receiving our newsletter.

Back to newsletter
${footer} `; export 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://free.direct-img.link/typewriter?i=2", 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.

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://free.direct-img.link/your+image+query
tags:
  - your-tag
  - another-tag
---
${footer} `; export const renderAuthorPage = (author) => ` ${shellHead({ title: `${author.name} — apophenia.news`, desc: author.bio, image: author.image || "https://free.direct-img.link/fountain+pen?i=7", url: `${SITE_URL}/author/${author.slug}/`, type: "profile" })} ${nav}

Author

${escapeHtml(author.name)}

Photo coming soon

${escapeHtml(author.bio)}

Contact: ${escapeHtml(author.contact)}

${footer} `; export 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} `; }; export const renderSitemap = (articles, authors = []) => { const now = toISODate(new Date()); const urls = [ { loc: `${SITE_URL}/`, lastmod: now }, { loc: `${SITE_URL}/newsletter/`, lastmod: now }, { loc: `${SITE_URL}/write/`, lastmod: now }, ...authors.map((a) => ({ loc: `${SITE_URL}/author/${a.slug}/`, lastmod: now })), ...articles.map((a) => ({ loc: `${SITE_URL}/${a.slug}/`, lastmod: toISODate(articleDates(a).modified) })) ]; return ` ${urls .map( (u) => ` ${escapeXml(u.loc)} ${u.lastmod} ` ) .join("\n")} `; }; export const renderRobots = () => `User-agent: * Allow: / Sitemap: ${SITE_URL}/sitemap.xml `;