need a status for my status for my .. nate.tngl.io
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

evergreen status page — CF Worker proxy + static site

24 services across 8 groups, batch health checks via
allowlisted Cloudflare Worker, 60s auto-refresh.
MCPs colocated with parent projects, checked via POST.
Groups collapsed by default with composite status dots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz f8980f6d

+2394
+1
.gitignore
··· 1 + node_modules/
+70
site/checker.js
··· 1 + import { PROXY_URL, GROUPS } from "./services.js"; 2 + 3 + const REFRESH_INTERVAL = 60; 4 + 5 + let countdown = REFRESH_INTERVAL; 6 + let timer = null; 7 + 8 + export function buildChecks() { 9 + return GROUPS.flatMap((g) => 10 + g.services.map((s) => ({ 11 + url: s.url, 12 + ...(s.check || {}), 13 + })) 14 + ); 15 + } 16 + 17 + export async function checkAll(onResult) { 18 + const checks = buildChecks(); 19 + 20 + try { 21 + const resp = await fetch(PROXY_URL, { 22 + method: "POST", 23 + headers: { "Content-Type": "application/json" }, 24 + body: JSON.stringify({ checks }), 25 + }); 26 + const results = await resp.json(); 27 + 28 + const byUrl = new Map(); 29 + for (const r of results) { 30 + byUrl.set(r.url, r); 31 + } 32 + onResult(byUrl); 33 + } catch (e) { 34 + console.error("batch check failed:", e); 35 + const byUrl = new Map(); 36 + for (const c of checks) { 37 + byUrl.set(c.url, { url: c.url, status: 0, ok: false, ms: 0 }); 38 + } 39 + onResult(byUrl); 40 + } 41 + } 42 + 43 + export function startAutoRefresh(onResult, onTick) { 44 + stopAutoRefresh(); 45 + countdown = REFRESH_INTERVAL; 46 + 47 + checkAll(onResult); 48 + 49 + timer = setInterval(() => { 50 + countdown--; 51 + onTick(countdown); 52 + if (countdown <= 0) { 53 + countdown = REFRESH_INTERVAL; 54 + checkAll(onResult); 55 + } 56 + }, 1000); 57 + } 58 + 59 + export function stopAutoRefresh() { 60 + if (timer) { 61 + clearInterval(timer); 62 + timer = null; 63 + } 64 + } 65 + 66 + export function checkNow(onResult, onTick) { 67 + countdown = REFRESH_INTERVAL; 68 + onTick(countdown); 69 + checkAll(onResult); 70 + }
+175
site/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>evergreen</title> 7 + <meta name="description" content="status page for services by @zzstoatzz.io"> 8 + <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%230d1117'/%3E%3Ccircle cx='16' cy='16' r='8' fill='%233fb950' opacity='0.85'/%3E%3C/svg%3E"> 9 + <meta property="og:title" content="evergreen"> 10 + <meta property="og:description" content="status page for services by @zzstoatzz.io"> 11 + <meta property="og:image" content="https://evergreen-proxy.nate-8fe.workers.dev/og"> 12 + <meta property="og:image:type" content="image/png"> 13 + <meta property="og:image:width" content="1200"> 14 + <meta property="og:image:height" content="630"> 15 + <meta property="og:type" content="website"> 16 + <meta name="twitter:card" content="summary_large_image"> 17 + <link rel="stylesheet" href="style.css"> 18 + </head> 19 + <body> 20 + <div class="container" role="main"> 21 + <header class="header"> 22 + <h1>evergreen</h1> 23 + <p class="header-byline">status page for services by <a href="https://bsky.app/profile/zzstoatzz.io" target="_blank" rel="noopener">@zzstoatzz.io</a></p> 24 + <div class="header-meta"> 25 + <span class="summary-count" id="summary" aria-live="polite">checking...</span> 26 + <span id="countdown" aria-live="off"></span> 27 + <button class="check-now" id="check-now" aria-label="run health checks now">check now</button> 28 + </div> 29 + </header> 30 + <div id="groups" role="list" aria-label="service groups"></div> 31 + </div> 32 + <script type="module"> 33 + import { GROUPS } from "./services.js"; 34 + import { startAutoRefresh, checkNow } from "./checker.js"; 35 + 36 + const groupsEl = document.getElementById("groups"); 37 + const summaryEl = document.getElementById("summary"); 38 + const countdownEl = document.getElementById("countdown"); 39 + const checkNowBtn = document.getElementById("check-now"); 40 + 41 + const totalServices = GROUPS.reduce((n, g) => n + g.services.length, 0); 42 + 43 + for (const group of GROUPS) { 44 + const div = document.createElement("div"); 45 + div.className = "group collapsed"; 46 + div.setAttribute("role", "listitem"); 47 + 48 + const headerId = `group-${group.name.replace(/\s+/g, "-")}`; 49 + const contentId = `content-${group.name.replace(/\s+/g, "-")}`; 50 + 51 + div.innerHTML = ` 52 + <button class="group-header" aria-expanded="false" aria-controls="${contentId}" id="${headerId}"> 53 + <span class="group-chevron" aria-hidden="true">&#9660;</span> 54 + <span class="group-name">${group.name}</span> 55 + <span class="group-count" data-group="${group.name}"></span> 56 + <span class="status-dot checking" data-group-dot="${group.name}" role="img" aria-label="group status: checking"></span> 57 + </button> 58 + <div class="group-services" id="${contentId}" role="list" aria-labelledby="${headerId}"> 59 + ${group.services.map(s => ` 60 + <div class="service-row" data-url="${s.url}" role="listitem"> 61 + <a class="service-name" href="${s.href}" target="_blank" rel="noopener">${s.name}</a> 62 + <div class="service-status" aria-label="status"> 63 + <span class="status-code"></span> 64 + <span class="status-ms"></span> 65 + <span class="status-dot checking" role="img" aria-label="checking"></span> 66 + </div> 67 + </div> 68 + `).join("")} 69 + </div> 70 + `; 71 + 72 + const btn = div.querySelector(".group-header"); 73 + btn.addEventListener("click", () => { 74 + const collapsed = div.classList.toggle("collapsed"); 75 + btn.setAttribute("aria-expanded", String(!collapsed)); 76 + }); 77 + 78 + groupsEl.appendChild(div); 79 + } 80 + 81 + function setChecking() { 82 + for (const dot of document.querySelectorAll(".status-dot")) { 83 + dot.className = "status-dot checking"; 84 + dot.setAttribute("aria-label", "checking"); 85 + } 86 + for (const code of document.querySelectorAll(".status-code")) { 87 + code.textContent = ""; 88 + } 89 + for (const ms of document.querySelectorAll(".status-ms")) { 90 + ms.textContent = ""; 91 + } 92 + } 93 + 94 + function statusLabel(ok, status) { 95 + if (ok) return "healthy"; 96 + if (status >= 400 || status === 0) return "down"; 97 + return "degraded"; 98 + } 99 + 100 + function onResult(byUrl) { 101 + let up = 0; 102 + 103 + for (const group of GROUPS) { 104 + let groupUp = 0; 105 + let groupHasError = false; 106 + 107 + for (const s of group.services) { 108 + const r = byUrl.get(s.url); 109 + const row = document.querySelector(`[data-url="${s.url}"]`); 110 + if (!row || !r) continue; 111 + 112 + const dot = row.querySelector(".status-dot"); 113 + const code = row.querySelector(".status-code"); 114 + const ms = row.querySelector(".status-ms"); 115 + 116 + dot.className = "status-dot"; 117 + if (r.ok) { 118 + dot.classList.add("ok"); 119 + up++; 120 + groupUp++; 121 + } else if (r.status >= 400 || r.status === 0) { 122 + dot.classList.add("error"); 123 + groupHasError = true; 124 + } else { 125 + dot.classList.add("warn"); 126 + } 127 + 128 + dot.setAttribute("aria-label", statusLabel(r.ok, r.status)); 129 + code.textContent = r.status || "---"; 130 + ms.textContent = r.ms ? `${r.ms}ms` : ""; 131 + } 132 + 133 + const groupDot = document.querySelector(`[data-group-dot="${group.name}"]`); 134 + if (groupDot) { 135 + groupDot.className = "status-dot"; 136 + if (groupUp === group.services.length) { 137 + groupDot.classList.add("ok"); 138 + groupDot.setAttribute("aria-label", "group status: all healthy"); 139 + } else if (groupHasError) { 140 + groupDot.classList.add("error"); 141 + groupDot.setAttribute("aria-label", "group status: some down"); 142 + } else { 143 + groupDot.classList.add("warn"); 144 + groupDot.setAttribute("aria-label", "group status: degraded"); 145 + } 146 + } 147 + 148 + const countEl = document.querySelector(`[data-group="${group.name}"]`); 149 + if (countEl) { 150 + countEl.textContent = `${groupUp}/${group.services.length}`; 151 + } 152 + } 153 + 154 + summaryEl.textContent = `${up}/${totalServices} up`; 155 + 156 + const now = new Date(); 157 + const ts = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); 158 + countdownEl.textContent = `checked ${ts}`; 159 + } 160 + 161 + function onTick(secs) { 162 + countdownEl.textContent = `next check in ${secs}s`; 163 + } 164 + 165 + checkNowBtn.addEventListener("click", () => { 166 + setChecking(); 167 + summaryEl.textContent = "checking..."; 168 + checkNow(onResult, onTick); 169 + }); 170 + 171 + setChecking(); 172 + startAutoRefresh(onResult, onTick); 173 + </script> 174 + </body> 175 + </html>
+86
site/services.js
··· 1 + export const PROXY_URL = "https://evergreen-proxy.nate-8fe.workers.dev"; 2 + 3 + const MCP_CHECK = { 4 + method: "POST", 5 + headers: { 6 + "Content-Type": "application/json", 7 + "Accept": "application/json, text/event-stream", 8 + }, 9 + body: JSON.stringify({ 10 + jsonrpc: "2.0", 11 + method: "initialize", 12 + id: 1, 13 + params: { 14 + capabilities: {}, 15 + clientInfo: { name: "evergreen", version: "0.1" }, 16 + protocolVersion: "2024-11-05", 17 + }, 18 + }), 19 + }; 20 + 21 + export const GROUPS = [ 22 + { 23 + name: "plyr.fm", 24 + services: [ 25 + { name: "api", url: "https://api.plyr.fm/health", href: "https://api.plyr.fm" }, 26 + { name: "moderation", url: "https://moderation.plyr.fm/", href: "https://moderation.plyr.fm" }, 27 + { name: "transcoder", url: "https://plyr-transcoder.fly.dev/health", href: "https://plyr-transcoder.fly.dev" }, 28 + { name: "frontend", url: "https://plyr.fm", href: "https://plyr.fm" }, 29 + { name: "docs", url: "https://docs.plyr.fm", href: "https://docs.plyr.fm" }, 30 + { name: "mcp", url: "https://plyrfm.fastmcp.app/mcp", href: "https://plyrfm.fastmcp.app", check: MCP_CHECK }, 31 + ], 32 + }, 33 + { 34 + name: "pub-search", 35 + services: [ 36 + { name: "backend", url: "https://leaflet-search-backend.fly.dev/health", href: "https://leaflet-search-backend.fly.dev" }, 37 + { name: "tap", url: "https://leaflet-search-tap.fly.dev/health", href: "https://leaflet-search-tap.fly.dev" }, 38 + { name: "frontend", url: "https://pub-search.waow.tech", href: "https://pub-search.waow.tech" }, 39 + { name: "mcp", url: "https://pub-search-by-zzstoatzz.fastmcp.app/mcp", href: "https://pub-search-by-zzstoatzz.fastmcp.app", check: MCP_CHECK }, 40 + ], 41 + }, 42 + { 43 + name: "relays", 44 + services: [ 45 + { name: "indigo", url: "https://relay.waow.tech/xrpc/_health", href: "https://relay.waow.tech" }, 46 + { name: "zlay", url: "https://zlay.waow.tech/_health", href: "https://zlay.waow.tech" }, 47 + { name: "relay-eval", url: "https://relay-eval.waow.tech", href: "https://relay-eval.waow.tech" }, 48 + ], 49 + }, 50 + { 51 + name: "coral", 52 + services: [ 53 + { name: "backend", url: "https://coral.fly.dev/health", href: "https://coral.fly.dev" }, 54 + { name: "frontend", url: "https://coral-8hh.pages.dev", href: "https://coral-8hh.pages.dev" }, 55 + ], 56 + }, 57 + { 58 + name: "find-bufo", 59 + services: [ 60 + { name: "search", url: "https://find-bufo.com/", href: "https://find-bufo.com" }, 61 + { name: "bot", url: "https://bufo-bot.fly.dev/", href: "https://bufo-bot.fly.dev" }, 62 + ], 63 + }, 64 + { 65 + name: "pollz", 66 + services: [ 67 + { name: "backend", url: "https://pollz-backend.fly.dev/health", href: "https://pollz-backend.fly.dev" }, 68 + { name: "frontend", url: "https://pollz.waow.tech", href: "https://pollz.waow.tech" }, 69 + ], 70 + }, 71 + { 72 + name: "pds infra", 73 + services: [ 74 + { name: "PDS", url: "https://pds.zzstoatzz.io/xrpc/_health", href: "https://pds.zzstoatzz.io" }, 75 + { name: "pdsx mcp", url: "https://pdsx-by-zzstoatzz.fastmcp.app/mcp", href: "https://pdsx-by-zzstoatzz.fastmcp.app", check: MCP_CHECK }, 76 + ], 77 + }, 78 + { 79 + name: "misc", 80 + services: [ 81 + { name: "music-feed", url: "https://zig-bsky-feed.fly.dev/health", href: "https://zig-bsky-feed.fly.dev" }, 82 + { name: "at-me", url: "https://at-me.wisp.place", href: "https://at-me.wisp.place" }, 83 + { name: "status", url: "https://status.zzstoatzz.io", href: "https://status.zzstoatzz.io" }, 84 + ], 85 + }, 86 + ];
+270
site/style.css
··· 1 + * { 2 + margin: 0; 3 + padding: 0; 4 + box-sizing: border-box; 5 + } 6 + 7 + :root { 8 + --bg: #0d1117; 9 + --surface: #161b22; 10 + --border: #21262d; 11 + --border-strong: #30363d; 12 + --text: #c9d1d9; 13 + --text-muted: #8b949e; 14 + --text-bright: #f0f6fc; 15 + --green: #3fb950; 16 + --red: #f85149; 17 + --yellow: #d29922; 18 + --accent: #58a6ff; 19 + } 20 + 21 + body { 22 + background: var(--bg); 23 + color: var(--text); 24 + font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace; 25 + font-size: 13px; 26 + line-height: 1.55; 27 + -webkit-font-smoothing: antialiased; 28 + -moz-osx-font-smoothing: grayscale; 29 + } 30 + 31 + .container { 32 + max-width: 720px; 33 + margin: 0 auto; 34 + padding: 2rem 1.5rem; 35 + } 36 + 37 + /* header */ 38 + .header { 39 + margin-bottom: 2rem; 40 + } 41 + 42 + .header h1 { 43 + font-size: 1rem; 44 + font-weight: 600; 45 + color: var(--text-bright); 46 + margin-bottom: 0.25rem; 47 + } 48 + 49 + .header-byline { 50 + font-size: 0.75rem; 51 + color: var(--text-muted); 52 + margin-bottom: 0.5rem; 53 + } 54 + 55 + .header-byline a { 56 + color: var(--accent); 57 + text-decoration: none; 58 + } 59 + 60 + .header-byline a:hover { 61 + text-decoration: underline; 62 + } 63 + 64 + .header-meta { 65 + display: flex; 66 + align-items: center; 67 + gap: 1rem; 68 + font-size: 0.75rem; 69 + color: var(--text-muted); 70 + } 71 + 72 + .summary-count { 73 + color: var(--text); 74 + } 75 + 76 + .check-now { 77 + background: none; 78 + border: 1px solid var(--border-strong); 79 + color: var(--accent); 80 + font-family: inherit; 81 + font-size: 0.75rem; 82 + padding: 0.2rem 0.6rem; 83 + border-radius: 4px; 84 + cursor: pointer; 85 + transition: border-color 0.15s; 86 + } 87 + 88 + .check-now:hover { 89 + border-color: var(--accent); 90 + } 91 + 92 + .check-now:focus-visible { 93 + outline: 2px solid var(--accent); 94 + outline-offset: 2px; 95 + } 96 + 97 + /* groups */ 98 + .group { 99 + border-top: 1px solid var(--border); 100 + margin-bottom: 0.25rem; 101 + } 102 + 103 + .group-header { 104 + display: flex; 105 + align-items: center; 106 + gap: 0.5rem; 107 + padding: 0.6rem 0; 108 + cursor: pointer; 109 + user-select: none; 110 + width: 100%; 111 + background: none; 112 + border: none; 113 + color: inherit; 114 + font: inherit; 115 + text-align: left; 116 + } 117 + 118 + .group-header:hover { 119 + color: var(--text-bright); 120 + } 121 + 122 + .group-header:focus-visible { 123 + outline: 2px solid var(--accent); 124 + outline-offset: 2px; 125 + border-radius: 4px; 126 + } 127 + 128 + .group-name { 129 + font-size: 0.8rem; 130 + font-weight: 600; 131 + text-transform: uppercase; 132 + letter-spacing: 0.05em; 133 + color: var(--text-muted); 134 + transition: color 0.1s; 135 + } 136 + 137 + .group-header:hover .group-name { 138 + color: var(--text); 139 + } 140 + 141 + .group-chevron { 142 + font-size: 0.7rem; 143 + color: var(--text-muted); 144 + transition: transform 0.15s; 145 + } 146 + 147 + .group.collapsed .group-chevron { 148 + transform: rotate(-90deg); 149 + } 150 + 151 + .group-count { 152 + font-size: 0.7rem; 153 + color: var(--text-muted); 154 + margin-left: auto; 155 + } 156 + 157 + .group-header .status-dot { 158 + margin-left: 0.5rem; 159 + } 160 + 161 + .group-services { 162 + padding-bottom: 0.5rem; 163 + } 164 + 165 + .group.collapsed .group-services { 166 + display: none; 167 + } 168 + 169 + /* service rows */ 170 + .service-row { 171 + display: flex; 172 + align-items: center; 173 + justify-content: space-between; 174 + padding: 0.35rem 0.5rem; 175 + border-radius: 4px; 176 + transition: background 0.1s; 177 + } 178 + 179 + .service-row:hover { 180 + background: rgba(22, 27, 34, 0.5); 181 + } 182 + 183 + .service-name { 184 + font-size: 0.82rem; 185 + color: var(--accent); 186 + text-decoration: none; 187 + } 188 + 189 + .service-name:hover { 190 + text-decoration: underline; 191 + } 192 + 193 + .service-name:focus-visible { 194 + outline: 2px solid var(--accent); 195 + outline-offset: 2px; 196 + border-radius: 2px; 197 + } 198 + 199 + .service-status { 200 + display: flex; 201 + align-items: center; 202 + gap: 0.5rem; 203 + } 204 + 205 + .status-dot { 206 + width: 8px; 207 + height: 8px; 208 + border-radius: 50%; 209 + background: var(--border-strong); 210 + flex-shrink: 0; 211 + } 212 + 213 + .status-dot.ok { background: var(--green); } 214 + .status-dot.error { background: var(--red); } 215 + .status-dot.warn { background: var(--yellow); } 216 + 217 + .status-dot.checking { 218 + background: var(--text-muted); 219 + animation: pulse 1.2s ease-in-out infinite; 220 + } 221 + 222 + @keyframes pulse { 223 + 0%, 100% { opacity: 0.3; } 224 + 50% { opacity: 1; } 225 + } 226 + 227 + .status-code { 228 + font-size: 0.75rem; 229 + color: var(--text-muted); 230 + min-width: 2rem; 231 + text-align: right; 232 + } 233 + 234 + .status-ms { 235 + font-size: 0.75rem; 236 + color: var(--text-muted); 237 + min-width: 3.5rem; 238 + text-align: right; 239 + } 240 + 241 + /* responsive */ 242 + @media (max-width: 640px) { 243 + .container { 244 + padding: 1.5rem 1rem; 245 + } 246 + 247 + .group-header { 248 + padding: 0.75rem 0; 249 + min-height: 44px; 250 + } 251 + 252 + .service-row { 253 + padding: 0.5rem; 254 + min-height: 44px; 255 + } 256 + 257 + .service-name { 258 + padding: 0.25rem 0; 259 + } 260 + 261 + .header-meta { 262 + flex-wrap: wrap; 263 + gap: 0.5rem; 264 + } 265 + 266 + .check-now { 267 + padding: 0.4rem 0.8rem; 268 + min-height: 36px; 269 + } 270 + }
+1514
worker/package-lock.json
··· 1 + { 2 + "name": "evergreen-proxy", 3 + "lockfileVersion": 3, 4 + "requires": true, 5 + "packages": { 6 + "": { 7 + "name": "evergreen-proxy", 8 + "dependencies": { 9 + "@resvg/resvg-wasm": "^2.6.2" 10 + }, 11 + "devDependencies": { 12 + "wrangler": "^4" 13 + } 14 + }, 15 + "node_modules/@cloudflare/kv-asset-handler": { 16 + "version": "0.4.2", 17 + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", 18 + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", 19 + "dev": true, 20 + "license": "MIT OR Apache-2.0", 21 + "engines": { 22 + "node": ">=18.0.0" 23 + } 24 + }, 25 + "node_modules/@cloudflare/unenv-preset": { 26 + "version": "2.15.0", 27 + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.15.0.tgz", 28 + "integrity": "sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw==", 29 + "dev": true, 30 + "license": "MIT OR Apache-2.0", 31 + "peerDependencies": { 32 + "unenv": "2.0.0-rc.24", 33 + "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" 34 + }, 35 + "peerDependenciesMeta": { 36 + "workerd": { 37 + "optional": true 38 + } 39 + } 40 + }, 41 + "node_modules/@cloudflare/workerd-darwin-64": { 42 + "version": "1.20260312.1", 43 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260312.1.tgz", 44 + "integrity": "sha512-HUAtDWaqUduS6yasV6+NgsK7qBpP1qGU49ow/Wb117IHjYp+PZPUGReDYocpB4GOMRoQlvdd4L487iFxzdARpw==", 45 + "cpu": [ 46 + "x64" 47 + ], 48 + "dev": true, 49 + "license": "Apache-2.0", 50 + "optional": true, 51 + "os": [ 52 + "darwin" 53 + ], 54 + "engines": { 55 + "node": ">=16" 56 + } 57 + }, 58 + "node_modules/@cloudflare/workerd-darwin-arm64": { 59 + "version": "1.20260312.1", 60 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260312.1.tgz", 61 + "integrity": "sha512-DOn7TPTHSxJYfi4m4NYga/j32wOTqvJf/pY4Txz5SDKWIZHSTXFyGz2K4B+thoPWLop/KZxGoyTv7db0mk/qyw==", 62 + "cpu": [ 63 + "arm64" 64 + ], 65 + "dev": true, 66 + "license": "Apache-2.0", 67 + "optional": true, 68 + "os": [ 69 + "darwin" 70 + ], 71 + "engines": { 72 + "node": ">=16" 73 + } 74 + }, 75 + "node_modules/@cloudflare/workerd-linux-64": { 76 + "version": "1.20260312.1", 77 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260312.1.tgz", 78 + "integrity": "sha512-TdkIh3WzPXYHuvz7phAtFEEvAxvFd30tHrm4gsgpw0R0F5b8PtoM3hfL2uY7EcBBWVYUBtkY2ahDYFfufnXw/g==", 79 + "cpu": [ 80 + "x64" 81 + ], 82 + "dev": true, 83 + "license": "Apache-2.0", 84 + "optional": true, 85 + "os": [ 86 + "linux" 87 + ], 88 + "engines": { 89 + "node": ">=16" 90 + } 91 + }, 92 + "node_modules/@cloudflare/workerd-linux-arm64": { 93 + "version": "1.20260312.1", 94 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260312.1.tgz", 95 + "integrity": "sha512-kNauZhL569Iy94t844OMwa1zP6zKFiL3xiJ4tGLS+TFTEfZ3pZsRH6lWWOtkXkjTyCmBEOog0HSEKjIV4oAffw==", 96 + "cpu": [ 97 + "arm64" 98 + ], 99 + "dev": true, 100 + "license": "Apache-2.0", 101 + "optional": true, 102 + "os": [ 103 + "linux" 104 + ], 105 + "engines": { 106 + "node": ">=16" 107 + } 108 + }, 109 + "node_modules/@cloudflare/workerd-windows-64": { 110 + "version": "1.20260312.1", 111 + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260312.1.tgz", 112 + "integrity": "sha512-5dBrlSK+nMsZy5bYQpj8t9iiQNvCRlkm9GGvswJa9vVU/1BNO4BhJMlqOLWT24EmFyApZ+kaBiPJMV8847NDTg==", 113 + "cpu": [ 114 + "x64" 115 + ], 116 + "dev": true, 117 + "license": "Apache-2.0", 118 + "optional": true, 119 + "os": [ 120 + "win32" 121 + ], 122 + "engines": { 123 + "node": ">=16" 124 + } 125 + }, 126 + "node_modules/@cspotcode/source-map-support": { 127 + "version": "0.8.1", 128 + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 129 + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 130 + "dev": true, 131 + "license": "MIT", 132 + "dependencies": { 133 + "@jridgewell/trace-mapping": "0.3.9" 134 + }, 135 + "engines": { 136 + "node": ">=12" 137 + } 138 + }, 139 + "node_modules/@emnapi/runtime": { 140 + "version": "1.9.0", 141 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", 142 + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", 143 + "dev": true, 144 + "license": "MIT", 145 + "optional": true, 146 + "dependencies": { 147 + "tslib": "^2.4.0" 148 + } 149 + }, 150 + "node_modules/@esbuild/aix-ppc64": { 151 + "version": "0.27.3", 152 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", 153 + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", 154 + "cpu": [ 155 + "ppc64" 156 + ], 157 + "dev": true, 158 + "license": "MIT", 159 + "optional": true, 160 + "os": [ 161 + "aix" 162 + ], 163 + "engines": { 164 + "node": ">=18" 165 + } 166 + }, 167 + "node_modules/@esbuild/android-arm": { 168 + "version": "0.27.3", 169 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", 170 + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", 171 + "cpu": [ 172 + "arm" 173 + ], 174 + "dev": true, 175 + "license": "MIT", 176 + "optional": true, 177 + "os": [ 178 + "android" 179 + ], 180 + "engines": { 181 + "node": ">=18" 182 + } 183 + }, 184 + "node_modules/@esbuild/android-arm64": { 185 + "version": "0.27.3", 186 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", 187 + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", 188 + "cpu": [ 189 + "arm64" 190 + ], 191 + "dev": true, 192 + "license": "MIT", 193 + "optional": true, 194 + "os": [ 195 + "android" 196 + ], 197 + "engines": { 198 + "node": ">=18" 199 + } 200 + }, 201 + "node_modules/@esbuild/android-x64": { 202 + "version": "0.27.3", 203 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", 204 + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", 205 + "cpu": [ 206 + "x64" 207 + ], 208 + "dev": true, 209 + "license": "MIT", 210 + "optional": true, 211 + "os": [ 212 + "android" 213 + ], 214 + "engines": { 215 + "node": ">=18" 216 + } 217 + }, 218 + "node_modules/@esbuild/darwin-arm64": { 219 + "version": "0.27.3", 220 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", 221 + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", 222 + "cpu": [ 223 + "arm64" 224 + ], 225 + "dev": true, 226 + "license": "MIT", 227 + "optional": true, 228 + "os": [ 229 + "darwin" 230 + ], 231 + "engines": { 232 + "node": ">=18" 233 + } 234 + }, 235 + "node_modules/@esbuild/darwin-x64": { 236 + "version": "0.27.3", 237 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", 238 + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", 239 + "cpu": [ 240 + "x64" 241 + ], 242 + "dev": true, 243 + "license": "MIT", 244 + "optional": true, 245 + "os": [ 246 + "darwin" 247 + ], 248 + "engines": { 249 + "node": ">=18" 250 + } 251 + }, 252 + "node_modules/@esbuild/freebsd-arm64": { 253 + "version": "0.27.3", 254 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", 255 + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", 256 + "cpu": [ 257 + "arm64" 258 + ], 259 + "dev": true, 260 + "license": "MIT", 261 + "optional": true, 262 + "os": [ 263 + "freebsd" 264 + ], 265 + "engines": { 266 + "node": ">=18" 267 + } 268 + }, 269 + "node_modules/@esbuild/freebsd-x64": { 270 + "version": "0.27.3", 271 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", 272 + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", 273 + "cpu": [ 274 + "x64" 275 + ], 276 + "dev": true, 277 + "license": "MIT", 278 + "optional": true, 279 + "os": [ 280 + "freebsd" 281 + ], 282 + "engines": { 283 + "node": ">=18" 284 + } 285 + }, 286 + "node_modules/@esbuild/linux-arm": { 287 + "version": "0.27.3", 288 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", 289 + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", 290 + "cpu": [ 291 + "arm" 292 + ], 293 + "dev": true, 294 + "license": "MIT", 295 + "optional": true, 296 + "os": [ 297 + "linux" 298 + ], 299 + "engines": { 300 + "node": ">=18" 301 + } 302 + }, 303 + "node_modules/@esbuild/linux-arm64": { 304 + "version": "0.27.3", 305 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", 306 + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", 307 + "cpu": [ 308 + "arm64" 309 + ], 310 + "dev": true, 311 + "license": "MIT", 312 + "optional": true, 313 + "os": [ 314 + "linux" 315 + ], 316 + "engines": { 317 + "node": ">=18" 318 + } 319 + }, 320 + "node_modules/@esbuild/linux-ia32": { 321 + "version": "0.27.3", 322 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", 323 + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", 324 + "cpu": [ 325 + "ia32" 326 + ], 327 + "dev": true, 328 + "license": "MIT", 329 + "optional": true, 330 + "os": [ 331 + "linux" 332 + ], 333 + "engines": { 334 + "node": ">=18" 335 + } 336 + }, 337 + "node_modules/@esbuild/linux-loong64": { 338 + "version": "0.27.3", 339 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", 340 + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", 341 + "cpu": [ 342 + "loong64" 343 + ], 344 + "dev": true, 345 + "license": "MIT", 346 + "optional": true, 347 + "os": [ 348 + "linux" 349 + ], 350 + "engines": { 351 + "node": ">=18" 352 + } 353 + }, 354 + "node_modules/@esbuild/linux-mips64el": { 355 + "version": "0.27.3", 356 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", 357 + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", 358 + "cpu": [ 359 + "mips64el" 360 + ], 361 + "dev": true, 362 + "license": "MIT", 363 + "optional": true, 364 + "os": [ 365 + "linux" 366 + ], 367 + "engines": { 368 + "node": ">=18" 369 + } 370 + }, 371 + "node_modules/@esbuild/linux-ppc64": { 372 + "version": "0.27.3", 373 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", 374 + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", 375 + "cpu": [ 376 + "ppc64" 377 + ], 378 + "dev": true, 379 + "license": "MIT", 380 + "optional": true, 381 + "os": [ 382 + "linux" 383 + ], 384 + "engines": { 385 + "node": ">=18" 386 + } 387 + }, 388 + "node_modules/@esbuild/linux-riscv64": { 389 + "version": "0.27.3", 390 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", 391 + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", 392 + "cpu": [ 393 + "riscv64" 394 + ], 395 + "dev": true, 396 + "license": "MIT", 397 + "optional": true, 398 + "os": [ 399 + "linux" 400 + ], 401 + "engines": { 402 + "node": ">=18" 403 + } 404 + }, 405 + "node_modules/@esbuild/linux-s390x": { 406 + "version": "0.27.3", 407 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", 408 + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", 409 + "cpu": [ 410 + "s390x" 411 + ], 412 + "dev": true, 413 + "license": "MIT", 414 + "optional": true, 415 + "os": [ 416 + "linux" 417 + ], 418 + "engines": { 419 + "node": ">=18" 420 + } 421 + }, 422 + "node_modules/@esbuild/linux-x64": { 423 + "version": "0.27.3", 424 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", 425 + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", 426 + "cpu": [ 427 + "x64" 428 + ], 429 + "dev": true, 430 + "license": "MIT", 431 + "optional": true, 432 + "os": [ 433 + "linux" 434 + ], 435 + "engines": { 436 + "node": ">=18" 437 + } 438 + }, 439 + "node_modules/@esbuild/netbsd-arm64": { 440 + "version": "0.27.3", 441 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", 442 + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", 443 + "cpu": [ 444 + "arm64" 445 + ], 446 + "dev": true, 447 + "license": "MIT", 448 + "optional": true, 449 + "os": [ 450 + "netbsd" 451 + ], 452 + "engines": { 453 + "node": ">=18" 454 + } 455 + }, 456 + "node_modules/@esbuild/netbsd-x64": { 457 + "version": "0.27.3", 458 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", 459 + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", 460 + "cpu": [ 461 + "x64" 462 + ], 463 + "dev": true, 464 + "license": "MIT", 465 + "optional": true, 466 + "os": [ 467 + "netbsd" 468 + ], 469 + "engines": { 470 + "node": ">=18" 471 + } 472 + }, 473 + "node_modules/@esbuild/openbsd-arm64": { 474 + "version": "0.27.3", 475 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", 476 + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", 477 + "cpu": [ 478 + "arm64" 479 + ], 480 + "dev": true, 481 + "license": "MIT", 482 + "optional": true, 483 + "os": [ 484 + "openbsd" 485 + ], 486 + "engines": { 487 + "node": ">=18" 488 + } 489 + }, 490 + "node_modules/@esbuild/openbsd-x64": { 491 + "version": "0.27.3", 492 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", 493 + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", 494 + "cpu": [ 495 + "x64" 496 + ], 497 + "dev": true, 498 + "license": "MIT", 499 + "optional": true, 500 + "os": [ 501 + "openbsd" 502 + ], 503 + "engines": { 504 + "node": ">=18" 505 + } 506 + }, 507 + "node_modules/@esbuild/openharmony-arm64": { 508 + "version": "0.27.3", 509 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", 510 + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", 511 + "cpu": [ 512 + "arm64" 513 + ], 514 + "dev": true, 515 + "license": "MIT", 516 + "optional": true, 517 + "os": [ 518 + "openharmony" 519 + ], 520 + "engines": { 521 + "node": ">=18" 522 + } 523 + }, 524 + "node_modules/@esbuild/sunos-x64": { 525 + "version": "0.27.3", 526 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", 527 + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", 528 + "cpu": [ 529 + "x64" 530 + ], 531 + "dev": true, 532 + "license": "MIT", 533 + "optional": true, 534 + "os": [ 535 + "sunos" 536 + ], 537 + "engines": { 538 + "node": ">=18" 539 + } 540 + }, 541 + "node_modules/@esbuild/win32-arm64": { 542 + "version": "0.27.3", 543 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", 544 + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", 545 + "cpu": [ 546 + "arm64" 547 + ], 548 + "dev": true, 549 + "license": "MIT", 550 + "optional": true, 551 + "os": [ 552 + "win32" 553 + ], 554 + "engines": { 555 + "node": ">=18" 556 + } 557 + }, 558 + "node_modules/@esbuild/win32-ia32": { 559 + "version": "0.27.3", 560 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", 561 + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", 562 + "cpu": [ 563 + "ia32" 564 + ], 565 + "dev": true, 566 + "license": "MIT", 567 + "optional": true, 568 + "os": [ 569 + "win32" 570 + ], 571 + "engines": { 572 + "node": ">=18" 573 + } 574 + }, 575 + "node_modules/@esbuild/win32-x64": { 576 + "version": "0.27.3", 577 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", 578 + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", 579 + "cpu": [ 580 + "x64" 581 + ], 582 + "dev": true, 583 + "license": "MIT", 584 + "optional": true, 585 + "os": [ 586 + "win32" 587 + ], 588 + "engines": { 589 + "node": ">=18" 590 + } 591 + }, 592 + "node_modules/@img/colour": { 593 + "version": "1.1.0", 594 + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", 595 + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", 596 + "dev": true, 597 + "license": "MIT", 598 + "engines": { 599 + "node": ">=18" 600 + } 601 + }, 602 + "node_modules/@img/sharp-darwin-arm64": { 603 + "version": "0.34.5", 604 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", 605 + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", 606 + "cpu": [ 607 + "arm64" 608 + ], 609 + "dev": true, 610 + "license": "Apache-2.0", 611 + "optional": true, 612 + "os": [ 613 + "darwin" 614 + ], 615 + "engines": { 616 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 617 + }, 618 + "funding": { 619 + "url": "https://opencollective.com/libvips" 620 + }, 621 + "optionalDependencies": { 622 + "@img/sharp-libvips-darwin-arm64": "1.2.4" 623 + } 624 + }, 625 + "node_modules/@img/sharp-darwin-x64": { 626 + "version": "0.34.5", 627 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", 628 + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", 629 + "cpu": [ 630 + "x64" 631 + ], 632 + "dev": true, 633 + "license": "Apache-2.0", 634 + "optional": true, 635 + "os": [ 636 + "darwin" 637 + ], 638 + "engines": { 639 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 640 + }, 641 + "funding": { 642 + "url": "https://opencollective.com/libvips" 643 + }, 644 + "optionalDependencies": { 645 + "@img/sharp-libvips-darwin-x64": "1.2.4" 646 + } 647 + }, 648 + "node_modules/@img/sharp-libvips-darwin-arm64": { 649 + "version": "1.2.4", 650 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", 651 + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", 652 + "cpu": [ 653 + "arm64" 654 + ], 655 + "dev": true, 656 + "license": "LGPL-3.0-or-later", 657 + "optional": true, 658 + "os": [ 659 + "darwin" 660 + ], 661 + "funding": { 662 + "url": "https://opencollective.com/libvips" 663 + } 664 + }, 665 + "node_modules/@img/sharp-libvips-darwin-x64": { 666 + "version": "1.2.4", 667 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", 668 + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", 669 + "cpu": [ 670 + "x64" 671 + ], 672 + "dev": true, 673 + "license": "LGPL-3.0-or-later", 674 + "optional": true, 675 + "os": [ 676 + "darwin" 677 + ], 678 + "funding": { 679 + "url": "https://opencollective.com/libvips" 680 + } 681 + }, 682 + "node_modules/@img/sharp-libvips-linux-arm": { 683 + "version": "1.2.4", 684 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", 685 + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", 686 + "cpu": [ 687 + "arm" 688 + ], 689 + "dev": true, 690 + "license": "LGPL-3.0-or-later", 691 + "optional": true, 692 + "os": [ 693 + "linux" 694 + ], 695 + "funding": { 696 + "url": "https://opencollective.com/libvips" 697 + } 698 + }, 699 + "node_modules/@img/sharp-libvips-linux-arm64": { 700 + "version": "1.2.4", 701 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", 702 + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", 703 + "cpu": [ 704 + "arm64" 705 + ], 706 + "dev": true, 707 + "license": "LGPL-3.0-or-later", 708 + "optional": true, 709 + "os": [ 710 + "linux" 711 + ], 712 + "funding": { 713 + "url": "https://opencollective.com/libvips" 714 + } 715 + }, 716 + "node_modules/@img/sharp-libvips-linux-ppc64": { 717 + "version": "1.2.4", 718 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", 719 + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", 720 + "cpu": [ 721 + "ppc64" 722 + ], 723 + "dev": true, 724 + "license": "LGPL-3.0-or-later", 725 + "optional": true, 726 + "os": [ 727 + "linux" 728 + ], 729 + "funding": { 730 + "url": "https://opencollective.com/libvips" 731 + } 732 + }, 733 + "node_modules/@img/sharp-libvips-linux-riscv64": { 734 + "version": "1.2.4", 735 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", 736 + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", 737 + "cpu": [ 738 + "riscv64" 739 + ], 740 + "dev": true, 741 + "license": "LGPL-3.0-or-later", 742 + "optional": true, 743 + "os": [ 744 + "linux" 745 + ], 746 + "funding": { 747 + "url": "https://opencollective.com/libvips" 748 + } 749 + }, 750 + "node_modules/@img/sharp-libvips-linux-s390x": { 751 + "version": "1.2.4", 752 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", 753 + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", 754 + "cpu": [ 755 + "s390x" 756 + ], 757 + "dev": true, 758 + "license": "LGPL-3.0-or-later", 759 + "optional": true, 760 + "os": [ 761 + "linux" 762 + ], 763 + "funding": { 764 + "url": "https://opencollective.com/libvips" 765 + } 766 + }, 767 + "node_modules/@img/sharp-libvips-linux-x64": { 768 + "version": "1.2.4", 769 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", 770 + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", 771 + "cpu": [ 772 + "x64" 773 + ], 774 + "dev": true, 775 + "license": "LGPL-3.0-or-later", 776 + "optional": true, 777 + "os": [ 778 + "linux" 779 + ], 780 + "funding": { 781 + "url": "https://opencollective.com/libvips" 782 + } 783 + }, 784 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 785 + "version": "1.2.4", 786 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", 787 + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", 788 + "cpu": [ 789 + "arm64" 790 + ], 791 + "dev": true, 792 + "license": "LGPL-3.0-or-later", 793 + "optional": true, 794 + "os": [ 795 + "linux" 796 + ], 797 + "funding": { 798 + "url": "https://opencollective.com/libvips" 799 + } 800 + }, 801 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 802 + "version": "1.2.4", 803 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", 804 + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", 805 + "cpu": [ 806 + "x64" 807 + ], 808 + "dev": true, 809 + "license": "LGPL-3.0-or-later", 810 + "optional": true, 811 + "os": [ 812 + "linux" 813 + ], 814 + "funding": { 815 + "url": "https://opencollective.com/libvips" 816 + } 817 + }, 818 + "node_modules/@img/sharp-linux-arm": { 819 + "version": "0.34.5", 820 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", 821 + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", 822 + "cpu": [ 823 + "arm" 824 + ], 825 + "dev": true, 826 + "license": "Apache-2.0", 827 + "optional": true, 828 + "os": [ 829 + "linux" 830 + ], 831 + "engines": { 832 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 833 + }, 834 + "funding": { 835 + "url": "https://opencollective.com/libvips" 836 + }, 837 + "optionalDependencies": { 838 + "@img/sharp-libvips-linux-arm": "1.2.4" 839 + } 840 + }, 841 + "node_modules/@img/sharp-linux-arm64": { 842 + "version": "0.34.5", 843 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", 844 + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", 845 + "cpu": [ 846 + "arm64" 847 + ], 848 + "dev": true, 849 + "license": "Apache-2.0", 850 + "optional": true, 851 + "os": [ 852 + "linux" 853 + ], 854 + "engines": { 855 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 856 + }, 857 + "funding": { 858 + "url": "https://opencollective.com/libvips" 859 + }, 860 + "optionalDependencies": { 861 + "@img/sharp-libvips-linux-arm64": "1.2.4" 862 + } 863 + }, 864 + "node_modules/@img/sharp-linux-ppc64": { 865 + "version": "0.34.5", 866 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", 867 + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", 868 + "cpu": [ 869 + "ppc64" 870 + ], 871 + "dev": true, 872 + "license": "Apache-2.0", 873 + "optional": true, 874 + "os": [ 875 + "linux" 876 + ], 877 + "engines": { 878 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 879 + }, 880 + "funding": { 881 + "url": "https://opencollective.com/libvips" 882 + }, 883 + "optionalDependencies": { 884 + "@img/sharp-libvips-linux-ppc64": "1.2.4" 885 + } 886 + }, 887 + "node_modules/@img/sharp-linux-riscv64": { 888 + "version": "0.34.5", 889 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", 890 + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", 891 + "cpu": [ 892 + "riscv64" 893 + ], 894 + "dev": true, 895 + "license": "Apache-2.0", 896 + "optional": true, 897 + "os": [ 898 + "linux" 899 + ], 900 + "engines": { 901 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 902 + }, 903 + "funding": { 904 + "url": "https://opencollective.com/libvips" 905 + }, 906 + "optionalDependencies": { 907 + "@img/sharp-libvips-linux-riscv64": "1.2.4" 908 + } 909 + }, 910 + "node_modules/@img/sharp-linux-s390x": { 911 + "version": "0.34.5", 912 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", 913 + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", 914 + "cpu": [ 915 + "s390x" 916 + ], 917 + "dev": true, 918 + "license": "Apache-2.0", 919 + "optional": true, 920 + "os": [ 921 + "linux" 922 + ], 923 + "engines": { 924 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 925 + }, 926 + "funding": { 927 + "url": "https://opencollective.com/libvips" 928 + }, 929 + "optionalDependencies": { 930 + "@img/sharp-libvips-linux-s390x": "1.2.4" 931 + } 932 + }, 933 + "node_modules/@img/sharp-linux-x64": { 934 + "version": "0.34.5", 935 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", 936 + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", 937 + "cpu": [ 938 + "x64" 939 + ], 940 + "dev": true, 941 + "license": "Apache-2.0", 942 + "optional": true, 943 + "os": [ 944 + "linux" 945 + ], 946 + "engines": { 947 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 948 + }, 949 + "funding": { 950 + "url": "https://opencollective.com/libvips" 951 + }, 952 + "optionalDependencies": { 953 + "@img/sharp-libvips-linux-x64": "1.2.4" 954 + } 955 + }, 956 + "node_modules/@img/sharp-linuxmusl-arm64": { 957 + "version": "0.34.5", 958 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", 959 + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", 960 + "cpu": [ 961 + "arm64" 962 + ], 963 + "dev": true, 964 + "license": "Apache-2.0", 965 + "optional": true, 966 + "os": [ 967 + "linux" 968 + ], 969 + "engines": { 970 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 971 + }, 972 + "funding": { 973 + "url": "https://opencollective.com/libvips" 974 + }, 975 + "optionalDependencies": { 976 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" 977 + } 978 + }, 979 + "node_modules/@img/sharp-linuxmusl-x64": { 980 + "version": "0.34.5", 981 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", 982 + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", 983 + "cpu": [ 984 + "x64" 985 + ], 986 + "dev": true, 987 + "license": "Apache-2.0", 988 + "optional": true, 989 + "os": [ 990 + "linux" 991 + ], 992 + "engines": { 993 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 994 + }, 995 + "funding": { 996 + "url": "https://opencollective.com/libvips" 997 + }, 998 + "optionalDependencies": { 999 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" 1000 + } 1001 + }, 1002 + "node_modules/@img/sharp-wasm32": { 1003 + "version": "0.34.5", 1004 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", 1005 + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", 1006 + "cpu": [ 1007 + "wasm32" 1008 + ], 1009 + "dev": true, 1010 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 1011 + "optional": true, 1012 + "dependencies": { 1013 + "@emnapi/runtime": "^1.7.0" 1014 + }, 1015 + "engines": { 1016 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1017 + }, 1018 + "funding": { 1019 + "url": "https://opencollective.com/libvips" 1020 + } 1021 + }, 1022 + "node_modules/@img/sharp-win32-arm64": { 1023 + "version": "0.34.5", 1024 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", 1025 + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", 1026 + "cpu": [ 1027 + "arm64" 1028 + ], 1029 + "dev": true, 1030 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1031 + "optional": true, 1032 + "os": [ 1033 + "win32" 1034 + ], 1035 + "engines": { 1036 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1037 + }, 1038 + "funding": { 1039 + "url": "https://opencollective.com/libvips" 1040 + } 1041 + }, 1042 + "node_modules/@img/sharp-win32-ia32": { 1043 + "version": "0.34.5", 1044 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", 1045 + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", 1046 + "cpu": [ 1047 + "ia32" 1048 + ], 1049 + "dev": true, 1050 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1051 + "optional": true, 1052 + "os": [ 1053 + "win32" 1054 + ], 1055 + "engines": { 1056 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1057 + }, 1058 + "funding": { 1059 + "url": "https://opencollective.com/libvips" 1060 + } 1061 + }, 1062 + "node_modules/@img/sharp-win32-x64": { 1063 + "version": "0.34.5", 1064 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", 1065 + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", 1066 + "cpu": [ 1067 + "x64" 1068 + ], 1069 + "dev": true, 1070 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1071 + "optional": true, 1072 + "os": [ 1073 + "win32" 1074 + ], 1075 + "engines": { 1076 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1077 + }, 1078 + "funding": { 1079 + "url": "https://opencollective.com/libvips" 1080 + } 1081 + }, 1082 + "node_modules/@jridgewell/resolve-uri": { 1083 + "version": "3.1.2", 1084 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 1085 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 1086 + "dev": true, 1087 + "license": "MIT", 1088 + "engines": { 1089 + "node": ">=6.0.0" 1090 + } 1091 + }, 1092 + "node_modules/@jridgewell/sourcemap-codec": { 1093 + "version": "1.5.5", 1094 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 1095 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 1096 + "dev": true, 1097 + "license": "MIT" 1098 + }, 1099 + "node_modules/@jridgewell/trace-mapping": { 1100 + "version": "0.3.9", 1101 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 1102 + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 1103 + "dev": true, 1104 + "license": "MIT", 1105 + "dependencies": { 1106 + "@jridgewell/resolve-uri": "^3.0.3", 1107 + "@jridgewell/sourcemap-codec": "^1.4.10" 1108 + } 1109 + }, 1110 + "node_modules/@poppinss/colors": { 1111 + "version": "4.1.6", 1112 + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", 1113 + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", 1114 + "dev": true, 1115 + "license": "MIT", 1116 + "dependencies": { 1117 + "kleur": "^4.1.5" 1118 + } 1119 + }, 1120 + "node_modules/@poppinss/dumper": { 1121 + "version": "0.6.5", 1122 + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", 1123 + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", 1124 + "dev": true, 1125 + "license": "MIT", 1126 + "dependencies": { 1127 + "@poppinss/colors": "^4.1.5", 1128 + "@sindresorhus/is": "^7.0.2", 1129 + "supports-color": "^10.0.0" 1130 + } 1131 + }, 1132 + "node_modules/@poppinss/exception": { 1133 + "version": "1.2.3", 1134 + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", 1135 + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", 1136 + "dev": true, 1137 + "license": "MIT" 1138 + }, 1139 + "node_modules/@resvg/resvg-wasm": { 1140 + "version": "2.6.2", 1141 + "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.6.2.tgz", 1142 + "integrity": "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==", 1143 + "license": "MPL-2.0", 1144 + "engines": { 1145 + "node": ">= 10" 1146 + } 1147 + }, 1148 + "node_modules/@sindresorhus/is": { 1149 + "version": "7.2.0", 1150 + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", 1151 + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", 1152 + "dev": true, 1153 + "license": "MIT", 1154 + "engines": { 1155 + "node": ">=18" 1156 + }, 1157 + "funding": { 1158 + "url": "https://github.com/sindresorhus/is?sponsor=1" 1159 + } 1160 + }, 1161 + "node_modules/@speed-highlight/core": { 1162 + "version": "1.2.14", 1163 + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz", 1164 + "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", 1165 + "dev": true, 1166 + "license": "CC0-1.0" 1167 + }, 1168 + "node_modules/blake3-wasm": { 1169 + "version": "2.1.5", 1170 + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", 1171 + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", 1172 + "dev": true, 1173 + "license": "MIT" 1174 + }, 1175 + "node_modules/cookie": { 1176 + "version": "1.1.1", 1177 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", 1178 + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", 1179 + "dev": true, 1180 + "license": "MIT", 1181 + "engines": { 1182 + "node": ">=18" 1183 + }, 1184 + "funding": { 1185 + "type": "opencollective", 1186 + "url": "https://opencollective.com/express" 1187 + } 1188 + }, 1189 + "node_modules/detect-libc": { 1190 + "version": "2.1.2", 1191 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1192 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1193 + "dev": true, 1194 + "license": "Apache-2.0", 1195 + "engines": { 1196 + "node": ">=8" 1197 + } 1198 + }, 1199 + "node_modules/error-stack-parser-es": { 1200 + "version": "1.0.5", 1201 + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", 1202 + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", 1203 + "dev": true, 1204 + "license": "MIT", 1205 + "funding": { 1206 + "url": "https://github.com/sponsors/antfu" 1207 + } 1208 + }, 1209 + "node_modules/esbuild": { 1210 + "version": "0.27.3", 1211 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", 1212 + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", 1213 + "dev": true, 1214 + "hasInstallScript": true, 1215 + "license": "MIT", 1216 + "bin": { 1217 + "esbuild": "bin/esbuild" 1218 + }, 1219 + "engines": { 1220 + "node": ">=18" 1221 + }, 1222 + "optionalDependencies": { 1223 + "@esbuild/aix-ppc64": "0.27.3", 1224 + "@esbuild/android-arm": "0.27.3", 1225 + "@esbuild/android-arm64": "0.27.3", 1226 + "@esbuild/android-x64": "0.27.3", 1227 + "@esbuild/darwin-arm64": "0.27.3", 1228 + "@esbuild/darwin-x64": "0.27.3", 1229 + "@esbuild/freebsd-arm64": "0.27.3", 1230 + "@esbuild/freebsd-x64": "0.27.3", 1231 + "@esbuild/linux-arm": "0.27.3", 1232 + "@esbuild/linux-arm64": "0.27.3", 1233 + "@esbuild/linux-ia32": "0.27.3", 1234 + "@esbuild/linux-loong64": "0.27.3", 1235 + "@esbuild/linux-mips64el": "0.27.3", 1236 + "@esbuild/linux-ppc64": "0.27.3", 1237 + "@esbuild/linux-riscv64": "0.27.3", 1238 + "@esbuild/linux-s390x": "0.27.3", 1239 + "@esbuild/linux-x64": "0.27.3", 1240 + "@esbuild/netbsd-arm64": "0.27.3", 1241 + "@esbuild/netbsd-x64": "0.27.3", 1242 + "@esbuild/openbsd-arm64": "0.27.3", 1243 + "@esbuild/openbsd-x64": "0.27.3", 1244 + "@esbuild/openharmony-arm64": "0.27.3", 1245 + "@esbuild/sunos-x64": "0.27.3", 1246 + "@esbuild/win32-arm64": "0.27.3", 1247 + "@esbuild/win32-ia32": "0.27.3", 1248 + "@esbuild/win32-x64": "0.27.3" 1249 + } 1250 + }, 1251 + "node_modules/fsevents": { 1252 + "version": "2.3.3", 1253 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1254 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1255 + "dev": true, 1256 + "hasInstallScript": true, 1257 + "license": "MIT", 1258 + "optional": true, 1259 + "os": [ 1260 + "darwin" 1261 + ], 1262 + "engines": { 1263 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1264 + } 1265 + }, 1266 + "node_modules/kleur": { 1267 + "version": "4.1.5", 1268 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1269 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1270 + "dev": true, 1271 + "license": "MIT", 1272 + "engines": { 1273 + "node": ">=6" 1274 + } 1275 + }, 1276 + "node_modules/miniflare": { 1277 + "version": "4.20260312.0", 1278 + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260312.0.tgz", 1279 + "integrity": "sha512-pieP2rfXynPT6VRINYaiHe/tfMJ4c5OIhqRlIdLF6iZ9g5xgpEmvimvIgMpgAdDJuFlrLcwDUi8MfAo2R6dt/w==", 1280 + "dev": true, 1281 + "license": "MIT", 1282 + "dependencies": { 1283 + "@cspotcode/source-map-support": "0.8.1", 1284 + "sharp": "^0.34.5", 1285 + "undici": "7.18.2", 1286 + "workerd": "1.20260312.1", 1287 + "ws": "8.18.0", 1288 + "youch": "4.1.0-beta.10" 1289 + }, 1290 + "bin": { 1291 + "miniflare": "bootstrap.js" 1292 + }, 1293 + "engines": { 1294 + "node": ">=18.0.0" 1295 + } 1296 + }, 1297 + "node_modules/path-to-regexp": { 1298 + "version": "6.3.0", 1299 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", 1300 + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", 1301 + "dev": true, 1302 + "license": "MIT" 1303 + }, 1304 + "node_modules/pathe": { 1305 + "version": "2.0.3", 1306 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 1307 + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 1308 + "dev": true, 1309 + "license": "MIT" 1310 + }, 1311 + "node_modules/semver": { 1312 + "version": "7.7.4", 1313 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", 1314 + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", 1315 + "dev": true, 1316 + "license": "ISC", 1317 + "bin": { 1318 + "semver": "bin/semver.js" 1319 + }, 1320 + "engines": { 1321 + "node": ">=10" 1322 + } 1323 + }, 1324 + "node_modules/sharp": { 1325 + "version": "0.34.5", 1326 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", 1327 + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", 1328 + "dev": true, 1329 + "hasInstallScript": true, 1330 + "license": "Apache-2.0", 1331 + "dependencies": { 1332 + "@img/colour": "^1.0.0", 1333 + "detect-libc": "^2.1.2", 1334 + "semver": "^7.7.3" 1335 + }, 1336 + "engines": { 1337 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1338 + }, 1339 + "funding": { 1340 + "url": "https://opencollective.com/libvips" 1341 + }, 1342 + "optionalDependencies": { 1343 + "@img/sharp-darwin-arm64": "0.34.5", 1344 + "@img/sharp-darwin-x64": "0.34.5", 1345 + "@img/sharp-libvips-darwin-arm64": "1.2.4", 1346 + "@img/sharp-libvips-darwin-x64": "1.2.4", 1347 + "@img/sharp-libvips-linux-arm": "1.2.4", 1348 + "@img/sharp-libvips-linux-arm64": "1.2.4", 1349 + "@img/sharp-libvips-linux-ppc64": "1.2.4", 1350 + "@img/sharp-libvips-linux-riscv64": "1.2.4", 1351 + "@img/sharp-libvips-linux-s390x": "1.2.4", 1352 + "@img/sharp-libvips-linux-x64": "1.2.4", 1353 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", 1354 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", 1355 + "@img/sharp-linux-arm": "0.34.5", 1356 + "@img/sharp-linux-arm64": "0.34.5", 1357 + "@img/sharp-linux-ppc64": "0.34.5", 1358 + "@img/sharp-linux-riscv64": "0.34.5", 1359 + "@img/sharp-linux-s390x": "0.34.5", 1360 + "@img/sharp-linux-x64": "0.34.5", 1361 + "@img/sharp-linuxmusl-arm64": "0.34.5", 1362 + "@img/sharp-linuxmusl-x64": "0.34.5", 1363 + "@img/sharp-wasm32": "0.34.5", 1364 + "@img/sharp-win32-arm64": "0.34.5", 1365 + "@img/sharp-win32-ia32": "0.34.5", 1366 + "@img/sharp-win32-x64": "0.34.5" 1367 + } 1368 + }, 1369 + "node_modules/supports-color": { 1370 + "version": "10.2.2", 1371 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", 1372 + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", 1373 + "dev": true, 1374 + "license": "MIT", 1375 + "engines": { 1376 + "node": ">=18" 1377 + }, 1378 + "funding": { 1379 + "url": "https://github.com/chalk/supports-color?sponsor=1" 1380 + } 1381 + }, 1382 + "node_modules/tslib": { 1383 + "version": "2.8.1", 1384 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1385 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1386 + "dev": true, 1387 + "license": "0BSD", 1388 + "optional": true 1389 + }, 1390 + "node_modules/undici": { 1391 + "version": "7.18.2", 1392 + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", 1393 + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", 1394 + "dev": true, 1395 + "license": "MIT", 1396 + "engines": { 1397 + "node": ">=20.18.1" 1398 + } 1399 + }, 1400 + "node_modules/unenv": { 1401 + "version": "2.0.0-rc.24", 1402 + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", 1403 + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", 1404 + "dev": true, 1405 + "license": "MIT", 1406 + "dependencies": { 1407 + "pathe": "^2.0.3" 1408 + } 1409 + }, 1410 + "node_modules/workerd": { 1411 + "version": "1.20260312.1", 1412 + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260312.1.tgz", 1413 + "integrity": "sha512-nNpPkw9jaqo79B+iBCOiksx+N62xC+ETIfyzofUEdY3cSOHJg6oNnVSHm7vHevzVblfV76c8Gr0cXHEapYMBEg==", 1414 + "dev": true, 1415 + "hasInstallScript": true, 1416 + "license": "Apache-2.0", 1417 + "bin": { 1418 + "workerd": "bin/workerd" 1419 + }, 1420 + "engines": { 1421 + "node": ">=16" 1422 + }, 1423 + "optionalDependencies": { 1424 + "@cloudflare/workerd-darwin-64": "1.20260312.1", 1425 + "@cloudflare/workerd-darwin-arm64": "1.20260312.1", 1426 + "@cloudflare/workerd-linux-64": "1.20260312.1", 1427 + "@cloudflare/workerd-linux-arm64": "1.20260312.1", 1428 + "@cloudflare/workerd-windows-64": "1.20260312.1" 1429 + } 1430 + }, 1431 + "node_modules/wrangler": { 1432 + "version": "4.73.0", 1433 + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.73.0.tgz", 1434 + "integrity": "sha512-VJXsqKDFCp6OtFEHXITSOR5kh95JOknwPY8m7RyQuWJQguSybJy43m4vhoCSt42prutTef7eeuw7L4V4xiynGw==", 1435 + "dev": true, 1436 + "license": "MIT OR Apache-2.0", 1437 + "dependencies": { 1438 + "@cloudflare/kv-asset-handler": "0.4.2", 1439 + "@cloudflare/unenv-preset": "2.15.0", 1440 + "blake3-wasm": "2.1.5", 1441 + "esbuild": "0.27.3", 1442 + "miniflare": "4.20260312.0", 1443 + "path-to-regexp": "6.3.0", 1444 + "unenv": "2.0.0-rc.24", 1445 + "workerd": "1.20260312.1" 1446 + }, 1447 + "bin": { 1448 + "wrangler": "bin/wrangler.js", 1449 + "wrangler2": "bin/wrangler.js" 1450 + }, 1451 + "engines": { 1452 + "node": ">=20.0.0" 1453 + }, 1454 + "optionalDependencies": { 1455 + "fsevents": "~2.3.2" 1456 + }, 1457 + "peerDependencies": { 1458 + "@cloudflare/workers-types": "^4.20260312.1" 1459 + }, 1460 + "peerDependenciesMeta": { 1461 + "@cloudflare/workers-types": { 1462 + "optional": true 1463 + } 1464 + } 1465 + }, 1466 + "node_modules/ws": { 1467 + "version": "8.18.0", 1468 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 1469 + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 1470 + "dev": true, 1471 + "license": "MIT", 1472 + "engines": { 1473 + "node": ">=10.0.0" 1474 + }, 1475 + "peerDependencies": { 1476 + "bufferutil": "^4.0.1", 1477 + "utf-8-validate": ">=5.0.2" 1478 + }, 1479 + "peerDependenciesMeta": { 1480 + "bufferutil": { 1481 + "optional": true 1482 + }, 1483 + "utf-8-validate": { 1484 + "optional": true 1485 + } 1486 + } 1487 + }, 1488 + "node_modules/youch": { 1489 + "version": "4.1.0-beta.10", 1490 + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", 1491 + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", 1492 + "dev": true, 1493 + "license": "MIT", 1494 + "dependencies": { 1495 + "@poppinss/colors": "^4.1.5", 1496 + "@poppinss/dumper": "^0.6.4", 1497 + "@speed-highlight/core": "^1.2.7", 1498 + "cookie": "^1.0.2", 1499 + "youch-core": "^0.3.3" 1500 + } 1501 + }, 1502 + "node_modules/youch-core": { 1503 + "version": "0.3.3", 1504 + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", 1505 + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", 1506 + "dev": true, 1507 + "license": "MIT", 1508 + "dependencies": { 1509 + "@poppinss/exception": "^1.2.2", 1510 + "error-stack-parser-es": "^1.0.5" 1511 + } 1512 + } 1513 + } 1514 + }
+14
worker/package.json
··· 1 + { 2 + "name": "evergreen-proxy", 3 + "private": true, 4 + "scripts": { 5 + "dev": "wrangler dev", 6 + "deploy": "wrangler deploy" 7 + }, 8 + "devDependencies": { 9 + "wrangler": "^4" 10 + }, 11 + "dependencies": { 12 + "@resvg/resvg-wasm": "^2.6.2" 13 + } 14 + }
+258
worker/src/index.ts
··· 1 + import { Resvg, initWasm } from "@resvg/resvg-wasm"; 2 + import resvgWasm from "@resvg/resvg-wasm/index_bg.wasm"; 3 + 4 + const ALLOWED_HOSTS = new Set([ 5 + "api.plyr.fm", 6 + "moderation.plyr.fm", 7 + "plyr-transcoder.fly.dev", 8 + "plyr.fm", 9 + "docs.plyr.fm", 10 + "leaflet-search-backend.fly.dev", 11 + "leaflet-search-tap.fly.dev", 12 + "pub-search.waow.tech", 13 + "relay.waow.tech", 14 + "zlay.waow.tech", 15 + "relay-eval.waow.tech", 16 + "coral.fly.dev", 17 + "coral-8hh.pages.dev", 18 + "find-bufo.com", 19 + "bufo-bot.fly.dev", 20 + "pollz-backend.fly.dev", 21 + "pollz.waow.tech", 22 + "pds.zzstoatzz.io", 23 + "zig-bsky-feed.fly.dev", 24 + "at-me.wisp.place", 25 + "status.zzstoatzz.io", 26 + "pub-search-by-zzstoatzz.fastmcp.app", 27 + "plyrfm.fastmcp.app", 28 + "pdsx-by-zzstoatzz.fastmcp.app", 29 + ]); 30 + 31 + const CORS_HEADERS = { 32 + "Access-Control-Allow-Origin": "*", 33 + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 34 + "Access-Control-Allow-Headers": "Content-Type", 35 + }; 36 + 37 + interface Check { 38 + url: string; 39 + method?: string; 40 + headers?: Record<string, string>; 41 + body?: string; 42 + } 43 + 44 + function isAllowed(url: string): boolean { 45 + try { 46 + return ALLOWED_HOSTS.has(new URL(url).hostname); 47 + } catch { 48 + return false; 49 + } 50 + } 51 + 52 + async function checkUrl( 53 + check: Check 54 + ): Promise<{ url: string; status: number; ok: boolean; ms: number }> { 55 + const start = Date.now(); 56 + try { 57 + const resp = await fetch(check.url, { 58 + method: check.method || "GET", 59 + headers: check.headers, 60 + body: check.body, 61 + redirect: "follow", 62 + signal: AbortSignal.timeout(10_000), 63 + }); 64 + return { 65 + url: check.url, 66 + status: resp.status, 67 + ok: resp.ok, 68 + ms: Date.now() - start, 69 + }; 70 + } catch { 71 + return { url: check.url, status: 0, ok: false, ms: Date.now() - start }; 72 + } 73 + } 74 + 75 + const TOTAL_SERVICES = 24; 76 + 77 + function buildOgSvg(): string { 78 + const W = 1200; 79 + const H = 630; 80 + const cx = W / 2; // tree center x 81 + 82 + let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">`; 83 + 84 + // background + gradient 85 + svg += `<defs>`; 86 + svg += `<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">`; 87 + svg += `<stop offset="0%" stop-color="#3fb950" stop-opacity="0.05"/>`; 88 + svg += `<stop offset="100%" stop-color="#58a6ff" stop-opacity="0.05"/>`; 89 + svg += `</linearGradient>`; 90 + svg += `<radialGradient id="glow" cx="50%" cy="40%" r="50%">`; 91 + svg += `<stop offset="0%" stop-color="#3fb950" stop-opacity="0.08"/>`; 92 + svg += `<stop offset="100%" stop-color="#3fb950" stop-opacity="0"/>`; 93 + svg += `</radialGradient>`; 94 + svg += `</defs>`; 95 + svg += `<rect width="${W}" height="${H}" fill="#0d1117"/>`; 96 + svg += `<rect width="${W}" height="${H}" fill="url(#bg)"/>`; 97 + svg += `<rect width="${W}" height="${H}" fill="url(#glow)"/>`; 98 + 99 + // tree made of service dots — 24 dots arranged as an evergreen 100 + // rows from top: 1, 2, 3, 4, 5, 6, 2 (trunk), 1 (trunk) = 24 101 + const dotR = 7; 102 + const spacingX = 28; 103 + const spacingY = 32; 104 + const treeTop = 80; 105 + 106 + const treeRows = [1, 2, 3, 4, 5, 6, 2, 1]; 107 + // colors: canopy is green shades, trunk is muted 108 + const rowColors = [ 109 + "#2ea043", // tip — darker green 110 + "#3fb950", 111 + "#3fb950", 112 + "#3fb950", 113 + "#56d364", 114 + "#56d364", // wider — lighter green 115 + "#8b6c3e", // trunk 116 + "#8b6c3e", // trunk base 117 + ]; 118 + 119 + let dotIndex = 0; 120 + for (let row = 0; row < treeRows.length; row++) { 121 + const count = treeRows[row]; 122 + const y = treeTop + row * spacingY; 123 + const startX = cx - ((count - 1) * spacingX) / 2; 124 + 125 + for (let j = 0; j < count; j++) { 126 + const x = startX + j * spacingX; 127 + const color = rowColors[row]; 128 + // slight glow on canopy dots 129 + if (row < 6) { 130 + svg += `<circle cx="${x}" cy="${y}" r="${dotR + 4}" fill="${color}" opacity="0.15"/>`; 131 + } 132 + svg += `<circle cx="${x}" cy="${y}" r="${dotR}" fill="${color}" opacity="0.9"/>`; 133 + dotIndex++; 134 + } 135 + } 136 + 137 + // title below tree 138 + const textY = treeTop + treeRows.length * spacingY + 40; 139 + svg += `<text x="${cx}" y="${textY}" text-anchor="middle" fill="#f0f6fc" font-family="JetBrains Mono" font-size="48" font-weight="bold">evergreen</text>`; 140 + svg += `<text x="${cx}" y="${textY + 35}" text-anchor="middle" fill="#8b949e" font-family="JetBrains Mono" font-size="16">status page for services by @zzstoatzz.io · ${TOTAL_SERVICES} endpoints</text>`; 141 + 142 + // footer 143 + svg += `<text x="${cx}" y="${H - 30}" text-anchor="middle" fill="#30363d" font-family="JetBrains Mono" font-size="13">nate.tngl.io</text>`; 144 + 145 + svg += `</svg>`; 146 + return svg; 147 + } 148 + 149 + let wasmInitialized = false; 150 + let fontRegular: Uint8Array | null = null; 151 + let fontBold: Uint8Array | null = null; 152 + 153 + async function loadFonts(): Promise<Uint8Array[]> { 154 + const urls = [ 155 + "https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf", 156 + "https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf", 157 + ]; 158 + if (!fontRegular || !fontBold) { 159 + const [reg, bold] = await Promise.all(urls.map((u) => fetch(u))); 160 + fontRegular = new Uint8Array(await reg.arrayBuffer()); 161 + fontBold = new Uint8Array(await bold.arrayBuffer()); 162 + } 163 + return [fontRegular, fontBold]; 164 + } 165 + 166 + async function handleOg(): Promise<Response> { 167 + if (!wasmInitialized) { 168 + await initWasm(resvgWasm); 169 + wasmInitialized = true; 170 + } 171 + 172 + const fonts = await loadFonts(); 173 + const svg = buildOgSvg(); 174 + const resvg = new Resvg(svg, { 175 + font: { 176 + fontBuffers: fonts, 177 + loadSystemFonts: false, 178 + defaultFontFamily: "JetBrains Mono", 179 + }, 180 + fitTo: { mode: "width", value: 1200 }, 181 + }); 182 + const png = resvg.render(); 183 + const pngData = png.asPng(); 184 + 185 + return new Response(pngData, { 186 + headers: { 187 + "Content-Type": "image/png", 188 + "Cache-Control": "public, max-age=3600", 189 + ...CORS_HEADERS, 190 + }, 191 + }); 192 + } 193 + 194 + export default { 195 + async fetch(request: Request): Promise<Response> { 196 + if (request.method === "OPTIONS") { 197 + return new Response(null, { status: 204, headers: CORS_HEADERS }); 198 + } 199 + 200 + const pathname = new URL(request.url).pathname; 201 + 202 + // OG image endpoint 203 + if (pathname === "/og") { 204 + return handleOg(); 205 + } 206 + 207 + // batch POST — accepts { checks: [{ url, method?, headers?, body? }] } 208 + if (request.method === "POST") { 209 + let body: { checks?: Check[] }; 210 + try { 211 + body = await request.json(); 212 + } catch { 213 + return Response.json( 214 + { error: "invalid json" }, 215 + { status: 400, headers: CORS_HEADERS } 216 + ); 217 + } 218 + 219 + const checks = body.checks; 220 + if (!Array.isArray(checks) || checks.length === 0) { 221 + return Response.json( 222 + { error: "checks must be a non-empty array" }, 223 + { status: 400, headers: CORS_HEADERS } 224 + ); 225 + } 226 + 227 + const blocked = checks.filter((c) => !isAllowed(c.url)); 228 + if (blocked.length > 0) { 229 + return Response.json( 230 + { error: "blocked hosts", blocked: blocked.map((c) => c.url) }, 231 + { status: 403, headers: CORS_HEADERS } 232 + ); 233 + } 234 + 235 + const results = await Promise.all(checks.map(checkUrl)); 236 + return Response.json(results, { headers: CORS_HEADERS }); 237 + } 238 + 239 + // single GET 240 + const url = new URL(request.url).searchParams.get("url"); 241 + if (!url) { 242 + return Response.json( 243 + { error: "missing ?url= parameter" }, 244 + { status: 400, headers: CORS_HEADERS } 245 + ); 246 + } 247 + 248 + if (!isAllowed(url)) { 249 + return Response.json( 250 + { error: "host not allowed" }, 251 + { status: 403, headers: CORS_HEADERS } 252 + ); 253 + } 254 + 255 + const result = await checkUrl({ url }); 256 + return Response.json(result, { headers: CORS_HEADERS }); 257 + }, 258 + };
+6
worker/wrangler.jsonc
··· 1 + { 2 + "name": "evergreen-proxy", 3 + "main": "src/index.ts", 4 + "compatibility_date": "2024-12-01", 5 + "compatibility_flags": ["nodejs_compat"] 6 + }