Feat: Add logo, favicon and bad.webp; move images to /assets

- New logo (svg + jpg) and multi-size favicon.ico
- /favicon.ico still served at root so direct image views get the icon
- Generated missing bad.webp placeholder
- Moved limit.webp into /assets and routed error images via helper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 17:49:18 -07:00
co-authored by Claude Opus 5.5
parent 21b85acff5
commit 2976236c00
7 changed files with 20 additions and 7 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" stroke-linecap="round" stroke-linejoin="round">
<style>
.f{stroke:#1a1a1a}.a{stroke:#0070f3}
@media (prefers-color-scheme:dark){.f{stroke:#f5f5f5}}
</style>
<!-- frame -->
<rect class="f" x="7" y="7" width="50" height="50" rx="13" stroke-width="4.5"/>
<!-- the URL "/" rising into a peak -->
<path class="f" d="M19 57 29 32l10 14 5-7 13 5" stroke-width="4.5"/>
<!-- a link as the sun -->
<path class="a" d="M-2-4h-3a4 4 0 0 0 0 8h3M2-4h3a4 4 0 0 1 0 8H2M-4 0h8" stroke-width="2.5" transform="translate(42 22) rotate(-45)"/>
</svg>

After

Width:  |  Height:  |  Size: 627 B

+7 -7
View File
@@ -7,17 +7,17 @@ export async function onRequest(context) {
const { request, env, params } = context; const { request, env, params } = context;
const url = new URL(request.url); const url = new URL(request.url);
const path = params.path?.join("/") || ""; const path = params.path?.join("/") || "";
const asset = f => env.ASSETS.fetch(new Request(new URL(`/assets/${f}`, url.origin)));
if (!path || path === "index.html" || path === "favicon.ico" || path === "robots.txt" || path === "limit.webp" || path === "bad.webp") { if (!path || path === "index.html" || path === "robots.txt" || path.startsWith("assets/")) {
return env.ASSETS.fetch(request); return env.ASSETS.fetch(request);
} }
if (path === "favicon.ico") return asset("favicon.ico");
if (path === "_setup") return setupSurreal(env); if (path === "_setup") return setupSurreal(env);
const rawQueryPart = url.pathname.slice(1).replace(/\/+$/, ""); const rawQueryPart = url.pathname.slice(1).replace(/\/+$/, "");
if (rawQueryPart.includes(".") || rawQueryPart.includes("/")) { if (rawQueryPart.includes(".") || rawQueryPart.includes("/")) return asset("bad.webp");
return env.ASSETS.fetch(new Request(new URL("/bad.webp", url.origin)));
}
const query = normalizeQuery(path); const query = normalizeQuery(path);
if (!query) return jsonResponse(400, { error: "Empty query" }); if (!query) return jsonResponse(400, { error: "Empty query" });
@@ -28,7 +28,7 @@ export async function onRequest(context) {
const cached = await env.DIRECT_IMG_CACHE.get(cacheKey, "json"); const cached = await env.DIRECT_IMG_CACHE.get(cacheKey, "json");
if (cached) { if (cached) {
if (cached.err) return env.ASSETS.fetch(new Request(new URL("/bad.webp", url.origin))); if (cached.err) return asset("bad.webp");
const obj = await env.R2_IMAGES.get(r2Key); const obj = await env.R2_IMAGES.get(r2Key);
if (obj) { if (obj) {
const nowSec = Math.floor(Date.now() / 1000); const nowSec = Math.floor(Date.now() / 1000);
@@ -120,7 +120,7 @@ export async function onRequest(context) {
if (count > 20) { if (count > 20) {
context.waitUntil(notify(env, { title: "Rate Limit Hit", message: `IP ${ip} hit limit for: ${query}`, tags: "warning,no_entry", priority: 2 })); context.waitUntil(notify(env, { title: "Rate Limit Hit", message: `IP ${ip} hit limit for: ${query}`, tags: "warning,no_entry", priority: 2 }));
return env.ASSETS.fetch(new Request(new URL("/limit.webp", url.origin))); return asset("limit.webp");
} }
context.waitUntil(notify(env, { title: "New Search", message: `Query: ${query} (Search #${count} for ${ip})\n${url.origin}/${path}`, tags: "mag", priority: 2 })); context.waitUntil(notify(env, { title: "New Search", message: `Query: ${query} (Search #${count} for ${ip})\n${url.origin}/${path}`, tags: "mag", priority: 2 }));
@@ -128,7 +128,7 @@ export async function onRequest(context) {
const fail = async (t, m, tag, p) => { const fail = async (t, m, tag, p) => {
context.waitUntil(notify(env, { title: t, message: m, tags: tag, priority: p })); context.waitUntil(notify(env, { title: t, message: m, tags: tag, priority: p }));
await env.DIRECT_IMG_CACHE.put(cacheKey, JSON.stringify({ t: Math.floor(Date.now() / 1000), err: true }), { expirationTtl: 86400 }); await env.DIRECT_IMG_CACHE.put(cacheKey, JSON.stringify({ t: Math.floor(Date.now() / 1000), err: true }), { expirationTtl: 86400 });
return env.ASSETS.fetch(new Request(new URL("/bad.webp", url.origin))); return asset("bad.webp");
}; };
let imageUrls = await braveImageSearch(query, env.BRAVE_API_KEY); let imageUrls = await braveImageSearch(query, env.BRAVE_API_KEY);
+1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>direct-img.link | Live images in markdown</title> <title>direct-img.link | Live images in markdown</title>
<meta name="description" content="Embed live images in markdown powered by search. No APIs, no tokens, just search."> <meta name="description" content="Embed live images in markdown powered by search. No APIs, no tokens, just search.">
<link rel="icon" href="/assets/favicon.ico" sizes="16x16 32x32 48x48"><link rel="icon" href="/assets/logo.svg" type="image/svg+xml">
<script defer src="https://c.planetrenox.com/tracker.js"></script> <script defer src="https://c.planetrenox.com/tracker.js"></script>
<style> <style>
:root{--bg:#fff;--text:#1a1a1a;--text-muted:#666;--accent:#0070f3;--warning:#eb5757;--code-bg:#f5f5f5;--border:#eaeaea;--radius:8px;} :root{--bg:#fff;--text:#1a1a1a;--text-muted:#666;--accent:#0070f3;--warning:#eb5757;--code-bg:#f5f5f5;--border:#eaeaea;--radius:8px;}