diff --git a/.gitignore b/.gitignore
index 9c95c307d..2dc9041de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,6 @@ tests/visual/snapshots/**/*
# IDEs and Editors
.idea
.vscode
+
+# Wrangler local dev state
+.wrangler/
diff --git a/eslint.config.js b/eslint.config.js
index 9ef67c906..e7fdd469c 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -111,6 +111,7 @@ export default [
reportUnusedDisableDirectives: "error",
},
files: ["**/*.js", "**/*.ts", "**/*.svelte"],
+ ignores: ["workers/**/*"],
})),
...svelte.configs["flat/recommended"],
...svelte.configs["flat/prettier"],
@@ -133,12 +134,38 @@ export default [
"@typescript-eslint/no-explicit-any": "error",
},
},
+ // Workers: basic JS linting without TypeScript type-checking.
+ {
+ files: ["workers/**/*.js"],
+ languageOptions: {
+ globals: { ...globals.browser, ...globals.node },
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ "prefer-arrow-callback": "warn",
+ "prefer-const": "warn",
+ "no-const-assign": "error",
+ "no-var": "warn",
+ eqeqeq: "warn",
+ "no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
+ "no-shadow": "warn",
+ "no-throw-literal": "error",
+ "no-implicit-coercion": "warn",
+ "no-param-reassign": "warn",
+ "no-return-assign": "error",
+ "no-sequences": "error",
+ "no-template-curly-in-string": "warn",
+ "prefer-template": "warn",
+ },
+ },
{
ignores: [
"node_modules/**/*",
"build/**/*",
"public/**/*",
"radicle-httpd/**/*",
+ "workers/**/node_modules/**/*",
+ "workers/**/.wrangler/**/*",
],
},
];
diff --git a/index.html b/index.html
index 88ebf9aad..f2f11f1bf 100644
--- a/index.html
+++ b/index.html
@@ -8,23 +8,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
`, ``,
+and related Open Graph tags into every HTML response, pointing to the
+OG image worker.
+
+**open-graph** (`open-graph/index.js`) is a standalone Cloudflare
+Worker that generates PNG cards on the fly using Satori (HTML/CSS to
+SVG) and resvg-wasm (SVG to PNG). It fetches data from the radicle-httpd
+API, generates procedural avatars, and caches results at the edge.
+
+Request flow: request hits app -> og-injector injects og:image URL ->
+social crawler fetches image from open-graph worker -> worker calls
+httpd API, renders PNG, caches and returns it.
+
+## Routes
+
+| URL pattern | Card type |
+|-------------|-----------|
+| `/` | Home (forest image + wordmark) |
+| `/nodes/:host` | Node (avatar + hostname + stats) |
+| `/nodes/:host/users/:did` | User (avatar + alias + DID) |
+| `/nodes/:host/:rid` | Repository (avatar + name + stats + delegates) |
+| `/nodes/:host/:rid/issues` | Issues listing (open + closed counts) |
+| `/nodes/:host/:rid/patches` | Patches listing (open/draft/archived/merged counts) |
+| `/nodes/:host/:rid/history` | Commit history (branch + commit count) |
+| `/nodes/:host/:rid/remotes/:peer/history/:branch?` | Commit history (peer branch) |
+| `/nodes/:host/:rid/issues/:id` | Single issue (state pill + title + author) |
+| `/nodes/:host/:rid/patches/:id` | Single patch (state pill + title + author) |
+| `/nodes/:host/:rid/commits/:sha` | Single commit (title + author/committer gravatars) |
+
+## Fallback chain
+
+When data fetching fails, cards degrade gracefully:
+
+ single issue/patch/commit -> repo card -> node card
+ issues/patches/history listing -> repo card -> node card
+ repo card -> node card
+ node/user/home -> always renders
+
+## Fonts
+
+Two weights of Booton are bundled as worker data assets:
+- `Booton-Regular.ttf` (400) for body text
+- `Booton-SemiBold.ttf` (600) for headings and labels
+
+## Caching
+
+Two layers of caching:
+
+- **open-graph worker**: Uses Cloudflare's Cache API (`caches.default`)
+ at the edge. Responses carry `Cache-Control: public, max-age=120`
+ (2 minutes).
+- **og-injector**: Injected HTML responses carry
+ `Cache-Control: public, max-age=300` (5 minutes).
+
+## Deployment
+
+### og-injector
+
+Deployed as part of the Cloudflare Pages project via `npm run deploy`.
+The `OG_IMAGE_BASE` env var is set to the open-graph worker origin
+(`https://open-graph.radicle.network`).
+
+### open-graph worker
+
+```sh
+npm run deploy:open-graph
+```
+
+For local development:
+
+```sh
+npm run start:open-graph
+```
+
+The worker bundles fonts (`.ttf`), the forest image (`.jpg`), and the
+resvg WASM module as data assets configured in `wrangler.toml`.
diff --git a/workers/og-injector.js b/workers/og-injector.js
new file mode 100644
index 000000000..cf4362503
--- /dev/null
+++ b/workers/og-injector.js
@@ -0,0 +1,166 @@
+function parseRoute(pathname) {
+ const segments = pathname.replace(/^\//, "").split("/");
+ const first = segments[0];
+
+ // Home
+ if (first === "" || first === undefined) {
+ return { type: "home" };
+ }
+
+ if (first !== "nodes" && first !== "seeds") return null;
+
+ const host = segments[1];
+ if (!host) return { type: "home" };
+
+ const rid = segments[2];
+
+ // /nodes/:host
+ if (!rid) return { type: "node", host };
+
+ // /nodes/:host/users/:did
+ if (rid === "users") {
+ const did = segments[3];
+ return did ? { type: "user", host, did } : { type: "node", host };
+ }
+
+ // /nodes/:host/:rid/issues/:id
+ if (segments[3] === "issues" && segments[4]) {
+ return { type: "issue", host, rid, id: segments[4] };
+ }
+ // /nodes/:host/:rid/issues
+ if (segments[3] === "issues") {
+ return { type: "issues", host, rid };
+ }
+ // /nodes/:host/:rid/patches/:id
+ if (segments[3] === "patches" && segments[4]) {
+ return { type: "patch", host, rid, id: segments[4] };
+ }
+ // /nodes/:host/:rid/patches
+ if (segments[3] === "patches") {
+ return { type: "patches", host, rid };
+ }
+ // /nodes/:host/:rid/commits/:sha
+ if (segments[3] === "commits" && segments[4]) {
+ return { type: "commit", host, rid, sha: segments[4] };
+ }
+
+ // /nodes/:host/:rid/history/:branch
+ if (segments[3] === "history") {
+ return { type: "history", host, rid };
+ }
+
+ // /nodes/:host/:rid/remotes/:peer/history/:branch
+ if (segments[3] === "remotes" && segments[4] && segments[5] === "history") {
+ return { type: "history", host, rid };
+ }
+
+ // /nodes/:host/:rid — source, tree, etc. share the repo card
+ return { type: "repo", host, rid };
+}
+
+function shortenDid(did) {
+ const match = /^did:key:(z[a-zA-Z0-9]+)$/.exec(did);
+ if (!match) return did;
+ const pubkey = match[1];
+ return `${pubkey.substring(0, 8)}…${pubkey.slice(-8)}`;
+}
+
+function titleForRoute(route) {
+ switch (route.type) {
+ case "home":
+ return "Radicle Explorer · Decentralized Code Collaboration";
+ case "node":
+ return `Radicle Seed Node · ${route.host}`;
+ case "user":
+ return `Radicle User · ${shortenDid(route.did)} · ${route.host}`;
+ case "repo":
+ return `Radicle Repo · ${route.rid} · ${route.host}`;
+ case "issues":
+ return `Issues · ${route.rid} · ${route.host}`;
+ case "patches":
+ return `Patches · ${route.rid} · ${route.host}`;
+ case "issue":
+ return `Issue ${route.id.slice(0, 7)} · ${route.rid} · ${route.host}`;
+ case "patch":
+ return `Patch ${route.id.slice(0, 7)} · ${route.rid} · ${route.host}`;
+ case "commit":
+ return `Commit ${route.sha.slice(0, 7)} · ${route.rid} · ${route.host}`;
+ case "history":
+ return `Commit History · ${route.rid} · ${route.host}`;
+ default:
+ return "Radicle Explorer · Decentralized Code Collaboration";
+ }
+}
+
+function descriptionForRoute(route) {
+ const h = route.host;
+ switch (route.type) {
+ case "home":
+ return "Explore open-source repositories, issues, and patches on the Radicle peer-to-peer code collaboration network. Sovereign hosting without central servers.";
+ case "node":
+ return `Browse repositories hosted on the ${h} Radicle seed node. Explore source code, issues, patches, and contributor activity.`;
+ case "user":
+ return `Radicle user profile on ${h}. Browse repositories, patches, and contributions across the peer-to-peer network.`;
+ case "repo":
+ return `Radicle repository hosted on ${h}. Browse source code, issues, patches, commit history, and contributor activity.`;
+ case "issues":
+ return `Browse issues for this repository on ${h}. View bug reports, feature requests, and ongoing discussions on the Radicle network.`;
+ case "patches":
+ return `Browse patches for this repository on ${h}. View proposed changes, code reviews, and merge status on the Radicle network.`;
+ case "issue":
+ return `View this issue and its discussion in a Radicle repository on ${h}. Track progress, comments, and resolution.`;
+ case "patch":
+ return `View this patch and its review in a Radicle repository on ${h}. Browse revisions, review comments, and merge status.`;
+ case "commit":
+ return `View this commit and its diff in a Radicle repository on ${h}. Inspect changed files, author details, and additions.`;
+ case "history":
+ return `Browse the commit history of this Radicle repository on ${h}. View commits, contributors, and development activity.`;
+ default:
+ return "Explore open-source repositories, issues, and patches on the Radicle peer-to-peer code collaboration network.";
+ }
+}
+
+export default {
+ async fetch(request, env) {
+ const url = new URL(request.url);
+ const route = parseRoute(url.pathname);
+
+ if (!route) {
+ return env.ASSETS.fetch(request);
+ }
+
+ const hostParam = route.type === "home" ? `?host=${url.hostname}` : "";
+ const ogImageUrl = `${env.OG_IMAGE_BASE}${url.pathname}${hostParam}`;
+ const title = titleForRoute(route);
+ const description = descriptionForRoute(route);
+
+ const indexResponse = await env.ASSETS.fetch(
+ new Request(new URL("/", url)),
+ );
+ const html = await indexResponse.text();
+
+ const tags = [
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ].join("\n ");
+
+ const injected = html.replace("", ` ${tags}\n`);
+
+ return new Response(injected, {
+ status: indexResponse.status,
+ headers: {
+ "content-type": "text/html;charset=UTF-8",
+ "cache-control": "public, max-age=300",
+ },
+ });
+ },
+};
diff --git a/workers/open-graph/Booton-Regular.ttf b/workers/open-graph/Booton-Regular.ttf
new file mode 100644
index 000000000..cdb062cef
Binary files /dev/null and b/workers/open-graph/Booton-Regular.ttf differ
diff --git a/workers/open-graph/Booton-SemiBold.ttf b/workers/open-graph/Booton-SemiBold.ttf
new file mode 100644
index 000000000..20db63833
Binary files /dev/null and b/workers/open-graph/Booton-SemiBold.ttf differ
diff --git a/workers/open-graph/forest.jpg b/workers/open-graph/forest.jpg
new file mode 100644
index 000000000..a40b73fd5
Binary files /dev/null and b/workers/open-graph/forest.jpg differ
diff --git a/workers/open-graph/index.js b/workers/open-graph/index.js
new file mode 100644
index 000000000..0ac9842bb
--- /dev/null
+++ b/workers/open-graph/index.js
@@ -0,0 +1,2284 @@
+import satori from "satori";
+import { Resvg, initWasm } from "@resvg/resvg-wasm";
+import resvgWasm from "@resvg/resvg-wasm/index_bg.wasm";
+import bootonRegular from "./Booton-Regular.ttf";
+import bootonSemiBold from "./Booton-SemiBold.ttf";
+import forestJpg from "./forest.jpg";
+
+const WIDTH = 1200;
+const HEIGHT = 630;
+
+const C = {
+ bg: "#ffffff", // --color-surface-canvas
+ text: "#0b0d12", // --color-text-primary
+ textSub: "#3a3f49", // --color-text-secondary
+ textMid: "#5a5f6b", // --color-text-tertiary
+ textMuted: "#7a8190", // --color-text-quaternary
+};
+
+let initPromise = null;
+let font = null;
+let forestDataUri = null;
+
+function ensureInit() {
+ if (!initPromise) {
+ initPromise = initWasm(resvgWasm).then(() => {
+ font = [
+ { name: "Booton", data: bootonRegular, weight: 400, style: "normal" },
+ { name: "Booton", data: bootonSemiBold, weight: 600, style: "normal" },
+ ];
+ const bytes = new Uint8Array(forestJpg);
+ let binary = "";
+ for (let i = 0; i < bytes.length; i++)
+ binary += String.fromCharCode(bytes[i]);
+ forestDataUri = `data:image/jpeg;base64,${btoa(binary)}`;
+ });
+ }
+ return initPromise;
+}
+
+function parseRoute(pathname) {
+ const path = pathname;
+ const segments = path.replace(/^\//, "").split("/");
+ const first = segments[0];
+
+ if (first === "" || first === undefined) return { type: "home" };
+ if (first !== "nodes" && first !== "seeds") return null;
+
+ const host = segments[1];
+ if (!host) return { type: "home" };
+
+ const rid = segments[2];
+ if (!rid) return { type: "node", host };
+
+ if (rid === "users") {
+ const did = segments[3];
+ return did ? { type: "user", host, did } : { type: "node", host };
+ }
+
+ if (segments[3] === "issues" && segments[4])
+ return { type: "issue", host, rid, id: segments[4] };
+ if (segments[3] === "issues") return { type: "issues", host, rid };
+ if (segments[3] === "patches" && segments[4])
+ return { type: "patch", host, rid, id: segments[4] };
+ if (segments[3] === "patches") return { type: "patches", host, rid };
+ if (segments[3] === "commits" && segments[4])
+ return { type: "commit", host, rid, sha: segments[4] };
+
+ // /nodes/:host/:rid/history/:branch
+ if (segments[3] === "history")
+ return { type: "history", host, rid, branch: segments[4] };
+
+ // /nodes/:host/:rid/remotes/:peer/history/:branch
+ if (segments[3] === "remotes" && segments[4] && segments[5] === "history") {
+ return {
+ type: "history",
+ host,
+ rid,
+ peer: segments[4],
+ branch: segments[6],
+ };
+ }
+
+ return { type: "repo", host, rid };
+}
+
+function apiBase(host) {
+ const local = host.startsWith("localhost") || host.startsWith("127.");
+ return `${local ? "http" : "https"}://${host}/api/v1`;
+}
+
+async function apiFetch(url) {
+ const res = await fetch(url);
+ if (!res.ok) return null;
+ return res.json();
+}
+
+const fetchRepo = (host, rid) => apiFetch(`${apiBase(host)}/repos/${rid}`);
+const fetchIssue = (host, rid, id) =>
+ apiFetch(`${apiBase(host)}/repos/${rid}/issues/${id}`);
+const fetchPatch = (host, rid, id) =>
+ apiFetch(`${apiBase(host)}/repos/${rid}/patches/${id}`);
+const fetchCommit = (host, rid, sha) =>
+ apiFetch(`${apiBase(host)}/repos/${rid}/commits/${sha}`);
+const fetchTreeStats = (host, rid, sha) =>
+ apiFetch(`${apiBase(host)}/repos/${rid}/stats/tree/${sha}`);
+const fetchRemote = (host, rid, peer) =>
+ apiFetch(`${apiBase(host)}/repos/${rid}/remotes/${peer}`);
+const fetchNode = host => apiFetch(`${apiBase(host)}/node`);
+const fetchNodeStats = host => apiFetch(`${apiBase(host)}/stats`);
+const fetchNodeIdentity = (host, nid) =>
+ apiFetch(`${apiBase(host)}/nodes/${nid}`);
+
+function md5(str) {
+ function rotl(v, n) {
+ return (v << n) | (v >>> (32 - n));
+ }
+ const K = [],
+ S = [];
+ for (let i = 0; i < 64; i++)
+ K[i] = Math.floor(4294967296 * Math.abs(Math.sin(i + 1)));
+ S.push(7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22);
+ S.push(5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20);
+ S.push(4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23);
+ S.push(6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21);
+
+ const bytes = new Uint8Array(new TextEncoder().encode(str));
+ const bitLen = bytes.length * 8;
+ const padLen = (bytes.length % 64 < 56 ? 56 : 120) - (bytes.length % 64);
+ const padded = new Uint8Array(bytes.length + padLen + 8);
+ padded.set(bytes);
+ padded[bytes.length] = 0x80;
+ const dv = new DataView(padded.buffer);
+ dv.setUint32(padded.length - 8, bitLen & 0xffffffff, true);
+ dv.setUint32(padded.length - 4, Math.floor(bitLen / 4294967296), true);
+
+ let a0 = 0x67452301,
+ b0 = 0xefcdab89,
+ c0 = 0x98badcfe,
+ d0 = 0x10325476;
+ for (let off = 0; off < padded.length; off += 64) {
+ const M = [];
+ for (let j = 0; j < 16; j++) M[j] = dv.getUint32(off + j * 4, true);
+ let A = a0,
+ B = b0,
+ H = c0,
+ D = d0;
+ for (let i = 0; i < 64; i++) {
+ let F, g;
+ if (i < 16) {
+ F = (B & H) | (~B & D);
+ g = i;
+ } else if (i < 32) {
+ F = (D & B) | (~D & H);
+ g = (5 * i + 1) % 16;
+ } else if (i < 48) {
+ F = B ^ H ^ D;
+ g = (3 * i + 5) % 16;
+ } else {
+ F = H ^ (B | ~D);
+ g = (7 * i) % 16;
+ }
+ const tmp = D;
+ D = H;
+ H = B;
+ B = (B + rotl((A + F + K[i] + M[g]) | 0, S[i])) | 0;
+ A = tmp;
+ }
+ a0 = (a0 + A) | 0;
+ b0 = (b0 + B) | 0;
+ c0 = (c0 + H) | 0;
+ d0 = (d0 + D) | 0;
+ }
+ const hex = v =>
+ Array.from(new Uint8Array(new Int32Array([v]).buffer))
+ .map(b => b.toString(16).padStart(2, "0"))
+ .join("");
+ return hex(a0) + hex(b0) + hex(c0) + hex(d0);
+}
+
+function gravatarUrl(email) {
+ return `https://www.gravatar.com/avatar/${md5(email.trim().toLowerCase())}?s=64`;
+}
+
+async function fetchGravatarDataUri(email) {
+ if (!email) return null;
+ try {
+ const res = await fetch(gravatarUrl(email));
+ if (!res.ok) return null;
+ const buf = await res.arrayBuffer();
+ const bytes = new Uint8Array(buf);
+ let binary = "";
+ for (let i = 0; i < bytes.length; i++)
+ binary += String.fromCharCode(bytes[i]);
+ const contentType = res.headers.get("content-type") || "image/jpeg";
+ return `data:${contentType};base64,${btoa(binary)}`;
+ } catch {
+ return null;
+ }
+}
+
+function gravatarImg(dataUri, size = 28) {
+ if (!dataUri) {
+ // Gray placeholder box when gravatar fetch fails.
+ return el("div", {
+ width: `${size}px`,
+ height: `${size}px`,
+ backgroundColor: "#e1e4e8",
+ flexShrink: "0",
+ });
+ }
+ return {
+ type: "img",
+ props: {
+ src: dataUri,
+ width: size,
+ height: size,
+ style: { display: "flex", flexShrink: "0" },
+ },
+ };
+}
+
+function repoPayload(repo) {
+ return repo?.payloads?.["xyz.radicle.project"];
+}
+
+function authorLabel(author) {
+ if (!author) return null;
+ return author.alias || author.id.slice(0, 7);
+}
+
+function formatCount(n) {
+ if (n >= 1000) return `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k`;
+ return String(n);
+}
+
+function shortId(id) {
+ return id.slice(0, 7);
+}
+
+function shortDid(did) {
+ const prefix = "did:key:";
+ if (!did.startsWith(prefix)) return did;
+ const key = did.slice(prefix.length);
+ return `${prefix}${key.slice(0, 6)}\u2026${key.slice(-6)}`;
+}
+
+function nidFromDid(did) {
+ return did.startsWith("did:key:") ? did.slice(8) : did;
+}
+
+const AVATAR_PALETTE = [
+ "#00D4DA",
+ "#886BF2",
+ "#FFA5FF",
+ "#009F67",
+ "#CCFF38",
+ "#585600",
+];
+
+function _hash32(str) {
+ let h = 2166136261 >>> 0;
+ for (let i = 0; i < str.length; i++) {
+ h ^= str.charCodeAt(i);
+ h = Math.imul(h, 16777619);
+ }
+ return h >>> 0;
+}
+
+function _xmur3(str) {
+ let h = 1779033703 ^ str.length;
+ for (let i = 0; i < str.length; i++) {
+ h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
+ h = (h << 13) | (h >>> 19);
+ }
+ return function () {
+ h = Math.imul(h ^ (h >>> 16), 2246822507);
+ h = Math.imul(h ^ (h >>> 13), 3266489909);
+ return (h ^= h >>> 16) >>> 0;
+ };
+}
+
+function _mulberry32(a) {
+ return function () {
+ // eslint-disable-next-line no-param-reassign
+ let t = (a += 0x6d2b79f5);
+ t = Math.imul(t ^ (t >>> 15), t | 1);
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
+ };
+}
+
+function _makeRNG(key) {
+ return _mulberry32(_xmur3(key)());
+}
+function _chooseK(arr, k, rnd) {
+ const pool = arr.slice(),
+ out = [];
+ for (let i = 0; i < k; i++) {
+ const idx = Math.floor(rnd() * pool.length);
+ out.push(pool.splice(idx, 1)[0]);
+ }
+ return out;
+}
+function _pick(rng, arr) {
+ return arr[Math.floor(rng() * arr.length)];
+}
+
+function _makeSvgCtx() {
+ const elements = [];
+ return {
+ fillRect(x, y, w, h, fill) {
+ elements.push({
+ type: "rect",
+ props: { x, y, width: w, height: h, fill },
+ });
+ },
+ fillCircle(cx, cy, diameter, fill) {
+ elements.push({
+ type: "circle",
+ props: { cx, cy, r: diameter / 2, fill },
+ });
+ },
+ fillEllipse(cx, cy, w, h, fill) {
+ elements.push({
+ type: "ellipse",
+ props: { cx, cy, rx: w / 2, ry: h / 2, fill },
+ });
+ },
+ elements,
+ };
+}
+
+function _makeAtomRenderer(cellSize, circleB, circleC, useEllipse) {
+ function drawCircleOrEllipse(ctx, x, y, w, h, fill) {
+ if (useEllipse) ctx.fillEllipse(x, y, w, h, fill);
+ else ctx.fillCircle(x, y, w, fill);
+ }
+ return {
+ drawAtomA(ctx, gx, gy, c1) {
+ ctx.fillRect(gx * cellSize, gy * cellSize, cellSize, cellSize, c1);
+ },
+ drawAtomB(ctx, gx, gy, c1, c2) {
+ const x = gx * cellSize,
+ y = gy * cellSize;
+ ctx.fillRect(x, y, cellSize, cellSize, c1);
+ drawCircleOrEllipse(
+ ctx,
+ x + cellSize / 2,
+ y + cellSize / 2,
+ circleB,
+ circleB,
+ c2,
+ );
+ },
+ drawAtomC(ctx, gx, gy, c2, c3) {
+ const x = gx * cellSize,
+ y = gy * cellSize;
+ ctx.fillRect(x, y, cellSize, cellSize, c2);
+ drawCircleOrEllipse(
+ ctx,
+ x + cellSize / 2,
+ y + cellSize / 2,
+ circleC,
+ circleC,
+ c3,
+ );
+ },
+ drawAtomD(ctx, gx, gy, c3) {
+ ctx.fillRect(gx * cellSize, gy * cellSize, cellSize, cellSize, c3);
+ },
+ drawAtom(ctx, atom, gx, gy, c1, c2, c3) {
+ if (atom === "A") this.drawAtomA(ctx, gx, gy, c1);
+ else if (atom === "B") this.drawAtomB(ctx, gx, gy, c1, c2);
+ else if (atom === "C") this.drawAtomC(ctx, gx, gy, c2, c3);
+ else this.drawAtomD(ctx, gx, gy, c3);
+ },
+ };
+}
+
+const LETTER_5X7 = (() => {
+ const L = {};
+ const r = s => s.map(row => row.split("").map(ch => (ch === "1" ? 1 : 0)));
+ L["A"] = r(["01110", "10001", "10001", "11111", "10001", "10001", "10001"]);
+ L["B"] = r(["11110", "10001", "10001", "11110", "10001", "10001", "11110"]);
+ L["C"] = r(["01111", "10000", "10000", "10000", "10000", "10000", "01111"]);
+ L["D"] = r(["11110", "10001", "10001", "10001", "10001", "10001", "11110"]);
+ L["E"] = r(["11111", "10000", "10000", "11110", "10000", "10000", "11111"]);
+ L["F"] = r(["11111", "10000", "10000", "11110", "10000", "10000", "10000"]);
+ L["G"] = r(["01111", "10000", "10000", "10111", "10001", "10001", "01111"]);
+ L["H"] = r(["10001", "10001", "10001", "11111", "10001", "10001", "10001"]);
+ L["I"] = r(["11111", "00100", "00100", "00100", "00100", "00100", "11111"]);
+ L["J"] = r(["11111", "00001", "00001", "00001", "10001", "10001", "01110"]);
+ L["K"] = r(["10001", "10010", "10100", "11000", "10100", "10010", "10001"]);
+ L["L"] = r(["10000", "10000", "10000", "10000", "10000", "10000", "11111"]);
+ L["M"] = r(["10001", "11011", "10101", "10001", "10001", "10001", "10001"]);
+ L["N"] = r(["10001", "11001", "10101", "10011", "10001", "10001", "10001"]);
+ L["O"] = r(["01110", "10001", "10001", "10001", "10001", "10001", "01110"]);
+ L["P"] = r(["11110", "10001", "10001", "11110", "10000", "10000", "10000"]);
+ L["Q"] = r(["01110", "10001", "10001", "10001", "10101", "10010", "01101"]);
+ L["R"] = r(["11110", "10001", "10001", "11110", "10100", "10010", "10001"]);
+ L["S"] = r(["01111", "10000", "11110", "00001", "00001", "10001", "11110"]);
+ L["T"] = r(["11111", "00100", "00100", "00100", "00100", "00100", "00100"]);
+ L["U"] = r(["10001", "10001", "10001", "10001", "10001", "10001", "01110"]);
+ L["V"] = r(["10001", "10001", "10001", "01010", "01010", "00100", "00100"]);
+ L["W"] = r(["10001", "10001", "10001", "10101", "10101", "11011", "10001"]);
+ L["X"] = r(["10001", "01010", "00100", "00100", "00100", "01010", "10001"]);
+ L["Y"] = r(["10001", "01010", "00100", "00100", "00100", "00100", "00100"]);
+ L["Z"] = r(["11111", "00001", "00010", "00100", "01000", "10000", "11111"]);
+ L["0"] = r(["01110", "10001", "10011", "10101", "11001", "10001", "01110"]);
+ L["1"] = r(["00100", "01100", "00100", "00100", "00100", "00100", "01110"]);
+ L["2"] = r(["01110", "10001", "00001", "00110", "01000", "10000", "11111"]);
+ L["3"] = r(["11110", "00001", "01110", "00001", "00001", "00001", "11110"]);
+ L["4"] = r(["10010", "10010", "10010", "11111", "00010", "00010", "00010"]);
+ L["5"] = r(["11111", "10000", "11110", "00001", "00001", "00001", "11110"]);
+ L["6"] = r(["01110", "10000", "11110", "10001", "10001", "10001", "01110"]);
+ L["7"] = r(["11111", "00001", "00010", "00100", "01000", "01000", "01000"]);
+ L["8"] = r(["01110", "10001", "01110", "10001", "10001", "10001", "01110"]);
+ L["9"] = r(["01110", "10001", "10001", "01111", "00001", "00001", "11110"]);
+ L["?"] = r(["11111", "00001", "01110", "00000", "00100", "00000", "00100"]);
+ return L;
+})();
+
+function _getInitials(name) {
+ if (!name || typeof name !== "string") return ["?"];
+ const parts = name
+ .trim()
+ .replace(/\s+/g, " ")
+ .split(/[^A-Za-z0-9]+/)
+ .filter(Boolean);
+ const first = parts[0] ? parts[0][0].toUpperCase() : "?";
+ const second = parts[1] ? parts[1][0].toUpperCase() : null;
+ return second ? [first, second] : [first];
+}
+
+function _polarFromCell(gx, gy, cx, cy) {
+ const x = gx - cx + 0.5,
+ y = gy - cy + 0.5;
+ let a = Math.atan2(y, x);
+ if (a < 0) a += 2 * Math.PI;
+ return { r: Math.hypot(x, y), a };
+}
+
+function _shapeRose(theta, petals, tol) {
+ const sector = Math.PI / petals;
+ const diff = Math.abs(theta - Math.round(theta / sector) * sector);
+ return Math.min(diff, 2 * Math.PI - diff) <= tol;
+}
+function _shapeStarburst(theta, petals, softness) {
+ const period = (2 * Math.PI) / petals;
+ const local = theta % period;
+ return (
+ Math.pow(
+ Math.cos(
+ ((Math.min(local, period - local) / (period / 2)) * Math.PI) / 2,
+ ),
+ softness,
+ ) > 0.5
+ );
+}
+function _shapeRinged(theta, petals, ringPhase, tol) {
+ const sector = (2 * Math.PI) / petals;
+ const diff = Math.abs(
+ theta - (Math.floor(theta / sector) * sector + sector * ringPhase),
+ );
+ return Math.min(diff, 2 * Math.PI - diff) <= tol;
+}
+function _shapeTip(theta, petals, tol, t) {
+ return _shapeRose(theta, petals, tol * (0.5 + t)) && t > 0.45;
+}
+function _shapeNotched(theta, petals, tol) {
+ const sector = (2 * Math.PI) / petals;
+ return (
+ _shapeRose(theta, petals, tol) &&
+ Math.abs((theta % sector) / sector - 0.5) > 0.25
+ );
+}
+function _shapeHollow(theta, petals, tol, t) {
+ return _shapeRose(theta, petals, tol) && t > 0.28 && t < 0.92;
+}
+function _sectorGate(theta, petals, mask) {
+ return mask[Math.floor(theta / ((2 * Math.PI) / petals)) % mask.length];
+}
+function _makeAssigner(mode, activeAtoms) {
+ if (mode === "bands-ABC") return rCell => activeAtoms[rCell % 3];
+ if (mode === "angle-stripes")
+ return (_r, _t, sIdx) => activeAtoms[(sIdx || 0) % activeAtoms.length];
+ if (mode === "parity-ACB") return rCell => activeAtoms[rCell % 2 ? 1 : 0];
+ return (rCell, theta, sIdx) => {
+ const v =
+ (Math.sin((theta || 0) * 13.37 + rCell * 2.17 + (sIdx || 0) * 0.73) + 1) /
+ 2;
+ return v < 0.33
+ ? activeAtoms[0]
+ : v < 0.66
+ ? activeAtoms[1]
+ : activeAtoms[2];
+ };
+}
+
+function _avatarSvgNode(elements, logicalSize, renderSize) {
+ return {
+ type: "div",
+ props: {
+ style: {
+ display: "flex",
+ width: renderSize,
+ height: renderSize,
+ flexShrink: 0,
+ },
+ children: {
+ type: "svg",
+ props: {
+ width: renderSize,
+ height: renderSize,
+ viewBox: `0 0 ${logicalSize} ${logicalSize}`,
+ style: { display: "flex" },
+ children: elements,
+ },
+ },
+ },
+ };
+}
+
+function repoAvatarSvg(key, size = 64) {
+ const GRID = 16,
+ CELL = 32,
+ W = GRID * CELL;
+ const ctx = _makeSvgCtx();
+ const atoms = _makeAtomRenderer(CELL, CELL * 0.55, CELL * 0.67, false);
+
+ const initials = _getInitials(key);
+ const seed = _hash32(key.toLowerCase());
+ const rnd = _mulberry32(seed);
+ const [c1, c2, c3] = _chooseK(AVATAR_PALETTE, 3, rnd);
+ const letterSolidAtom = ((seed >>> 7) & 1) === 0 ? "A" : "D";
+ const bgAtoms = ["A", "B", "C", "D"].filter(a => a !== letterSolidAtom);
+
+ for (let gy = 0; gy < GRID; gy++) {
+ for (let gx = 0; gx < GRID; gx++) {
+ const k = (gy * 131 + gx * 197 + seed) >>> 0;
+ atoms.drawAtom(ctx, bgAtoms[k % bgAtoms.length], gx, gy, c1, c2, c3);
+ }
+ }
+
+ const GW = 5,
+ GH = 7,
+ SPACING = 2;
+ function placeLetter(glyph, startX, startY, scale2x) {
+ for (let r = 0; r < GH; r++) {
+ for (let c = 0; c < GW; c++) {
+ if (!glyph[r][c]) continue;
+ if (scale2x) {
+ const gx = startX + c * 2,
+ gy = startY + r * 2;
+ for (let dy = 0; dy < 2; dy++)
+ for (let dx = 0; dx < 2; dx++) {
+ if (letterSolidAtom === "A")
+ atoms.drawAtomA(ctx, gx + dx, gy + dy, c1);
+ else atoms.drawAtomD(ctx, gx + dx, gy + dy, c3);
+ }
+ } else {
+ const gx = startX + c,
+ gy = startY + r;
+ if (letterSolidAtom === "A") atoms.drawAtomA(ctx, gx, gy, c1);
+ else atoms.drawAtomD(ctx, gx, gy, c3);
+ }
+ }
+ }
+ }
+
+ if (initials.length === 1) {
+ placeLetter(
+ LETTER_5X7[initials[0]] || LETTER_5X7["?"],
+ Math.floor((GRID - 10) / 2),
+ Math.floor((GRID - 14) / 2),
+ true,
+ );
+ } else {
+ const startX = Math.floor((GRID - (GW * 2 + SPACING)) / 2);
+ const startY = Math.floor((GRID - GH) / 2);
+ placeLetter(
+ LETTER_5X7[initials[0]] || LETTER_5X7["?"],
+ startX,
+ startY,
+ false,
+ );
+ placeLetter(
+ LETTER_5X7[initials[1]] || LETTER_5X7["?"],
+ startX + GW + SPACING,
+ startY,
+ false,
+ );
+ }
+
+ return _avatarSvgNode(ctx.elements, W, size);
+}
+
+function userAvatarSvg(key, size = 64) {
+ const TILE = 32,
+ GRID = 10,
+ W = GRID * TILE;
+ const ctx = _makeSvgCtx();
+ const atoms = _makeAtomRenderer(TILE, 17, 21, true);
+
+ const rng = _makeRNG(key);
+ const [c1, c2, c3] = AVATAR_PALETTE.slice()
+ .sort(() => rng() - 0.5)
+ .slice(0, 3);
+ const allAtoms = ["A", "B", "C", "D"];
+ const bgAtom = _pick(rng, allAtoms);
+ const petalAtoms = allAtoms.filter(a => a !== bgAtom).sort(() => rng() - 0.5);
+
+ const cx = Math.floor(GRID / 2),
+ cy = Math.floor(GRID / 2);
+ const maxR = Math.min(cx, cy);
+ const petals = _pick(rng, [5, 6, 7, 8, 9, 10]);
+ const petalDepth = Math.max(5, Math.floor(maxR * (0.6 + 0.35 * rng())));
+ const radialThickness = _pick(rng, [1, 2, 2, 3, 3]);
+ const shapeModel = _pick(rng, [
+ "rose",
+ "starburst",
+ "ringed",
+ "tip",
+ "notched",
+ "hollow",
+ ]);
+ const atomMode = _pick(rng, [
+ "bands-ABC",
+ "angle-stripes",
+ "parity-ACB",
+ "balanced-rand",
+ ]);
+ const assignAtom = _makeAssigner(atomMode, petalAtoms);
+ const angleTolNear = (Math.PI / 28) * (0.7 + rng() * 0.5);
+ const angleTolFar = (Math.PI / 7) * (0.7 + rng() * 0.5);
+ const softness = 1.2 + rng() * 3.0;
+ const ringPhase = 0.2 + rng() * 0.6;
+ const sectorMask = Array.from({ length: petals }, () => rng() > 0.3);
+ if (sectorMask.every(v => !v)) sectorMask[Math.floor(petals / 2)] = true;
+
+ function drawAt(type, gx, gy) {
+ if (gx >= 0 && gy >= 0 && gx < GRID && gy < GRID)
+ atoms.drawAtom(ctx, type, gx, gy, c1, c2, c3);
+ }
+ function drawQuad(type, gx, gy) {
+ const N = GRID - 1;
+ drawAt(type, gx, gy);
+ drawAt(type, N - gx, gy);
+ drawAt(type, gx, N - gy);
+ drawAt(type, N - gx, N - gy);
+ }
+
+ for (let gy = 0; gy < GRID; gy++)
+ for (let gx = 0; gx < GRID; gx++) drawAt(bgAtom, gx, gy);
+
+ const edgeThickness = _pick(rng, [1, 1, 2]);
+ for (let t = 0; t < edgeThickness; t++)
+ for (let i = 0; i < GRID; i++) {
+ drawAt(bgAtom, i, t);
+ drawAt(bgAtom, i, GRID - 1 - t);
+ drawAt(bgAtom, t, i);
+ drawAt(bgAtom, GRID - 1 - t, i);
+ }
+
+ if (rng() < 0.7) {
+ const midR = Math.floor(petalDepth * (0.5 + 0.2 * rng()));
+ for (let i = 0; i < GRID; i++)
+ for (const [gx, gy] of [
+ [cx - midR, i],
+ [cx + midR, i],
+ [i, cy - midR],
+ [i, cy + midR],
+ ])
+ drawQuad(bgAtom, gx, gy);
+ }
+ if (rng() < 0.6) {
+ const gateEvery = _pick(rng, [2, 3, 4]);
+ for (let s = 0; s < petals; s++) {
+ if (s % gateEvery !== 0) continue;
+ const theta = s * ((2 * Math.PI) / petals);
+ for (let r = 1; r <= petalDepth; r++) {
+ const gx = Math.round(cx + r * Math.cos(theta));
+ const gy = Math.round(cy + r * Math.sin(theta));
+ drawQuad(bgAtom, gx, gy);
+ }
+ }
+ }
+
+ for (const [dx, dy, t] of [
+ [0, 0, "D"],
+ [0, -1, petalAtoms[0]],
+ [1, 0, petalAtoms[1]],
+ [0, 1, petalAtoms[2]],
+ [-1, 0, petalAtoms[0]],
+ ])
+ drawQuad(t, cx + dx, cy + dy);
+
+ const half = Math.ceil(GRID / 2);
+ for (let gy = 0; gy < half; gy++) {
+ for (let gx = 0; gx < half; gx++) {
+ const { r, a } = _polarFromCell(gx, gy, cx, cy);
+ const rCell = Math.floor(r);
+ if (rCell === 0 || rCell > petalDepth) continue;
+ const t = rCell / petalDepth;
+ const tol = angleTolNear * (1 - t) + angleTolFar * t;
+ const sectorIdx = Math.floor(a / ((2 * Math.PI) / petals));
+ if (!_sectorGate(a, petals, sectorMask)) continue;
+ let inside = false;
+ if (shapeModel === "rose") inside = _shapeRose(a, petals, tol);
+ else if (shapeModel === "starburst")
+ inside = _shapeStarburst(a, petals, softness);
+ else if (shapeModel === "ringed")
+ inside = _shapeRinged(a, petals, ringPhase, tol * 0.7);
+ else if (shapeModel === "tip") inside = _shapeTip(a, petals, tol, t);
+ else if (shapeModel === "notched") inside = _shapeNotched(a, petals, tol);
+ else inside = _shapeHollow(a, petals, tol, t);
+ if (!inside) continue;
+ const type = assignAtom(rCell, a, sectorIdx);
+ for (let dr = 0; dr < radialThickness; dr++) {
+ const x1 = gx + dr,
+ y1 = gy + dr;
+ const N = GRID - 1;
+ for (const [ix, iy] of [
+ [x1, y1],
+ [N - x1, y1],
+ [x1, N - y1],
+ [N - x1, N - y1],
+ ])
+ drawAt(type, ix, iy);
+ }
+ }
+ }
+
+ for (const [gx, gy, t] of [
+ [cx, cy - 2, petalAtoms[0]],
+ [cx + 1, cy, petalAtoms[1]],
+ [cx, cy + 2, petalAtoms[2]],
+ ])
+ drawQuad(t, gx, gy);
+
+ for (const [dx, dy, t] of [
+ [0, 0, "D"],
+ [1, 0, petalAtoms[1]],
+ [0, 1, petalAtoms[2]],
+ [1, 1, petalAtoms[0]],
+ ])
+ drawQuad(t, cx + dx, cy + dy);
+
+ return _avatarSvgNode(ctx.elements, W, size);
+}
+
+function el(tag, style, ...children) {
+ const flat = children
+ .flat()
+ .filter(c => c !== null && c !== undefined && c !== false);
+ return {
+ type: tag,
+ props: {
+ style: { display: "flex", fontFamily: "Booton", ...style },
+ children:
+ flat.length === 0 ? undefined : flat.length === 1 ? flat[0] : flat,
+ },
+ };
+}
+
+const ICON_PATHS = {
+ seed: [
+ "M12.3535 6.35352L7.5 11.207V14H6.5V11.207L4.64648 9.35352L5.35352 8.64648L7 10.293L11.6465 5.64648L12.3535 6.35352Z",
+ "M14 6.5V4H11.5C10.1193 4 9 5.11929 9 6.5C9 7.88071 10.1193 9 11.5 9V10C9.567 10 8 8.433 8 6.5C8 4.567 9.567 3 11.5 3H15V6.5C15 8.433 13.433 10 11.5 10V9C12.8807 9 14 7.88071 14 6.5Z",
+ "M5 7.5C5 6.67157 4.32843 6 3.5 6C2.67157 6 2 6.67157 2 7.5C2 8.32843 2.67157 9 3.5 9V10C2.11929 10 1 8.88071 1 7.5C1 6.11929 2.11929 5 3.5 5C4.88071 5 6 6.11929 6 7.5C6 8.88071 4.88071 10 3.5 10V9C4.32843 9 5 8.32843 5 7.5Z",
+ ],
+ issue: [
+ "M9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5C8.82843 9.5 9.5 8.82843 9.5 8ZM10.5 8C10.5 9.38071 9.38071 10.5 8 10.5C6.61929 10.5 5.5 9.38071 5.5 8C5.5 6.61929 6.61929 5.5 8 5.5C9.38071 5.5 10.5 6.61929 10.5 8Z",
+ "M13.5 8C13.5 4.96243 11.0376 2.5 8 2.5C4.96243 2.5 2.5 4.96243 2.5 8C2.5 11.0376 4.96243 13.5 8 13.5C11.0376 13.5 13.5 11.0376 13.5 8ZM14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8Z",
+ ],
+ "issue-closed": [
+ "M13.5 8C13.5 4.96243 11.0376 2.5 8 2.5C4.96243 2.5 2.5 4.96243 2.5 8C2.5 11.0376 4.96243 13.5 8 13.5C11.0376 13.5 13.5 11.0376 13.5 8ZM14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8Z",
+ "M10.707 6L8.70605 8.00098L10.7051 10L9.99805 10.707L7.99902 8.70801L6 10.707L5.29297 10L7.29199 8.00098L5.29297 6.00195L6 5.29492L7.99902 7.29395L10 5.29297L10.707 6Z",
+ ],
+ patch: [
+ "M5.5 12C5.5 11.1716 4.82843 10.5 4 10.5C3.17157 10.5 2.5 11.1716 2.5 12C2.5 12.8284 3.17157 13.5 4 13.5C4.82843 13.5 5.5 12.8284 5.5 12ZM13.5 12C13.5 11.1716 12.8284 10.5 12 10.5C11.1716 10.5 10.5 11.1716 10.5 12C10.5 12.8284 11.1716 13.5 12 13.5C12.8284 13.5 13.5 12.8284 13.5 12ZM7.5 2.5H11.5V3.5H9.20703L12.5 6.79297V9.5498C13.6411 9.78142 14.5 10.7905 14.5 12C14.5 13.3807 13.3807 14.5 12 14.5C10.6193 14.5 9.5 13.3807 9.5 12C9.5 10.7905 10.3589 9.78142 11.5 9.5498V7.20703L8.5 4.20703V6.5H7.5V2.5ZM5.5 4C5.5 3.17157 4.82843 2.5 4 2.5C3.17157 2.5 2.5 3.17157 2.5 4C2.5 4.82843 3.17157 5.5 4 5.5C4.82843 5.5 5.5 4.82843 5.5 4ZM6.5 4C6.5 5.20943 5.64105 6.21753 4.5 6.44922V9.5498C5.64114 9.78142 6.5 10.7905 6.5 12C6.5 13.3807 5.38071 14.5 4 14.5C2.61929 14.5 1.5 13.3807 1.5 12C1.5 10.7905 2.35886 9.78142 3.5 9.5498V6.44922C2.35895 6.21753 1.5 5.20943 1.5 4C1.5 2.61929 2.61929 1.5 4 1.5C5.38071 1.5 6.5 2.61929 6.5 4Z",
+ ],
+ "patch-draft": [
+ "M5 4C5 3.44772 4.55228 3 4 3C3.44772 3 3 3.44772 3 4C3 4.55228 3.44772 5 4 5V6C2.89543 6 2 5.10457 2 4C2 2.89543 2.89543 2 4 2C5.10457 2 6 2.89543 6 4C6 5.10457 5.10457 6 4 6V5C4.55228 5 5 4.55228 5 4Z",
+ "M4.5 12V13H3.5V12H4.5ZM4.5 10V11H3.5V10H4.5ZM4.5 8V9H3.5V8H4.5ZM4.5 5V7H3.5V5H4.5Z",
+ "M13 4C13 3.44772 12.5523 3 12 3C11.4477 3 11 3.44772 11 4C11 4.55228 11.4477 5 12 5V6C10.8954 6 10 5.10457 10 4C10 2.89543 10.8954 2 12 2C13.1046 2 14 2.89543 14 4C14 5.10457 13.1046 6 12 6V5C12.5523 5 13 4.55228 13 4Z",
+ "M13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13V14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12C14 13.1046 13.1046 14 12 14V13C12.5523 13 13 12.5523 13 12Z",
+ "M12.5 5.5L12.5 10.5H11.5L11.5 5.5H12.5Z",
+ ],
+ "patch-merged": [
+ "M5 4C5 3.44772 4.55228 3 4 3C3.44772 3 3 3.44772 3 4C3 4.55228 3.44772 5 4 5V6C2.89543 6 2 5.10457 2 4C2 2.89543 2.89543 2 4 2C5.10457 2 6 2.89543 6 4C6 5.10457 5.10457 6 4 6V5C4.55228 5 5 4.55228 5 4Z",
+ "M13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13V14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12C14 13.1046 13.1046 14 12 14V13C12.5523 13 13 12.5523 13 12Z",
+ "M5 12C5 11.4477 4.55228 11 4 11C3.44772 11 3 11.4477 3 12C3 12.5523 3.44772 13 4 13V14C2.89543 14 2 13.1046 2 12C2 10.8954 2.89543 10 4 10C5.10457 10 6 10.8954 6 12C6 13.1046 5.10457 14 4 14V13C4.55228 13 5 12.5523 5 12Z",
+ "M4.5 5.5V10.5H3.5V5.5H4.5Z",
+ "M9.20703 3.5L12.5 6.79297V10.5H11.5V7.20703L8.79297 4.5H5.5V3.5H9.20703Z",
+ ],
+ "patch-archived": [
+ "M13.5 2.5V13.5H2.5V2.5H13.5ZM3.5 12.5H12.5V8.5H10.4502C10.2186 9.64114 9.20949 10.5 8 10.5C6.79051 10.5 5.78142 9.64114 5.5498 8.5H3.5V12.5ZM8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5ZM3.5 7.5H5.5498C5.78142 6.35886 6.79051 5.5 8 5.5C9.20949 5.5 10.2186 6.35886 10.4502 7.5H12.5V3.5H3.5V7.5Z",
+ ],
+ commit: [
+ "M8 4.5C9.7632 4.5 11.2212 5.8039 11.4639 7.5H15V8.5H11.4639C11.2212 10.1961 9.7632 11.5 8 11.5C6.2368 11.5 4.77879 10.1961 4.53613 8.5H1V7.5H4.53613C4.77879 5.8039 6.2368 4.5 8 4.5ZM8 5.5C6.61929 5.5 5.5 6.61929 5.5 8C5.5 9.38071 6.61929 10.5 8 10.5C9.38071 10.5 10.5 9.38071 10.5 8C10.5 6.61929 9.38071 5.5 8 5.5Z",
+ ],
+ repository: [
+ "M8.22363 9.44727L8 9.55859L7.77637 9.44727L2.5 6.80859V9.69043L8 12.4404L13.5 9.69043V6.80859L8.22363 9.44727ZM3.11816 6L8 8.44043L12.8818 6L8 3.55859L3.11816 6ZM14.5 10.3086L14.2236 10.4473L8.22363 13.4473L8 13.5586L7.77637 13.4473L1.77637 10.4473L1.5 10.3086V5.69141L1.77637 5.55273L7.77637 2.55273L8 2.44141L8.22363 2.55273L14.2236 5.55273L14.5 5.69141V10.3086Z",
+ ],
+ pin: [
+ "M10.3906 1.5L11.4287 5.65527L13.2236 6.55273L13.5 6.69141V10.5H8.5V15H7.5V10.5H2.5V6.69141L2.77637 6.55273L4.57031 5.65527L5.60938 1.5H10.3906ZM5.48535 6.12109L5.42969 6.34473L5.22363 6.44727L3.5 7.30859V9.5H12.5V7.30859L10.7764 6.44727L10.5703 6.34473L10.5146 6.12109L9.60938 2.5H6.39062L5.48535 6.12109Z",
+ ],
+ key: [
+ "M7.5 10C7.5 8.61929 6.38071 7.5 5 7.5C3.61929 7.5 2.5 8.61929 2.5 10C2.5 11.3807 3.61929 12.5 5 12.5C6.38071 12.5 7.5 11.3807 7.5 10ZM8.5 10C8.5 11.933 6.933 13.5 5 13.5C3.067 13.5 1.5 11.933 1.5 10C1.5 8.067 3.067 6.5 5 6.5C6.933 6.5 8.5 8.067 8.5 10Z",
+ "M5 10.5C5 10.7761 4.77614 11 4.5 11C4.22386 11 4 10.7761 4 10.5C4 10.2239 4.22386 10 4.5 10C4.77614 10 5 10.2239 5 10.5Z",
+ "M4.75 10.5C4.75 10.3619 4.63807 10.25 4.5 10.25C4.36193 10.25 4.25 10.3619 4.25 10.5C4.25 10.6381 4.36193 10.75 4.5 10.75C4.63807 10.75 4.75 10.6381 4.75 10.5ZM5.25 10.5C5.25 10.9142 4.91421 11.25 4.5 11.25C4.08579 11.25 3.75 10.9142 3.75 10.5C3.75 10.0858 4.08579 9.75 4.5 9.75C4.91421 9.75 5.25 10.0858 5.25 10.5Z",
+ "M14.3535 4.64648L13.6465 5.35352L12 3.70703L10.707 5L12.3535 6.64648L11.6465 7.35352L10 5.70703L7.35352 8.35352L6.64648 7.64648L12 2.29297L14.3535 4.64648Z",
+ ],
+};
+
+function icon(name, size = 20, fill = C.textMid) {
+ const paths = ICON_PATHS[name];
+ if (!paths) return null;
+ return {
+ type: "svg",
+ props: {
+ width: size,
+ height: size,
+ viewBox: "0 0 16 16",
+ style: { display: "flex", flexShrink: "0" },
+ children:
+ paths.length === 1
+ ? { type: "path", props: { d: paths[0], fill } }
+ : paths.map(d => ({ type: "path", props: { d, fill } })),
+ },
+ };
+}
+
+function issueIconName(status) {
+ return status === "closed" ? "issue-closed" : "issue";
+}
+
+function patchIconName(status) {
+ if (status === "draft") return "patch-draft";
+ if (status === "merged") return "patch-merged";
+ if (status === "archived") return "patch-archived";
+ return "patch";
+}
+
+const WORDMARK_PATHS = [
+ "M1.84555 13.7149H0V15.7954H6.28119V13.7149H4.466V7.01257H8.02772V4.61917H1.84555V13.7149Z",
+ "M9.20923 12.8172C9.20923 10.7169 10.627 9.62514 13.2475 9.23834L15.1855 8.96243C15.8785 8.86078 16.2152 8.63636 16.2152 8.06474C16.2152 6.98355 15.4706 6.37233 14.1861 6.37233C12.8198 6.37233 12.0964 7.10633 11.8812 8.14659L9.3518 7.76903C9.73992 5.91293 11.2792 4.41458 14.1848 4.41458C17.0904 4.41458 18.8449 6.0159 18.8449 8.56507V15.7941H16.2653V13.7241H16.1637C15.8072 14.9479 14.594 15.9974 12.7485 15.9974C10.5967 15.9974 9.21055 14.7327 9.21055 12.8159L9.20923 12.8172ZM16.2138 10.9109V10.544L13.7874 10.9928C12.5333 11.2278 11.9828 11.7572 11.9828 12.5624C11.9828 13.4601 12.6455 13.9895 13.6554 13.9895C15.369 13.9895 16.2152 12.7459 16.2152 10.9096L16.2138 10.9109Z",
+ "M20.5122 10.1861C20.5122 6.65872 22.3274 4.41449 25.0191 4.41449C26.5492 4.41449 27.7518 5.28182 28.3234 6.61647H28.4158V0.826372H31.0469V15.7953H28.4158V13.7465H28.3142C27.6211 15.1841 26.5505 16 25.031 16C22.3287 16 20.5135 13.7267 20.5135 10.1874L20.5122 10.1861ZM25.8455 13.7953C27.4152 13.7953 28.4053 12.3775 28.4053 10.1861C28.4053 7.99469 27.4165 6.58611 25.8455 6.58611C24.2746 6.58611 23.2858 7.99337 23.2858 10.1861C23.2858 12.3788 24.2851 13.7953 25.8455 13.7953Z",
+ "M37.3981 13.7149H39.2436V15.7954H32.9624V13.7149H34.7776V6.69967H33.0957V4.61914H37.3981V13.7149ZM34.441 1.54983C34.441 0.693069 35.1551 0 36.0212 0C36.8872 0 37.5816 0.693069 37.5816 1.54983C37.5816 2.4066 36.899 3.08911 36.0212 3.08911C35.1433 3.08911 34.441 2.4066 34.441 1.54983Z",
+ "M50.0937 7.98415L47.5644 8.52409C47.299 7.28052 46.5756 6.54653 45.3413 6.54653C43.7505 6.54653 42.7406 7.8825 42.7406 10.2073C42.7406 12.532 43.7598 13.868 45.3505 13.868C46.4924 13.868 47.3083 13.2462 47.5433 12.0026H50.1532C49.8574 14.5109 48.1954 16 45.3505 16C42.0871 16 39.9868 13.7267 39.9868 10.2086C39.9868 6.69042 42.0871 4.41716 45.34 4.41716C48.0317 4.41716 49.765 5.86534 50.0911 7.98547L50.0937 7.98415Z",
+ "M55.5287 13.7148H57.3742V15.7953H51.093V13.7148H52.9082V2.90558H51.2264V0.826372H55.5287V13.7148Z",
+ "M57.9749 10.2271C57.9749 6.74989 60.1267 4.41458 63.3174 4.41458C66.2442 4.41458 68.5386 6.34197 68.2323 10.808H60.7273C60.8805 12.7855 61.9207 13.938 63.328 13.938C64.5412 13.938 65.3055 13.336 65.6013 12.2958H68.2626C67.9775 14.1822 66.1729 15.9974 63.328 15.9974C60.0963 15.9974 57.9749 13.744 57.9749 10.2258V10.2271ZM65.6013 9.01392C65.5405 7.37167 64.7036 6.38289 63.328 6.38289C62.0739 6.38289 61.0851 7.10633 60.7894 9.01392H65.6026H65.6013Z",
+];
+
+function wordmark(width = 139) {
+ const height = width * (16 / 69);
+ return {
+ type: "svg",
+ props: {
+ width,
+ height,
+ viewBox: "0 0 69 16",
+ style: { display: "flex", flexShrink: "0" },
+ children: WORDMARK_PATHS.map(d => ({
+ type: "path",
+ props: { d, fill: C.text },
+ })),
+ },
+ };
+}
+
+function homeCard(host) {
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ {
+ type: "img",
+ props: {
+ src: forestDataUri,
+ width: 630,
+ height: 630,
+ style: { display: "flex", objectFit: "cover", flexShrink: "0" },
+ },
+ },
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el("div", {}, wordmark(139)),
+ el(
+ "div",
+ { flexDirection: "column", gap: "48px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 80,
+ fontWeight: 600,
+ letterSpacing: "-1.6px",
+ lineHeight: "57px",
+ },
+ "Explorer",
+ ),
+ el(
+ "div",
+ {
+ color: C.textSub,
+ fontSize: 40,
+ lineHeight: "48px",
+ },
+ "Explore code on the Radicle network",
+ ),
+ ),
+ el("div", { color: C.textMid, fontSize: 28, lineHeight: "40px" }, host),
+ ),
+ );
+}
+
+function nodeCard(host, node, stats) {
+ const repoCount = stats?.repos?.total;
+ const pinnedCount = stats?.repos?.pinned;
+ const nid = node?.id ?? host;
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "630px",
+ height: "630px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ userAvatarSvg(nid, 630),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ },
+ el(
+ "div",
+ { flexShrink: "0" },
+ {
+ type: "div",
+ props: {
+ style: {
+ display: "flex",
+ color: "#1C77FF",
+ backgroundColor: "rgba(28, 119, 255, 0.10)",
+ fontFamily: "Booton",
+ fontSize: 32,
+ fontWeight: 600,
+ height: 84,
+ paddingLeft: 30,
+ paddingRight: 30,
+ paddingTop: 18,
+ paddingBottom: 18,
+ borderRadius: 6,
+ lineHeight: "36px",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ children: "Node",
+ },
+ },
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ host,
+ ),
+ node?.description
+ ? el(
+ "div",
+ {
+ color: C.textSub,
+ fontSize: 24,
+ lineHeight: "32px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ node.description,
+ )
+ : null,
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "16px", width: "100%" },
+ repoCount !== undefined
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("repository", 32, C.textMid),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ formatCount(repoCount),
+ ),
+ )
+ : null,
+ pinnedCount !== undefined
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("pin", 32, C.textMid),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ String(pinnedCount),
+ ),
+ )
+ : null,
+ ),
+ ),
+ ),
+ );
+}
+
+function userCard(host, did, identity) {
+ const nid = nidFromDid(did);
+ const displayName = identity?.alias ?? shortDid(did);
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "630px",
+ height: "630px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ userAvatarSvg(nid, 630),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ },
+ el(
+ "div",
+ { flexShrink: "0" },
+ {
+ type: "div",
+ props: {
+ style: {
+ display: "flex",
+ color: "#1C77FF",
+ backgroundColor: "rgba(28, 119, 255, 0.10)",
+ fontFamily: "Booton",
+ fontSize: 32,
+ fontWeight: 600,
+ height: 84,
+ paddingLeft: 30,
+ paddingRight: 30,
+ paddingTop: 18,
+ paddingBottom: 18,
+ borderRadius: 6,
+ lineHeight: "36px",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ children: "User",
+ },
+ },
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ displayName,
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("key", 32, C.textMid),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ shortDid(did),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+function repoCard(repo, host) {
+ if (!repo) return nodeCard(host, null, null);
+ const p = repoPayload(repo);
+ const issueCount = p?.meta?.issues?.open ?? 0;
+ const patchCount = p?.meta?.patches?.open ?? 0;
+ const delegates = repo.delegates ?? [];
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "630px",
+ height: "630px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 630),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 1,
+ WebkitBoxOrient: "vertical",
+ },
+ repoName || repo.rid,
+ ),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 20, lineHeight: "20px" },
+ repo.rid,
+ ),
+ p?.data?.description
+ ? el(
+ "div",
+ {
+ color: C.textSub,
+ fontSize: 24,
+ lineHeight: "32px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ p.data.description,
+ )
+ : null,
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ { alignItems: "center", gap: "16px" },
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("seed", 32, C.textMid),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ formatCount(repo.seeding),
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("issue", 32, C.textMid),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ String(issueCount),
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("patch", 32, C.textMid),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ String(patchCount),
+ ),
+ ),
+ ),
+ delegates.length > 0
+ ? el(
+ "div",
+ { alignItems: "center", flexWrap: "wrap", gap: "16px" },
+ ...delegates.slice(0, 3).map(d => {
+ const nid = nidFromDid(d.id);
+ return el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ userAvatarSvg(nid, 32),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ d.alias ?? `${nid.slice(0, 4)}\u2026${nid.slice(-4)}`,
+ ),
+ );
+ }),
+ delegates.length > 3
+ ? el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ `+${delegates.length - 3} more`,
+ )
+ : null,
+ )
+ : null,
+ ),
+ ),
+ );
+}
+
+function historyCard(repo, host, stats, branch, peer, peerAlias) {
+ if (!repo) return repoCard(repo, host);
+ const p = repoPayload(repo);
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+ const commitCount = stats?.commits;
+ const branchLabel = branch || p?.data?.defaultBranch;
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "630px",
+ height: "630px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 630),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 1,
+ WebkitBoxOrient: "vertical",
+ },
+ repoName || repo.rid,
+ ),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 20, lineHeight: "20px" },
+ repo.rid,
+ ),
+ p?.data?.description
+ ? el(
+ "div",
+ {
+ color: C.textSub,
+ fontSize: 24,
+ lineHeight: "32px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ p.data.description,
+ )
+ : null,
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ { alignItems: "center", gap: "8px", flexWrap: "wrap" },
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ "Commits on",
+ ),
+ ...(peer
+ ? [
+ userAvatarSvg(peer, 28),
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ peerAlias ? `${peerAlias} /` : `${peer.slice(0, 6)}\u2026 /`,
+ ),
+ ]
+ : []),
+ branchLabel
+ ? el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ branchLabel,
+ )
+ : null,
+ ...(!peer
+ ? [
+ el(
+ "div",
+ {
+ color: "#ffffff",
+ backgroundColor: "#165fcc",
+ fontSize: 20,
+ lineHeight: "28px",
+ borderRadius: "4px",
+ padding: "4px 10px",
+ },
+ "Canonical",
+ ),
+ ]
+ : []),
+ ),
+ commitCount !== undefined
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("commit", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ commitCount.toLocaleString("en-US"),
+ ),
+ )
+ : null,
+ ),
+ ),
+ );
+}
+
+function issuesCard(repo, host) {
+ if (!repo) return repoCard(repo, host);
+ const p = repoPayload(repo);
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+ const issues = p?.meta?.issues ?? {};
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "630px",
+ height: "630px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 630),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 1,
+ WebkitBoxOrient: "vertical",
+ },
+ repoName || repo.rid,
+ ),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 20, lineHeight: "20px" },
+ repo.rid,
+ ),
+ p?.data?.description
+ ? el(
+ "div",
+ {
+ color: C.textSub,
+ fontSize: 24,
+ lineHeight: "32px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ p.data.description,
+ )
+ : null,
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ "Issues",
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "32px" },
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("issue", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ String(issues.open ?? 0),
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("issue-closed", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ String(issues.closed ?? 0),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+function patchesCard(repo, host) {
+ if (!repo) return repoCard(repo, host);
+ const p = repoPayload(repo);
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+ const patches = p?.meta?.patches ?? {};
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "630px",
+ height: "630px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 630),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 1,
+ WebkitBoxOrient: "vertical",
+ },
+ repoName || repo.rid,
+ ),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 20, lineHeight: "20px" },
+ repo.rid,
+ ),
+ p?.data?.description
+ ? el(
+ "div",
+ {
+ color: C.textSub,
+ fontSize: 24,
+ lineHeight: "32px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+ },
+ p.data.description,
+ )
+ : null,
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px", width: "100%" },
+ el(
+ "div",
+ { color: C.textMid, fontSize: 28, lineHeight: "40px" },
+ "Patches",
+ ),
+ el(
+ "div",
+ { alignItems: "center", flexWrap: "wrap", gap: "32px" },
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("patch", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ String(patches.open ?? 0),
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("patch-draft", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ String(patches.draft ?? 0),
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("patch-archived", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ String(patches.archived ?? 0),
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ icon("patch-merged", 64, C.textMuted),
+ el(
+ "div",
+ { color: C.textSub, fontSize: 56, lineHeight: "80px" },
+ String(patches.merged ?? 0),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+const STATE_PILL = {
+ open: { bg: "#bef98a", text: "#256400" },
+ closed: { bg: "#afcfff", text: "#0b3266" },
+ merged: { bg: "#afcfff", text: "#0b3266" },
+ draft: { bg: "#e9ebef", text: "#5a5f6b" },
+ archived: { bg: "#ffdbff", text: "#664266" },
+};
+
+function statePill(label, status, iconName) {
+ const colors = STATE_PILL[status] ?? STATE_PILL.open;
+ return el(
+ "div",
+ { flexShrink: "0" },
+ {
+ type: "div",
+ props: {
+ style: {
+ display: "flex",
+ backgroundColor: colors.bg,
+ color: colors.text,
+ fontFamily: "Booton",
+ fontSize: 32,
+ fontWeight: 600,
+ paddingLeft: 18,
+ paddingRight: 30,
+ paddingTop: 18,
+ paddingBottom: 18,
+ borderRadius: 6,
+ lineHeight: "36px",
+ gap: 12,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ children: [icon(iconName, 48, colors.text), label],
+ },
+ },
+ );
+}
+
+function issueCard(repo, host, issue) {
+ if (!issue) return repoCard(repo, host);
+ const p = repoPayload(repo);
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+ const status = issue.state?.status ?? "open";
+ const statusLabel = status === "closed" ? "Closed Issue" : "Open Issue";
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "315px",
+ height: "630px",
+ flexShrink: "0",
+ flexDirection: "column",
+ backgroundColor: "#f8f9fa",
+ },
+ el(
+ "div",
+ {
+ width: "315px",
+ height: "315px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 315),
+ ),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ },
+ statePill(statusLabel, status, issueIconName(status)),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px" },
+ el(
+ "div",
+ { alignItems: "center", gap: "16px" },
+ icon("repository", 32, C.textMid),
+ el(
+ "div",
+ {
+ color: C.textMid,
+ fontSize: 28,
+ lineHeight: "40px",
+ },
+ repoName || repo.rid,
+ ),
+ ),
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: "vertical",
+ },
+ issue.title,
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "24px" },
+ issue.author
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ userAvatarSvg(nidFromDid(issue.author.id), 28),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ authorLabel(issue.author),
+ ),
+ )
+ : null,
+ issue.author
+ ? el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ "\u00B7",
+ )
+ : null,
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ shortId(issue.id),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+function patchCard(repo, host, patch) {
+ if (!patch) return repoCard(repo, host);
+ const p = repoPayload(repo);
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+ const status = patch.state?.status ?? "open";
+ const statusLabels = {
+ open: "Open Patch",
+ closed: "Closed Patch",
+ merged: "Merged Patch",
+ draft: "Draft Patch",
+ archived: "Archived Patch",
+ };
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "315px",
+ height: "630px",
+ flexShrink: "0",
+ flexDirection: "column",
+ backgroundColor: "#f8f9fa",
+ },
+ el(
+ "div",
+ {
+ width: "315px",
+ height: "315px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 315),
+ ),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ },
+ statePill(
+ statusLabels[status] ?? "Patch",
+ status,
+ patchIconName(status),
+ ),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px" },
+ el(
+ "div",
+ { alignItems: "center", gap: "16px" },
+ icon("repository", 32, C.textMid),
+ el(
+ "div",
+ {
+ color: C.textMid,
+ fontSize: 28,
+ lineHeight: "40px",
+ },
+ repoName || repo.rid,
+ ),
+ ),
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: "vertical",
+ },
+ patch.title,
+ ),
+ ),
+ el(
+ "div",
+ { alignItems: "center", gap: "24px" },
+ patch.author
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px" },
+ userAvatarSvg(nidFromDid(patch.author.id), 28),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ authorLabel(patch.author),
+ ),
+ )
+ : null,
+ patch.author
+ ? el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ "\u00B7",
+ )
+ : null,
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ shortId(patch.id),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+function commitCard(repo, host, commit, authorAvatar, committerAvatar) {
+ if (!commit) return repoCard(repo, host);
+ const p = repoPayload(repo);
+ const repoName = p?.data?.name ?? "";
+ const avatarKey = `${repoName}${repo.rid.replace("rad:z", "")}${repoName}`;
+ const c = commit.commit ?? {};
+ const author = c.author;
+ const committer = c.committer;
+ const sameAuthorCommitter = author?.name === committer?.name;
+
+ return el(
+ "div",
+ {
+ width: "100%",
+ height: "100%",
+ backgroundColor: C.bg,
+ },
+ el(
+ "div",
+ {
+ width: "315px",
+ height: "630px",
+ flexShrink: "0",
+ flexDirection: "column",
+ backgroundColor: "#f8f9fa",
+ },
+ el(
+ "div",
+ {
+ width: "315px",
+ height: "315px",
+ overflow: "hidden",
+ flexShrink: "0",
+ },
+ repoAvatarSvg(avatarKey, 315),
+ ),
+ ),
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ padding: "48px",
+ height: "100%",
+ },
+ el(
+ "div",
+ {
+ flex: "1",
+ flexDirection: "column",
+ justifyContent: "space-between",
+ },
+ statePill("Commit", "draft", "commit"),
+ el(
+ "div",
+ { flexDirection: "column", gap: "24px" },
+ el(
+ "div",
+ { alignItems: "center", gap: "16px" },
+ icon("repository", 32, C.textMid),
+ el(
+ "div",
+ {
+ color: C.textMid,
+ fontSize: 28,
+ lineHeight: "40px",
+ },
+ repoName || repo.rid,
+ ),
+ ),
+ el(
+ "div",
+ {
+ color: C.text,
+ fontSize: 48,
+ fontWeight: 600,
+ letterSpacing: "-0.96px",
+ lineHeight: "52px",
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 3,
+ WebkitBoxOrient: "vertical",
+ },
+ c.summary ?? "Commit",
+ ),
+ ),
+ el(
+ "div",
+ {
+ flexDirection: "column",
+ gap: "16px",
+ flexWrap: "wrap",
+ width: "100%",
+ },
+ author?.name
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px", lineHeight: "40px" },
+ gravatarImg(authorAvatar, 28),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ author.name,
+ ),
+ !sameAuthorCommitter
+ ? el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ "authored",
+ )
+ : null,
+ )
+ : null,
+ !sameAuthorCommitter && committer?.name
+ ? el(
+ "div",
+ { alignItems: "center", gap: "8px", lineHeight: "40px" },
+ gravatarImg(committerAvatar, 28),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ committer.name,
+ ),
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ "committed",
+ ),
+ )
+ : null,
+ el(
+ "div",
+ { alignItems: "center", gap: "24px" },
+ el(
+ "div",
+ { color: C.textMuted, fontSize: 28, lineHeight: "40px" },
+ shortId(c.id ?? commit.id ?? ""),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+async function renderPng(template) {
+ const svg = await satori(template, {
+ width: WIDTH,
+ height: HEIGHT,
+ fonts: font,
+ });
+ const resvg = new Resvg(svg, { fitTo: { mode: "width", value: WIDTH } });
+ return resvg.render().asPng();
+}
+
+export default {
+ async fetch(request) {
+ if (request.method === "HEAD") {
+ return new Response(null, {
+ headers: {
+ "content-type": "image/png",
+ "cache-control": "public, max-age=120",
+ },
+ });
+ }
+
+ const cache = caches.default;
+ const cached = await cache.match(request);
+ if (cached) return cached;
+
+ const url = new URL(request.url);
+ const route = parseRoute(url.pathname);
+
+ if (!route) {
+ return new Response("Not found", { status: 404 });
+ }
+
+ try {
+ await ensureInit();
+
+ let template;
+
+ switch (route.type) {
+ case "home": {
+ const appHost = url.searchParams.get("host") || url.hostname;
+ template = homeCard(appHost);
+ break;
+ }
+
+ case "node": {
+ const [node, stats] = await Promise.all([
+ fetchNode(route.host),
+ fetchNodeStats(route.host),
+ ]);
+ template = nodeCard(route.host, node, stats);
+ break;
+ }
+
+ case "user": {
+ const nid = route.did.startsWith("did:key:")
+ ? route.did.slice(8)
+ : route.did;
+ const identity = await fetchNodeIdentity(route.host, nid);
+ template = userCard(route.host, route.did, identity);
+ break;
+ }
+
+ case "repo": {
+ const repo = await fetchRepo(route.host, route.rid);
+ template = repoCard(repo, route.host);
+ break;
+ }
+
+ case "issues": {
+ const repo = await fetchRepo(route.host, route.rid);
+ template = issuesCard(repo, route.host);
+ break;
+ }
+
+ case "patches": {
+ const repo = await fetchRepo(route.host, route.rid);
+ template = patchesCard(repo, route.host);
+ break;
+ }
+
+ case "issue": {
+ const [repo, issue] = await Promise.all([
+ fetchRepo(route.host, route.rid),
+ fetchIssue(route.host, route.rid, route.id),
+ ]);
+ template = issueCard(repo, route.host, issue);
+ break;
+ }
+
+ case "patch": {
+ const [repo, patch] = await Promise.all([
+ fetchRepo(route.host, route.rid),
+ fetchPatch(route.host, route.rid, route.id),
+ ]);
+ template = patchCard(repo, route.host, patch);
+ break;
+ }
+
+ case "history": {
+ const [repo, remote] = await Promise.all([
+ fetchRepo(route.host, route.rid),
+ route.peer ? fetchRemote(route.host, route.rid, route.peer) : null,
+ ]);
+ const p = repoPayload(repo);
+ let sha = p?.meta?.head;
+ let peerAlias = null;
+ let branch = route.branch;
+
+ if (route.peer && remote) {
+ const peerDid = `did:key:${route.peer}`;
+ const delegate = (repo?.delegates ?? []).find(
+ d => d.id === peerDid,
+ );
+ peerAlias = delegate?.alias ?? null;
+ if (remote.heads) {
+ const targetBranch = branch || p?.data?.defaultBranch;
+ sha =
+ remote.heads[targetBranch] ??
+ Object.values(remote.heads)[0] ??
+ sha;
+ if (!branch) branch = targetBranch;
+ }
+ }
+
+ const stats = sha
+ ? await fetchTreeStats(route.host, route.rid, sha)
+ : null;
+ template = historyCard(
+ repo,
+ route.host,
+ stats,
+ branch,
+ route.peer,
+ peerAlias,
+ );
+ break;
+ }
+
+ case "commit": {
+ const [repo, commit] = await Promise.all([
+ fetchRepo(route.host, route.rid),
+ fetchCommit(route.host, route.rid, route.sha),
+ ]);
+ const c = commit?.commit ?? {};
+ const [authorAvatar, committerAvatar] = await Promise.all([
+ fetchGravatarDataUri(c.author?.email),
+ c.author?.email !== c.committer?.email
+ ? fetchGravatarDataUri(c.committer?.email)
+ : null,
+ ]);
+ template = commitCard(
+ repo,
+ route.host,
+ commit,
+ authorAvatar,
+ committerAvatar ?? authorAvatar,
+ );
+ break;
+ }
+
+ default:
+ template = homeCard();
+ }
+
+ const png = await renderPng(template);
+ const response = new Response(png, {
+ headers: {
+ "content-type": "image/png",
+ "cache-control": "public, max-age=120",
+ },
+ });
+
+ await cache.put(request, response.clone());
+ return response;
+ } catch (err) {
+ console.error("OG image generation failed:", err);
+ return new Response("Image generation failed", { status: 500 });
+ }
+ },
+};
diff --git a/workers/open-graph/package-lock.json b/workers/open-graph/package-lock.json
new file mode 100644
index 000000000..b6c16ac73
--- /dev/null
+++ b/workers/open-graph/package-lock.json
@@ -0,0 +1,1698 @@
+{
+ "name": "open-graph",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "open-graph",
+ "dependencies": {
+ "@resvg/resvg-wasm": "^2.6.0",
+ "satori": "^0.10.13"
+ },
+ "devDependencies": {
+ "wrangler": "^4"
+ }
+ },
+ "node_modules/@cloudflare/kv-asset-handler": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz",
+ "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@cloudflare/unenv-preset": {
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.0.tgz",
+ "integrity": "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "peerDependencies": {
+ "unenv": "2.0.0-rc.24",
+ "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "workerd": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-64": {
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260317.1.tgz",
+ "integrity": "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-arm64": {
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260317.1.tgz",
+ "integrity": "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-64": {
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260317.1.tgz",
+ "integrity": "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-arm64": {
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260317.1.tgz",
+ "integrity": "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-windows-64": {
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260317.1.tgz",
+ "integrity": "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@poppinss/colors": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
+ "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^4.1.5"
+ }
+ },
+ "node_modules/@poppinss/dumper": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",
+ "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@poppinss/colors": "^4.1.5",
+ "@sindresorhus/is": "^7.0.2",
+ "supports-color": "^10.0.0"
+ }
+ },
+ "node_modules/@poppinss/exception": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz",
+ "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@resvg/resvg-wasm": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.6.2.tgz",
+ "integrity": "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==",
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@shuding/opentype.js": {
+ "version": "1.4.0-beta.0",
+ "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
+ "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
+ "license": "MIT",
+ "dependencies": {
+ "fflate": "^0.7.3",
+ "string.prototype.codepointat": "^0.2.1"
+ },
+ "bin": {
+ "ot": "bin/ot"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz",
+ "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@speed-highlight/core": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.15.tgz",
+ "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/base64-js": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/blake3-wasm": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
+ "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/css-background-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
+ "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==",
+ "license": "MIT"
+ },
+ "node_modules/css-box-shadow": {
+ "version": "1.0.0-3",
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==",
+ "license": "MIT"
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/error-stack-parser-es": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
+ "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/fflate": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
+ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/hex-rgb": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
+ "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/linebreak": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "0.0.8",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/miniflare": {
+ "version": "4.20260317.2",
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260317.2.tgz",
+ "integrity": "sha512-qNL+yWAFMX6fr0pWU6Lx1vNpPobpnDSF1V8eunIckWvoIQl8y1oBjL2RJFEGY3un+l3f9gwW9dirDPP26usYJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.8.1",
+ "sharp": "^0.34.5",
+ "undici": "7.24.4",
+ "workerd": "1.20260317.1",
+ "ws": "8.18.0",
+ "youch": "4.1.0-beta.10"
+ },
+ "bin": {
+ "miniflare": "bootstrap.js"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
+ "license": "MIT"
+ },
+ "node_modules/parse-css-color": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
+ "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.1.4",
+ "hex-rgb": "^4.1.0"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/satori": {
+ "version": "0.10.14",
+ "resolved": "https://registry.npmjs.org/satori/-/satori-0.10.14.tgz",
+ "integrity": "sha512-abovcqmwl97WKioxpkfuMeZmndB1TuDFY/R+FymrZyiGP+pMYomvgSzVPnbNMWHHESOPosVHGL352oFbdAnJcA==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "@shuding/opentype.js": "1.4.0-beta.0",
+ "css-background-parser": "^0.1.0",
+ "css-box-shadow": "1.0.0-3",
+ "css-to-react-native": "^3.0.0",
+ "emoji-regex": "^10.2.1",
+ "escape-html": "^1.0.3",
+ "linebreak": "^1.1.0",
+ "parse-css-color": "^0.2.1",
+ "postcss-value-parser": "^4.2.0",
+ "yoga-wasm-web": "^0.3.3"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
+ "license": "MIT"
+ },
+ "node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
+ "license": "MIT"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/undici": {
+ "version": "7.24.4",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz",
+ "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/unenv": {
+ "version": "2.0.0-rc.24",
+ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
+ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/unicode-trie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ }
+ },
+ "node_modules/workerd": {
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260317.1.tgz",
+ "integrity": "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "workerd": "bin/workerd"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "@cloudflare/workerd-darwin-64": "1.20260317.1",
+ "@cloudflare/workerd-darwin-arm64": "1.20260317.1",
+ "@cloudflare/workerd-linux-64": "1.20260317.1",
+ "@cloudflare/workerd-linux-arm64": "1.20260317.1",
+ "@cloudflare/workerd-windows-64": "1.20260317.1"
+ }
+ },
+ "node_modules/wrangler": {
+ "version": "4.77.0",
+ "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.77.0.tgz",
+ "integrity": "sha512-E2Gm69+K++BFd3QvoWjC290RPQj1vDOUotA++sNHmtKPb7EP6C8Qv+1D5Ii73tfZtyNgakpqHlh8lBBbVWTKAQ==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@cloudflare/kv-asset-handler": "0.4.2",
+ "@cloudflare/unenv-preset": "2.16.0",
+ "blake3-wasm": "2.1.5",
+ "esbuild": "0.27.3",
+ "miniflare": "4.20260317.2",
+ "path-to-regexp": "6.3.0",
+ "unenv": "2.0.0-rc.24",
+ "workerd": "1.20260317.1"
+ },
+ "bin": {
+ "wrangler": "bin/wrangler.js",
+ "wrangler2": "bin/wrangler.js"
+ },
+ "engines": {
+ "node": ">=20.3.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.20260317.1"
+ },
+ "peerDependenciesMeta": {
+ "@cloudflare/workers-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yoga-wasm-web": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
+ "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==",
+ "license": "MIT"
+ },
+ "node_modules/youch": {
+ "version": "4.1.0-beta.10",
+ "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz",
+ "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@poppinss/colors": "^4.1.5",
+ "@poppinss/dumper": "^0.6.4",
+ "@speed-highlight/core": "^1.2.7",
+ "cookie": "^1.0.2",
+ "youch-core": "^0.3.3"
+ }
+ },
+ "node_modules/youch-core": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz",
+ "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@poppinss/exception": "^1.2.2",
+ "error-stack-parser-es": "^1.0.5"
+ }
+ }
+ }
+}
diff --git a/workers/open-graph/package.json b/workers/open-graph/package.json
new file mode 100644
index 000000000..022a15cf0
--- /dev/null
+++ b/workers/open-graph/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "open-graph",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@resvg/resvg-wasm": "^2.6.0",
+ "satori": "^0.10.13"
+ },
+ "devDependencies": {
+ "wrangler": "^4"
+ }
+}
diff --git a/workers/open-graph/tsconfig.json b/workers/open-graph/tsconfig.json
new file mode 100644
index 000000000..9bb44ef02
--- /dev/null
+++ b/workers/open-graph/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "lib": ["ES2022"],
+ "moduleResolution": "bundler",
+ "strict": true
+ }
+}
diff --git a/workers/open-graph/wrangler.toml b/workers/open-graph/wrangler.toml
new file mode 100644
index 000000000..2a576eca3
--- /dev/null
+++ b/workers/open-graph/wrangler.toml
@@ -0,0 +1,17 @@
+name = "open-graph"
+compatibility_date = "2025-04-03"
+main = "index.js"
+
+routes = [
+ { pattern = "open-graph.radicle.network/*", zone_name = "radicle.network" },
+]
+
+[[rules]]
+type = "CompiledWasm"
+globs = ["**/*.wasm"]
+fallthrough = true
+
+[[rules]]
+type = "Data"
+globs = ["**/*.ttf", "**/*.jpg"]
+fallthrough = true
diff --git a/wrangler.toml b/wrangler.toml
index c46cff9fc..83df8a87d 100644
--- a/wrangler.toml
+++ b/wrangler.toml
@@ -1,7 +1,13 @@
name = "explorer"
compatibility_date = "2025-04-03"
account_id = "dcd58b4607e42dafa1592d13077a60bc"
+main = "workers/og-injector.js"
[assets]
directory = "build"
+binding = "ASSETS"
not_found_handling = "single-page-application"
+run_worker_first = true
+
+[vars]
+OG_IMAGE_BASE = "https://open-graph.radicle.network"