Four changes, one coherent pass:
1. Prometheus gets its own flame mark (SVG, not an emoji). Used in the
topnav brand, in the hero at 88 px, and as the favicon. Replaces the
🔥 glyph in the service and in site/prometheus/index.html.
2. silentmode.st/prometheus is now a brief introduction with two big
buttons (Open prometheus.x · Open apps.silentmode.st mirror) and a
short "what / who / status" block, instead of the full category
grid that duplicated what prometheus.x already renders. The /prometheus
page's job on silentmode.st is "point you at the real site," not host
the content.
3. Footer on the Prometheus service drops the "Silent Mode — a Deviant
project" byline and reframes the sibling-x links as "Also live in the
decentralized web:" so Prometheus reads as its own site rather than a
Silent Mode subpage. Keeps the attribution via the MPL-2.0 + source
link on the final line. aegis.x and pithos.x added to the sibling row.
4. Four Silent Mode apps seeded as first-wave listings
(scripts/seed-listings.json + scripts/seed.mjs). Theseus Navigator,
Ariadne's Thread (Windows + Android), Pithos. Each carries the
project's own SVG icon from public/icons/, pulled from the project's
own site. The seed is a direct-DB insert (not through /api/listing)
with signer_address = chipnet operator and signature = sentinel
`SM_OPERATOR_SEED`; scan_report is an operator-attested envelope
signed by the service key so the display pipeline is unchanged. Run
with `node scripts/seed.mjs`; idempotent (skips existing (publisher,
slug)).
Deliberately NOT seeded: Theseus extensions that have no standalone
form — pdf-editor, docx-editor, shield-blocker, bergamot provider.
They ship via Theseus's own OTA, not as Prometheus listings. Aegis
is in the same boat today (a Theseus bundled add-on without a
standalone binary); its icon is included in public/icons/ for when
it has its own distribution.
Real file sizes + SHAs come from the current dl.silentmode.st manifest
(theseus 0.3.89, ariadne resolver 0.1.13, ariadne android 0.22,
pithos-desktop 0.3.26). When APS1 lands cross-session, these listings
migrate to real on-chain records signed by each project's own publisher
key and the sentinel values go away.
112 lines
4.2 KiB
JavaScript
112 lines
4.2 KiB
JavaScript
/**
|
|
* Shared document shell for every rendered page.
|
|
*
|
|
* A page function returns `{title, description, body}`; `layout()` wraps it in
|
|
* the `<html>` document with the right meta, topnav, and footer. The brand mark
|
|
* is inlined as SVG so pages do not fetch an image to render a 20-pixel logo.
|
|
*/
|
|
|
|
const BRAND_MARK = `<svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true">
|
|
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
|
<path d="M32 6 C 38 18 46 24 46 36 C 46 48 40 56 32 56 C 24 56 18 48 18 36 C 18 28 24 22 20 14 C 26 18 30 20 32 6 Z" fill="#d6ff3d"/>
|
|
<path d="M32 20 C 36 28 40 32 40 40 C 40 48 36 52 32 52 C 28 52 24 48 24 40 C 24 34 28 30 32 20 Z" fill="#0b0e14"/>
|
|
<circle cx="32" cy="44" r="3" fill="#d6ff3d"/>
|
|
</svg>`;
|
|
|
|
const NAV = [
|
|
{ href: '/browse', label: 'Browse' },
|
|
{ href: '/submit', label: 'Submit' },
|
|
{ href: '/publish', label: 'How publishing works' },
|
|
{ href: '/boundaries', label: 'Boundaries' },
|
|
];
|
|
|
|
/**
|
|
* Compose a full HTML document from a page.
|
|
*
|
|
* @param {{title: string, description: string, body: string, here?: string}} page
|
|
* @returns {string} the HTML document, ready to send.
|
|
*/
|
|
export function layout(page) {
|
|
const {
|
|
title, description, body, here = '',
|
|
ogType = 'website',
|
|
ogImage = 'https://prometheus.x/static/favicon.svg',
|
|
headExtra = '',
|
|
} = page;
|
|
|
|
// The canonical URL is always on prometheus.x — never the IP+port the server
|
|
// actually listens on, and never the SEO mirror at apps.silentmode.st, even
|
|
// when the mirror is what served this request. Readers who followed a link
|
|
// from search land on the mirror; the canonical sends search engines back to
|
|
// the real address and keeps the scorecard on prometheus.x.
|
|
const canonical = `https://prometheus.x${here}`;
|
|
|
|
const navHtml = NAV.map(n => {
|
|
const classAttr = n.href === here ? ' class="here"' : '';
|
|
return `<a href="${n.href}"${classAttr}>${n.label}</a>`;
|
|
}).join('\n ');
|
|
|
|
return `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>${escapeHtml(title)} — Prometheus</title>
|
|
<meta name="description" content="${escapeHtml(description)}">
|
|
<link rel="canonical" href="${canonical}">
|
|
<meta property="og:type" content="${escapeHtml(ogType)}">
|
|
<meta property="og:site_name" content="Prometheus">
|
|
<meta property="og:title" content="${escapeHtml(title)} — Prometheus">
|
|
<meta property="og:description" content="${escapeHtml(description)}">
|
|
<meta property="og:url" content="${canonical}">
|
|
<meta property="og:image" content="${escapeHtml(ogImage)}">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
|
<link rel="stylesheet" href="/static/style.css?v=1">
|
|
${headExtra}
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="topnav">
|
|
<a class="brand" href="/">${BRAND_MARK}Prometheus<span class="g">.X</span></a>
|
|
${navHtml}
|
|
</nav>
|
|
|
|
${body}
|
|
|
|
<footer>
|
|
<div class="fine">
|
|
Named for Prometheus, who gave fire to humans without asking the gods.
|
|
Distinct from the well-known Prometheus monitoring project — both take
|
|
the name from the same myth; neither has anything to do with the other.
|
|
</div>
|
|
<div class="fine" style="margin-top:1.4rem;color:var(--dim);font-size:12px">
|
|
Also live in the decentralized web:
|
|
<a href="//theseus.x/">theseus.x</a> ·
|
|
<a href="//sirius.x/">sirius.x</a> ·
|
|
<a href="//ariadne.x/">ariadne.x</a> ·
|
|
<a href="//aegis.x/">aegis.x</a> ·
|
|
<a href="//pithos.x/">pithos.x</a> ·
|
|
<a href="//hephaestus.x/">hephaestus.x</a>
|
|
</div>
|
|
<div class="fine" style="color:var(--dim);font-size:11px;margin-top:.6rem">
|
|
Source at <a href="https://code.silentmode.st/silentmode/prometheus">code.silentmode.st/silentmode/prometheus</a> · MPL-2.0
|
|
</div>
|
|
</footer>
|
|
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
/**
|
|
* Minimal HTML escape for text that goes into attributes or text nodes.
|
|
* Not for anything that must survive as markup; page bodies ship as-is.
|
|
*/
|
|
export function escapeHtml(s) {
|
|
return String(s)
|
|
.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('"', '"')
|
|
.replaceAll("'", ''');
|
|
}
|