Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
Add open-graph card generation via Cloudflare Workers
Rūdolfs Ošiņš committed 17 days ago
commit 74e15e25350ee1f37c86bdaa7ba406dad863cf4b
parent 21dc8a6
16 files changed +4309 -18
modified .gitignore
@@ -25,3 +25,6 @@ tests/visual/snapshots/**/*
# IDEs and Editors
.idea
.vscode
+

+
# Wrangler local dev state
+
.wrangler/
modified 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/**/*",
    ],
  },
];
modified index.html
@@ -8,23 +8,6 @@
    <meta name="name" content="Radicle Explorer" />
    <meta name="description" content="Explore the Radicle network" />

-
    <meta property="og:url" content="https://app.radicle.xyz" />
-
    <meta property="og:type" content="website" />
-
    <meta property="og:title" content="Radicle Explorer" />
-
    <meta property="og:description" content="Explore the Radicle network" />
-
    <meta
-
      property="og:image"
-
      content="https://app.radicle.xyz/images/radicle-og-853x853-0569.png" />
-

-
    <meta name="twitter:card" content="summary" />
-
    <meta property="twitter:domain" content="app.radicle.xyz" />
-
    <meta property="twitter:url" content="https://app.radicle.xyz" />
-
    <meta name="twitter:title" content="Radicle Explorer" />
-
    <meta name="twitter:description" content="Explore the Radicle network" />
-
    <meta
-
      name="twitter:image"
-
      content="https://app.radicle.xyz/images/radicle-og-853x853-0569.png" />
-

    <link
      rel="preload"
      href="/fonts/Booton-Regular.woff2"
modified package.json
@@ -15,7 +15,9 @@
    "test:e2e:local": "scripts/compile-local-httpd && USE_LOCAL_HTTPD=true NODE_CONFIG_ENV='test' TZ='UTC' playwright test",
    "test:http-client:unit": "NODE_CONFIG_ENV='test' TZ='UTC' vitest run --config http-client/vite.config.ts --reporter verbose",
    "test:radicle-httpd": "cd radicle-httpd && cargo test --all-features",
-
    "deploy": "rimraf build && npm clean-install && npm run build && scripts/inject-plausible && npx wrangler deploy"
+
    "deploy": "rimraf build && npm clean-install && npm run build && scripts/inject-plausible && npx wrangler deploy",
+
    "deploy:open-graph": "cd workers/open-graph && npx wrangler deploy",
+
    "start:open-graph": "cd workers/open-graph && npx wrangler dev"
  },
  "type": "module",
  "engines": {
deleted public/images/radicle-og-853x853-0569.png
added workers/README.md
@@ -0,0 +1,84 @@
+
# OG Image Generation
+

+
Two components work together to serve Open Graph preview images for
+
social media embeds (Twitter/X, Slack, Discord, etc.).
+

+
## Components
+

+
**og-injector** (`og-injector.js`) runs as middleware on the app itself
+
(Cloudflare Workers). It injects `<meta og:image>`, `<meta og:title>`,
+
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`.
added 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 = [
+
      `<meta property="og:title" content="${title}" />`,
+
      `<meta property="og:description" content="${description}" />`,
+
      `<meta property="og:image" content="${ogImageUrl}" />`,
+
      `<meta property="og:image:width" content="1200" />`,
+
      `<meta property="og:image:height" content="630" />`,
+
      `<meta property="og:url" content="${url.href}" />`,
+
      `<meta property="og:type" content="website" />`,
+
      `<meta property="og:site_name" content="Radicle Explorer" />`,
+
      `<meta name="twitter:card" content="summary_large_image" />`,
+
      `<meta name="twitter:site" content="@radicle" />`,
+
      `<meta name="theme-color" content="#1c77ff" />`,
+
    ].join("\n    ");
+

+
    const injected = html.replace("</head>", `  ${tags}\n</head>`);
+

+
    return new Response(injected, {
+
      status: indexResponse.status,
+
      headers: {
+
        "content-type": "text/html;charset=UTF-8",
+
        "cache-control": "public, max-age=300",
+
      },
+
    });
+
  },
+
};
added workers/open-graph/Booton-Regular.ttf
added workers/open-graph/Booton-SemiBold.ttf
added workers/open-graph/forest.jpg
added 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 });
+
    }
+
  },
+
};
added 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"
+
      }
+
    }
+
  }
+
}
added 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"
+
  }
+
}
added workers/open-graph/tsconfig.json
@@ -0,0 +1,9 @@
+
{
+
  "compilerOptions": {
+
    "target": "ES2022",
+
    "module": "ES2022",
+
    "lib": ["ES2022"],
+
    "moduleResolution": "bundler",
+
    "strict": true
+
  }
+
}
added 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
modified 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"