From 24c07e99e99d106bacd8921ee6db390e4bf186eb Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 6 May 2026 15:40:57 +0000 Subject: [PATCH 1/2] add guestbook page with Supabase backend - new guestbook.html with form (name + message) and approved-only entry list - supabase-js CDN client with XSS-safe rendering and pending-approval UX - guestbook form/entry CSS added to style.css - guestbook nav card added to index.html grid https://claude.ai/code/session_01CtmRuKQSbuKpZf6DoXBNiT --- guestbook.html | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 4 ++ style.css | 48 +++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 guestbook.html diff --git a/guestbook.html b/guestbook.html new file mode 100644 index 0000000..89603d4 --- /dev/null +++ b/guestbook.html @@ -0,0 +1,140 @@ + + + + + +guestbook — jxherc + + + + + + + + + + + + + +
+
+ spongebob my eyes meme +

are you absolutely motherfucking sure you wanna switch to light mode

+ + +
+
+ +
+ jxherc + +
+
leave a note
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
entries
+
+
loading…
+
+
+ +
guestbook
+
+ + + + + + diff --git a/index.html b/index.html index 68ad435..b689bd2 100644 --- a/index.html +++ b/index.html @@ -80,6 +80,10 @@ bookmarks + + guestbook + +
diff --git a/style.css b/style.css index 0929f94..11a6faf 100644 --- a/style.css +++ b/style.css @@ -383,6 +383,54 @@ a.kv-row:hover .arrow { transform: translate(3px, -3px); color: var(--muted); } line-height: 1.55; } +/* =================== GUESTBOOK =================== */ +.gb-form { display: flex; flex-direction: column; gap: 1rem; } +.gb-field { display: flex; flex-direction: column; gap: 0.35rem; } +.gb-label { font-size: 0.7rem; color: var(--muted); letter-spacing: 0.04em; font-weight: 500; } +.gb-input { + background: none; + border: 1px solid var(--faint); + border-radius: 2px; + color: var(--text); + font-family: inherit; + font-size: 0.875rem; + padding: 0.6rem 0.75rem; + transition: border-color 0.2s ease; + outline: none; + width: 100%; +} +.gb-input::placeholder { color: var(--faint); } +.gb-input:focus { border-color: var(--muted); } +.gb-textarea { resize: vertical; min-height: 88px; line-height: 1.55; } +.gb-actions { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; margin-top: 0.25rem; } +.gb-submit { + background: none; + border: 1px solid var(--faint); + border-radius: 2px; + color: var(--muted); + font-family: inherit; + font-size: 0.72rem; + letter-spacing: 0.04em; + padding: 0.5rem 1rem; + cursor: pointer; + transition: border-color 0.2s ease, color 0.2s ease; +} +.gb-submit:hover:not(:disabled) { border-color: var(--muted); color: var(--text); } +.gb-submit:disabled { opacity: 0.5; cursor: default; } +.gb-status { font-size: 0.72rem; color: var(--faint); } +.gb-entries { display: flex; flex-direction: column; } +.gb-entry { + display: flex; flex-direction: column; gap: 0.3rem; + padding: 0.85rem 0; + border-bottom: 1px solid var(--faint); +} +.gb-entry:first-child { border-top: 1px solid var(--faint); } +.gb-entry-header { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; } +.gb-name { font-size: 0.8rem; color: var(--text); font-weight: 500; } +.gb-date { font-size: 0.65rem; color: var(--faint); letter-spacing: 0.04em; flex-shrink: 0; } +.gb-message { font-size: 0.85rem; color: var(--muted); line-height: 1.55; } +.gb-loading, .gb-empty { font-size: 0.8rem; color: var(--faint); font-style: italic; } + /* Page edit stamp */ .page-edit-stamp { font-size: 0.62rem; From 774fc1d72651b326ae8708cc5c9f6b0e71e01c1f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 6 May 2026 15:53:33 +0000 Subject: [PATCH 2/2] rewrite guestbook to use api.jxherc.com + KV storage - guestbook.html now fetches from api.jxherc.com/guestbook, matching the inline-style pattern of bookmarks/shitpost/photos - new _api/src/routes/guestbook.js with KV-backed CRUD: GET public (approved only), POST public (pending approval), PUT/DELETE require auth - GUESTBOOK_KV binding added to wrangler.toml - new _admin/guestbook.html to approve/delete pending entries - guestbook added to _admin/index.html manage section - removed unused entry CSS classes from style.css (now inline) https://claude.ai/code/session_01CtmRuKQSbuKpZf6DoXBNiT --- _admin/guestbook.html | 92 +++++++++++++++++++++++++++++++++ _admin/index.html | 1 + _api/src/index.js | 3 ++ _api/src/routes/guestbook.js | 53 +++++++++++++++++++ _api/wrangler.toml | 4 ++ guestbook.html | 98 +++++++++++++++--------------------- style.css | 12 ----- 7 files changed, 193 insertions(+), 70 deletions(-) create mode 100644 _admin/guestbook.html create mode 100644 _api/src/routes/guestbook.js diff --git a/_admin/guestbook.html b/_admin/guestbook.html new file mode 100644 index 0000000..7ac0c5a --- /dev/null +++ b/_admin/guestbook.html @@ -0,0 +1,92 @@ + + + + + +guestbook — admin + + + + + + +
+
+ ← admin +
guestbook
+
+ +
+
pending
+
+
loading…
+
+
+ +
+
approved
+
+
loading…
+
+
+
+ + + + diff --git a/_admin/index.html b/_admin/index.html index 621128f..327d45a 100644 --- a/_admin/index.html +++ b/_admin/index.html @@ -32,6 +32,7 @@ now status bookmarks photos + guestbook
diff --git a/_api/src/index.js b/_api/src/index.js index 81432fc..70c7232 100644 --- a/_api/src/index.js +++ b/_api/src/index.js @@ -4,6 +4,7 @@ import { handleStatus } from './routes/status.js'; import { handleBookmarks } from './routes/bookmarks.js'; import { handlePhotos } from './routes/photos.js'; import { handleDiscord } from './routes/discord.js'; +import { handleGuestbook } from './routes/guestbook.js'; import { cors, corsHeaders } from './lib/cors.js'; export default { @@ -27,6 +28,8 @@ export default { response = await handlePhotos(request, env, path); else if (path === '/discord' || path.startsWith('/discord/')) response = await handleDiscord(request, env); + else if (path === '/guestbook' || path.startsWith('/guestbook/')) + response = await handleGuestbook(request, env, path); else response = new Response('not found', { status: 404 }); diff --git a/_api/src/routes/guestbook.js b/_api/src/routes/guestbook.js new file mode 100644 index 0000000..a68811b --- /dev/null +++ b/_api/src/routes/guestbook.js @@ -0,0 +1,53 @@ +import { requireAuth } from '../lib/auth.js'; +import { json } from '../lib/json.js'; + +export async function handleGuestbook(request, env, path) { + const method = request.method; + const id = path.split('/')[2] || null; + + if (method === 'GET') { + const wantsAll = new URL(request.url).searchParams.get('all') === '1'; + if (wantsAll) { + const denied = await requireAuth(request, env); + if (denied) return denied; + } + const list = await env.GUESTBOOK_KV.list({ prefix: 'gb:' }); + const items = await Promise.all( + list.keys + .sort((a, b) => b.name.localeCompare(a.name)) + .map(k => env.GUESTBOOK_KV.get(k.name, 'json')) + ); + const entries = items.filter(Boolean).filter(e => wantsAll || e.approved); + return json({ entries }); + } + + if (method === 'POST') { + const body = await request.json().catch(() => ({})); + const name = String(body.name || '').trim().slice(0, 60); + const message = String(body.message || '').trim().slice(0, 500); + if (!name || !message) return json({ error: 'name and message required' }, 400); + const ts = Date.now(); + const entry = { id: `${ts}`, name, message, approved: false, date: new Date(ts).toISOString() }; + await env.GUESTBOOK_KV.put(`gb:${ts}`, JSON.stringify(entry)); + return json({ ok: true }, 201); + } + + const denied = await requireAuth(request, env); + if (denied) return denied; + + if (method === 'PUT' && id) { + const existing = await env.GUESTBOOK_KV.get(`gb:${id}`, 'json'); + if (!existing) return json({ error: 'not found' }, 404); + const body = await request.json().catch(() => ({})); + const entry = { ...existing, ...body, id: existing.id, date: existing.date }; + await env.GUESTBOOK_KV.put(`gb:${id}`, JSON.stringify(entry)); + return json(entry); + } + + if (method === 'DELETE' && id) { + await env.GUESTBOOK_KV.delete(`gb:${id}`); + return json({ ok: true }); + } + + return json({ error: 'not found' }, 404); +} diff --git a/_api/wrangler.toml b/_api/wrangler.toml index 9e7b6b9..8eb9db3 100644 --- a/_api/wrangler.toml +++ b/_api/wrangler.toml @@ -18,6 +18,10 @@ id = "REPLACE_WITH_BOOKMARKS_KV_ID" binding = "PHOTOS_KV" id = "REPLACE_WITH_PHOTOS_KV_ID" +[[kv_namespaces]] +binding = "GUESTBOOK_KV" +id = "REPLACE_WITH_GUESTBOOK_KV_ID" + [[r2_buckets]] binding = "PHOTOS_R2" bucket_name = "jxherc-photos" diff --git a/guestbook.html b/guestbook.html index 89603d4..921c0c7 100644 --- a/guestbook.html +++ b/guestbook.html @@ -49,91 +49,73 @@
entries
-
-
loading…
-
+
loading…
-
guestbook
+
- diff --git a/style.css b/style.css index 11a6faf..bcabe74 100644 --- a/style.css +++ b/style.css @@ -418,18 +418,6 @@ a.kv-row:hover .arrow { transform: translate(3px, -3px); color: var(--muted); } .gb-submit:hover:not(:disabled) { border-color: var(--muted); color: var(--text); } .gb-submit:disabled { opacity: 0.5; cursor: default; } .gb-status { font-size: 0.72rem; color: var(--faint); } -.gb-entries { display: flex; flex-direction: column; } -.gb-entry { - display: flex; flex-direction: column; gap: 0.3rem; - padding: 0.85rem 0; - border-bottom: 1px solid var(--faint); -} -.gb-entry:first-child { border-top: 1px solid var(--faint); } -.gb-entry-header { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; } -.gb-name { font-size: 0.8rem; color: var(--text); font-weight: 500; } -.gb-date { font-size: 0.65rem; color: var(--faint); letter-spacing: 0.04em; flex-shrink: 0; } -.gb-message { font-size: 0.85rem; color: var(--muted); line-height: 1.55; } -.gb-loading, .gb-empty { font-size: 0.8rem; color: var(--faint); font-style: italic; } /* Page edit stamp */ .page-edit-stamp {