<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>void publish</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/atom-one-dark.min.css">
<style>
:root {
--bg: #0F111A;
--bg-surface: #181B28;
--bg-hover: #1E2235;
--bg-active: #252940;
--bg-header: #14172A;
--fg: #C4C8D8;
--fg-dim: #6B7394;
--fg-bright: #E8ECF4;
--accent: #A277FF;
--accent-dim: #7C5CBF;
--border: #252940;
--green: #5CEB7A;
--red: #FF5C72;
--yellow: #FFD666;
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, monospace;
--sidebar-width: 240px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
font-family: var(--font-mono);
background: var(--bg);
color: var(--fg);
font-size: 13px;
line-height: 1.5;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
#app { display: flex; flex-direction: column; height: 100vh; }
#topbar {
background: var(--bg-header);
border-bottom: 1px solid var(--border);
padding: 0 20px;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
gap: 16px;
overflow: hidden;
}
#topbar-left {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
flex: 1;
}
#topbar-logo {
font-weight: 700;
color: var(--fg-bright);
font-size: 14px;
flex-shrink: 0;
letter-spacing: -0.02em;
}
#topbar-logo::before {
content: '▪ ';
color: var(--accent);
}
#topbar-message {
color: var(--fg-dim);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
#topbar-right {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
font-size: 12px;
}
.topbar-pill {
padding: 2px 10px;
border-radius: 10px;
background: var(--bg-active);
color: var(--fg-dim);
white-space: nowrap;
}
.topbar-dim {
color: var(--fg-dim);
white-space: nowrap;
}
.hash-expand {
cursor: pointer;
user-select: none;
}
.hash-expand:hover { color: var(--fg); }
.hash-expand .h-full {
display: none;
user-select: all;
cursor: text;
word-break: break-all;
}
.hash-expand.expanded .h-short { display: none; }
.hash-expand.expanded .h-full { display: inline; }
.sig-pill {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
white-space: nowrap;
cursor: pointer;
position: relative;
}
.sig-pill.signed { background: rgba(92,235,122,0.12); color: var(--green); }
.sig-pill.unsigned { background: rgba(107,115,148,0.12); color: var(--fg-dim); }
.sig-pill.invalid { background: rgba(255,92,114,0.12); color: var(--red); }
.sig-pill.unverified { background: rgba(255,214,102,0.12); color: var(--yellow); }
.sig-detail {
display: none;
position: absolute;
top: calc(100% + 6px);
right: 0;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px 14px;
font-size: 11px;
color: var(--fg-dim);
white-space: nowrap;
z-index: 200;
min-width: 260px;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
.sig-pill:hover .sig-detail,
.sig-pill:focus .sig-detail { display: block; }
.sig-detail-row { margin: 3px 0; }
.sig-detail-label { color: var(--fg-dim); }
.sig-detail-value { color: var(--fg); margin-left: 4px; }
#body { display: flex; flex: 1; overflow: hidden; }
#sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
background: var(--bg-surface);
border-right: 1px solid var(--border);
overflow-y: auto;
overflow-x: hidden;
padding: 8px 0;
flex-shrink: 0;
transition: margin-left 0.25s ease;
}
#sidebar-toggle {
display: none;
position: fixed; bottom: 16px; left: 16px; z-index: 100;
background: var(--accent); color: #fff; border: none;
width: 36px; height: 36px; border-radius: 50%; cursor: pointer;
font-size: 16px; line-height: 1;
font-family: var(--font-mono);
}
.tree-dir, .tree-file {
display: flex;
align-items: center;
padding: 1px 12px;
cursor: pointer;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: background 0.1s;
user-select: none;
border-left: 2px solid transparent;
}
.tree-dir:hover, .tree-file:hover { background: var(--bg-hover); }
.tree-file.active {
background: var(--bg-active);
color: var(--fg-bright);
border-left-color: var(--accent);
}
.tree-dir .arrow {
display: inline-block;
width: 16px;
font-size: 11px;
color: var(--fg-dim);
flex-shrink: 0;
text-align: center;
}
.tree-dir .dir-name { color: var(--fg); }
.tree-file .file-name { color: var(--fg); }
.tree-children { overflow: hidden; }
.tree-children.collapsed { display: none; }
#main {
flex: 1; overflow-y: auto; padding: 0;
display: flex; flex-direction: column;
}
#breadcrumb-bar {
display: none;
padding: 6px 20px;
border-bottom: 1px solid var(--border);
font-size: 12px;
color: var(--fg-dim);
justify-content: space-between;
align-items: center;
flex-shrink: 0;
background: var(--bg-header);
}
#breadcrumb-bar.visible { display: flex; }
#breadcrumb-path { display: flex; align-items: center; gap: 0; flex-wrap: wrap; }
.bc-segment {
color: var(--fg-dim);
cursor: pointer;
padding: 1px 2px;
border-radius: 3px;
transition: color 0.1s, background 0.1s;
}
.bc-segment:hover { color: var(--accent); background: var(--bg-hover); }
.bc-segment.current { color: var(--fg-bright); cursor: default; }
.bc-segment.current:hover { background: transparent; }
.bc-sep { color: var(--fg-dim); opacity: 0.5; margin: 0 3px; }
#breadcrumb-size { color: var(--fg-dim); flex-shrink: 0; margin-left: 12px; }
#welcome {
flex: 1; display: flex; align-items: center; justify-content: center;
text-align: center; padding: 40px;
flex-direction: column;
gap: 8px;
}
#welcome-name {
font-size: 28px;
font-weight: 700;
color: var(--fg-bright);
letter-spacing: -0.03em;
}
#welcome-message {
font-size: 13px;
color: var(--fg-dim);
font-style: italic;
max-width: 500px;
}
#welcome-hint {
font-size: 12px;
color: var(--fg-dim);
opacity: 0.5;
margin-top: 12px;
}
#owner-card {
display: none;
margin: 8px 10px;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
}
#owner-card.visible { display: block; }
#owner-card-name {
font-size: 12px;
font-weight: 700;
color: var(--fg-bright);
margin-bottom: 6px;
}
#owner-card-key,
#owner-card-recipient,
#owner-card-nostr {
font-size: 10px;
color: var(--fg-dim);
margin-bottom: 2px;
}
#owner-card-key .key-label,
#owner-card-recipient .key-label,
#owner-card-nostr .key-label {
color: var(--fg-dim);
opacity: 0.6;
}
#owner-card-copy {
display: none;
margin-top: 8px;
width: 100%;
padding: 4px 0;
font-family: var(--font-mono);
font-size: 10px;
color: var(--fg-dim);
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
}
#owner-card-copy:hover {
color: var(--fg-bright);
border-color: var(--accent-dim);
}
#content { flex: 1; display: none; flex-direction: column; }
#content.visible { display: flex; }
#code-container {
flex: 1; overflow: auto; padding: 0;
}
#code-container table.code-table {
border-collapse: collapse;
width: 100%;
border-spacing: 0;
}
#code-container table.code-table td {
padding: 0;
vertical-align: top;
border: none;
}
#code-container td.line-gutter {
width: 60px;
min-width: 60px;
text-align: right;
padding-right: 16px;
padding-left: 12px;
color: var(--fg-dim);
opacity: 0.4;
user-select: none;
font-size: 13px;
line-height: 1.7;
white-space: nowrap;
background: var(--bg);
border-right: 1px solid var(--border);
}
#code-container td.line-code {
padding-left: 16px;
padding-right: 20px;
font-size: 13px;
line-height: 1.7;
white-space: pre;
overflow-x: auto;
}
#code-container td.line-code code {
background: none !important;
padding: 0 !important;
}
#code-container pre { margin: 0; background: transparent !important; }
#code-container pre code.hljs { background: transparent !important; padding: 0 !important; }
#markdown-container {
flex: 1; overflow: auto; padding: 32px 48px; max-width: 860px;
display: none; line-height: 1.7;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 15px;
}
#markdown-container.visible { display: block; }
#markdown-container h1, #markdown-container h2, #markdown-container h3,
#markdown-container h4, #markdown-container h5, #markdown-container h6 {
color: var(--fg-bright); margin: 1.4em 0 0.6em; line-height: 1.3;
font-family: var(--font-mono);
}
#markdown-container h1 { font-size: 1.6em; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
#markdown-container h2 { font-size: 1.3em; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
#markdown-container p { margin: 0.8em 0; }
#markdown-container code {
font-family: var(--font-mono); font-size: 0.85em;
background: var(--bg-hover); padding: 2px 6px; border-radius: 3px;
color: var(--fg-bright);
}
#markdown-container pre {
background: var(--bg-surface); padding: 16px; border-radius: 6px;
overflow-x: auto; margin: 1em 0; border: 1px solid var(--border);
}
#markdown-container pre code { background: none; padding: 0; font-size: 13px; color: var(--fg); }
#markdown-container img { max-width: 100%; border-radius: 4px; margin: 8px 0; }
#markdown-container blockquote {
border-left: 3px solid var(--accent); padding-left: 16px;
color: var(--fg-dim); margin: 1em 0;
}
#markdown-container table { border-collapse: collapse; margin: 1em 0; width: 100%; }
#markdown-container th, #markdown-container td {
border: 1px solid var(--border); padding: 8px 12px; text-align: left;
}
#markdown-container th { background: var(--bg-hover); color: var(--fg-bright); }
#markdown-container ul, #markdown-container ol { padding-left: 24px; margin: 0.6em 0; }
#markdown-container li { margin: 0.3em 0; }
#markdown-container a { color: var(--accent); }
#markdown-container hr { border: none; border-top: 1px solid var(--border); margin: 2em 0; }
#asset-container { flex: 1; overflow: auto; padding: 24px; display: none; text-align: center; }
#asset-container.visible { display: block; }
#asset-container img { max-width: 100%; max-height: 80vh; border-radius: 4px; }
#asset-container .asset-link { font-size: 14px; color: var(--accent); margin-top: 16px; display: inline-block; }
#error {
flex: 1; display: none; align-items: center; justify-content: center;
padding: 40px; text-align: center;
}
#error.visible { display: flex; }
#error .error-title { font-size: 16px; color: var(--red); margin-bottom: 8px; }
#error .error-detail { font-size: 12px; color: var(--fg-dim); max-width: 500px; }
#footer {
border-top: 1px solid var(--border);
padding: 6px 20px;
font-size: 11px;
color: var(--fg-dim);
opacity: 0.4;
text-align: center;
flex-shrink: 0;
background: var(--bg);
}
#footer a { color: var(--fg-dim); }
#footer a:hover { color: var(--accent); }
#command-bars {
display: none;
flex-direction: column;
flex-shrink: 0;
background: var(--bg-header);
border-bottom: 1px solid var(--border);
}
#command-bars.visible { display: flex; }
.cmd-bar {
display: flex;
align-items: center;
height: 36px;
padding: 0 20px;
gap: 10px;
font-size: 12px;
}
.cmd-bar + .cmd-bar {
border-top: 1px solid var(--border);
}
.cmd-bar-label {
color: var(--fg-dim);
flex-shrink: 0;
min-width: 24px;
}
.cmd-bar-command {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
color: var(--fg);
padding: 4px 10px;
background: var(--bg);
border-radius: 4px;
border: 1px solid var(--border);
}
.cmd-bar-command .cmd-dim { color: var(--fg-dim); }
.cmd-bar-command .hash-expand { color: var(--fg-dim); }
.cmd-bar-command .hash-expand.expanded .h-full { word-break: keep-all; display: inline; }
.cmd-bar-copy {
flex-shrink: 0;
background: var(--accent);
color: #fff;
border: none;
padding: 4px 14px;
border-radius: 4px;
cursor: pointer;
font-family: var(--font-mono);
font-size: 12px;
transition: background 0.15s;
}
.cmd-bar-copy:hover { background: var(--accent-dim); }
.cmd-bar-copy.copied { background: var(--green); color: #000; }
@media (max-width: 768px) {
:root { --sidebar-width: 240px; }
#sidebar {
position: fixed; top: 0; left: 0; bottom: 0; z-index: 50;
margin-left: calc(-1 * var(--sidebar-width));
}
#sidebar.open { margin-left: 0; }
#sidebar-toggle { display: block; }
#topbar-message { display: none; }
#markdown-container { padding: 16px 20px; }
#welcome-name { font-size: 20px; }
.cmd-bar-label { display: none; }
.cmd-bar { padding: 0 12px; }
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--fg-dim); }
</style>
</head>
<body>
<div id="app">
<div id="topbar">
<div id="topbar-left">
<span id="topbar-logo">void</span>
<span id="topbar-message"></span>
</div>
<div id="topbar-right">
<span class="topbar-pill" id="topbar-branch"></span>
<span class="topbar-dim" id="topbar-hash"></span>
<span class="topbar-dim" id="topbar-date"></span>
<span class="sig-pill unsigned" id="sig-badge" tabindex="0">
Loading...
<span class="sig-detail" id="sig-detail"></span>
</span>
</div>
</div>
<div id="command-bars">
<div class="cmd-bar" id="fork-bar">
<span class="cmd-bar-label">Fork</span>
<span class="cmd-bar-command" id="fork-command"></span>
<button class="cmd-bar-copy" id="fork-copy">Copy</button>
</div>
<div class="cmd-bar" id="pr-bar">
<span class="cmd-bar-label">PR</span>
<span class="cmd-bar-command" id="pr-command"></span>
<button class="cmd-bar-copy" id="pr-copy">Copy</button>
</div>
</div>
<div id="body">
<nav id="sidebar">
<div id="owner-card">
<div id="owner-card-name"></div>
<div id="owner-card-key"></div>
<div id="owner-card-recipient"></div>
<div id="owner-card-nostr"></div>
<button id="owner-card-copy" title="Copy void identity URI">Copy ID</button>
</div>
<div id="file-tree"></div>
</nav>
<button id="sidebar-toggle" aria-label="Toggle sidebar">☰</button>
<div id="main">
<div id="welcome">
<div id="welcome-name"></div>
<div id="welcome-message"></div>
<div id="welcome-hint">Select a file to view</div>
</div>
<div id="content">
<div id="breadcrumb-bar">
<div id="breadcrumb-path"></div>
<span id="breadcrumb-size"></span>
</div>
<div id="code-container"></div>
<div id="markdown-container"></div>
<div id="asset-container"></div>
</div>
<div id="error">
<div>
<div class="error-title" id="error-title"></div>
<div class="error-detail" id="error-detail"></div>
</div>
</div>
</div>
</div>
<div id="footer">
published with void — anonymous encrypted source control
</div>
</div>
<template id="tmpl-hash-expand">
<span class="hash-expand" title="Click to expand">
<span class="h-short"></span>
<span class="h-full"></span>
</span>
</template>
<template id="tmpl-tree-file">
<div class="tree-file">
<span class="file-name"></span>
</div>
</template>
<template id="tmpl-tree-dir">
<div class="tree-dir">
<span class="arrow">▸</span>
<span class="dir-name"></span>
</div>
</template>
<template id="tmpl-sig-row">
<div class="sig-detail-row">
<span class="sig-detail-label"></span>
<span class="sig-detail-value"></span>
</div>
</template>
<script>
(function() {
'use strict';
function decodeCBOR(buffer) {
const view = new DataView(buffer);
let offset = 0;
function readU8() { return view.getUint8(offset++); }
function readU16() { const v = view.getUint16(offset); offset += 2; return v; }
function readU32() { const v = view.getUint32(offset); offset += 4; return v; }
function readU64() {
const hi = view.getUint32(offset);
const lo = view.getUint32(offset + 4);
offset += 8;
return hi * 0x100000000 + lo;
}
function readArgument(additional) {
if (additional < 24) return additional;
if (additional === 24) return readU8();
if (additional === 25) return readU16();
if (additional === 26) return readU32();
if (additional === 27) return readU64();
throw new Error('CBOR: unsupported additional info ' + additional);
}
function decode() {
const initial = readU8();
const major = initial >> 5;
const additional = initial & 0x1f;
switch (major) {
case 0: return readArgument(additional);
case 1: return -1 - readArgument(additional);
case 2: {
const len = readArgument(additional);
const bytes = new Uint8Array(buffer, offset, len);
offset += len;
return bytes.slice();
}
case 3: {
const len = readArgument(additional);
const bytes = new Uint8Array(buffer, offset, len);
offset += len;
return new TextDecoder().decode(bytes);
}
case 4: {
const len = readArgument(additional);
const arr = [];
for (let i = 0; i < len; i++) arr.push(decode());
return arr;
}
case 5: {
const len = readArgument(additional);
const obj = {};
for (let i = 0; i < len; i++) {
const key = decode();
const val = decode();
obj[key] = val;
}
return obj;
}
case 7: {
if (additional === 20) return false;
if (additional === 21) return true;
if (additional === 22) return null;
if (additional === 23) return undefined;
if (additional === 25) { offset += 2; return 0; }
if (additional === 26) { const val = view.getFloat32(offset); offset += 4; return val; }
if (additional === 27) { const val = view.getFloat64(offset); offset += 8; return val; }
return null;
}
default:
throw new Error('CBOR: unsupported major type ' + major + ' at offset ' + (offset - 1));
}
}
return decode();
}
let data = null;
let hljsLoaded = false;
let hljsLoading = false;
let markedLib = null;
let markedLoading = false;
const $topbarLogo = document.getElementById('topbar-logo');
const $topbarMessage = document.getElementById('topbar-message');
const $topbarBranch = document.getElementById('topbar-branch');
const $topbarHash = document.getElementById('topbar-hash');
const $topbarDate = document.getElementById('topbar-date');
const $sigBadge = document.getElementById('sig-badge');
const $sigDetail = document.getElementById('sig-detail');
const $fileTree = document.getElementById('file-tree');
const $welcome = document.getElementById('welcome');
const $welcomeName = document.getElementById('welcome-name');
const $welcomeMessage = document.getElementById('welcome-message');
const $content = document.getElementById('content');
const $breadcrumbBar = document.getElementById('breadcrumb-bar');
const $breadcrumbPath = document.getElementById('breadcrumb-path');
const $breadcrumbSize = document.getElementById('breadcrumb-size');
const $codeContainer = document.getElementById('code-container');
const $markdownContainer = document.getElementById('markdown-container');
const $assetContainer = document.getElementById('asset-container');
const $error = document.getElementById('error');
const $errorTitle = document.getElementById('error-title');
const $errorDetail = document.getElementById('error-detail');
const $sidebar = document.getElementById('sidebar');
const $sidebarToggle = document.getElementById('sidebar-toggle');
const $commandBars = document.getElementById('command-bars');
const $forkCommand = document.getElementById('fork-command');
const $forkCopy = document.getElementById('fork-copy');
const $prCommand = document.getElementById('pr-command');
const $prCopy = document.getElementById('pr-copy');
const $ownerCard = document.getElementById('owner-card');
const $ownerName = document.getElementById('owner-card-name');
const $ownerKey = document.getElementById('owner-card-key');
const $ownerRecipient = document.getElementById('owner-card-recipient');
const $ownerNostr = document.getElementById('owner-card-nostr');
const $ownerCopy = document.getElementById('owner-card-copy');
const tmplHashExpand = document.getElementById('tmpl-hash-expand');
const tmplTreeFile = document.getElementById('tmpl-tree-file');
const tmplTreeDir = document.getElementById('tmpl-tree-dir');
const tmplSigRow = document.getElementById('tmpl-sig-row');
function formatBytes(n) {
if (n < 1024) return n + ' B';
if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
return (n / 1048576).toFixed(1) + ' MB';
}
function formatDate(ts) {
try {
const d = new Date(ts);
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
return months[d.getMonth()] + ' ' + d.getDate() + ' ' + d.getFullYear();
} catch { return ''; }
}
function hexPrefix(hex, len) {
if (!hex) return '???';
const s = typeof hex === 'string' ? hex : bytesToHex(hex);
return s.slice(0, len || 8);
}
function bytesToHex(bytes) {
if (typeof bytes === 'string') return bytes;
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
function extOf(path) {
const dot = path.lastIndexOf('.');
return dot > -1 ? path.slice(dot + 1).toLowerCase() : '';
}
function escHtml(s) {
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
function makeHashSpan(full, prefixLen) {
prefixLen = prefixLen || 8;
var short = full.slice(0, prefixLen) + '...' + full.slice(-6);
return '<span class="hash-expand" title="Click to expand"><span class="h-short">' +
escHtml(short) + '</span><span class="h-full">' + escHtml(full) + '</span></span>';
}
const langMap = {
rs: 'rust', js: 'javascript', ts: 'typescript', jsx: 'javascript', tsx: 'typescript',
py: 'python', rb: 'ruby', go: 'go', java: 'java', c: 'c', cpp: 'cpp',
h: 'c', hpp: 'cpp', cs: 'csharp', swift: 'swift', kt: 'kotlin',
sh: 'bash', bash: 'bash', zsh: 'bash',
html: 'xml', css: 'css', scss: 'scss', less: 'less',
json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'ini', xml: 'xml',
sql: 'sql', md: 'markdown', markdown: 'markdown',
dockerfile: 'dockerfile', makefile: 'makefile',
lua: 'lua', php: 'php', r: 'r', dart: 'dart',
zig: 'rust', nix: 'nix', hs: 'haskell',
ex: 'elixir', exs: 'elixir', erl: 'erlang',
proto: 'protobuf', graphql: 'graphql', gql: 'graphql',
};
function langFor(path) {
const base = path.split('/').pop().toLowerCase();
if (base === 'dockerfile') return 'dockerfile';
if (base === 'makefile' || base === 'gnumakefile') return 'makefile';
if (base === 'cargo.toml' || base === 'cargo.lock') return 'ini';
return langMap[extOf(path)] || null;
}
const imageExts = new Set(['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp', 'avif']);
function isImage(path) { return imageExts.has(extOf(path)); }
function loadHighlightJs() {
if (hljsLoaded || hljsLoading) return;
hljsLoading = true;
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js';
script.onload = function() { hljsLoaded = true; hljsLoading = false; };
script.onerror = function() { hljsLoading = false; };
document.head.appendChild(script);
}
async function decompress(gzBytes) {
const blob = new Blob([gzBytes]);
const ds = new DecompressionStream('gzip');
const stream = blob.stream().pipeThrough(ds);
const resp = new Response(stream);
return await resp.text();
}
function renderHeader(meta) {
const repoName = meta.repo || 'Unnamed Repository';
$topbarLogo.textContent = repoName;
if (meta.message) {
const firstLine = meta.message.split(/\\n|\n/)[0];
$topbarMessage.textContent = firstLine;
$topbarMessage.title = meta.message.replace(/\\n/g, '\n');
}
if (meta.branch) {
$topbarBranch.textContent = meta.branch;
$topbarBranch.style.display = '';
} else {
$topbarBranch.style.display = 'none';
}
if (meta.commit_cid) {
var cidStr = typeof meta.commit_cid === 'string' ? meta.commit_cid : bytesToHex(meta.commit_cid);
$topbarHash.className = 'topbar-dim hash-expand';
$topbarHash.textContent = '';
var hShort = document.createElement('span');
hShort.className = 'h-short';
hShort.textContent = hexPrefix(cidStr, 8);
var hFull = document.createElement('span');
hFull.className = 'h-full';
hFull.textContent = cidStr;
$topbarHash.appendChild(hShort);
$topbarHash.appendChild(hFull);
$topbarHash.title = 'Click to expand';
$topbarHash.addEventListener('click', function() {
$topbarHash.classList.toggle('expanded');
$topbarHash.title = $topbarHash.classList.contains('expanded') ? 'Click to collapse' : 'Click to expand';
});
}
if (meta.timestamp) {
$topbarDate.textContent = formatDate(meta.timestamp);
}
$welcomeName.textContent = repoName;
if (meta.message) {
const firstLine = meta.message.split(/\\n|\n/)[0];
$welcomeMessage.textContent = firstLine;
}
const sig = meta.signature;
if (sig && sig.signing_key) {
$sigBadge.className = 'sig-pill unverified';
updateSigBadgeContent('Verifying...', null);
} else {
$sigBadge.className = 'sig-pill unsigned';
updateSigBadgeContent('Unsigned', null);
}
document.title = repoName + ' \u2014 void publish';
if (meta.content_key && meta.commit_cid) {
var cid = meta.commit_cid;
var key = meta.content_key;
var forkCmd = 'void fork ' + cid + ' --content-key ' + key;
$forkCommand.setAttribute('data-cmd', forkCmd);
$forkCommand.innerHTML = 'void fork ' + makeHashSpan(cid) + ' <span class="cmd-dim">--content-key</span> ' + makeHashSpan(key);
var prName = (meta.branch || 'contribution');
if (meta.identity) {
var m = meta.identity.match(/^void:\/\/([^@]+)@/);
if (m) prName = m[1].toLowerCase() + '/' + prName;
}
var prCmd = 'void pull-request ' + cid + ' --content-key ' + key + ' --name ' + prName;
$prCommand.setAttribute('data-cmd', prCmd);
$prCommand.innerHTML = 'void pull-request ' + makeHashSpan(cid) + ' <span class="cmd-dim">--content-key</span> ' + makeHashSpan(key) + ' <span class="cmd-dim">--name</span> ' + escHtml(prName);
$commandBars.classList.add('visible');
}
renderOwnerCard(meta);
}
function renderOwnerCard(meta) {
var authorName = null;
var identityUri = meta.identity || null;
if (identityUri) {
var match = identityUri.match(/^void:\/\/([^@]+)@/);
if (match) authorName = match[1];
}
var signingKey = null;
if (meta.signature && meta.signature.signing_key) {
signingKey = meta.signature.signing_key;
} else if (meta.author) {
signingKey = meta.author;
}
if (!authorName && !signingKey) return;
if (authorName) {
$ownerName.textContent = authorName;
}
function renderKeyRow(el, labelText, keyHex) {
el.textContent = '';
var label = document.createElement('span');
label.className = 'key-label';
label.textContent = labelText + ' ';
el.appendChild(label);
el.appendChild(expandableHash(keyHex));
}
if (signingKey) {
var keyStr = typeof signingKey === 'string' ? signingKey : bytesToHex(signingKey);
renderKeyRow($ownerKey, 'signing', keyStr);
}
if (identityUri) {
var recipientMatch = identityUri.match(/x25519:([0-9a-f]{64})/i);
if (recipientMatch) {
renderKeyRow($ownerRecipient, 'public', recipientMatch[1]);
}
var nostrMatch = identityUri.match(/nostr:([0-9a-f]{64})/i);
if (nostrMatch) {
renderKeyRow($ownerNostr, 'nostr', nostrMatch[1]);
}
$ownerCopy.style.display = 'block';
$ownerCopy.addEventListener('click', function() {
navigator.clipboard.writeText(identityUri).then(function() {
$ownerCopy.textContent = 'Copied!';
setTimeout(function() { $ownerCopy.textContent = 'Copy ID'; }, 1500);
});
});
}
$ownerCard.classList.add('visible');
}
function updateSigBadgeContent(label, detailFragment) {
const detailEl = $sigDetail;
$sigBadge.textContent = '';
$sigBadge.appendChild(document.createTextNode(label));
detailEl.textContent = '';
if (detailFragment) detailEl.appendChild(detailFragment);
$sigBadge.appendChild(detailEl);
}
function buildSigDetail(keyHex, statusText, statusColor) {
var frag = document.createDocumentFragment();
var keyRow = tmplSigRow.content.cloneNode(true).firstElementChild;
keyRow.querySelector('.sig-detail-label').textContent = 'Key:';
keyRow.querySelector('.sig-detail-value').appendChild(expandableHash(keyHex));
frag.appendChild(keyRow);
var statusRow = tmplSigRow.content.cloneNode(true).firstElementChild;
statusRow.querySelector('.sig-detail-label').textContent = 'Status:';
var sv = statusRow.querySelector('.sig-detail-value');
sv.textContent = statusText;
if (statusColor) sv.style.color = statusColor;
frag.appendChild(statusRow);
return frag;
}
function buildTree(files, assets) {
const root = {};
function insert(path, type) {
const parts = path.split('/');
let node = root;
for (let i = 0; i < parts.length - 1; i++) {
const dir = parts[i];
if (!node[dir]) node[dir] = { __children: {} };
node = node[dir].__children;
}
const fname = parts[parts.length - 1];
node[fname] = { __file: true, __path: path, __type: type };
}
if (files) Object.keys(files).sort().forEach(p => insert(p, 'text'));
if (assets) Object.keys(assets).sort().forEach(p => insert(p, 'asset'));
return root;
}
function renderTree(tree, container, depth) {
const entries = Object.keys(tree).sort((a, b) => {
const aIsDir = tree[a].__children !== undefined;
const bIsDir = tree[b].__children !== undefined;
if (aIsDir && !bIsDir) return -1;
if (!aIsDir && bIsDir) return 1;
return a.localeCompare(b);
});
entries.forEach(name => {
const entry = tree[name];
if (entry.__file) {
const el = tmplTreeFile.content.cloneNode(true).firstElementChild;
el.style.paddingLeft = (12 + depth * 12) + 'px';
el.querySelector('.file-name').textContent = name;
el.dataset.path = entry.__path;
el.dataset.type = entry.__type;
el.addEventListener('click', () => navigateTo(entry.__path));
container.appendChild(el);
} else {
const dirEl = tmplTreeDir.content.cloneNode(true).firstElementChild;
dirEl.style.paddingLeft = (12 + depth * 12) + 'px';
dirEl.querySelector('.dir-name').textContent = name;
const childContainer = document.createElement('div');
childContainer.className = 'tree-children collapsed';
dirEl.addEventListener('click', (e) => {
e.stopPropagation();
const arrow = dirEl.querySelector('.arrow');
const collapsed = childContainer.classList.toggle('collapsed');
arrow.textContent = collapsed ? '\u25B8' : '\u25BE';
});
container.appendChild(dirEl);
container.appendChild(childContainer);
renderTree(entry.__children, childContainer, depth + 1);
}
});
}
function highlightActiveFile(path) {
document.querySelectorAll('.tree-file').forEach(el => {
el.classList.toggle('active', el.dataset.path === path);
});
if (path) {
const activeEl = document.querySelector('.tree-file[data-path="' + CSS.escape(path) + '"]');
if (activeEl) {
let parent = activeEl.parentElement;
while (parent && parent !== $fileTree) {
if (parent.classList.contains('tree-children') && parent.classList.contains('collapsed')) {
parent.classList.remove('collapsed');
const dirEl = parent.previousElementSibling;
if (dirEl) {
const arrow = dirEl.querySelector('.arrow');
if (arrow) arrow.textContent = '\u25BE';
}
}
parent = parent.parentElement;
}
}
}
}
function renderBreadcrumbs(path, fileSize) {
$breadcrumbPath.innerHTML = '';
const parts = path.split('/');
for (let i = 0; i < parts.length; i++) {
if (i > 0) {
const sep = document.createElement('span');
sep.className = 'bc-sep';
sep.textContent = '\u203A';
$breadcrumbPath.appendChild(sep);
}
const seg = document.createElement('span');
seg.className = 'bc-segment';
seg.textContent = parts[i];
if (i === parts.length - 1) {
seg.classList.add('current');
} else {
const dirPath = parts.slice(0, i + 1).join('/');
seg.addEventListener('click', () => expandToDir(dirPath));
}
$breadcrumbPath.appendChild(seg);
}
$breadcrumbSize.textContent = fileSize || '';
$breadcrumbBar.classList.add('visible');
}
function expandToDir(dirPath) {
const parts = dirPath.split('/');
let container = $fileTree;
for (const part of parts) {
const dirs = container.querySelectorAll(':scope > .tree-dir');
for (const dirEl of dirs) {
const nameEl = dirEl.querySelector('.dir-name');
if (nameEl && nameEl.textContent === part) {
const childContainer = dirEl.nextElementSibling;
if (childContainer && childContainer.classList.contains('tree-children')) {
childContainer.classList.remove('collapsed');
const arrow = dirEl.querySelector('.arrow');
if (arrow) arrow.textContent = '\u25BE';
container = childContainer;
}
break;
}
}
}
}
function navigateTo(path) {
window.location.hash = '/' + path;
}
async function showFile(path) {
if (!data) return;
highlightActiveFile(path);
$welcome.style.display = 'none';
$content.classList.add('visible');
$codeContainer.style.display = 'none';
$markdownContainer.style.display = 'none';
$markdownContainer.classList.remove('visible');
$assetContainer.style.display = 'none';
$assetContainer.classList.remove('visible');
$breadcrumbBar.classList.remove('visible');
$error.classList.remove('visible');
const files = data.files || {};
const assets = data.assets || {};
if (files[path]) {
const entry = files[path];
const sizeStr = entry.raw ? formatBytes(entry.raw) : '';
renderBreadcrumbs(path, sizeStr);
try {
const text = await decompress(entry.gz);
const ext = extOf(path);
if (ext === 'md' || ext === 'markdown') {
await renderMarkdown(text, path);
} else {
await renderCode(text, path);
}
} catch (e) {
$codeContainer.style.display = 'block';
$codeContainer.innerHTML = '<pre style="padding:20px;color:var(--red)">Could not decompress file: ' + escHtml(e.message) + '</pre>';
}
} else if (assets[path]) {
const sizeStr = assets[path].size ? formatBytes(assets[path].size) : '';
renderBreadcrumbs(path, sizeStr);
renderAsset(path, assets[path]);
} else {
showError('File not found', 'The file "' + path + '" was not found in this published repository.');
}
}
async function renderCode(text, path) {
const lang = langFor(path);
const lines = text.split('\n');
if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
if (lang && !hljsLoaded && hljsLoading) {
await new Promise(resolve => {
const check = setInterval(() => {
if (hljsLoaded || !hljsLoading) { clearInterval(check); resolve(); }
}, 50);
});
}
const gutterWidth = String(lines.length).length;
let highlighted = false;
let highlightedLines = lines;
if (lang && hljsLoaded && window.hljs) {
try {
const result = window.hljs.highlight(text, { language: lang, ignoreIllegals: true });
highlightedLines = result.value.split('\n');
if (highlightedLines.length > 0 && highlightedLines[highlightedLines.length - 1] === '') {
highlightedLines.pop();
}
highlighted = true;
} catch {
}
}
let html = '<table class="code-table"><tbody>';
for (let i = 0; i < (highlighted ? highlightedLines : lines).length; i++) {
const num = String(i + 1).padStart(gutterWidth, ' ');
const lineContent = highlighted ? highlightedLines[i] : escHtml(lines[i]);
html += '<tr><td class="line-gutter">' + num + '</td><td class="line-code">' + lineContent + '</td></tr>';
}
html += '</tbody></table>';
$codeContainer.style.display = 'block';
$codeContainer.innerHTML = html;
}
async function renderMarkdown(text, filePath) {
const dirPath = filePath.includes('/') ? filePath.slice(0, filePath.lastIndexOf('/')) : '';
if (!markedLib && !markedLoading) {
markedLoading = true;
try {
const mod = await import('https://esm.sh/marked@12.0.0');
markedLib = mod;
} catch {
markedLib = null;
}
markedLoading = false;
}
if (markedLib) {
try {
const marked = new markedLib.Marked();
marked.use({
walkTokens(token) {
if (token.type === 'image') token.href = rewriteImagePath(token.href, dirPath);
if (token.type === 'link') token.href = rewriteLinkPath(token.href, dirPath);
}
});
const html = marked.parse(text);
$markdownContainer.innerHTML = html;
$markdownContainer.style.display = 'block';
$markdownContainer.classList.add('visible');
if (hljsLoaded && window.hljs) {
$markdownContainer.querySelectorAll('pre code').forEach(block => {
window.hljs.highlightElement(block);
});
}
return;
} catch {
}
}
await renderCode(text, filePath);
}
function rewriteImagePath(href, dirPath) {
if (!href || href.startsWith('http://') || href.startsWith('https://') || href.startsWith('data:')) return href;
let resolved = href;
if (href.startsWith('./')) resolved = href.slice(2);
if (dirPath) resolved = dirPath + '/' + resolved;
resolved = normalizePath(resolved);
return './assets/' + resolved;
}
function rewriteLinkPath(href, dirPath) {
if (!href || href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#') || href.startsWith('data:')) return href;
let resolved = href;
if (href.startsWith('./')) resolved = href.slice(2);
if (dirPath && !href.startsWith('/')) resolved = dirPath + '/' + resolved;
resolved = normalizePath(resolved);
const allFiles = data.files || {};
const allAssets = data.assets || {};
if (allFiles[resolved] || allAssets[resolved]) return '#/' + resolved;
return href;
}
function normalizePath(p) {
const parts = p.split('/');
const out = [];
for (const part of parts) {
if (part === '.' || part === '') continue;
if (part === '..' && out.length > 0) { out.pop(); continue; }
out.push(part);
}
return out.join('/');
}
function renderAsset(path, info) {
$assetContainer.innerHTML = '';
$assetContainer.style.display = 'block';
$assetContainer.classList.add('visible');
if (isImage(path)) {
const img = document.createElement('img');
img.src = './assets/' + path;
img.alt = path;
$assetContainer.appendChild(img);
} else {
const link = document.createElement('a');
link.href = './assets/' + path;
link.className = 'asset-link';
link.target = '_blank';
link.rel = 'noopener';
link.textContent = 'Download ' + path.split('/').pop() + (info.size ? ' (' + formatBytes(info.size) + ')' : '');
$assetContainer.appendChild(link);
if (info.mime) {
const mimeEl = document.createElement('div');
mimeEl.style.cssText = 'font-size:12px;color:var(--fg-dim);margin-top:8px;';
mimeEl.textContent = info.mime;
$assetContainer.appendChild(mimeEl);
}
}
}
function showError(title, detail) {
$welcome.style.display = 'none';
$content.classList.remove('visible');
$error.classList.add('visible');
$errorTitle.textContent = title;
$errorDetail.textContent = detail || '';
}
async function verifySignature(meta) {
const sig = meta.signature;
if (!sig || !sig.signing_key || !sig.signature || !sig.signed_data) return;
try {
const ed = await import('https://esm.sh/@noble/ed25519@2.0.0');
let sigBytes = sig.signature;
let keyBytes = sig.signing_key;
let msgBytes = sig.signed_data;
if (typeof sigBytes === 'string') sigBytes = hexToBytes(sigBytes);
if (typeof keyBytes === 'string') keyBytes = hexToBytes(keyBytes);
if (typeof msgBytes === 'string') msgBytes = hexToBytes(msgBytes);
const valid = await ed.verifyAsync(sigBytes, msgBytes, keyBytes);
const keyFull = typeof sig.signing_key === 'string' ? sig.signing_key : bytesToHex(sig.signing_key);
if (valid) {
$sigBadge.className = 'sig-pill signed';
updateSigBadgeContent('\u2713 Signed', buildSigDetail(keyFull, 'Verified client-side', 'var(--green)'));
} else {
$sigBadge.className = 'sig-pill invalid';
updateSigBadgeContent('\u2717 Invalid', buildSigDetail(keyFull, 'Verification failed', 'var(--red)'));
}
} catch {
const keyFallback = typeof sig.signing_key === 'string' ? sig.signing_key : bytesToHex(sig.signing_key);
$sigBadge.className = 'sig-pill unverified';
updateSigBadgeContent('Signed (unverified)', buildSigDetail(keyFallback, 'Could not load verification library', null));
}
}
function expandableHash(fullHex) {
var short = fullHex.slice(0, 8) + '...' + fullHex.slice(-8);
var el = tmplHashExpand.content.cloneNode(true).firstElementChild;
el.querySelector('.h-short').textContent = short;
el.querySelector('.h-full').textContent = fullHex;
el.addEventListener('click', function() {
this.classList.toggle('expanded');
this.title = this.classList.contains('expanded') ? 'Click to collapse' : 'Click to expand';
});
return el;
}
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return bytes;
}
function onHashChange() {
const hash = window.location.hash;
if (hash.startsWith('#/') && hash.length > 2) {
const path = decodeURIComponent(hash.slice(2));
showFile(path);
} else {
$welcome.style.display = 'flex';
$content.classList.remove('visible');
$breadcrumbBar.classList.remove('visible');
$error.classList.remove('visible');
highlightActiveFile(null);
}
}
$sidebarToggle.addEventListener('click', () => {
$sidebar.classList.toggle('open');
});
$fileTree.addEventListener('click', (e) => {
if (e.target.closest('.tree-file')) {
$sidebar.classList.remove('open');
}
});
function setupCopyButton(btn, cmdEl) {
btn.addEventListener('click', function() {
var text = cmdEl.getAttribute('data-cmd') || cmdEl.textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(flash, fallback);
} else {
fallback();
}
function fallback() {
var range = document.createRange();
range.selectNodeContents(cmdEl);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
try { document.execCommand('copy'); flash(); } catch(e) {}
sel.removeAllRanges();
}
function flash() {
btn.textContent = 'Copied!';
btn.classList.add('copied');
setTimeout(function() {
btn.textContent = 'Copy';
btn.classList.remove('copied');
}, 2000);
}
});
}
setupCopyButton($forkCopy, $forkCommand);
setupCopyButton($prCopy, $prCommand);
$commandBars.addEventListener('click', function(e) {
var he = e.target.closest('.hash-expand');
if (he) {
he.classList.toggle('expanded');
he.title = he.classList.contains('expanded') ? 'Click to collapse' : 'Click to expand';
e.stopPropagation();
}
});
function autoSelectFirst() {
const files = data.files || {};
const keys = Object.keys(files).sort();
const readme = keys.find(k => k.toLowerCase() === 'readme.md');
if (readme && !window.location.hash.startsWith('#/')) {
navigateTo(readme);
return;
}
if (window.location.hash.startsWith('#/')) {
onHashChange();
}
}
async function init() {
loadHighlightJs();
try {
const resp = await fetch('./content.cbor');
if (!resp.ok) {
showError('Content not found', 'Could not load content.cbor (HTTP ' + resp.status + ').');
return;
}
const buf = await resp.arrayBuffer();
try {
data = decodeCBOR(buf);
} catch (e) {
showError('Failed to decode content', 'CBOR decode error: ' + e.message);
return;
}
const meta = data.meta || {};
renderHeader(meta);
const tree = buildTree(data.files, data.assets);
renderTree(tree, $fileTree, 0);
window.addEventListener('hashchange', onHashChange);
autoSelectFirst();
verifySignature(meta);
} catch (e) {
showError('Failed to load', 'Network error: ' + e.message);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
</body>
</html>