···11+import { serve } from "@hono/node-server";
22+import { Hono } from "hono";
33+import { readFileSync } from "node:fs";
44+import { resolve, dirname } from "node:path";
55+import { fileURLToPath } from "node:url";
66+77+// In ESM (ES Modules), __dirname doesn't exist like it does in CommonJS.
88+// This is a common pattern you'll see everywhere — it reconstructs __dirname
99+// from import.meta.url. Think of it like std::env::current_dir() in Rust.
1010+const __dirname = dirname(fileURLToPath(import.meta.url));
1111+1212+const app = new Hono();
1313+1414+// Serve the main page
1515+// Hono uses a similar pattern to Rust frameworks like Axum:
1616+// app.get("/path", handlerFunction)
1717+// The `c` parameter is the "Context" — it holds the request and
1818+// gives you methods to build responses. Similar to how Axum handlers
1919+// receive extractors.
2020+app.get("/", (c) => {
2121+ const html = readFileSync(resolve(__dirname, "public/index.html"), "utf-8");
2222+ return c.html(html);
2323+});
2424+2525+// A simple API route so you can see how JSON responses work.
2626+// We'll replace this with real guestbook routes later.
2727+app.get("/api/health", (c) => {
2828+ return c.json({ status: "ok", timestamp: new Date().toISOString() });
2929+});
3030+3131+// Start the server
3232+const port = 3000;
3333+console.log(`Guestbook server running at http://localhost:${port}`);
3434+serve({ fetch: app.fetch, port });
+176
src/public/index.html
···11+<!DOCTYPE html>
22+<html lang="en">
33+<head>
44+ <meta charset="UTF-8">
55+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
66+ <title>AT Protocol Guestbook</title>
77+ <style>
88+ /* Keeping styles minimal and inline for now.
99+ No build tools, no CSS frameworks — just plain CSS. */
1010+ * { margin: 0; padding: 0; box-sizing: border-box; }
1111+1212+ body {
1313+ font-family: system-ui, -apple-system, sans-serif;
1414+ max-width: 640px;
1515+ margin: 0 auto;
1616+ padding: 2rem 1rem;
1717+ background: #fafafa;
1818+ color: #1a1a1a;
1919+ }
2020+2121+ h1 { margin-bottom: 0.5rem; }
2222+2323+ .subtitle {
2424+ color: #666;
2525+ margin-bottom: 2rem;
2626+ }
2727+2828+ .auth-section {
2929+ background: white;
3030+ border: 1px solid #e0e0e0;
3131+ border-radius: 8px;
3232+ padding: 1.5rem;
3333+ margin-bottom: 2rem;
3434+ }
3535+3636+ .auth-section input[type="text"] {
3737+ padding: 0.5rem 0.75rem;
3838+ border: 1px solid #ccc;
3939+ border-radius: 4px;
4040+ font-size: 1rem;
4141+ width: 260px;
4242+ }
4343+4444+ .auth-section button, .entry-form button {
4545+ padding: 0.5rem 1rem;
4646+ background: #0066ff;
4747+ color: white;
4848+ border: none;
4949+ border-radius: 4px;
5050+ font-size: 1rem;
5151+ cursor: pointer;
5252+ }
5353+5454+ .auth-section button:hover, .entry-form button:hover {
5555+ background: #0052cc;
5656+ }
5757+5858+ .entry-form {
5959+ margin-bottom: 2rem;
6060+ }
6161+6262+ .entry-form textarea {
6363+ width: 100%;
6464+ padding: 0.75rem;
6565+ border: 1px solid #ccc;
6666+ border-radius: 4px;
6767+ font-size: 1rem;
6868+ resize: vertical;
6969+ min-height: 80px;
7070+ margin-bottom: 0.5rem;
7171+ }
7272+7373+ .entries { list-style: none; }
7474+7575+ .entries li {
7676+ background: white;
7777+ border: 1px solid #e0e0e0;
7878+ border-radius: 8px;
7979+ padding: 1rem 1.25rem;
8080+ margin-bottom: 0.75rem;
8181+ }
8282+8383+ .entry-handle {
8484+ font-weight: 600;
8585+ color: #0066ff;
8686+ }
8787+8888+ .entry-date {
8989+ color: #999;
9090+ font-size: 0.85rem;
9191+ margin-left: 0.5rem;
9292+ }
9393+9494+ .entry-message {
9595+ margin-top: 0.5rem;
9696+ }
9797+9898+ #status {
9999+ color: #666;
100100+ font-style: italic;
101101+ margin-top: 1rem;
102102+ }
103103+ </style>
104104+</head>
105105+<body>
106106+ <h1>Guestbook</h1>
107107+ <p class="subtitle">Sign in with your Bluesky handle to leave a message.</p>
108108+109109+ <!-- Auth section: shown when not logged in -->
110110+ <div class="auth-section" id="auth-logged-out">
111111+ <input type="text" id="handle-input" placeholder="e.g. alice.bsky.social" />
112112+ <button onclick="handleLogin()">Sign In</button>
113113+ </div>
114114+115115+ <!-- Auth section: shown when logged in -->
116116+ <div class="auth-section" id="auth-logged-in" style="display: none;">
117117+ <span>Signed in as <strong id="display-handle"></strong></span>
118118+ <button onclick="handleLogout()" style="background: #666; margin-left: 1rem;">Sign Out</button>
119119+ </div>
120120+121121+ <!-- New entry form: only visible when logged in -->
122122+ <div class="entry-form" id="entry-form" style="display: none;">
123123+ <textarea id="message-input" placeholder="Leave a message..."></textarea>
124124+ <button onclick="handleSubmit()">Post</button>
125125+ </div>
126126+127127+ <!-- Guestbook entries -->
128128+ <ul class="entries" id="entries-list">
129129+ <!-- Entries will be rendered here by JavaScript -->
130130+ </ul>
131131+132132+ <p id="status">Loading entries...</p>
133133+134134+ <script>
135135+ // =========================================================
136136+ // This is where our client-side logic will live.
137137+ // For now, it's just placeholder functions so the page
138138+ // doesn't throw errors when you click buttons.
139139+ //
140140+ // We'll fill these in as we build out the server routes.
141141+ // =========================================================
142142+143143+ async function loadEntries() {
144144+ // TODO: fetch entries from GET /api/entries
145145+ document.getElementById("status").textContent =
146146+ "No entries yet. Be the first to sign!";
147147+ }
148148+149149+ function handleLogin() {
150150+ const handle = document.getElementById("handle-input").value.trim();
151151+ if (!handle) return;
152152+153153+ // TODO: redirect to our server's OAuth start endpoint
154154+ // For now, just log it so we can confirm the UI works
155155+ console.log("Login requested for handle:", handle);
156156+ alert("OAuth not wired up yet! Handle: " + handle);
157157+ }
158158+159159+ function handleLogout() {
160160+ // TODO: call POST /api/auth/logout
161161+ console.log("Logout requested");
162162+ }
163163+164164+ async function handleSubmit() {
165165+ const message = document.getElementById("message-input").value.trim();
166166+ if (!message) return;
167167+168168+ // TODO: call POST /api/entries with the message
169169+ console.log("Submit entry:", message);
170170+ }
171171+172172+ // Load entries when the page loads
173173+ loadEntries();
174174+ </script>
175175+</body>
176176+</html>