Skip to content

Commit 76e4099

Browse files
committed
feat(catalogue): static site rendering the live catalogue
Dependency-free page (catalogue/site/index.html) that fetches catalogue.json same-origin and renders one card per app (id, version, description, install command, bundle link; methods when an entry provides them). The Pages deploy workflow is added separately (requires workflow scope on the push token).
1 parent ff8cf81 commit 76e4099

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

catalogue/site/index.html

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Pilot App Store — Catalogue</title>
7+
<meta name="description" content="Apps installable via pilotctl appstore install — the Pilot Protocol app store catalogue." />
8+
<style>
9+
:root {
10+
--bg: #0b0e14; --panel: #131823; --panel2: #1b2230; --line: #273043;
11+
--fg: #e6e9ef; --muted: #93a0b5; --accent: #5b8cff; --ok: #46d39a; --code: #0f1420;
12+
}
13+
* { box-sizing: border-box; }
14+
body {
15+
margin: 0; background: var(--bg); color: var(--fg);
16+
font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
17+
}
18+
header { padding: 56px 24px 28px; max-width: 1040px; margin: 0 auto; }
19+
h1 { font-size: 30px; margin: 0 0 8px; letter-spacing: -0.02em; }
20+
.sub { color: var(--muted); margin: 0; }
21+
.meta { color: var(--muted); font-size: 13px; margin-top: 14px; }
22+
main { max-width: 1040px; margin: 0 auto; padding: 0 24px 64px; }
23+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 18px; }
24+
.card {
25+
background: linear-gradient(180deg, var(--panel), var(--panel2));
26+
border: 1px solid var(--line); border-radius: 14px; padding: 20px 20px 18px;
27+
display: flex; flex-direction: column; gap: 12px;
28+
}
29+
.card h2 { font-size: 18px; margin: 0; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
30+
.ver { color: var(--ok); font-size: 13px; font-weight: 600; background: rgba(70,211,154,.12); padding: 2px 8px; border-radius: 999px; }
31+
.desc { color: var(--fg); margin: 0; }
32+
.label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; margin: 4px 0 2px; }
33+
.methods { display: flex; flex-wrap: wrap; gap: 6px; }
34+
.pill { background: var(--code); border: 1px solid var(--line); border-radius: 8px; padding: 2px 8px; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent); }
35+
.muted { color: var(--muted); font-size: 13px; }
36+
pre { background: var(--code); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; margin: 0; overflow-x: auto; }
37+
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; color: var(--fg); }
38+
.row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
39+
a { color: var(--accent); text-decoration: none; }
40+
a:hover { text-decoration: underline; }
41+
.foot { color: var(--muted); font-size: 13px; margin-top: 36px; border-top: 1px solid var(--line); padding-top: 18px; }
42+
.err { background: #2a1620; border: 1px solid #5a2740; color: #ffb4c4; padding: 16px; border-radius: 12px; }
43+
.sha { font-family: ui-monospace, monospace; font-size: 11px; color: var(--muted); word-break: break-all; }
44+
</style>
45+
</head>
46+
<body>
47+
<header>
48+
<h1>Pilot App Store</h1>
49+
<p class="sub">Apps installable with <code>pilotctl appstore install &lt;id&gt;</code> over the Pilot overlay network.</p>
50+
<p class="meta" id="meta"></p>
51+
</header>
52+
<main>
53+
<div id="content"><p class="muted">Loading catalogue…</p></div>
54+
<p class="foot">
55+
The catalogue is fetched from <code id="src"></code> and is signed (detached ed25519);
56+
<code>pilotctl</code> verifies the signature before any install.
57+
</p>
58+
</main>
59+
<script>
60+
// Same-origin catalogue copied next to this page at deploy time. Override
61+
// with ?src= for local testing against another catalogue URL.
62+
const params = new URLSearchParams(location.search);
63+
const SRC = params.get("src") || "./catalogue.json";
64+
65+
function el(tag, cls, text) {
66+
const e = document.createElement(tag);
67+
if (cls) e.className = cls;
68+
if (text != null) e.textContent = text;
69+
return e;
70+
}
71+
72+
function card(app) {
73+
const c = el("div", "card");
74+
const h = el("h2");
75+
h.appendChild(el("span", null, app.id || "(unknown id)"));
76+
if (app.version) h.appendChild(el("span", "ver", "v" + app.version));
77+
c.appendChild(h);
78+
79+
if (app.description) c.appendChild(el("p", "desc", app.description));
80+
81+
// Methods aren't part of the v1 catalogue schema (they live in each
82+
// app's manifest); render them when an entry provides them.
83+
if (Array.isArray(app.methods) && app.methods.length) {
84+
c.appendChild(el("div", "label", "Methods"));
85+
const m = el("div", "methods");
86+
app.methods.forEach(x => m.appendChild(el("span", "pill", x)));
87+
c.appendChild(m);
88+
}
89+
90+
c.appendChild(el("div", "label", "Install"));
91+
const pre = el("pre");
92+
pre.appendChild(el("code", null, "pilotctl appstore install " + (app.id || "")));
93+
c.appendChild(pre);
94+
95+
if (app.bundle_url) {
96+
const row = el("div", "row");
97+
const a = el("a", null, "bundle ↗");
98+
a.href = app.bundle_url; a.rel = "noopener"; a.target = "_blank";
99+
row.appendChild(a);
100+
if (app.bundle_sha256) row.appendChild(el("span", "sha", app.bundle_sha256.slice(0, 16) + "…"));
101+
c.appendChild(row);
102+
}
103+
return c;
104+
}
105+
106+
async function render() {
107+
const content = document.getElementById("content");
108+
document.getElementById("src").textContent = SRC;
109+
try {
110+
const res = await fetch(SRC, { cache: "no-store" });
111+
if (!res.ok) throw new Error("HTTP " + res.status);
112+
const cat = await res.json();
113+
document.getElementById("meta").textContent =
114+
"Schema v" + cat.version + (cat.updated_at ? " · updated " + cat.updated_at : "") +
115+
" · " + (cat.apps ? cat.apps.length : 0) + " app(s)";
116+
content.textContent = "";
117+
if (!cat.apps || !cat.apps.length) {
118+
content.appendChild(el("p", "muted", "The catalogue is empty."));
119+
return;
120+
}
121+
const grid = el("div", "grid");
122+
cat.apps.forEach(a => grid.appendChild(card(a)));
123+
content.appendChild(grid);
124+
} catch (e) {
125+
content.innerHTML = "";
126+
const box = el("div", "err", "Could not load the catalogue: " + e.message);
127+
content.appendChild(box);
128+
}
129+
}
130+
render();
131+
</script>
132+
</body>
133+
</html>

0 commit comments

Comments
 (0)