feat(hephaestus.x): per-repo detail pages under /silentmode/*

Adds live pages for each repo without leaving hephaestus.x:

  hephaestus.x/silentmode/            → index of all silentmode/* repos
  hephaestus.x/silentmode/theseus/    → README + recent commits + clone URLs
  hephaestus.x/silentmode/sirius/     → same
  hephaestus.x/silentmode/ariadne/    → same, gated ("private, sign in to browse")
  hephaestus.x/silentmode/hephaestus/ → same, gated

Single _template.html hardcoded per repo via sed; each detail page pulls
metadata + README + last 8 commits from code.silentmode.st via the
CORS-enabled Forgejo API on load, renders README with a minimal
markdown-to-HTML pass, and gracefully falls back to a "sign in to browse"
note when the API returns 401/404 (private repo).

The URL stays on hephaestus.x while browsing — user only leaves when they
click "Browse full source →" or a specific commit link.
This commit is contained in:
Local Dev 2026-08-31 15:29:49 +02:00
parent e4e5314704
commit 941851ea0e
6 changed files with 1262 additions and 0 deletions

View file

@ -0,0 +1,227 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>silentmode/__REPO__ — Hephaestus</title>
<meta name="description" content="silentmode/__REPO__ on Hephaestus — Silent Mode's decentralized code host.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
display:flex;flex-direction:column}
a{color:var(--acid);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
.top a{color:var(--mut)}
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
.meta span{margin-right:14px}
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
.btn:hover{background:var(--panel);text-decoration:none}
.btn.acid:hover{background:var(--acid)}
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
section:last-of-type{border-bottom:none}
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
overflow-x:auto;font-size:14px;line-height:1.7}
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
#readme h2{font-size:1.2rem}
#readme h3{font-size:1.05rem;color:var(--acid)}
#readme p{margin:.6rem 0;color:var(--ink)}
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
#readme pre code{background:transparent;border:none;padding:0}
#readme a{color:var(--acid)}
#readme ul,#readme ol{padding-left:1.4rem}
#readme li{margin:.25rem 0}
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
.commits{display:grid;gap:8px}
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>__REPO__</strong>
</div>
<header class="repo">
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">__REPO__</span></h1>
<p class="meta" id="meta"><span>loading …</span></p>
<p class="desc" id="desc"></p>
<div class="cta">
<a class="btn acid" href="https://code.silentmode.st/silentmode/__REPO__">Browse full source →</a>
<a class="btn" href="https://code.silentmode.st/silentmode/__REPO__/issues">Issues</a>
<a class="btn" href="https://code.silentmode.st/silentmode/__REPO__/releases">Releases</a>
</div>
</header>
<section>
<h2>README</h2>
<div id="readme" class="empty">Loading README from the forge…</div>
</section>
<section>
<h2>Recent commits</h2>
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
</section>
<section>
<h2>Clone</h2>
<div class="clone-label">HTTPS (with a personal access token)</div>
<pre class="clone">git clone https://&lt;you&gt;:&lt;token&gt;@code.silentmode.st/silentmode/__REPO__.git</pre>
<div class="clone-label">SSH (with a key registered under your account)</div>
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/__REPO__.git</pre>
</section>
</div>
<footer>
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
</footer>
<script type="module">
const REPO = "__REPO__";
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
}
function fmtDate(iso) {
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return "";
const days = (Date.now() - d.getTime()) / 86400000;
if (days < 1) return "today";
if (days < 30) return `${Math.round(days)}d ago`;
return d.toISOString().slice(0, 10);
}
// Minimal markdown → HTML renderer for the README preview.
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
// fenced code blocks, and blockquotes. For anything richer, users click through
// to the full forge which has Forgejo's proper markdown renderer.
function mdToHtml(md) {
// Escape first, then apply markdown patterns
let s = esc(md);
// Fenced code blocks
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
// Headers
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
// Bold + italic
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
// Links [text](url)
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Unordered lists
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
// Blockquotes
s = s.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
// Paragraphs: any line-block not already wrapped in a block tag
s = s.split(/\n{2,}/).map(chunk => {
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
}).join("\n");
return s;
}
async function loadRepo() {
const meta = document.getElementById("meta");
const desc = document.getElementById("desc");
const readme = document.getElementById("readme");
const commits = document.getElementById("commits");
try {
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
const parts = [];
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
meta.innerHTML = parts.join("");
desc.textContent = info.description || "";
// README
try {
const branch = info.default_branch || "master";
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
if (rmRes.ok) {
const md = await rmRes.text();
readme.classList.remove("empty");
readme.innerHTML = mdToHtml(md);
} else {
readme.textContent = "(no README.md in this repo)";
}
} catch (e) {
readme.textContent = `(couldn't load README: ${e.message})`;
}
// Recent commits
try {
const cRes = await fetch(`${API}/commits?limit=8`);
if (cRes.ok) {
const list = await cRes.json();
commits.innerHTML = list.slice(0, 8).map(c => {
const msg = (c.commit?.message || "").split("\n")[0];
const author = c.commit?.author?.name || c.author?.login || "unknown";
const when = fmtDate(c.commit?.author?.date);
const sha = (c.sha || "").slice(0, 7);
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
<div class="msg">${esc(msg)}</div>
<div class="sha">${esc(sha)}</div>
<div class="author">by ${esc(author)}</div>
<div class="when">${esc(when)}</div>
</a>`;
}).join("");
}
} catch (e) {
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
}
} catch (e) {
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
readme.innerHTML = `
<div class="private-note">
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
you'll be able to read it there.
</div>`;
commits.innerHTML = "";
}
}
loadRepo();
</script>
</body>
</html>

View file

@ -0,0 +1,227 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>silentmode/ariadne — Hephaestus</title>
<meta name="description" content="silentmode/ariadne on Hephaestus — Silent Mode's decentralized code host.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
display:flex;flex-direction:column}
a{color:var(--acid);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
.top a{color:var(--mut)}
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
.meta span{margin-right:14px}
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
.btn:hover{background:var(--panel);text-decoration:none}
.btn.acid:hover{background:var(--acid)}
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
section:last-of-type{border-bottom:none}
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
overflow-x:auto;font-size:14px;line-height:1.7}
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
#readme h2{font-size:1.2rem}
#readme h3{font-size:1.05rem;color:var(--acid)}
#readme p{margin:.6rem 0;color:var(--ink)}
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
#readme pre code{background:transparent;border:none;padding:0}
#readme a{color:var(--acid)}
#readme ul,#readme ol{padding-left:1.4rem}
#readme li{margin:.25rem 0}
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
.commits{display:grid;gap:8px}
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>ariadne</strong>
</div>
<header class="repo">
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">ariadne</span></h1>
<p class="meta" id="meta"><span>loading …</span></p>
<p class="desc" id="desc"></p>
<div class="cta">
<a class="btn acid" href="https://code.silentmode.st/silentmode/ariadne">Browse full source →</a>
<a class="btn" href="https://code.silentmode.st/silentmode/ariadne/issues">Issues</a>
<a class="btn" href="https://code.silentmode.st/silentmode/ariadne/releases">Releases</a>
</div>
</header>
<section>
<h2>README</h2>
<div id="readme" class="empty">Loading README from the forge…</div>
</section>
<section>
<h2>Recent commits</h2>
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
</section>
<section>
<h2>Clone</h2>
<div class="clone-label">HTTPS (with a personal access token)</div>
<pre class="clone">git clone https://&lt;you&gt;:&lt;token&gt;@code.silentmode.st/silentmode/ariadne.git</pre>
<div class="clone-label">SSH (with a key registered under your account)</div>
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/ariadne.git</pre>
</section>
</div>
<footer>
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
</footer>
<script type="module">
const REPO = "ariadne";
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
}
function fmtDate(iso) {
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return "";
const days = (Date.now() - d.getTime()) / 86400000;
if (days < 1) return "today";
if (days < 30) return `${Math.round(days)}d ago`;
return d.toISOString().slice(0, 10);
}
// Minimal markdown → HTML renderer for the README preview.
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
// fenced code blocks, and blockquotes. For anything richer, users click through
// to the full forge which has Forgejo's proper markdown renderer.
function mdToHtml(md) {
// Escape first, then apply markdown patterns
let s = esc(md);
// Fenced code blocks
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
// Headers
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
// Bold + italic
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
// Links [text](url)
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Unordered lists
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
// Blockquotes
s = s.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
// Paragraphs: any line-block not already wrapped in a block tag
s = s.split(/\n{2,}/).map(chunk => {
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
}).join("\n");
return s;
}
async function loadRepo() {
const meta = document.getElementById("meta");
const desc = document.getElementById("desc");
const readme = document.getElementById("readme");
const commits = document.getElementById("commits");
try {
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
const parts = [];
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
meta.innerHTML = parts.join("");
desc.textContent = info.description || "";
// README
try {
const branch = info.default_branch || "master";
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
if (rmRes.ok) {
const md = await rmRes.text();
readme.classList.remove("empty");
readme.innerHTML = mdToHtml(md);
} else {
readme.textContent = "(no README.md in this repo)";
}
} catch (e) {
readme.textContent = `(couldn't load README: ${e.message})`;
}
// Recent commits
try {
const cRes = await fetch(`${API}/commits?limit=8`);
if (cRes.ok) {
const list = await cRes.json();
commits.innerHTML = list.slice(0, 8).map(c => {
const msg = (c.commit?.message || "").split("\n")[0];
const author = c.commit?.author?.name || c.author?.login || "unknown";
const when = fmtDate(c.commit?.author?.date);
const sha = (c.sha || "").slice(0, 7);
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
<div class="msg">${esc(msg)}</div>
<div class="sha">${esc(sha)}</div>
<div class="author">by ${esc(author)}</div>
<div class="when">${esc(when)}</div>
</a>`;
}).join("");
}
} catch (e) {
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
}
} catch (e) {
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
readme.innerHTML = `
<div class="private-note">
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
you'll be able to read it there.
</div>`;
commits.innerHTML = "";
}
}
loadRepo();
</script>
</body>
</html>

View file

@ -0,0 +1,227 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>silentmode/hephaestus — Hephaestus</title>
<meta name="description" content="silentmode/hephaestus on Hephaestus — Silent Mode's decentralized code host.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
display:flex;flex-direction:column}
a{color:var(--acid);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
.top a{color:var(--mut)}
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
.meta span{margin-right:14px}
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
.btn:hover{background:var(--panel);text-decoration:none}
.btn.acid:hover{background:var(--acid)}
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
section:last-of-type{border-bottom:none}
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
overflow-x:auto;font-size:14px;line-height:1.7}
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
#readme h2{font-size:1.2rem}
#readme h3{font-size:1.05rem;color:var(--acid)}
#readme p{margin:.6rem 0;color:var(--ink)}
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
#readme pre code{background:transparent;border:none;padding:0}
#readme a{color:var(--acid)}
#readme ul,#readme ol{padding-left:1.4rem}
#readme li{margin:.25rem 0}
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
.commits{display:grid;gap:8px}
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>hephaestus</strong>
</div>
<header class="repo">
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">hephaestus</span></h1>
<p class="meta" id="meta"><span>loading …</span></p>
<p class="desc" id="desc"></p>
<div class="cta">
<a class="btn acid" href="https://code.silentmode.st/silentmode/hephaestus">Browse full source →</a>
<a class="btn" href="https://code.silentmode.st/silentmode/hephaestus/issues">Issues</a>
<a class="btn" href="https://code.silentmode.st/silentmode/hephaestus/releases">Releases</a>
</div>
</header>
<section>
<h2>README</h2>
<div id="readme" class="empty">Loading README from the forge…</div>
</section>
<section>
<h2>Recent commits</h2>
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
</section>
<section>
<h2>Clone</h2>
<div class="clone-label">HTTPS (with a personal access token)</div>
<pre class="clone">git clone https://&lt;you&gt;:&lt;token&gt;@code.silentmode.st/silentmode/hephaestus.git</pre>
<div class="clone-label">SSH (with a key registered under your account)</div>
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/hephaestus.git</pre>
</section>
</div>
<footer>
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
</footer>
<script type="module">
const REPO = "hephaestus";
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
}
function fmtDate(iso) {
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return "";
const days = (Date.now() - d.getTime()) / 86400000;
if (days < 1) return "today";
if (days < 30) return `${Math.round(days)}d ago`;
return d.toISOString().slice(0, 10);
}
// Minimal markdown → HTML renderer for the README preview.
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
// fenced code blocks, and blockquotes. For anything richer, users click through
// to the full forge which has Forgejo's proper markdown renderer.
function mdToHtml(md) {
// Escape first, then apply markdown patterns
let s = esc(md);
// Fenced code blocks
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
// Headers
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
// Bold + italic
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
// Links [text](url)
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Unordered lists
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
// Blockquotes
s = s.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
// Paragraphs: any line-block not already wrapped in a block tag
s = s.split(/\n{2,}/).map(chunk => {
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
}).join("\n");
return s;
}
async function loadRepo() {
const meta = document.getElementById("meta");
const desc = document.getElementById("desc");
const readme = document.getElementById("readme");
const commits = document.getElementById("commits");
try {
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
const parts = [];
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
meta.innerHTML = parts.join("");
desc.textContent = info.description || "";
// README
try {
const branch = info.default_branch || "master";
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
if (rmRes.ok) {
const md = await rmRes.text();
readme.classList.remove("empty");
readme.innerHTML = mdToHtml(md);
} else {
readme.textContent = "(no README.md in this repo)";
}
} catch (e) {
readme.textContent = `(couldn't load README: ${e.message})`;
}
// Recent commits
try {
const cRes = await fetch(`${API}/commits?limit=8`);
if (cRes.ok) {
const list = await cRes.json();
commits.innerHTML = list.slice(0, 8).map(c => {
const msg = (c.commit?.message || "").split("\n")[0];
const author = c.commit?.author?.name || c.author?.login || "unknown";
const when = fmtDate(c.commit?.author?.date);
const sha = (c.sha || "").slice(0, 7);
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
<div class="msg">${esc(msg)}</div>
<div class="sha">${esc(sha)}</div>
<div class="author">by ${esc(author)}</div>
<div class="when">${esc(when)}</div>
</a>`;
}).join("");
}
} catch (e) {
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
}
} catch (e) {
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
readme.innerHTML = `
<div class="private-note">
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
you'll be able to read it there.
</div>`;
commits.innerHTML = "";
}
}
loadRepo();
</script>
</body>
</html>

View file

@ -0,0 +1,127 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>silentmode/* — Hephaestus</title>
<meta name="description" content="All silentmode/* repos on Hephaestus.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
display:flex;flex-direction:column}
a{color:var(--acid);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:820px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
.top a{color:var(--mut)}
header.hero{text-align:center;padding:3rem 1.2rem 1rem}
.mark{font-size:52px;line-height:1}
h1{font-size:clamp(1.8rem,4.5vw,2.4rem);margin:.4rem 0 .3rem;letter-spacing:-0.01em;font-family:ui-monospace,monospace;font-weight:500}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1rem;max-width:560px;margin:0 auto}
section{padding:2rem 0 1rem}
.repo{display:grid;grid-template-columns:1fr auto;gap:16px;align-items:baseline;
background:var(--panel);border:1px solid var(--line);border-radius:12px;
padding:16px 20px;margin-bottom:10px;transition:border-color .1s,background .1s;text-decoration:none}
.repo:hover{border-color:var(--acid);background:var(--panel2);text-decoration:none}
.repo .name{font-family:ui-monospace,monospace;font-size:14.5px;color:var(--ink)}
.repo .name .owner{color:var(--dim)}
.repo .name .sep{color:var(--dim);margin:0 2px}
.repo .name .repo-label{color:var(--acid)}
.repo .desc{color:var(--mut);font-size:13.5px;line-height:1.5;margin:.3rem 0 0;grid-column:1/-1}
.repo .meta{color:var(--dim);font-size:12px;font-family:ui-monospace,monospace;text-align:right;white-space:nowrap}
.repo.priv .name .repo-label{color:var(--mut)}
.empty{color:var(--dim);font-size:14px;text-align:center;padding:2rem}
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<a href="../">hephaestus.x</a> / <strong>silentmode</strong>
</div>
<header class="hero">
<div class="mark">🛠</div>
<h1><span class="g">silentmode</span>/*</h1>
<p class="tag">Public repositories under the <code>silentmode</code> org. Click through for a per-repo overview or straight into the full forge.</p>
</header>
<section>
<div id="repos"><div class="empty">Loading from the forge…</div></div>
</section>
</div>
<footer>
<a href="../">hephaestus.x</a> · Silent Mode's decentralized code host
</footer>
<script type="module">
// Static index: enumerate the known silentmode/* repos. Public ones link to the
// per-repo detail page we generate at hephaestus.x/silentmode/<name>/ ; private
// ones link straight to code.silentmode.st since the detail page shows a
// "sign in to browse" message anyway.
const KNOWN = [
{name: "theseus", private: false},
{name: "sirius", private: false},
{name: "ariadne", private: true},
{name: "hephaestus", private: true},
];
const container = document.getElementById("repos");
function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
}
function fmtDate(iso) {
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return "";
const days = (Date.now() - d.getTime()) / 86400000;
if (days < 1) return "today";
if (days < 30) return `${Math.round(days)}d ago`;
return d.toISOString().slice(0, 10);
}
async function loadRepo(entry) {
const priv = entry.private;
// Public repos: fetch fresh info via CORS. Private: fill in a static line.
if (!priv) {
try {
const info = await fetch(`https://code.silentmode.st/api/v1/repos/silentmode/${entry.name}`).then(r => r.json());
return {...entry, description: info.description, updated_at: info.updated_at, size: info.size};
} catch { return entry; }
}
return entry;
}
Promise.all(KNOWN.map(loadRepo)).then(list => {
container.innerHTML = list.map(r => {
// Public → per-repo detail page on hephaestus.x; private → direct to code.silentmode.st
const href = r.private
? `https://code.silentmode.st/silentmode/${r.name}`
: `./${r.name}/`;
const badge = r.private ? '<span style="color:var(--dim)">private</span>' : "";
const updated = r.updated_at ? `updated ${fmtDate(r.updated_at)}` : "";
const desc = r.description ? `<div class="desc">${esc(r.description)}</div>` : "";
return `
<a class="repo${r.private ? " priv" : ""}" href="${href}">
<div class="name">
<span class="owner">silentmode</span><span class="sep">/</span><span class="repo-label">${esc(r.name)}</span>
${desc}
</div>
<div class="meta">${badge}${badge && updated ? " · " : ""}${updated}</div>
</a>`;
}).join("");
});
</script>
</body>
</html>

View file

@ -0,0 +1,227 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>silentmode/sirius — Hephaestus</title>
<meta name="description" content="silentmode/sirius on Hephaestus — Silent Mode's decentralized code host.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
display:flex;flex-direction:column}
a{color:var(--acid);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
.top a{color:var(--mut)}
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
.meta span{margin-right:14px}
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
.btn:hover{background:var(--panel);text-decoration:none}
.btn.acid:hover{background:var(--acid)}
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
section:last-of-type{border-bottom:none}
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
overflow-x:auto;font-size:14px;line-height:1.7}
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
#readme h2{font-size:1.2rem}
#readme h3{font-size:1.05rem;color:var(--acid)}
#readme p{margin:.6rem 0;color:var(--ink)}
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
#readme pre code{background:transparent;border:none;padding:0}
#readme a{color:var(--acid)}
#readme ul,#readme ol{padding-left:1.4rem}
#readme li{margin:.25rem 0}
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
.commits{display:grid;gap:8px}
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>sirius</strong>
</div>
<header class="repo">
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">sirius</span></h1>
<p class="meta" id="meta"><span>loading …</span></p>
<p class="desc" id="desc"></p>
<div class="cta">
<a class="btn acid" href="https://code.silentmode.st/silentmode/sirius">Browse full source →</a>
<a class="btn" href="https://code.silentmode.st/silentmode/sirius/issues">Issues</a>
<a class="btn" href="https://code.silentmode.st/silentmode/sirius/releases">Releases</a>
</div>
</header>
<section>
<h2>README</h2>
<div id="readme" class="empty">Loading README from the forge…</div>
</section>
<section>
<h2>Recent commits</h2>
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
</section>
<section>
<h2>Clone</h2>
<div class="clone-label">HTTPS (with a personal access token)</div>
<pre class="clone">git clone https://&lt;you&gt;:&lt;token&gt;@code.silentmode.st/silentmode/sirius.git</pre>
<div class="clone-label">SSH (with a key registered under your account)</div>
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/sirius.git</pre>
</section>
</div>
<footer>
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
</footer>
<script type="module">
const REPO = "sirius";
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
}
function fmtDate(iso) {
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return "";
const days = (Date.now() - d.getTime()) / 86400000;
if (days < 1) return "today";
if (days < 30) return `${Math.round(days)}d ago`;
return d.toISOString().slice(0, 10);
}
// Minimal markdown → HTML renderer for the README preview.
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
// fenced code blocks, and blockquotes. For anything richer, users click through
// to the full forge which has Forgejo's proper markdown renderer.
function mdToHtml(md) {
// Escape first, then apply markdown patterns
let s = esc(md);
// Fenced code blocks
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
// Headers
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
// Bold + italic
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
// Links [text](url)
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Unordered lists
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
// Blockquotes
s = s.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
// Paragraphs: any line-block not already wrapped in a block tag
s = s.split(/\n{2,}/).map(chunk => {
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
}).join("\n");
return s;
}
async function loadRepo() {
const meta = document.getElementById("meta");
const desc = document.getElementById("desc");
const readme = document.getElementById("readme");
const commits = document.getElementById("commits");
try {
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
const parts = [];
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
meta.innerHTML = parts.join("");
desc.textContent = info.description || "";
// README
try {
const branch = info.default_branch || "master";
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
if (rmRes.ok) {
const md = await rmRes.text();
readme.classList.remove("empty");
readme.innerHTML = mdToHtml(md);
} else {
readme.textContent = "(no README.md in this repo)";
}
} catch (e) {
readme.textContent = `(couldn't load README: ${e.message})`;
}
// Recent commits
try {
const cRes = await fetch(`${API}/commits?limit=8`);
if (cRes.ok) {
const list = await cRes.json();
commits.innerHTML = list.slice(0, 8).map(c => {
const msg = (c.commit?.message || "").split("\n")[0];
const author = c.commit?.author?.name || c.author?.login || "unknown";
const when = fmtDate(c.commit?.author?.date);
const sha = (c.sha || "").slice(0, 7);
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
<div class="msg">${esc(msg)}</div>
<div class="sha">${esc(sha)}</div>
<div class="author">by ${esc(author)}</div>
<div class="when">${esc(when)}</div>
</a>`;
}).join("");
}
} catch (e) {
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
}
} catch (e) {
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
readme.innerHTML = `
<div class="private-note">
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
you'll be able to read it there.
</div>`;
commits.innerHTML = "";
}
}
loadRepo();
</script>
</body>
</html>

View file

@ -0,0 +1,227 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>silentmode/theseus — Hephaestus</title>
<meta name="description" content="silentmode/theseus on Hephaestus — Silent Mode's decentralized code host.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
display:flex;flex-direction:column}
a{color:var(--acid);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
.top a{color:var(--mut)}
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
.meta span{margin-right:14px}
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
.btn:hover{background:var(--panel);text-decoration:none}
.btn.acid:hover{background:var(--acid)}
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
section:last-of-type{border-bottom:none}
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
overflow-x:auto;font-size:14px;line-height:1.7}
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
#readme h2{font-size:1.2rem}
#readme h3{font-size:1.05rem;color:var(--acid)}
#readme p{margin:.6rem 0;color:var(--ink)}
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
#readme pre code{background:transparent;border:none;padding:0}
#readme a{color:var(--acid)}
#readme ul,#readme ol{padding-left:1.4rem}
#readme li{margin:.25rem 0}
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
.commits{display:grid;gap:8px}
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>theseus</strong>
</div>
<header class="repo">
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">theseus</span></h1>
<p class="meta" id="meta"><span>loading …</span></p>
<p class="desc" id="desc"></p>
<div class="cta">
<a class="btn acid" href="https://code.silentmode.st/silentmode/theseus">Browse full source →</a>
<a class="btn" href="https://code.silentmode.st/silentmode/theseus/issues">Issues</a>
<a class="btn" href="https://code.silentmode.st/silentmode/theseus/releases">Releases</a>
</div>
</header>
<section>
<h2>README</h2>
<div id="readme" class="empty">Loading README from the forge…</div>
</section>
<section>
<h2>Recent commits</h2>
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
</section>
<section>
<h2>Clone</h2>
<div class="clone-label">HTTPS (with a personal access token)</div>
<pre class="clone">git clone https://&lt;you&gt;:&lt;token&gt;@code.silentmode.st/silentmode/theseus.git</pre>
<div class="clone-label">SSH (with a key registered under your account)</div>
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/theseus.git</pre>
</section>
</div>
<footer>
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
</footer>
<script type="module">
const REPO = "theseus";
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
}
function fmtDate(iso) {
if (!iso) return "";
const d = new Date(iso);
if (isNaN(d)) return "";
const days = (Date.now() - d.getTime()) / 86400000;
if (days < 1) return "today";
if (days < 30) return `${Math.round(days)}d ago`;
return d.toISOString().slice(0, 10);
}
// Minimal markdown → HTML renderer for the README preview.
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
// fenced code blocks, and blockquotes. For anything richer, users click through
// to the full forge which has Forgejo's proper markdown renderer.
function mdToHtml(md) {
// Escape first, then apply markdown patterns
let s = esc(md);
// Fenced code blocks
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
// Headers
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
// Bold + italic
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
// Links [text](url)
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Unordered lists
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
// Blockquotes
s = s.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
// Paragraphs: any line-block not already wrapped in a block tag
s = s.split(/\n{2,}/).map(chunk => {
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
}).join("\n");
return s;
}
async function loadRepo() {
const meta = document.getElementById("meta");
const desc = document.getElementById("desc");
const readme = document.getElementById("readme");
const commits = document.getElementById("commits");
try {
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
const parts = [];
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
meta.innerHTML = parts.join("");
desc.textContent = info.description || "";
// README
try {
const branch = info.default_branch || "master";
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
if (rmRes.ok) {
const md = await rmRes.text();
readme.classList.remove("empty");
readme.innerHTML = mdToHtml(md);
} else {
readme.textContent = "(no README.md in this repo)";
}
} catch (e) {
readme.textContent = `(couldn't load README: ${e.message})`;
}
// Recent commits
try {
const cRes = await fetch(`${API}/commits?limit=8`);
if (cRes.ok) {
const list = await cRes.json();
commits.innerHTML = list.slice(0, 8).map(c => {
const msg = (c.commit?.message || "").split("\n")[0];
const author = c.commit?.author?.name || c.author?.login || "unknown";
const when = fmtDate(c.commit?.author?.date);
const sha = (c.sha || "").slice(0, 7);
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
<div class="msg">${esc(msg)}</div>
<div class="sha">${esc(sha)}</div>
<div class="author">by ${esc(author)}</div>
<div class="when">${esc(when)}</div>
</a>`;
}).join("");
}
} catch (e) {
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
}
} catch (e) {
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
readme.innerHTML = `
<div class="private-note">
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
you'll be able to read it there.
</div>`;
commits.innerHTML = "";
}
}
loadRepo();
</script>
</body>
</html>