diff --git a/content/guestbook.html b/content/guestbook.html
new file mode 100644
index 0000000..a9d1315
--- /dev/null
+++ b/content/guestbook.html
@@ -0,0 +1,28 @@
+---
+title: "Guestbook"
+date: 2025-06-26T20:17:07-07:00
+---
+
+
+
+
diff --git a/static/guestbook.js b/static/guestbook.js
new file mode 100644
index 0000000..dada823
--- /dev/null
+++ b/static/guestbook.js
@@ -0,0 +1,106 @@
+
+class CommentList extends HTMLElement {
+ static get observedAttributes() {
+ return ['src'];
+ }
+
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ this.comments = [];
+ this.loading = false;
+ this.error = null;
+ }
+
+ connectedCallback() {
+ if (this.hasAttribute('src')) {
+ this.fetchComments();
+ }
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (name === 'src' && oldValue !== newValue) {
+ this.fetchComments();
+ }
+ }
+
+ async fetchComments() {
+ const src = this.getAttribute('src');
+ if (!src) return;
+ this.loading = true;
+ this.error = null;
+ this.render();
+
+ try {
+ const response = await fetch(src);
+ if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
+ const data = await response.json();
+ this.comments = Array.isArray(data) ? data : [];
+ this.loading = false;
+ this.render();
+ } catch (err) {
+ this.error = err.message;
+ this.loading = false;
+ this.render();
+ }
+ }
+
+ formatDate(isoString) {
+ if (!isoString) return '';
+ const date = new Date(isoString);
+ return date.toLocaleString();
+ }
+
+ render() {
+ this.shadowRoot.innerHTML = `
+
+
+ `;
+ }
+}
+
+customElements.define('comment-list', CommentList);