import { existsSync, readFileSync, readdirSync } from "node:fs"; import { dirname, join, relative, resolve } from "node:path"; import { createElement as h } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import katex from "katex"; import { build, bytes, walk, type Node, type Output, type Route, type Site, type Spec } from "../kit/ssg/build.ts"; import { isGit } from "../kit/git/git.ts"; import { resolve as resolveLink } from "../kit/ssg/links.ts"; import { escape, front, inline, plain, render as md, summary, title } from "../lib/md.js"; import { sidebar, tree } from "../lib/tree.js"; import { Glyph, Grid, Menu, Shell } from "../kit/ui/chrome.jsx"; import { headScript, tintCss } from "../kit/ui/config.js"; import SITE from "../lib/site.js"; import { shelf } from "./shelf.ts"; const org = resolve(import.meta.dir, ".."); const dist = process.env.MRLY_DIST ? resolve(process.env.MRLY_DIST) : join(org, "dist"); const BLOG = join(org, "blog"); const postFile = (slug: string) => join(BLOG, `${slug}.md`); const root = (process.env.MRLY_SITE ?? SITE.root).replace(/\/$/, ""); const AUTHOR = "MrlyProd"; const LIST = /^- \[([^\]]+)\]\([^)]*\) - (.+)$/gm; const HEADING = /(.*?)<\/h\1>/g; const AVATAR = /^(!\[avatar\]\(figures\/avatar\.png\)|.*?figures\/avatar-light\.png.*?<\/picture>)\n?/m; const WORD = renderToStaticMarkup(h(Glyph, { text: SITE.title.toUpperCase() })); const BOOT = headScript(SITE.prefix); const TINT = ``; const ICONS = [ ``, ``, ``, ``, ].join("\n"); const read = (p: string) => readFileSync(p, "utf8"); const math = (tex: string, display: boolean) => katex.renderToString(tex, { output: "mathml", throwOnError: false, displayMode: display }); const untag = (html: string) => html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"'); const brand = (name: string) => (name === SITE.title ? name : `${name} · ${SITE.title}`); /* LINKS */ const NAME = /^[a-z0-9-]+$/; function links(site: Site, from: string, out?: Output[]) { const home = site.input("figures").path; return (url: string) => { if (out && NAME.test(url) && existsSync(join(home, `${url}.png`))) { const path = `figures/${url}.png`; if (!out.some((item) => item.path === path)) out.push({ path, bytes: bytes(join(home, `${url}.png`)) }); return `/${path}`; } return resolveLink(site, from, url); }; } /* FIGURES */ const SIDES = ["dark", "light"] as const; function figure(home: string, name: string, route: string) { const file = join(home, `${name}.png`); if (!existsSync(file)) throw new Error(`site: ${name}.png missing from ${relative(org, home)} for ${route}; draw it with bun run figures`); return file; } function press(site: Site, out: Output[]) { const home = site.input("figures").path; return (name: string, route: string) => { const pair = { dark: "", light: "" }; for (const side of SIDES) { const file = figure(home, `${name}-${side}`, route); const path = `figures/${name}-${side}.png`; if (!out.some((item) => item.path === path)) out.push({ path, bytes: bytes(file) }); pair[side] = `/${path}`; } return pair; }; } type Fig = ReturnType; const pic = (fig: Fig, name: string, route: string, alt: string, extra = "", cls = "") => { const pair = fig(name, route); return SIDES.map((side) => `${escape(alt)}`).join(""); }; const hero = (fig: Fig, name: string, route: string, alt: string) => `
${pic(fig, name, route, alt)}
`; const grid = (nodes: Node[]) => renderToStaticMarkup(h(Grid, { nodes })); /* HEAD */ type Picture = { url: string; width: number; height: number }; const OG: Picture = { url: `${root}/og.png`, width: 1200, height: 630 }; function picture(site: Site, name: string, route: string, fig?: Fig): Picture { const file = figure(site.input("figures").path, `${name}-dark`, route); if (fig) fig(name, route); const png = bytes(file); const view = new DataView(png.buffer, png.byteOffset, png.byteLength); return { url: `${root}/figures/${name}-dark.png`, width: view.getUint32(16), height: view.getUint32(20) }; } function meta(route: string, name: string, description: string, type: string, image = OG) { const url = root + route; return [ ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ICONS, ].join("\n"); } function headings(body: string) { return [...body.matchAll(HEADING)].map((m) => ({ level: Number(m[1]), id: m[2], text: untag(m[3]) })); } type Leaf = { route: string; name: string; description: string; body: string; type?: string; wide?: boolean; bare?: boolean; code?: boolean; data?: object; tree?: Node[]; image?: Picture; scripts?: string[]; }; function shell(site: Site, leaf: Leaf) { const { route, name, description, body, type = "article", wide = false, bare = false, code = false, data } = leaf; const article = h(bare ? "div" : "article", { className: bare ? undefined : "prose", dangerouslySetInnerHTML: { __html: body } }); const main = renderToStaticMarkup(h(Shell, { route, tree: leaf.tree ?? site.nav, contents: headings(body), wide }, article)); const ld = data ? `\n` : ""; const more = (leaf.scripts ?? []).map((src) => `\n`).join(""); const image = leaf.image ?? (code ? picture(site, "site-code", route) : OG); return ` ${BOOT} ${escape(brand(name))} ${meta(route, name, description, type, image)} ${TINT} ${code ? `\n\n` : ""}${ld}${more} ${main} `; } /* DEMOS */ type Card = { name: string; title: string; blurb: string; shelf: string; order: number; reads: Read[] }; type Read = { name: string; href: string }; type Shelf = { key: string; group: string; title: string; blurb: string }; type Bay = Node & { key: string }; const SHELVES = (SITE.shelves ?? []) as Shelf[]; const TITLE = /([^<]*)<\/title>/; const OWN = /[ \t]*<meta name="?(?:description|shelf|order)"?[^>]*>\n?/g; const tag = (html: string, name: string) => { const found = html.match(new RegExp(`<meta name="${name}" content="([^"]*)">`)); return found ? untag(found[1]) : ""; }; function demoNames(site: Site) { const home = site.input("demos").path; return site .input("demos") .files.filter((f) => f.endsWith("/index.html")) .map((f) => dirname(f).slice(home.length + 1)) .sort((a, b) => (a === "" ? -1 : b === "" ? 1 : a.localeCompare(b))); } const demoRoute = (name: string) => (name ? `/demos/${name}/` : "/demos/"); function cards(site: Site): Card[] { const home = site.input("demos").path; return demoNames(site).map((name) => { const html = read(join(home, name, "index.html")); const found = html.match(TITLE); return { name, title: found ? untag(found[1]) : name, blurb: tag(html, "description"), shelf: tag(html, "shelf"), order: Number(tag(html, "order")) || 0, reads: [], }; }); } const MENTION = (name: string) => new RegExp(`demos/${name}/`); function readers(list: Card[], pages: { name: string; href: string; md: string }[]) { for (const card of list) { if (!card.name) continue; const seen = MENTION(card.name); card.reads = pages.filter((p) => seen.test(p.md)).map((p) => ({ name: p.name, href: p.href })); } } export function shelved(list: Card[]): Bay[] { return SHELVES.map((one) => ({ key: one.key, name: one.title, nodes: list .filter((d) => d.shelf === one.key) .sort((a, b) => a.order - b.order || a.title.localeCompare(b.title)) .map((d) => ({ name: d.title, href: demoRoute(d.name), text: d.blurb })), })).filter((one) => one.nodes.length); } export const demoTree = (site: Site) => shelved(cards(site)); function demoGroup(site: Site): Route { const home = site.input("demos").path; const list = cards(site); const inputs = ["demos", "lib", "pkg", "ui"].flatMap((one) => site.input(one).files); return { route: "/demos/", kind: "demos", name: "Demos", data: list, source: home, inputs, urls: list.map((d) => ({ route: demoRoute(d.name), name: d.title, source: join(home, d.name) })), }; } function seo(source: string, card: Card, nav: string, image: Picture) { const html = source .replace(/<html([^>]*)>/, (_, attrs: string) => `<html${attrs.replace(/ data-prefix="[^"]*"/, "")} data-prefix="${SITE.prefix}">`) .replace(/<script data-boot>[\s\S]*?<\/script>\n?/, "") .replace(OWN, ""); const route = demoRoute(card.name); const found = html.match(TITLE); const name = found ? untag(found[1]) : card.title; const tags = meta(route, name, card.blurb || name, "website", image); const reads = `<script type="application/json" id="${SITE.prefix}reads">${JSON.stringify(card.reads).replace(/</g, "\\u003c")}</script>`; const block = `${BOOT}\n<title>${escape(brand(name))}\n${tags}\n${nav}\n${reads}\n`; const page = found ? html.replace(found[0], block) : html.replace("", `\n${block}`); return page.replace("", `${TINT}\n`); } const widgetFiles = (site: Site) => site.input("demos").files.filter((f) => f.endsWith("/widget.jsx")); async function demos(site: Site, route: Route): Promise { const list = route.data as Card[]; const home = site.input("demos").path; const built = await Bun.build({ entrypoints: [...list.map((d) => join(home, d.name, "index.html")), ...widgetFiles(site)], root: org, splitting: true, minify: true, define: { "process.env.NODE_ENV": '"production"' }, naming: { chunk: "lib-[hash].[ext]", asset: "[name]-[hash].[ext]" }, }); if (!built.success) throw new Error(`site: the demos failed to bundle\n${built.logs.join("\n")}`); const shells = new Map(list.map((d) => [`${demoRoute(d.name).slice(1)}index.html`, d])); const json = JSON.stringify(shelved(list)).replace(/${json}`; const out: Output[] = [{ path: "demos/tree.json", bytes: json, type: "application/json" }]; const fig = press(site, out); for (const d of list) if (d.name) fig(`demo-${d.name}`, "/demos/"); for (const item of built.outputs) { const path = item.path.replace(/^\.\//, ""); const card = shells.get(path); const image = card ? picture(site, card.name ? `demo-${card.name}` : "site-demos", demoRoute(card.name), fig) : OG; out.push({ path, bytes: card ? seo(await item.text(), card, nav, image) : new Uint8Array(await item.arrayBuffer()) }); } return out; } /* PAPERS */ type Paper = { slug: string; name: string; lead: string; date: string; revised: string; figure: string; shelf: string; body: string; file: string }; function papers(site: Site): Paper[] { const home = site.input("papers"); return home.files .map((file) => { const slug = file.slice(home.path.length + 1, -3); const { data, body } = front(read(file)); return { slug, name: data.title ?? slug, lead: data.lead ?? summary(body), date: data.date ?? "", revised: data.revised ?? "", figure: data.figure || `paper-${slug}`, shelf: data.shelf ?? "", body, file }; }) .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : a.slug.localeCompare(b.slug))); } const marked = (p: Paper) => [p.date, p.revised && `revised ${p.revised}`].filter(Boolean) as string[]; function written(site: Site, route: Route): Output[] { const p = route.data as Paper; const out: Output[] = []; const fig = press(site, out); const when = [p.date && `First published ${p.date}`, p.revised && `revised ${p.revised}`].filter(Boolean).join(", "); const avatar = pic(fig, p.figure, route.route, p.name, "", "avatar"); const shelf = p.shelf ? `\n

The LaTeX and PDF of the first edition, on the shelf

` : ""; const plate = `
${avatar}

${escape(p.name)}

${escape(AUTHOR)}

${escape(when)}

${shelf}`; const body = `${plate}\n${md(p.body, { math, link: links(site, p.file, out) })}`; const data = { "@context": "https://schema.org", "@type": "ScholarlyArticle", headline: p.name, description: p.lead, url: root + route.route, image: `${root}/figures/${p.figure}-dark.png`, author: { "@type": "Organization", name: AUTHOR }, datePublished: p.date || undefined, dateModified: p.revised || p.date || undefined, license: "https://creativecommons.org/licenses/by/4.0/", }; out.push({ path: `papers/${p.slug}/index.html`, bytes: shell(site, { route: route.route, name: p.name, description: p.lead, body, data, image: picture(site, p.figure, route.route, fig) }) }); return out; } type Lane = { slug: string; blurb: string; name: string; md: string; published: string; revised: string; pdf: boolean }; let SHELF = ""; function lanes(): Lane[] { const readme = read(join(SHELF, "README.md")); const order = [...readme.matchAll(LIST)].map((m) => ({ slug: m[1], blurb: plain(m[2]) })); const found = readdirSync(SHELF).filter( (d) => d !== "template" && existsSync(join(SHELF, d, "README.md")) && existsSync(join(SHELF, d, "paper.tex")), ); const known = new Set(order.map((o) => o.slug)); const list = order.filter((o) => found.includes(o.slug)).concat(found.filter((l) => !known.has(l)).map((slug) => ({ slug, blurb: "" }))); return list.map(({ slug, blurb }) => { const lane = join(SHELF, slug); const doc = read(join(lane, "README.md")); const tex = read(join(lane, "paper.tex")); const date = tex.match(/\\date\{First published (\d{4}-\d{2}-\d{2})(?:, revised (\d{4}-\d{2}-\d{2}))?\}/); return { slug, blurb, name: title(doc) || slug, md: doc, published: date?.[1] ?? "", revised: date?.[2] ?? "", pdf: existsSync(join(lane, "paper.pdf")), }; }); } const dated = (p: Lane) => p.revised || p.published; const stamps = (p: Lane) => [p.published, p.revised && `revised ${p.revised}`].filter(Boolean) as string[]; function paper(site: Site, route: Route): Output[] { const p = route.data as Lane; const out: Output[] = []; const fig = press(site, out); const lane = join(SHELF, p.slug); const at = `papers/${p.slug}`; if (p.pdf) out.push({ path: `${at}/paper.pdf`, bytes: bytes(join(lane, "paper.pdf")) }); out.push({ path: `${at}/paper.tex`, bytes: bytes(join(lane, "paper.tex")) }); for (const file of walk(join(lane, "figures"))) out.push({ path: `${at}/figures/${file.slice(join(lane, "figures").length + 1)}`, bytes: bytes(file) }); const when = [p.published && `First published ${p.published}`, p.revised && `revised ${p.revised}`].filter(Boolean).join(", "); const files = [p.pdf && `PDF`, `TeX`].filter(Boolean).join(" · "); const avatar = pic(fig, `paper-${p.slug}`, route.route, p.name, "", "avatar"); const plate = `
${avatar}

${escape(p.name)}

${escape(AUTHOR)}

${escape(when)}

\n

${files}

`; const body = `${plate}\n${md(p.md.replace(/^# .+\n/, "").replace(AVATAR, ""), { math, link: links(site, join(lane, "README.md"), out) })}`; const data = { "@context": "https://schema.org", "@type": "ScholarlyArticle", headline: p.name, description: p.blurb || summary(p.md), url: root + route.route, image: `${root}/figures/paper-${p.slug}-dark.png`, author: { "@type": "Organization", name: AUTHOR }, datePublished: p.published || undefined, dateModified: dated(p) || undefined, license: "https://creativecommons.org/licenses/by/4.0/", }; out.push({ path: `${at}/index.html`, bytes: shell(site, { route: route.route, name: p.name, description: p.blurb || summary(p.md), body, data, image: picture(site, `paper-${p.slug}`, route.route, fig) }) }); return out; } function paperIndex(site: Site, route: Route): Output[] { const out: Output[] = []; const fig = press(site, out); const lead = "Write-ups of MrlyMath, each claim a theorem with a proof, a computational fact with its exact finite domain, or a conjecture labelled as one; markdown that prints as a paper."; const fresh = new Set(DRESS.papers.map((p) => `/papers/${p.slug}/`)); const nodes = wear(site, "/papers/", fig, route.route); const shelfNote = `

The shelf

The first editions, in LaTeX with a PDF each, deprecated: every paper is rewritten here in turn and the shelf is never edited.

${grid(nodes.filter((n) => !fresh.has(n.href ?? "")))}
`; const body = `

Papers

${escape(lead)}

\n${grid(nodes.filter((n) => fresh.has(n.href ?? "")))}\n${nodes.some((n) => !fresh.has(n.href ?? "")) ? shelfNote : ""}`; out.push({ path: "papers/index.html", bytes: shell(site, { route: route.route, name: "Papers", description: lead, body, type: "website", wide: true, bare: true, image: picture(site, "site-papers", route.route, fig) }) }); return out; } /* RESEARCH */ type Note = { file: string; name: string; md: string; home: boolean; topic: boolean; title: string; lead: string; figure: string }; const SHARED = new Set(["REFS"]); const ROW = /(?:)?([0-9a-f]{8})(?:<\/code>)?<\/td>/g; function anchored(html: string) { const seen = new Set(); return html.replace(ROW, (whole, id: string) => { if (seen.has(id)) return whole; seen.add(id); return `${id}`; }); } function notes(site: Site): Note[] { const dir = site.input("research"); if (dir.missing) { console.warn(`site: no research tree at ${relative(org, dir.path)}, research skipped`); return []; } const shared = dir.files.map((source) => { const file = source.slice(dir.path.length + 1); const name = file.slice(0, -3); const md = read(source); const figure = SHARED.has(name) ? "research-index" : `research-${name}`; return { file, name, md, home: file === "README.md", topic: false, title: title(md) || name, lead: summary(md), figure }; }); const home = site.input("notes"); const topics = home.files.map((source) => { const name = source.slice(home.path.length + 1, -3); const { data, body } = front(read(source)); return { file: `notes/${name}.md`, name, md: body, home: false, topic: true, title: data.title ?? name, lead: data.lead ?? summary(body), figure: data.figure || `research-${name}` }; }); return [...shared, ...topics].sort((a, b) => (a.home ? -1 : b.home ? 1 : a.name.localeCompare(b.name))); } function researchIndex(site: Site, route: Route): Output[] { const { note: n } = route.data as { note: Note; cards: string[][] }; const out: Output[] = []; const fig = press(site, out); out.push({ path: `research/${n.file}`, bytes: n.md }); const lead = summary(n.md); const prose = md(n.md.replace(/^# .+\n/, ""), { math, link: links(site, join(site.input("research").path, n.file), out) }); const body = `

Research

${escape(lead)}

\n${grid(wear(site, "/research/", fig, route.route))}\n
${prose}
`; out.push({ path: "research/index.html", bytes: shell(site, { route: route.route, name: "Research", description: lead, body, type: "website", wide: true, bare: true, image: picture(site, "site-research", route.route, fig) }) }); return out; } function note(site: Site, route: Route): Output[] { const n = route.data as Note; const out: Output[] = []; const fig = press(site, out); out.push({ path: `research/${n.file}`, bytes: n.md }); const name = n.title; const lead = n.lead; const head = n.topic ? `

${escape(name)}

\n` : ""; const used = new Set(); const prose = md(n.md, { math, link: links(site, join(site.input("research").path, n.file), out), widget: widgets(site, used) }); const body = `${hero(fig, n.figure, route.route, name)}\n${head}${n.name === "sequences" ? anchored(prose) : prose}`; const data = { "@context": "https://schema.org", "@type": "Article", headline: name, description: lead, url: root + route.route, image: `${root}/figures/${n.figure}-dark.png`, author: { "@type": "Organization", name: AUTHOR }, }; const at = n.home ? "research/index.html" : `research/${n.name}/index.html`; const scripts = [...used].sort().map((name) => `/demos/${name}/widget.js`); out.push({ path: at, bytes: shell(site, { route: route.route, name, description: lead, body, type: n.home ? "website" : "article", data, image: picture(site, n.figure, route.route, fig), scripts }) }); return out; } /* CLAIMS */ type Claim = { slug: string; title: string; md: string; file: string }; const TAGS = ["Proved", "Verified", "Conjecture", "Refuted"]; const LINE = /
  • (\d{4}-\d{2}-\d{2}) \[(Proved|Verified|Conjecture|Refuted)\] /g; function claims(site: Site): Claim[] { const home = site.input("claims"); return home.files .map((file) => { const slug = file.slice(home.path.length + 1, -3); const md = read(file); return { slug, title: title(md) || slug, md, file }; }) .sort((a, b) => a.title.localeCompare(b.title)); } function discoveries(site: Site, route: Route): Output[] { const list = route.data as Claim[]; const out: Output[] = []; const fig = press(site, out); const lead = "Every claim of the tree on one dated, tagged line with its witness, one section per topic, filtered by tag, topic and date."; const counts = new Map(TAGS.map((tag) => [tag, 0])); const sections = list.map((c) => { const html = md(c.md.replace(/^# .+\n/, ""), { math, link: links(site, c.file, out) }).replace(LINE, (_, date: string, tag: string) => { counts.set(tag, (counts.get(tag) ?? 0) + 1); return `
  • ${tag} `; }); return `

    ${escape(c.title)}

    ${html}
    `; }); const total = [...counts.values()].reduce((a, b) => a + b, 0); const chips = ["", ...TAGS].map((tag) => ``).join(""); const topics = ``; const since = ``; const bar = `
    ${chips}${topics}${since}${total} claims
    `; const script = ``; const body = `${hero(fig, "research-index", route.route, "Discoveries")}\n

    Discoveries

    ${escape(lead)}

    \n${bar}\n${sections.join("\n")}\n${script}`; const data = { "@context": "https://schema.org", "@type": "Article", headline: "Discoveries", description: lead, url: root + route.route, image: `${root}/figures/research-index-dark.png`, author: { "@type": "Organization", name: AUTHOR }, }; out.push({ path: "research/discoveries/index.html", bytes: shell(site, { route: route.route, name: "Discoveries", description: lead, body, data, image: picture(site, "research-index", route.route, fig) }) }); return out; } /* BLOG */ type Post = { slug: string; name: string; date: string; lead: string; figure: string; body: string }; function posts(): Post[] { if (!existsSync(BLOG)) return []; return readdirSync(BLOG) .filter((f) => f.endsWith(".md")) .map((file) => { const { data, body } = front(read(join(BLOG, file))); const slug = file.slice(0, -3); return { slug, name: data.title ?? slug, date: data.date ?? "", lead: data.lead ?? summary(body), figure: data.figure || `blog-${slug}`, body }; }) .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : a.slug < b.slug ? 1 : -1)); } function post(site: Site, route: Route): Output[] { const p = route.data as Post; const out: Output[] = []; const fig = press(site, out); const head = `${hero(fig, p.figure, route.route, p.name)}\n

    ${escape(p.name)}

    ${escape(p.date)} · ${escape(AUTHOR)}

    `; const data = { "@context": "https://schema.org", "@type": "BlogPosting", headline: p.name, description: p.lead, url: root + route.route, image: `${root}/figures/${p.figure}-dark.png`, author: { "@type": "Organization", name: AUTHOR }, datePublished: p.date || undefined, }; out.push({ path: `blog/${p.slug}/index.html`, bytes: shell(site, { route: route.route, name: p.name, description: p.lead, body: `${head}\n${md(p.body, { math, link: links(site, postFile(p.slug), out) })}`, data, image: picture(site, p.figure, route.route, fig) }) }); return out; } function blogIndex(site: Site, route: Route): Output[] { const out: Output[] = []; const fig = press(site, out); const lead = "Notes on what lands on this site and in the crates behind it."; const body = `

    Blog

    ${escape(lead)}

    \n${grid(wear(site, "/blog/", fig, route.route))}`; out.push({ path: "blog/index.html", bytes: shell(site, { route: route.route, name: "Blog", description: lead, body, type: "website", wide: true, bare: true }) }); return out; } /* PAGES */ function page(site: Site, route: Route): Output[] { const source = route.source as string; const slug = route.route.slice(1, -1); const { data, body } = front(read(source)); const name = data.title ?? slug; const lead = data.lead ?? summary(body); const out: Output[] = []; const fig = press(site, out); const open = data.figure ? `${hero(fig, data.figure, route.route, name)}\n` : ""; const head = `

    ${escape(name)}

    ${escape(lead)}

    `; const act = data.button && data.link ? `\n

    ${escape(data.button)}

    ` : ""; const own = data.figure || FIXED[route.route]; const image = own ? picture(site, own, route.route, fig) : OG; const html = shell(site, { route: route.route, name, description: lead, body: `${open}${head}\n${md(body, { math, link: links(site, source, out) })}${act}`, type: "website", image }); out.push({ path: `${slug}/index.html`, bytes: html }); return out; } type Dress = { lanes: Lane[]; papers: Paper[]; notes: Note[]; posts: Post[]; demos: Card[]; wiki: Entry[] }; type Mark = { figure: string; text: string; dates?: string[] }; let DRESS: Dress = { lanes: [], papers: [], notes: [], posts: [], demos: [], wiki: [] }; const FIXED: Record = { "/": "site-home", "/wiki/": "site-wiki", "/book/": "site-wiki", "/tools/": "site-tools", "/math/": "site-math", "/git/": "site-code", "/about/": "site-icon", "/contact/": "site-contact", "/donate/": "site-donate", }; function marks(data: Dress): Map { const map = new Map(); for (const d of data.demos) if (d.name) map.set(demoRoute(d.name), { figure: `demo-${d.name}`, text: d.blurb }); for (const p of data.papers) map.set(`/papers/${p.slug}/`, { figure: p.figure, text: p.lead, dates: marked(p) }); for (const p of data.lanes) map.set(`/papers/${p.slug}/`, { figure: `paper-${p.slug}`, text: p.blurb, dates: stamps(p) }); for (const n of data.notes) { if (n.home) continue; map.set(`/research/${n.name}/`, { figure: n.figure, text: n.lead }); } for (const p of data.posts) map.set(`/blog/${p.slug}/`, { figure: p.figure, text: p.lead, dates: [p.date] }); for (const e of data.wiki) map.set(wikiRoute(e.slug), { figure: e.figure, text: e.lead }); for (const [href, figure] of Object.entries(FIXED)) map.set(href, { figure, text: "" }); return map; } function dress(nodes: Node[], map: Map, fig: Fig, route: string): Node[] { return nodes.map((node) => { if (node.nodes?.length) return { ...node, nodes: dress(node.nodes, map, fig, route) }; const mark = node.href ? map.get(node.href) : undefined; if (!mark) return node; return { ...node, figure: fig(mark.figure, route), text: mark.text || undefined, dates: mark.dates }; }); } let NAV: Node[] = []; const wear = (site: Site, href: string, fig: Fig, route: string) => dress(NAV.find((node) => node.href === href)?.nodes ?? [], marks(DRESS), fig, route); function elsewhere() { const links = SITE.socials.map((s) => `
  • ${escape(s.name)}
  • `).join(""); const mail = `
  • ${escape(SITE.contact)}
  • `; return `

    Elsewhere

      ${links}${mail}
    `; } function menu(site: Site, route: Route): Output[] { const lead = "Every page on mrly.net."; const out: Output[] = []; const fig = press(site, out); const nav = dress(NAV, marks(route.data as Dress), fig, route.route); const list = renderToStaticMarkup(h(Menu, { tree: nav })); const body = `

    ${WORD}

    ${escape(lead)}

    \n${list}\n${elsewhere()}`; out.push({ path: "menu/index.html", bytes: shell(site, { route: route.route, name: "Menu", description: lead, body, type: "website", wide: true, bare: true }) }); return out; } function cart(site: Site, route: Route): Output[] { const lead = "Coming soon."; const body = `

    Cart

    ${escape(lead)}

    \n

    mrly.net has no shop yet.

    \n

    Back to the home page.

    `; return [{ path: "cart/index.html", bytes: shell(site, { route: route.route, name: "Cart", description: lead, body, type: "website" }) }]; } const MISSION = SITE.tagline; const DOORS = [ { name: "Wiki", href: "/wiki/", figure: "site-wiki", text: "One concept per page, in the order you need them, for a reader with school mathematics." }, { name: "Demos", href: "/demos/", figure: "site-demos", text: "Browser pages that draw a design and the numbers around it, live." }, { name: "Discoveries", href: "/research/discoveries/", figure: "research-index", text: "Every claim of the tree on one dated, tagged line with its witness." }, { name: "Papers", href: "/papers/", figure: "site-papers", text: "Write-ups that print as papers, every claim tagged and every number generated." }, { name: "Research", href: "/research/", figure: "site-research", text: "The working notes behind the demos and the papers, one page per idea." }, ]; type Newest = { date: string; tag: string; text: string; slug: string; file: string }; function newest(list: Claim[]): Newest | null { let best: Newest | null = null; for (const c of list) { for (const line of c.md.split("\n")) { const hit = line.match(/^- (\d{4}-\d{2}-\d{2}) \[(Proved|Verified|Conjecture|Refuted)\] (.+)$/); if (hit && (!best || hit[1]! > best.date)) best = { date: hit[1]!, tag: hit[2]!, text: hit[3]!, slug: c.slug, file: c.file }; } } return best; } function home(site: Site, route: Route): Output[] { const { lanes: list, papers: fresh, posts: posted, claim } = route.data as { lanes: Lane[]; papers: Paper[]; posts: Post[]; claim: Newest | null }; const out: Output[] = []; const fig = press(site, out); const latestClaim = claim ? `

    Newest claim

    ${claim.tag} ${inline(claim.text, { math, link: links(site, claim.file, out) })}

    Every claim, dated and tagged

    ` : ""; const latest = [...fresh.map((p) => ({ name: p.name, slug: p.slug, at: p.revised || p.date })), ...list.map((p) => ({ name: p.name, slug: p.slug, at: dated(p) }))] .map((p, i) => ({ p, i })) .sort((a, b) => (a.p.at < b.p.at ? 1 : a.p.at > b.p.at ? -1 : a.i - b.i)) .slice(0, 3) .map(({ p }) => ({ name: p.name, href: `/papers/${p.slug}/` })); const doors = DOORS.map((d) => ({ name: d.name, href: d.href, figure: fig(d.figure, "/"), text: d.text })); const first = posted[0]; const news = first ? `

    From the blog

    ${escape(first.name)} · ${escape(first.date)}

    ${escape(first.lead)}

    ` : ""; const what = `

    What is MrlyMath

    A design is a rule on the corners of a cube: a code says which of the eight corners are filled. The Kronecker product grows that rule into itself, level by level, and the object it converges to is a fractal - the Sierpinski carpet and the Menger sponge are two of them.

    Everything else is measurement. Count the fills, the voids and the exposed faces; cut the solid with a plane; join the filled cells into a graph and read its spectrum; collect the integer sequences the counts write down. The Rust crates do the arithmetic, the browser only paints, and a claim is either proved, checked over a stated finite domain, or labelled a conjecture.

    `; const body = `
    ${hero(fig, "site-home", "/", SITE.title)}

    ${WORD}

    ${escape(MISSION)}

    Five doors

    ${grid(doors)}
    ${latestClaim}

    Latest papers

    ${grid(dress(latest, marks(DRESS), fig, "/"))}
    ${news} ${what}
    `; out.push({ path: "index.html", bytes: shell(site, { route: "/", name: SITE.title, description: MISSION, body, type: "website", wide: true, bare: true, image: picture(site, "site-home", "/", fig) }) }); return out; } function missing(site: Site, route: Route): Output[] { const doors = DOORS.map((d) => `${d.name}`); const body = `

    Nothing here

    That page does not exist. The Menu lists every page on this site, and the doors are ${doors.slice(0, -1).join(", ")} and ${doors[doors.length - 1]}.

    `; return [{ path: "404.html", bytes: shell(site, { route: route.route, name: "Nothing here", description: "That page does not exist.", body, type: "website", bare: true }) }]; } /* THIN */ const THIN: Record = { "/tools/": { name: "Tools", figure: "site-tools", lead: "A canvas for mrly objects: tiles, slices, rings and roulettes on one sheet, the perforator first.", note: `The first tool is on its way. The demos already draw every object it will place, and the standard names them.`, }, }; function thin(site: Site, route: Route): Output[] { const t = THIN[route.route]!; const out: Output[] = []; const fig = press(site, out); const slug = route.route.slice(1, -1); const body = `${hero(fig, t.figure, route.route, t.name)}\n

    ${t.name}

    ${escape(t.lead)}

    \n

    ${t.note}

    `; out.push({ path: `${slug}/index.html`, bytes: shell(site, { route: route.route, name: t.name, description: t.lead, body, type: "website", image: picture(site, t.figure, route.route, fig) }) }); return out; } /* WIKI */ type Entry = { slug: string; name: string; lead: string; figure: string; needs: string[]; body: string; file: string }; type Concept = Omit & { before: { slug: string; name: string }[]; after: { slug: string; name: string }[] }; const WIKI = "One concept per page, in the order you need them, for a reader with school mathematics."; function ordered(list: Entry[]): Entry[] { const byslug = new Map(list.map((e) => [e.slug, e])); const done = new Set(); const out: Entry[] = []; const pending = [...list].sort((a, b) => a.slug.localeCompare(b.slug)); while (pending.length) { const at = pending.findIndex((e) => e.needs.every((need) => done.has(need))); if (at < 0) throw new Error(`site: the wiki prerequisites run in a circle through ${pending.map((e) => e.slug).join(", ")}`); const [next] = pending.splice(at, 1); done.add(next!.slug); out.push(byslug.get(next!.slug)!); } const deep = depths(out); return out.sort((a, b) => deep.get(a.slug)! - deep.get(b.slug)! || a.slug.localeCompare(b.slug)); } function wiki(site: Site): Entry[] { const home = site.input("wiki"); if (home.missing) return []; const list = home.files.map((file) => { const slug = file.slice(home.path.length + 1, -3); const { data, body } = front(read(file)); const needs = (data.prerequisites ?? "").split(",").map((s: string) => s.trim()).filter(Boolean); return { slug, name: data.title ?? slug, lead: data.lead ?? summary(body), figure: data.figure || `wiki-${slug}`, needs, body, file }; }); const known = new Set(list.map((e) => e.slug)); for (const e of list) for (const need of e.needs) if (!known.has(need)) throw new Error(`site: wiki/${e.slug}.md needs ${need}, and wiki/${need}.md does not exist`); return ordered(list); } const wikiRoute = (slug: string) => `/wiki/${slug}/`; function concepts(list: Entry[]): [Concept, string][] { const name = (slug: string) => ({ slug, name: list.find((e) => e.slug === slug)?.name ?? slug }); return list.map(({ body: _, file, ...e }) => [{ ...e, before: e.needs.map(name), after: list.filter((o) => o.needs.includes(e.slug)).map((o) => name(o.slug)) }, file]); } const cite = (rows: { slug: string; name: string }[]) => rows.map((r) => `${escape(r.name)}`).join(", "); /* WIDGETS */ const EMBEDS = /^!\[[^\]]*\]\(demos\/([a-z0-9-]+)\/[a-z0-9-]+\)$/gm; const VIEW = (view: string) => new RegExp(`^export (?:function|const) ${view}\\b`, "m"); const widgetFile = (site: Site, name: string) => join(site.input("demos").path, name, "widget.jsx"); const embeds = (site: Site, body: string) => [...body.matchAll(EMBEDS)].map((m) => widgetFile(site, m[1]!)); function widgets(site: Site, used: Set) { return (name: string, view: string, caption: string) => { const file = widgetFile(site, name); if (!existsSync(file)) throw new Error(`site: demos/${name}/ has no widget.jsx to embed`); if (!VIEW(view).test(read(file))) throw new Error(`site: demos/${name}/widget.jsx exports no view named ${view}`); used.add(name); return `
    ${caption}
    `; }; } function concept(site: Site, route: Route): Output[] { const c = route.data as Concept; const file = route.source as string; const out: Output[] = []; const fig = press(site, out); const used = new Set(); const before = c.before.length ? `\n

    Before this: ${cite(c.before)}.

    ` : ""; const after = c.after.length ? `\n

    Read next

    ${cite(c.after)}.

    ` : ""; const head = `

    ${escape(c.name)}

    ${escape(c.lead)}

    `; const prose = md(front(read(file)).body, { math, link: links(site, file, out), widget: widgets(site, used) }); const body = `${hero(fig, c.figure, route.route, c.name)}\n${head}${before}\n${prose}${after}`; const data = { "@context": "https://schema.org", "@type": "Article", headline: c.name, description: c.lead, url: root + route.route, image: `${root}/figures/${c.figure}-dark.png`, author: { "@type": "Organization", name: AUTHOR }, }; const scripts = [...used].sort().map((name) => `/demos/${name}/widget.js`); out.push({ path: `wiki/${c.slug}/index.html`, bytes: shell(site, { route: route.route, name: c.name, description: c.lead, body, data, image: picture(site, c.figure, route.route, fig), scripts }) }); return out; } type Leaf_ = { slug: string; name: string; lead: string; figure: string; needs: string[] }; const STEPS = ["Start here", "One step in", "Two steps in", "Three steps in", "Four steps in", "Five steps in", "Six steps in"]; const step = (n: number) => STEPS[n] ?? `${n} steps in`; function depths(list: Leaf_[]): Map { const deep = new Map(); for (const e of list) deep.set(e.slug, e.needs.length ? 1 + Math.max(...e.needs.map((need) => deep.get(need) ?? 0)) : 0); return deep; } function tile(fig: Fig, route: string, e: Leaf_, names: Map) { const pair = fig(e.figure, route); const needs = e.needs.length ? `

    After ${e.needs.map((need) => `${escape(names.get(need) ?? need)}`).join(", ")}

    ` : ""; const img = SIDES.map((side) => ``).join(""); return ``; } function wikiIndex(site: Site, route: Route): Output[] { const list = (route.data as [string, string, string, string, string[]][]).map(([slug, name, lead, figure, needs]) => ({ slug, name, lead, figure, needs })); const out: Output[] = []; const fig = press(site, out); const names = new Map(list.map((e) => [e.slug, e.name])); const deep = depths(list); const rows = new Map(); for (const e of list) rows.set(deep.get(e.slug)!, [...(rows.get(deep.get(e.slug)!) ?? []), e]); const sections = [...rows.keys()].sort((a, b) => a - b).map((n) => `

    ${step(n)}

    `); const book = list.length ? `

    Every page needs only the pages above it, and the book reads them all in that order on one page.

    ` : ""; const body = `${hero(fig, "site-wiki", route.route, "Wiki")}\n

    Wiki

    ${escape(WIKI)}

    ${book}
    \n${sections.join("\n")}`; out.push({ path: "wiki/index.html", bytes: shell(site, { route: route.route, name: "Wiki", description: WIKI, body, type: "website", wide: true, bare: true, image: picture(site, "site-wiki", route.route, fig) }) }); return out; } /* BOOK */ const BOOK = "The wiki read in prerequisite order as one page: every concept once, each after the ones it needs."; function book(site: Site, route: Route): Output[] { const list = (route.data as [string, string, string, string, string[]][]).map(([slug, name, lead, figure, needs]) => ({ slug, name, lead, figure, needs })); const home = site.input("wiki").path; const out: Output[] = []; const fig = press(site, out); const used = new Set(); const names = new Map(list.map((e) => [e.slug, e.name])); const chapters = list.map((e) => { const file = join(home, `${e.slug}.md`); const prose = md(front(read(file)).body, { math, link: links(site, file, out), widget: widgets(site, used) }).replace(/ `After ${e.needs.map((need) => `${escape(names.get(need) ?? need)}`).join(", ")}.

    ` : ""; return `
    ${hero(fig, e.figure, route.route, e.name)}\n

    ${escape(e.name)}

    ${escape(e.lead)}

    ${needs}\n${prose}\n

    This page on its own

    `; }); const body = `

    The book

    ${escape(BOOK)}

    \n${chapters.join("\n")}`; const data = { "@context": "https://schema.org", "@type": "Book", name: "The book", description: BOOK, url: root + route.route, image: `${root}/figures/site-wiki-dark.png`, author: { "@type": "Organization", name: AUTHOR }, }; const scripts = [...used].sort().map((name) => `/demos/${name}/widget.js`); out.push({ path: "book/index.html", bytes: shell(site, { route: route.route, name: "The book", description: BOOK, body, data, image: picture(site, "site-wiki", route.route, fig), scripts }) }); return out; } /* MATH */ const STANDARD = "A design is one string: one JSON object per named thing, and every other name a view cut from it."; function standard(site: Site, route: Route): Output[] { const out: Output[] = []; const fig = press(site, out); const file = route.source as string; const body = `${hero(fig, "site-math", route.route, "MrlyMath")}\n

    MrlyMath

    ${escape(STANDARD)}

    \n${md(read(file).replace(/^# .+\n/, ""), { math, link: links(site, file, out) })}`; out.push({ path: "math/index.html", bytes: shell(site, { route: route.route, name: "Math", description: STANDARD, body, type: "website", image: picture(site, "site-math", route.route, fig) }) }); return out; } /* COLLECT */ const counts = { papers: 0, research: 0, blog: 0, demos: 0, wiki: 0 }; async function collect(site: Site) { SHELF = await shelf(); const paperList = papers(site); const fresh = new Set(paperList.map((p) => p.slug)); const laneList = lanes().filter((p) => !fresh.has(p.slug)); const noteList = notes(site); const postList = posts(); const group = demoGroup(site); const demoList = group.data as Card[]; const claimList = claims(site); const wikiList = wiki(site); readers(demoList, [ ...noteList.filter((n) => !n.home).map((n) => ({ name: n.title, href: `/research/${n.name}/`, md: n.md })), ...paperList.map((p) => ({ name: p.name, href: `/papers/${p.slug}/`, md: p.body })), ...wikiList.map((e) => ({ name: e.name, href: wikiRoute(e.slug), md: e.body })), ]); counts.papers = laneList.length + paperList.length; counts.research = noteList.length; counts.blog = postList.length; counts.demos = demoList.length; counts.wiki = wikiList.length; const lists = { wiki: wikiList.map((e) => ({ name: e.name, href: wikiRoute(e.slug) })), demos: shelved(demoList), papers: [...paperList, ...laneList].map((p) => ({ name: p.name, href: `/papers/${p.slug}/` })), research: [...(claimList.length ? [{ name: "Discoveries", href: "/research/discoveries/" }] : []), ...noteList.filter((n) => !n.home).map((n) => ({ name: n.title, href: `/research/${n.name}/` }))], blog: postList.map((p) => ({ name: p.name, href: `/blog/${p.slug}/` })), }; NAV = tree(lists); const nav = sidebar(lists); const routes: Route[] = []; const readme = join(org, "README.md"); routes.push({ route: "/", kind: "home", name: SITE.title, data: { lanes: laneList, papers: paperList, posts: postList, claim: newest(claimList) }, source: readme, inputs: [readme], }); for (const route of Object.keys(THIN)) routes.push({ route, kind: "thin", name: THIN[route]!.name }); const shelfOfWiki = site.input("wiki"); routes.push({ route: "/wiki/", kind: "wiki", name: "Wiki", data: wikiList.map((e) => [e.slug, e.name, e.lead, e.figure, e.needs]), source: shelfOfWiki.path, inputs: shelfOfWiki.missing ? [] : wikiList.map((e) => e.file) }); for (const [c, file] of concepts(wikiList)) { routes.push({ route: wikiRoute(c.slug), kind: "concept", name: c.name, data: c, source: file, inputs: [file, ...embeds(site, read(file))] }); } if (wikiList.length) { const pages = wikiList.map((e) => [e.slug, e.name, e.lead, e.figure, e.needs]); routes.push({ route: "/book/", kind: "book", name: "The book", data: pages, inputs: wikiList.flatMap((e) => [e.file, ...embeds(site, e.body)]) }); } const names = site.input("names"); if (!names.missing) routes.push({ route: "/math/", kind: "math", name: "Math", source: names.files[0]!, inputs: names.files }); routes.push(group); if (laneList.length || paperList.length) { const index = join(SHELF, "README.md"); routes.push({ route: "/papers/", kind: "papers", name: "Papers", data: { lanes: laneList, papers: paperList.map((p) => [p.slug, p.name, p.lead, p.date, p.revised, p.figure]) }, source: index, inputs: [index, ...paperList.map((p) => p.file)] }); for (const p of paperList) { routes.push({ route: `/papers/${p.slug}/`, kind: "written", name: p.name, data: p, source: p.file, inputs: [p.file] }); } for (const p of laneList) { const lane = join(SHELF, p.slug); routes.push({ route: `/papers/${p.slug}/`, kind: "paper", name: p.name, data: p, source: lane, inputs: [lane] }); } } const notesHome = site.input("research").path; if (claimList.length) { routes.push({ route: "/research/discoveries/", kind: "discoveries", name: "Discoveries", data: claimList.map((c) => c.slug), source: site.input("claims").path, inputs: claimList.map((c) => c.file), }); } for (const n of noteList) { const source = join(notesHome, n.file); routes.push({ route: n.home ? "/research/" : `/research/${n.name}/`, kind: n.home ? "research" : "note", name: n.home ? "Research" : n.title, data: n.home ? { note: n, cards: noteList.map((one) => [one.name, one.title, one.lead, one.figure]) } : n, source, inputs: [source, ...embeds(site, n.md)], }); } if (postList.length) { const files = postList.map((p) => postFile(p.slug)); routes.push({ route: "/blog/", kind: "blog", name: "Blog", data: postList, source: BLOG, inputs: files }); for (const p of postList) { const source = postFile(p.slug); routes.push({ route: `/blog/${p.slug}/`, kind: "post", name: p.name, data: p, source, inputs: [source] }); } } const written = site.input("pages"); for (const source of written.files) { const slug = source.slice(written.path.length + 1, -3); const { data } = front(read(source)); routes.push({ route: `/${slug}/`, kind: "page", name: data.title ?? slug, source, inputs: [source] }); } DRESS = { lanes: laneList, papers: paperList, notes: noteList, posts: postList, demos: demoList, wiki: wikiList }; routes.push({ route: "/menu/", kind: "menu", name: "Menu", data: DRESS, }); routes.push({ route: "/cart/", kind: "cart", name: "Cart", hidden: true }); routes.push({ route: "/404.html", kind: "missing", name: "Nothing here", hidden: true }); return { routes, nav }; } /* RENDER */ const KINDS: Record Output[] | Promise> = { home, demos, papers: paperIndex, paper, written, note, research: researchIndex, discoveries, blog: blogIndex, post, page, menu, cart, missing, thin, math: standard, wiki: wikiIndex, concept, book, }; function draw(site: Site, route: Route) { const fn = KINDS[route.kind ?? ""]; if (!fn) throw new Error(`site: no template for ${route.route}`); if (route.kind === "discoveries") return fn(site, { ...route, data: claims(site) }); return fn(site, route); } /* EXTRAS */ function extras(site: Site): Output[] { const out: Output[] = []; const home = site.input("figures").path; out.push({ path: "og.png", bytes: bytes(figure(home, "site-og-dark", "/og.png")) }); return out; } /* SPEC */ export const MANIFEST = process.env.MRLY_DIST ? join(dist, ".manifest.json") : ".cache/manifest.json"; export const counted = () => ({ ...counts }); export const spec: Spec = { root: org, out: dist, templates: ["lib", "scripts"], collect, render: draw, globals: extras, git: { page: shell, md: (site, text, from) => md(text, { math, link: links(site, from) }), }, }; if (import.meta.main) { const done = await build(spec, { manifest: MANIFEST }); const site = done.site; const code = site.routes.filter(isGit).length; console.log( `site: ${site.routes.length} routes, ${counts.demos} demo shells, ${counts.papers} papers, ${counts.research} research pages, ${counts.blog} posts, ${code} code pages, ${done.rendered} rendered, ${done.written} files written, ${done.removed} removed`, ); }