From 45e6003d41309c91c614d3e54edcf60becaafb2d Mon Sep 17 00:00:00 2001
From: Danny Morabito <danny@arx-ccn.com>
Date: Tue, 18 Mar 2025 16:02:16 +0100
Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E2=9C=A8=20Forum=20overhaul:=20?=
 =?UTF-8?q?Phora=20=E2=86=92=20Arbor=20=20=20=20=20=F0=9F=94=A7=20Replace?=
 =?UTF-8?q?=20legacy=20system=20with=20NIP-BB=20implementation=20=20=20=20?=
 =?UTF-8?q?=20=F0=9F=9A=80=20Enhance=20forum=20user=20experience=20with=20?=
 =?UTF-8?q?improved=20navigation=20and=20interactions=20=20=20=20=20?=
 =?UTF-8?q?=F0=9F=8E=A8=20Redesign=20UI=20for=20forum=20=20=20=20=20?=
 =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Rebrand=20from=20"Phora"=20to=20"Arbor"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Readme.md                                     |   2 +-
 Security.md                                   |   2 +-
 .../{PhoraButton.ts => Arbor/Button.ts}       |  11 +-
 .../ForumCategory.ts}                         |   8 +-
 src/components/Arbor/ForumPost.ts             | 341 ++++++++++++++++++
 .../ForumTopic.ts}                            |  30 +-
 src/components/Breadcrumbs.ts                 |  52 ++-
 src/components/BreadcrumbsItem.ts             | 116 +++++-
 src/components/ForumPost.ts                   | 125 -------
 src/components/Prompt.ts                      | 261 ++++++++++++++
 src/routes/Arbor/Home.ts                      | 202 +++++++++++
 src/routes/{Phora => Arbor}/NewPost.ts        |  59 +--
 src/routes/{Phora => Arbor}/NewTopic.ts       |  18 +-
 src/routes/Arbor/TopicView.ts                 | 209 +++++++++++
 src/routes/Home.ts                            |   4 +-
 src/routes/Phora/Home.ts                      | 122 -------
 src/routes/Phora/NewCategory.ts               |  97 -----
 src/routes/Phora/TopicView.ts                 | 154 --------
 src/routes/router.ts                          |  30 +-
 19 files changed, 1233 insertions(+), 610 deletions(-)
 rename src/components/{PhoraButton.ts => Arbor/Button.ts} (94%)
 rename src/components/{PhoraForumCategory.ts => Arbor/ForumCategory.ts} (82%)
 create mode 100644 src/components/Arbor/ForumPost.ts
 rename src/components/{PhoraForumTopic.ts => Arbor/ForumTopic.ts} (85%)
 delete mode 100644 src/components/ForumPost.ts
 create mode 100644 src/components/Prompt.ts
 create mode 100644 src/routes/Arbor/Home.ts
 rename src/routes/{Phora => Arbor}/NewPost.ts (75%)
 rename src/routes/{Phora => Arbor}/NewTopic.ts (90%)
 create mode 100644 src/routes/Arbor/TopicView.ts
 delete mode 100644 src/routes/Phora/Home.ts
 delete mode 100644 src/routes/Phora/NewCategory.ts
 delete mode 100644 src/routes/Phora/TopicView.ts

diff --git a/Readme.md b/Readme.md
index 454eaf4..dda8136 100644
--- a/Readme.md
+++ b/Readme.md
@@ -51,7 +51,7 @@ EVE provides building blocks for digital sovereignty, which we call Arxlets:
 
 ### Core Arxlets
 
-- [x] **Phora**: Threaded discussions and knowledge sharing
+- [x] **Arbor**: Threaded discussions and knowledge sharing
 - [ ] **Nexus**: Central community hub
 - [ ] **Whisper**: One-to-one and group messaging
 - [ ] **Vault**: Secure file storage and sharing
diff --git a/Security.md b/Security.md
index aa3a90b..81335f4 100644
--- a/Security.md
+++ b/Security.md
@@ -73,7 +73,7 @@ This security policy applies to the following official EVE repositories and comp
 
 - Main EVE application: https://git.arx-ccn.com/Arx/Eve
 - EVE Relay: https://git.arx-ccn.com/Arx/Eve-Relay
-- All published Arxlets (Phora, Nexus, etc.)
+- All published Arxlets (Arbor, Nexus, etc.)
 
 ### Out of Scope
 
diff --git a/src/components/PhoraButton.ts b/src/components/Arbor/Button.ts
similarity index 94%
rename from src/components/PhoraButton.ts
rename to src/components/Arbor/Button.ts
index aa4cba9..596f301 100644
--- a/src/components/PhoraButton.ts
+++ b/src/components/Arbor/Button.ts
@@ -3,8 +3,8 @@ import { customElement, property } from 'lit/decorators.js';
 
 import '@components/EveLink';
 
-@customElement('arx-phora-button')
-export class PhoraButton extends LitElement {
+@customElement('arx-arbor-button')
+export class ArborButton extends LitElement {
   @property({ type: String }) href = '';
   @property({ type: String }) target = '';
   @property({ type: String }) rel = '';
@@ -18,13 +18,16 @@ export class PhoraButton extends LitElement {
   static override styles = [
     css`
       arx-eve-link::part(link) {
+        color: white;
+        text-decoration: none;
+      }
+
+      arx-eve-link {
         display: inline-flex;
         align-items: center;
         justify-content: center;
         background: var(--accent);
-        color: white;
         border: none;
-        text-decoration: none;
         padding: 0.75rem 0.75rem;
         border-radius: 0.25rem;
         text-decoration: none;
diff --git a/src/components/PhoraForumCategory.ts b/src/components/Arbor/ForumCategory.ts
similarity index 82%
rename from src/components/PhoraForumCategory.ts
rename to src/components/Arbor/ForumCategory.ts
index aed7324..f9dd5e3 100644
--- a/src/components/PhoraForumCategory.ts
+++ b/src/components/Arbor/ForumCategory.ts
@@ -1,8 +1,8 @@
 import { LitElement, css, html } from 'lit';
 import { customElement, property } from 'lit/decorators.js';
 
-@customElement('arx-phora-forum-category')
-export class PhoraForumCategory extends LitElement {
+@customElement('arx-arbor-forum-category')
+export class ArborForumCategory extends LitElement {
   @property({ type: String }) override title = '';
   @property({ type: String }) override id = '';
 
@@ -33,7 +33,9 @@ export class PhoraForumCategory extends LitElement {
       <div class="forum-category">
         <div class="category-header">
           <span>${this.title}</span>
-          <arx-phora-button href="/phora/new-topic/${this.id}">New Topic</phora-button>
+          <arx-arbor-button href="/arbor/new-topic/${this.id}">
+            New Topic
+          </arx-arbor-button>
         </div>
         <slot>
           <div style="padding: 1rem 1.5rem">No topics...</div>
diff --git a/src/components/Arbor/ForumPost.ts b/src/components/Arbor/ForumPost.ts
new file mode 100644
index 0000000..aeab7c6
--- /dev/null
+++ b/src/components/Arbor/ForumPost.ts
@@ -0,0 +1,341 @@
+import formatDateTime from '@utils/formatDateTime';
+import { LitElement, css, html } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { classMap } from 'lit/directives/class-map.js';
+
+import '@components/MarkdownContent';
+
+@customElement('arx-forum-post')
+export class ForumPost extends LitElement {
+  @property({ type: String }) override id = '';
+  @property({ type: String }) topicId = '';
+  @property({ type: String }) npub = '';
+  @property({ type: Date }) date = new Date();
+  @property({ type: String }) content = '';
+  @property({ type: Boolean }) isHighlighted = false;
+
+  static override styles = [
+    css`
+      .post {
+        display: flex;
+        flex-direction: row;
+        border-radius: 16px;
+        background: #ffffff;
+        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04),
+          0 1px 3px rgba(0, 0, 0, 0.03);
+        margin-block-end: 0.75rem;
+        overflow: hidden;
+        transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1);
+        isolation: isolate;
+        will-change: transform;
+
+        &:hover {
+          transform: translateY(2px);
+          box-shadow: 0 8px 28px rgba(0, 0, 0, 0.07),
+            0 2px 4px rgba(0, 0, 0, 0.04);
+        }
+      }
+
+      .post--highlighted {
+        position: relative;
+        border-inline-start: none;
+
+        &::before {
+          content: "";
+          position: absolute;
+          inset-inline-start: 0;
+          top: 0;
+          bottom: 0;
+          width: 4px;
+          background: #4361ee;
+          border-radius: 4px 0 0 4px;
+        }
+      }
+
+      .post__sidebar {
+        padding: clamp(1rem, 4vw, 1.5rem);
+        border-right: 1px solid rgba(0, 0, 0, 0.06);
+        flex: 0 0 auto;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        background: rgba(0, 0, 0, 0.01);
+      }
+
+      .post__main {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        min-width: 0;
+      }
+
+      .post__header {
+        display: flex;
+        align-items: center;
+        padding: 1rem clamp(1rem, 4vw, 1.5rem);
+        border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+        background: rgba(0, 0, 0, 0.01);
+        backdrop-filter: blur(8px);
+        -webkit-backdrop-filter: blur(8px);
+      }
+
+      .post__time {
+        display: flex;
+        align-items: center;
+        gap: 0.5rem;
+        font-size: 0.875rem;
+        color: rgba(0, 0, 0, 0.6);
+        font-weight: 500;
+
+        & iconify-icon {
+          color: rgba(0, 0, 0, 0.4);
+        }
+      }
+
+      .post__permalink {
+        margin-inline-start: auto;
+        color: #4361ee;
+        border: none;
+        background: transparent;
+        cursor: pointer;
+        border-radius: 50%;
+        width: 2.25rem;
+        height: 2.25rem;
+        display: grid;
+        place-items: center;
+        transition: all 0.2s ease;
+
+        &:hover {
+          background: rgba(67, 97, 238, 0.08);
+          transform: scale(1.05);
+        }
+
+        &:focus-visible {
+          outline: 2px solid #4361ee;
+          outline-offset: 2px;
+        }
+      }
+
+      .post__content {
+        padding: clamp(1.25rem, 5vw, 1.75rem);
+        line-height: 1.7;
+        color: rgba(0, 0, 0, 0.87);
+        overflow-wrap: break-word;
+        flex: 1;
+        font-size: clamp(0.95rem, 2vw, 1rem);
+        letter-spacing: 0.01em;
+      }
+
+      .post__actions {
+        display: flex;
+        padding: 0.75rem clamp(0.75rem, 3vw, 1.25rem);
+        border-top: 1px solid rgba(0, 0, 0, 0.06);
+        gap: clamp(0.5rem, 2vw, 0.75rem);
+        background: rgba(0, 0, 0, 0.01);
+      }
+
+      .action-button {
+        display: flex;
+        align-items: center;
+        gap: 0.5rem;
+        padding: 0.5rem 0.75rem;
+        border-radius: 10px;
+        background: transparent;
+        border: none;
+        font-family: inherit;
+        font-size: 0.875rem;
+        font-weight: 500;
+        color: rgba(0, 0, 0, 0.6);
+        cursor: pointer;
+        transition: all 0.2s ease;
+
+        & iconify-icon {
+          font-size: 1.25rem;
+        }
+
+        &:hover:not(:disabled) {
+          background: rgba(0, 0, 0, 0.04);
+          color: rgba(0, 0, 0, 0.87);
+          transform: translateY(-1px);
+        }
+
+        &:focus-visible {
+          outline: 2px solid rgba(0, 0, 0, 0.2);
+          outline-offset: 1px;
+        }
+
+        &:active:not(:disabled) {
+          transform: translateY(1px) scale(0.98);
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+      }
+
+      .action-button--primary {
+        color: #4361ee;
+
+        &:hover:not(:disabled) {
+          background: rgba(67, 97, 238, 0.08);
+        }
+
+        &:focus-visible {
+          outline-color: #4361ee;
+        }
+      }
+
+      @media (max-width: 768px) {
+        .post {
+          flex-direction: column;
+          margin-inline: -1rem;
+          border-radius: 0;
+          margin-block-end: 1.5rem;
+        }
+
+        .post__sidebar {
+          border-right: none;
+          border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+          padding: 1rem;
+          flex-direction: row;
+          justify-content: flex-start;
+          gap: 1rem;
+        }
+
+        .action-button {
+          padding: 0.625rem;
+          justify-content: center;
+          flex: 1;
+          border-radius: 10px;
+
+          & span {
+            display: none;
+          }
+        }
+      }
+
+      @media (max-width: 480px) {
+        .post__actions {
+          justify-content: space-between;
+          padding: 0.5rem 0.75rem;
+        }
+
+        .post__content {
+          padding: 1rem;
+        }
+      }
+    `,
+  ];
+
+  private _handleReply() {
+    alert('Replying is not yet implemented');
+    this.dispatchEvent(
+      new CustomEvent('reply', {
+        detail: { postId: this.id, npub: this.npub },
+        bubbles: true,
+        composed: true,
+      }),
+    );
+  }
+
+  private _handleZap() {
+    alert('Zapping is not yet implemented');
+    this.dispatchEvent(
+      new CustomEvent('zap', {
+        detail: { postId: this.id, npub: this.npub },
+        bubbles: true,
+        composed: true,
+      }),
+    );
+  }
+
+  private _handleDownzap() {
+    alert('Downzapping is not yet implemented');
+    this.dispatchEvent(
+      new CustomEvent('downzap', {
+        detail: { postId: this.id, npub: this.npub },
+        bubbles: true,
+        composed: true,
+      }),
+    );
+  }
+
+  private _copyPermalink() {
+    const permalink = `eve://phora/topics/${this.topicId}#post-${this.id}`;
+    navigator.clipboard.writeText(permalink);
+  }
+
+  override render() {
+    const permalink = `eve://phora/topics/${this.topicId}#post-${this.id}`;
+
+    const postClasses = {
+      post: true,
+      'post--highlighted': this.isHighlighted,
+    };
+
+    return html`
+      <div class=${classMap(postClasses)} id="post-${this.id}">
+        <div class="post__sidebar">
+          <arx-nostr-profile
+            .npub=${this.npub}
+            renderType="large"
+          ></arx-nostr-profile>
+        </div>
+
+        <div class="post__main">
+          <div class="post__header">
+            <div class="post__time">
+              <iconify-icon icon="mdi:clock-outline"></iconify-icon>
+              ${formatDateTime(this.date)}
+            </div>
+
+            <button
+              class="post__permalink"
+              title="Copy permalink"
+              @click=${this._copyPermalink}
+            >
+              <iconify-icon icon="mdi:link-variant"></iconify-icon>
+            </button>
+          </div>
+
+          <div class="post__content">
+            <arx-markdown-content
+              .content=${this.content || 'No content available'}
+            ></arx-markdown-content>
+          </div>
+
+          <div class="post__actions">
+            <button
+              class="action-button action-button--primary"
+              @click=${this._handleReply}
+              disabled
+            >
+              <iconify-icon icon="mdi:reply"></iconify-icon>
+              <span>Reply</span>
+            </button>
+
+            <a href=${permalink} class="action-button">
+              <iconify-icon icon="mdi:link-variant"></iconify-icon>
+              <span>Permalink</span>
+            </a>
+
+            <button class="action-button" @click=${this._handleZap} disabled>
+              <iconify-icon icon="mdi:lightning-bolt"></iconify-icon>
+              <span>Zap</span>
+            </button>
+
+            <button
+              class="action-button"
+              @click=${this._handleDownzap}
+              disabled
+            >
+              <iconify-icon icon="mdi:lightning-bolt-outline"></iconify-icon>
+              <span>Downzap</span>
+            </button>
+          </div>
+        </div>
+      </div>
+    `;
+  }
+}
diff --git a/src/components/PhoraForumTopic.ts b/src/components/Arbor/ForumTopic.ts
similarity index 85%
rename from src/components/PhoraForumTopic.ts
rename to src/components/Arbor/ForumTopic.ts
index 2cc1624..fd51093 100644
--- a/src/components/PhoraForumTopic.ts
+++ b/src/components/Arbor/ForumTopic.ts
@@ -2,9 +2,10 @@ import { LitElement, css, html } from 'lit';
 import { customElement, property } from 'lit/decorators.js';
 
 import '@components/EveLink';
+import formatDateTime from '@/utils/formatDateTime';
 
-@customElement('arx-phora-forum-topic')
-export class PhoraForumTopic extends LitElement {
+@customElement('arx-arbor-forum-topic')
+export class ArborForumTopic extends LitElement {
   static override styles = [
     css`
       .topic {
@@ -113,19 +114,6 @@ export class PhoraForumTopic extends LitElement {
         border-left: 2px solid var(--border);
       }
 
-      .post-count {
-        display: flex;
-        align-items: center;
-        gap: 0.625rem;
-        color: var(--secondary);
-        font-weight: 500;
-      }
-
-      .post-count :deep(iconify-icon) {
-        font-size: 1.375rem;
-        color: var(--accent);
-      }
-
       .last-post-section {
         background: oklch(from var(--primary) 98% calc(c * 0.2) h);
         padding: 0.875rem;
@@ -168,7 +156,6 @@ export class PhoraForumTopic extends LitElement {
   @property({ type: String }) override id = '';
   @property({ type: String }) override title = '';
   @property({ type: String }) description = '';
-  @property({ type: Number }) posts = 0;
   @property({ type: String }) lastPostBy = '';
   @property({ type: String }) lastPostTime = '';
   @property({ type: Boolean }) isNew = false;
@@ -188,7 +175,7 @@ export class PhoraForumTopic extends LitElement {
           <div class="topic-details">
             <arx-eve-link
               class="${this.status}"
-              href="/phora/topics/${this.id}"
+              href="/arbor/topics/${this.id}"
             >
               ${this.title}
             </arx-eve-link>
@@ -196,14 +183,11 @@ export class PhoraForumTopic extends LitElement {
           </div>
         </div>
         <div class="stats-container">
-          <div class="post-count">
-            <iconify-icon icon="material-symbols:forum-rounded"></iconify-icon>
-            <span>${this.posts.toLocaleString()} posts</span>
-          </div>
           <div class="last-post-section">
-            <div>Latest activity</div>
             <arx-nostr-profile npub="${this.lastPostBy}"></arx-nostr-profile>
-            <div class="last-post-info">${this.lastPostTime}</div>
+            <div class="last-post-info">
+              ${formatDateTime(this.lastPostTime)}
+            </div>
           </div>
         </div>
       </div>
diff --git a/src/components/Breadcrumbs.ts b/src/components/Breadcrumbs.ts
index 4937792..4423a11 100644
--- a/src/components/Breadcrumbs.ts
+++ b/src/components/Breadcrumbs.ts
@@ -1,6 +1,7 @@
 import { LitElement, css, html } from 'lit';
 import { customElement, property } from 'lit/decorators.js';
 import './BreadcrumbsItem';
+import { map } from 'lit/directives/map.js';
 
 @customElement('arx-breadcrumbs')
 export class Breadcrumbs extends LitElement {
@@ -8,11 +9,29 @@ export class Breadcrumbs extends LitElement {
 
   static override styles = [
     css`
+      :host {
+        display: block;
+        --breadcrumb-bg: var(--surface, #ffffff);
+        --breadcrumb-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+        --breadcrumb-border: 1px solid var(--border-color, rgba(0, 0, 0, 0.1));
+        --breadcrumb-radius: 8px;
+        --breadcrumb-padding: 0.75rem 1.25rem;
+        --breadcrumb-font: var(--font-family, system-ui, sans-serif);
+        --breadcrumb-text-color: var(--text-primary, #333);
+        --breadcrumb-separator-color: var(--text-secondary, #666);
+        --breadcrumb-accent-color: var(--accent, #0066cc);
+        --breadcrumb-hover-color: var(--accent-dark, #004c99);
+        --breadcrumb-active-bg: var(--accent-light, rgba(0, 102, 204, 0.1));
+      }
       nav {
         max-width: 1200px;
-        margin: 1rem auto;
-        padding-inline: 1rem;
-        font-size: 0.9rem;
+        margin: 1.25rem auto;
+        background-color: var(--breadcrumb-bg);
+        border-radius: var(--breadcrumb-radius);
+        box-shadow: var(--breadcrumb-shadow);
+        padding: 0.75rem 1.25rem;
+        font-size: 0.95rem;
+        transition: all 0.3s ease;
       }
 
       ol {
@@ -20,8 +39,17 @@ export class Breadcrumbs extends LitElement {
         padding: 0;
         margin: 0;
         display: flex;
-        flex-wrap: wrap;
+        flex-wrap: nowrap;
         align-items: center;
+        overflow-x: auto;
+        scrollbar-width: none;
+      }
+
+      @media (max-width: 640px) {
+        nav {
+          padding: 0.5rem 1rem;
+          margin: 0.75rem auto;
+        }
       }
     `,
   ];
@@ -30,14 +58,16 @@ export class Breadcrumbs extends LitElement {
     return html`
       <nav aria-label="Breadcrumb">
         <ol>
-          ${this.items.map(
+          ${map(
+            this.items,
             (item, index) => html`
-            <arx-breadcrumbs-item
-              .text=${item.text}
-              .href=${item.href}
-              .index=${index}
-            ></arx-breadcrumbs-item>
-          `,
+              <arx-breadcrumbs-item
+                .text=${item.text}
+                .href=${item.href}
+                .index=${index}
+                .isLast=${index === this.items.length - 1}
+              ></arx-breadcrumbs-item>
+            `,
           )}
         </ol>
       </nav>
diff --git a/src/components/BreadcrumbsItem.ts b/src/components/BreadcrumbsItem.ts
index 5a6dd5a..4f88882 100644
--- a/src/components/BreadcrumbsItem.ts
+++ b/src/components/BreadcrumbsItem.ts
@@ -2,49 +2,131 @@ import { LitElement, css, html } from 'lit';
 import { customElement, property } from 'lit/decorators.js';
 
 import '@components/EveLink';
+import { when } from 'lit/directives/when.js';
 
 @customElement('arx-breadcrumbs-item')
 export class BreadcrumbsItem extends LitElement {
   @property() text = '';
   @property() href?: string;
   @property() index = 0;
+  @property({ type: Boolean }) isLast = false;
 
   static override styles = [
     css`
       li {
-        display: inline-block;
-        margin-right: 0.5rem;
+        display: inline-flex;
+        align-items: center;
+        white-space: nowrap;
+        animation: fadeIn 0.3s ease-out forwards;
+        animation-delay: calc(var(--index, 0) * 0.05s);
+        opacity: 0;
       }
 
       .separator {
-        margin-inline: 0.5rem;
-        color: var(--secondary);
+        display: flex;
+        align-items: center;
+        margin-inline: 0.75rem;
+        color: var(--breadcrumb-separator);
         user-select: none;
+        font-size: 0.85rem;
+      }
+
+      svg.chevron {
+        width: 16px;
+        height: 16px;
+        fill: currentColor;
+        opacity: 0.75;
+      }
+
+      .breadcrumb-content {
+        display: inline-flex;
+        align-items: center;
+        padding: 0.4rem 0.7rem;
+        border-radius: 6px;
+        transition: all 0.2s ease;
+      }
+
+      .active {
+        background-color: var(--breadcrumb-active-bg);
+        font-weight: 500;
+        color: var(--breadcrumb-text);
       }
 
       .link {
-        color: var(--accent);
+        color: var(--breadcrumb-accent);
         text-decoration: none;
-        transition: text-decoration 0.2s;
       }
 
-      .secondary {
-        color: var(--secondary);
+      .link:hover .breadcrumb-content {
+        background-color: var(--breadcrumb-active-bg);
+      }
+
+      .link:focus-visible {
+        outline: 2px solid var(--breadcrumb-accent);
+        outline-offset: 2px;
+        border-radius: 6px;
+      }
+
+      .item-text {
+        position: relative;
+      }
+
+      .link .item-text::after {
+        content: "";
+        position: absolute;
+        bottom: -2px;
+        left: 0;
+        width: 0;
+        height: 2px;
+        background-color: var(--breadcrumb-accent);
+        transition: width 0.25s ease;
+      }
+
+      .link:hover .item-text::after {
+        width: 100%;
+      }
+
+      @keyframes fadeIn {
+        from {
+          opacity: 0;
+          transform: translateY(-2px);
+        }
+        to {
+          opacity: 1;
+          transform: translateY(0);
+        }
       }
     `,
   ];
 
   override render() {
     return html`
-      <li>
-        ${this.index > 0 ? html`<span class="separator" aria-hidden="true">/</span>` : ''}
-        ${
-          this.href
-            ? html`<arx-eve-link class="link" href=${this.href}
-              >${this.text}</arx-eve-link
-            >`
-            : html`<span class="secondary">${this.text}</span>`
-        }
+      <li style="--index: ${this.index}">
+        ${when(
+          this.index > 0,
+          () => html`
+            <span class="separator" aria-hidden="true">
+              <svg class="chevron" viewBox="0 0 24 24">
+                <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6-6-6z" />
+              </svg>
+            </span>
+          `,
+        )}
+        ${when(
+          this.href && !this.isLast,
+          () => html`
+            <arx-eve-link class="link" href=${this.href}>
+              <span class="breadcrumb-content">
+                <span class="item-text">${this.text}</span>
+              </span>
+            </arx-eve-link>
+          `,
+          () => html`
+            <span class="breadcrumb-content ${this.isLast ? 'active' : ''}">
+              <span class="item-text">${this.text}</span>
+            </span>
+          `,
+        )}
       </li>
     `;
   }
diff --git a/src/components/ForumPost.ts b/src/components/ForumPost.ts
deleted file mode 100644
index ab58311..0000000
--- a/src/components/ForumPost.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import formatDateTime from '@utils/formatDateTime';
-import { LitElement, css, html } from 'lit';
-import { customElement, property } from 'lit/decorators.js';
-
-import '@components/MarkdownContent';
-
-@customElement('arx-forum-post')
-export class ForumPost extends LitElement {
-  @property({ type: String }) override id = '';
-  @property({ type: String }) topicId = '';
-  @property({ type: String }) npub = '';
-  @property({ type: Date }) date = new Date();
-  @property({ type: String }) content = '';
-
-  static override styles = [
-    css`
-      .post {
-        grid-column: span 2;
-        display: grid;
-        grid-template-columns: subgrid;
-
-        gap: 1rem;
-        padding: 1.5em;
-        background: oklch(from var(--accent) l c h / 0.4);
-
-        & > :first-child {
-          padding-right: 1.5rem;
-          border-right: 2px solid var(--border);
-        }
-      }
-
-      .post-content {
-        display: grid;
-        gap: 1em;
-
-        & > :first-child {
-          display: flex;
-          align-items: center;
-          gap: 0.5rem;
-          border-top: 2px solid var(--border);
-          border-bottom: 2px solid var(--border);
-          padding: 1em;
-        }
-
-        & > :nth-child(2) {
-          margin-bottom: 1em;
-        }
-
-        & > :nth-child(3) {
-          display: grid;
-          grid-template-columns: repeat(4, 1fr);
-          gap: 1em;
-          border-top: 2px solid var(--border);
-          padding: 1em;
-
-          @media (max-width: 400px) {
-            grid-template-columns: 1fr;
-          }
-        }
-      }
-
-      .disabled {
-        pointer-events: none;
-        opacity: 0.5;
-      }
-    `,
-  ];
-
-  override render() {
-    const permalink = `eve://phora/topics/${this.topicId}#post-${this.id}`;
-
-    return html`
-      <div class="post" id="post-${this.id}">
-        <arx-nostr-profile
-          .npub=${this.npub}
-          renderType="large"
-        ></arx-nostr-profile>
-        <div class="post-content">
-          <div>
-            <iconify-icon icon="mdi:clock"></iconify-icon>
-            ${formatDateTime(this.date)}
-          </div>
-          <div>
-            <arx-markdown-content
-              .content=${this.content || 'no content'}
-            ></arx-markdown-content>
-          </div>
-          <div>
-            <arx-phora-button
-              href="#"
-              @click=${() => alert('TODO')}
-              class="disabled"
-            >
-              <iconify-icon size="32" icon="mdi:reply"></iconify-icon>
-              Reply
-            </arx-phora-button>
-            <arx-phora-button href=${permalink}>
-              <iconify-icon size="32" icon="mdi:link"></iconify-icon>
-              Permalink
-            </arx-phora-button>
-            <arx-phora-button
-              href="#"
-              @click=${() => alert('TODO')}
-              class="disabled"
-            >
-              <iconify-icon size="32" icon="bxs:zap"></iconify-icon>
-              Zap
-            </arx-phora-button>
-            <arx-phora-button
-              href="#"
-              @click=${() => alert('TODO')}
-              class="disabled"
-            >
-              <iconify-icon
-                size="32"
-                icon="bi:cloud-lightning-rain-fill"
-              ></iconify-icon>
-              Downzap
-            </arx-phora-button>
-          </div>
-        </div>
-      </div>
-    `;
-  }
-}
diff --git a/src/components/Prompt.ts b/src/components/Prompt.ts
new file mode 100644
index 0000000..44b0bb3
--- /dev/null
+++ b/src/components/Prompt.ts
@@ -0,0 +1,261 @@
+import { LitElement, css, html } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import { classMap } from 'lit/directives/class-map.js';
+
+@customElement('arx-prompt')
+export class EvePrompt extends LitElement {
+  @property({ type: String }) promptText = 'Please provide input';
+  @property({ type: String }) cancelText = 'Cancel';
+  @property({ type: String }) saveText = 'Save';
+  @property({ type: Boolean }) open = false;
+  @property({ type: Boolean }) showInput = false;
+  @property({ type: String }) placeholder = 'Enter your response';
+  @property({ type: String }) defaultValue = '';
+
+  @state() private _inputValue = '';
+  private _previousFocus: HTMLElement | null = null;
+
+  static override styles = css`
+    :host {
+      --prompt-primary: #3b82f6;
+      --prompt-primary-hover: #2563eb;
+      --prompt-bg: #ffffff;
+      --prompt-text: #1f2937;
+      --prompt-border: #e5e7eb;
+      --prompt-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+      --prompt-cancel-bg: #f3f4f6;
+      --prompt-cancel-hover: #e5e7eb;
+      --prompt-overlay: rgba(15, 23, 42, 0.6);
+
+      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+        sans-serif;
+    }
+
+    .overlay {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: var(--prompt-overlay);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      z-index: 9999;
+      opacity: 0;
+      pointer-events: none;
+      transition: opacity 0.2s ease;
+      backdrop-filter: blur(4px);
+    }
+
+    .overlay.active {
+      opacity: 1;
+      pointer-events: all;
+    }
+
+    .prompt-container {
+      background-color: var(--prompt-bg);
+      border-radius: 12px;
+      box-shadow: var(--prompt-shadow);
+      width: 90%;
+      max-width: 420px;
+      padding: 28px;
+      transform: scale(0.95) translateY(10px);
+      transition: transform 0.25s cubic-bezier(0.1, 1, 0.2, 1);
+      color: var(--prompt-text);
+    }
+
+    .overlay.active .prompt-container {
+      transform: scale(1) translateY(0);
+    }
+
+    .prompt-header {
+      font-size: 18px;
+      font-weight: 600;
+      margin: 0 0 16px 0;
+      line-height: 1.4;
+    }
+
+    .input-container {
+      margin: 20px 0;
+    }
+
+    .input-field {
+      width: 100%;
+      padding: 12px 14px;
+      border-radius: 8px;
+      border: 1px solid var(--prompt-border);
+      font-size: 15px;
+      transition: border-color 0.2s ease, box-shadow 0.2s ease;
+      color: inherit;
+      background-color: transparent;
+      outline: none;
+      box-sizing: border-box;
+    }
+
+    .input-field:focus {
+      border-color: var(--prompt-primary);
+      box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
+    }
+
+    .buttons {
+      display: flex;
+      justify-content: flex-end;
+      gap: 12px;
+      margin-top: 24px;
+    }
+
+    button {
+      padding: 10px 18px;
+      border-radius: 8px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      border: none;
+      outline: none;
+    }
+
+    button:focus-visible {
+      box-shadow: 0 0 0 2px var(--prompt-bg), 0 0 0 4px var(--prompt-primary);
+    }
+
+    .cancel-btn {
+      background-color: var(--prompt-cancel-bg);
+      color: var(--prompt-text);
+    }
+
+    .cancel-btn:hover {
+      background-color: var(--prompt-cancel-hover);
+    }
+
+    .save-btn {
+      background-color: var(--prompt-primary);
+      color: white;
+    }
+
+    .save-btn:hover {
+      background-color: var(--prompt-primary-hover);
+    }
+
+    @media (prefers-color-scheme: dark) {
+      :host {
+        --prompt-bg: #1e1e1e;
+        --prompt-text: #e5e7eb;
+        --prompt-border: #374151;
+        --prompt-cancel-bg: #374151;
+        --prompt-cancel-hover: #4b5563;
+      }
+    }
+  `;
+
+  constructor() {
+    super();
+    this._handleKeyDown = this._handleKeyDown.bind(this);
+  }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    document.addEventListener('keydown', this._handleKeyDown);
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    document.removeEventListener('keydown', this._handleKeyDown);
+  }
+
+  override updated(changedProps: Map<string, unknown>) {
+    if (changedProps.has('open') && this.open) {
+      this._inputValue = this.defaultValue;
+      this._previousFocus = document.activeElement as HTMLElement;
+
+      // Focus the input or save button after rendering
+      setTimeout(() => {
+        if (this.showInput) {
+          const input = this.shadowRoot?.querySelector('.input-field') as HTMLElement;
+          if (input) input.focus();
+        } else {
+          const saveBtn = this.shadowRoot?.querySelector('.save-btn') as HTMLElement;
+          if (saveBtn) saveBtn.focus();
+        }
+      }, 50);
+    } else if (changedProps.has('open') && !this.open && this._previousFocus) {
+      this._previousFocus.focus();
+    }
+  }
+
+  private _handleKeyDown(e: KeyboardEvent) {
+    if (!this.open) return;
+    if (e.key === 'Escape') this._handleCancel();
+    if (e.key === 'Enter' && !e.shiftKey) this._handleSave();
+  }
+
+  private _handleInputChange(e: Event) {
+    const target = e.target as HTMLInputElement;
+    this._inputValue = target.value;
+  }
+
+  private _handleCancel() {
+    this.open = false;
+    this.dispatchEvent(new CustomEvent('cancel'));
+  }
+
+  private _handleSave() {
+    this.open = false;
+    this.dispatchEvent(
+      new CustomEvent('save', {
+        detail: { value: this._inputValue },
+      }),
+    );
+  }
+
+  override render() {
+    return html`
+      <div
+        class="${classMap({ overlay: true, active: this.open })}"
+        @click="${(e: MouseEvent) => e.target === e.currentTarget && this._handleCancel()}"
+      >
+        <div class="prompt-container">
+          <div class="prompt-header">${this.promptText}</div>
+
+          ${
+            this.showInput
+              ? html`
+                <div class="input-container">
+                  <input
+                    type="text"
+                    class="input-field"
+                    .value=${this._inputValue}
+                    @input=${this._handleInputChange}
+                    placeholder=${this.placeholder}
+                  />
+                </div>
+              `
+              : ''
+          }
+
+          <div class="buttons">
+            <button @click=${this._handleCancel} class="cancel-btn">
+              ${this.cancelText}
+            </button>
+            <button @click=${this._handleSave} class="save-btn">
+              ${this.saveText}
+            </button>
+          </div>
+        </div>
+      </div>
+    `;
+  }
+
+  show() {
+    this.open = true;
+  }
+
+  hide() {
+    this.open = false;
+  }
+
+  getValue(): string {
+    return this._inputValue;
+  }
+}
diff --git a/src/routes/Arbor/Home.ts b/src/routes/Arbor/Home.ts
new file mode 100644
index 0000000..f939751
--- /dev/null
+++ b/src/routes/Arbor/Home.ts
@@ -0,0 +1,202 @@
+import { getSigner, ndk } from '@/ndk';
+import { NDKEvent, type NDKKind, type NDKSubscription } from '@nostr-dev-kit/ndk';
+import { LitElement, css, html } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+import '@components/Breadcrumbs';
+import '@components/Arbor/ForumTopic';
+import '@components/Arbor/ForumCategory';
+import '@components/Arbor/Button';
+import '@components/Prompt';
+
+import type { EvePrompt } from '@components/Prompt';
+import { map } from 'lit/directives/map.js';
+import { when } from 'lit/directives/when.js';
+
+interface ForumTopic {
+  id: string;
+  title: string;
+  author: string;
+  description: string;
+  created_at: string;
+}
+
+interface ForumCategory {
+  id: string;
+  name: string;
+  topics: ForumTopic[];
+}
+
+@customElement('arx-arbor-home')
+export class ArborForum extends LitElement {
+  @state()
+  private categories: ForumCategory[] = [];
+
+  private topicsQuery: NDKSubscription | undefined;
+
+  @state()
+  private forum: NDKEvent | null = null;
+
+  static override styles = css`
+    :host {
+      display: block;
+    }
+  `;
+
+  override async connectedCallback() {
+    super.connectedCallback();
+    await this.loadCategories();
+  }
+
+  newCategoryButtonClicked() {
+    const prompt = this.shadowRoot?.getElementById('new-category-prompt') as EvePrompt;
+    if (prompt) prompt.show();
+  }
+
+  private async doCreateCategory() {
+    if (!this.forum) return;
+    const prompt = this.shadowRoot?.getElementById('new-category-prompt') as EvePrompt;
+    const newCategory = prompt.getValue();
+
+    if (newCategory.length < 3) {
+      alert('Category name must be at least 3 characters long');
+      return;
+    }
+
+    try {
+      await getSigner();
+      const dtag = (Date.now() + Math.floor(Math.random() * 1000)).toString(32);
+      const newForum = new NDKEvent(ndk);
+      newForum.kind = 60890;
+      newForum.tags = [...this.forum.tags, ['forum', `60891:${dtag}`, newCategory]];
+      newForum.content = this.forum.content;
+      await newForum.sign();
+      await newForum.publish();
+      this.forum = newForum;
+
+      const categoryEvent = new NDKEvent(ndk);
+      categoryEvent.kind = 60891;
+      categoryEvent.tags = [
+        ['d', dtag],
+        ['name', newCategory],
+      ];
+      categoryEvent.content = '';
+      await categoryEvent.sign();
+      await categoryEvent.publish();
+
+      this.categories = [
+        ...this.categories,
+        {
+          id: dtag,
+          name: newCategory,
+          topics: [],
+        },
+      ];
+
+      this.loadTopics();
+    } catch (error) {
+      console.error('Failed to create category:', error);
+      alert('Failed to create category');
+    }
+  }
+
+  private async loadCategories() {
+    await getSigner();
+    this.forum = await ndk.fetchEvent({
+      kinds: [60890 as NDKKind],
+      '#d': ['arbor'],
+    });
+    if (!this.forum) {
+      const event = new NDKEvent(ndk);
+      event.kind = 60890;
+      event.tags.push(['d', 'arbor']);
+      event.content = '';
+      await event.sign();
+      await event.publish();
+      this.forum = event;
+    }
+    for (const tag of this.forum.tags) {
+      if (tag[0] === 'forum') {
+        const [_, categoryId] = tag[1].split(':');
+        this.categories = [
+          ...this.categories,
+          {
+            id: categoryId,
+            name: tag[2],
+            topics: [],
+          },
+        ];
+      }
+    }
+    this.loadTopics();
+  }
+
+  private async loadTopics() {
+    if (this.topicsQuery) this.topicsQuery.stop();
+    this.topicsQuery = ndk
+      .subscribe({
+        kinds: [892 as NDKKind],
+        '#d': this.categories.map((category) => `60891:${category.id}`),
+      })
+      .on('event', (event) => {
+        const categoryId = this.categories.findIndex(
+          (category) => category.id === event.tags.find((tag) => tag[0] === 'd')?.[1].split(':')[1],
+        );
+        if (categoryId === -1) return;
+        this.categories[categoryId].topics = [
+          ...this.categories[categoryId].topics,
+          {
+            id: event.id,
+            title: event.tags.find((tag) => tag[0] === 'name')?.[1] || '',
+            author: event.pubkey,
+            description: event.content,
+            created_at: new Date((event.created_at || 0) * 1000).toString(),
+          },
+        ];
+        this.categories = [...this.categories];
+      });
+  }
+
+  override render() {
+    return html`
+      <arx-breadcrumbs
+        .items=${[{ text: 'Home', href: '/' }, { text: 'Arbor' }]}
+      >
+      </arx-breadcrumbs>
+
+      <arx-arbor-button @click=${this.newCategoryButtonClicked}>
+        New Category
+      </arx-arbor-button>
+
+      <arx-prompt
+        id="new-category-prompt"
+        promptText="What would you like to call this category?"
+        showInput
+        placeholder="New Category"
+        @save=${this.doCreateCategory}
+      ></arx-prompt>
+
+      ${map(
+        this.categories,
+        (category) => html`
+          <arx-arbor-forum-category id=${category.id} title=${category.name}>
+            ${when(category.topics.length === 0, () => html` <div style="padding: 1rem 1.5rem">No topics...</div> `)}
+            ${map(
+              category.topics,
+              (topic) => html`
+                <arx-arbor-forum-topic
+                  id=${topic.id}
+                  title=${topic.title}
+                  description=${topic.description}
+                  lastPostBy=${topic.author}
+                  lastPostTime=${topic.created_at}
+                >
+                </arx-arbor-forum-topic>
+              `,
+            )}
+          </arx-arbor-forum-category>
+        `,
+      )}
+    `;
+  }
+}
diff --git a/src/routes/Phora/NewPost.ts b/src/routes/Arbor/NewPost.ts
similarity index 75%
rename from src/routes/Phora/NewPost.ts
rename to src/routes/Arbor/NewPost.ts
index bba6e52..acd278e 100644
--- a/src/routes/Phora/NewPost.ts
+++ b/src/routes/Arbor/NewPost.ts
@@ -3,14 +3,17 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
 import { LitElement, css, html } from 'lit';
 import { customElement, property, state } from 'lit/decorators.js';
 
-@customElement('arx-phora-post-creator')
-export class PhoraPostCreator extends LitElement {
+@customElement('arx-arbor-post-creator')
+export class ArborPostCreator extends LitElement {
   @property({ type: String })
   topicId = '';
 
   @state()
   private postContent = '';
 
+  @state()
+  private topic: NDKEvent | null = null;
+
   @state()
   private isCreating = false;
 
@@ -67,8 +70,26 @@ export class PhoraPostCreator extends LitElement {
     }
   `;
 
+  override connectedCallback() {
+    super.connectedCallback();
+    this.loadTopic();
+  }
+
+  private async loadTopic() {
+    try {
+      await getSigner();
+      this.topic = await ndk.fetchEvent(this.topicId);
+      if (!this.topic) {
+        throw new Error('Could not load topic');
+      }
+    } catch (error) {
+      console.error('Failed to load topic:', error);
+    }
+  }
+
   private async doCreatePost() {
     if (this.isCreating) return;
+    if (!this.topic) return;
 
     if (this.postContent.length < 10) {
       this.error = 'Post content must be at least 10 characters long';
@@ -78,26 +99,18 @@ export class PhoraPostCreator extends LitElement {
     this.error = null;
     this.isCreating = true;
 
+    const event = new NDKEvent(ndk);
+    event.kind = 893;
+    event.tags = [
+      ['A', this.topic.tags.find((tag) => tag[0] === 'd')?.[1] || ''],
+      ['E', this.topicId],
+      ['p', this.topic.pubkey],
+    ];
+    event.content = this.postContent;
+
     try {
-      await getSigner();
-      const event = new NDKEvent(ndk);
-      event.kind = 1111;
-      event.tags = [['e', this.topicId]];
-      event.content = this.postContent;
       await event.sign();
       await event.publish();
-
-      this.dispatchEvent(
-        new CustomEvent('post-created', {
-          bubbles: true,
-          composed: true,
-          detail: {
-            postId: event.id,
-            topicId: this.topicId,
-          },
-        }),
-      );
-
       // Reset form
       this.postContent = '';
     } catch (error) {
@@ -130,19 +143,19 @@ export class PhoraPostCreator extends LitElement {
         ${this.error ? html` <div class="error">${this.error}</div> ` : null}
 
         <div class="actions">
-          <arx-phora-button
+          <arx-arbor-button
             @click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
             ?disabled=${this.isCreating}
           >
             Cancel
-          </arx-phora-button>
+          </arx-arbor-button>
 
-          <arx-phora-button
+          <arx-arbor-button
             @click=${this.doCreatePost}
             ?disabled=${this.isCreating}
           >
             ${this.isCreating ? 'Creating...' : 'Create'}
-          </arx-phora-button>
+          </arx-arbor-button>
         </div>
       </div>
     `;
diff --git a/src/routes/Phora/NewTopic.ts b/src/routes/Arbor/NewTopic.ts
similarity index 90%
rename from src/routes/Phora/NewTopic.ts
rename to src/routes/Arbor/NewTopic.ts
index 630c7c9..f508e82 100644
--- a/src/routes/Phora/NewTopic.ts
+++ b/src/routes/Arbor/NewTopic.ts
@@ -3,8 +3,8 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
 import { LitElement, css, html } from 'lit';
 import { customElement, property, state } from 'lit/decorators.js';
 
-@customElement('arx-phora-topic-creator')
-export class PhoraTopicCreator extends LitElement {
+@customElement('arx-arbor-topic-creator')
+export class ArborTopicCreator extends LitElement {
   @property({ type: String })
   categoryId = '';
 
@@ -66,10 +66,10 @@ export class PhoraTopicCreator extends LitElement {
     try {
       await getSigner();
       const event = new NDKEvent(ndk);
-      event.kind = 11;
+      event.kind = 892;
       event.tags = [
-        ['subject', this.newTopic],
-        ['e', this.categoryId],
+        ['name', this.newTopic],
+        ['d', `60891:${this.categoryId}`],
       ];
       event.content = this.topicContent;
       await event.sign();
@@ -124,19 +124,19 @@ export class PhoraTopicCreator extends LitElement {
       ></textarea>
 
       <div class="button-group">
-        <arx-phora-button
+        <arx-arbor-button
           @click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
           ?disabled=${this.isCreating}
         >
           Cancel
-        </arx-phora-button>
+        </arx-arbor-button>
 
-        <arx-phora-button
+        <arx-arbor-button
           @click=${this.doCreateTopic}
           ?disabled=${this.isCreating}
         >
           ${this.isCreating ? 'Creating...' : 'Create'}
-        </arx-phora-button>
+        </arx-arbor-button>
       </div>
     `;
   }
diff --git a/src/routes/Arbor/TopicView.ts b/src/routes/Arbor/TopicView.ts
new file mode 100644
index 0000000..94e8fc3
--- /dev/null
+++ b/src/routes/Arbor/TopicView.ts
@@ -0,0 +1,209 @@
+import { getSigner, ndk } from '@/ndk';
+import type { NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
+import { LitElement, css, html } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+
+import '@components/Breadcrumbs';
+import '@components/Arbor/ForumPost';
+import '@components/Arbor/Button';
+
+import { map } from 'lit/directives/map.js';
+import { when } from 'lit/directives/when.js';
+
+interface ForumPost {
+  id: string;
+  npub: string;
+  date: Date;
+  content: string;
+}
+
+@customElement('arx-arbor-topic-view')
+export class ArborTopicView extends LitElement {
+  @property({ type: String })
+  topicId = '';
+
+  @state()
+  override title = '';
+
+  @state()
+  private posts: ForumPost[] = [];
+
+  private subscription: NDKSubscription | undefined;
+
+  static override styles = css`
+    :host {
+      display: block;
+      margin: 0 auto;
+      padding: 1rem;
+      color: #1a1a1a;
+      font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+    }
+
+    .topic-container {
+      background: #ffffff;
+      border-radius: 12px;
+      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
+      overflow: hidden;
+      margin-top: 1.5rem;
+      border: 1px solid rgba(0, 0, 0, 0.08);
+    }
+
+    .header {
+      background: var(--primary);
+      color: oklch(100% 0 0);
+      padding: 1.5rem 2rem;
+      font-weight: 600;
+      font-size: 1.25rem;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      position: relative;
+      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+      letter-spacing: 0.01em;
+    }
+
+    .posts-container {
+      display: flex;
+      flex-direction: column;
+      gap: 0;
+      background: #f9f9f9;
+      padding: 1.5rem;
+      min-height: 300px;
+    }
+
+    .actions {
+      background: #f9f9f9;
+      padding: 1.25rem 1.5rem;
+      display: flex;
+      justify-content: flex-end;
+      border-top: 1px solid rgba(0, 0, 0, 0.06);
+    }
+
+    .empty-state {
+      padding: 3rem;
+      text-align: center;
+      color: #666;
+      font-style: italic;
+    }
+
+    @media (max-width: 768px) {
+      .header {
+        padding: 1.25rem 1.5rem;
+        font-size: 1.1rem;
+      }
+
+      .posts-container {
+        padding: 1rem;
+      }
+
+      .actions {
+        padding: 1rem;
+      }
+    }
+
+    @media (max-width: 480px) {
+      :host {
+        padding: 0.5rem;
+      }
+
+      .header {
+        padding: 1rem;
+      }
+    }
+  `;
+
+  override async connectedCallback() {
+    super.connectedCallback();
+    await this.loadTopic();
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    if (this.subscription) {
+      this.subscription.stop();
+    }
+  }
+
+  private async loadTopic() {
+    try {
+      await getSigner();
+      const event = await ndk.fetchEvent(this.topicId);
+
+      if (!event) {
+        throw new Error('Could not load topic');
+      }
+
+      this.title = event.tags.find((tag) => tag[0] === 'name')?.[1] || '';
+
+      this.posts = [
+        {
+          id: event.id,
+          npub: event.pubkey,
+          date: new Date((event.created_at || 0) * 1000),
+          content: event.content,
+        },
+      ];
+
+      // Subscribe to new posts
+      this.subscription = ndk
+        .subscribe({
+          kinds: [893 as NDKKind],
+          '#E': [this.topicId],
+        })
+        .on('event', (event) => {
+          this.posts = [
+            ...this.posts,
+            {
+              id: event.id,
+              npub: event.pubkey,
+              date: new Date((event.created_at || 0) * 1000),
+              content: event.content,
+            },
+          ];
+        });
+    } catch (error) {
+      console.error('Failed to load topic:', error);
+      alert('Could not load topic');
+    }
+  }
+
+  override render() {
+    const breadcrumbItems = [{ text: 'Home', href: '/' }, { text: 'Arbor', href: '/arbor' }, { text: this.title }];
+
+    return html`
+      <arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
+
+      <div class="topic-container">
+        <div class="header">
+          <span>${this.title || 'Loading topic...'}</span>
+        </div>
+
+        <div class="posts-container">
+          ${when(
+            this.posts.length === 0,
+            () => html`<div class="empty-state">No posts available</div>`,
+            () =>
+              map(
+                this.posts,
+                (post) => html`
+                  <arx-forum-post
+                    id=${post.id}
+                    .topicId=${this.topicId}
+                    .npub=${post.npub}
+                    .date=${post.date}
+                    .content=${post.content}
+                  ></arx-forum-post>
+                `,
+              ),
+          )}
+        </div>
+
+        <div class="actions">
+          <arx-arbor-button href="/arbor/new-post/${this.topicId}">
+            New Post
+          </arx-arbor-button>
+        </div>
+      </div>
+    `;
+  }
+}
diff --git a/src/routes/Home.ts b/src/routes/Home.ts
index ad29897..baa3563 100644
--- a/src/routes/Home.ts
+++ b/src/routes/Home.ts
@@ -39,8 +39,8 @@ export class Home extends LitElement {
     },
     {
       id: 3,
-      href: 'phora',
-      name: 'Phora',
+      href: 'arbor',
+      name: 'Arbor',
       color: '#FF3B30',
       icon: 'bxs:conversation',
     },
diff --git a/src/routes/Phora/Home.ts b/src/routes/Phora/Home.ts
deleted file mode 100644
index de16989..0000000
--- a/src/routes/Phora/Home.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { getSigner, ndk } from '@/ndk';
-import type { NDKSubscription } from '@nostr-dev-kit/ndk';
-import formatDateTime from '@utils/formatDateTime';
-import { LitElement, css, html } from 'lit';
-import { customElement, state } from 'lit/decorators.js';
-
-import '@components/Breadcrumbs';
-import '@components/PhoraForumCategory';
-import '@components/PhoraForumTopic';
-import '@components/PhoraButton';
-
-interface ForumTopic {
-  id: string;
-  title: string;
-  author: string;
-  description: string;
-  created_at: string;
-}
-
-interface ForumCategory {
-  id: string;
-  name: string;
-  description: string;
-  topics: ForumTopic[];
-}
-
-@customElement('arx-phora-home')
-export class PhoraForum extends LitElement {
-  @state()
-  private categories: ForumCategory[] = [];
-
-  private categoriesQuery: NDKSubscription | undefined;
-
-  static override styles = css`
-    :host {
-      display: block;
-    }
-  `;
-
-  override async connectedCallback() {
-    super.connectedCallback();
-    await this.loadCategories();
-  }
-
-  override disconnectedCallback() {
-    super.disconnectedCallback();
-    if (this.categoriesQuery) this.categoriesQuery.stop();
-  }
-
-  private async loadCategories() {
-    await getSigner();
-    this.categoriesQuery = ndk
-      .subscribe({
-        kinds: [11],
-      })
-      .on('event', (event) => {
-        const subject = event.tags.find((tag: string[]) => tag[0] === 'subject');
-        const parent = event.tags.find((tag: string[]) => tag[0] === 'e');
-
-        if (!subject) return;
-
-        if (parent) {
-          const categoryIndex = this.categories.findIndex((category) => category.id === parent[1]);
-          if (categoryIndex === -1) return;
-
-          const updatedCategories = [...this.categories];
-          updatedCategories[categoryIndex].topics.push({
-            id: event.id,
-            title: subject[1],
-            author: event.pubkey,
-            created_at: formatDateTime((event.created_at || 0) * 1000),
-            description: event.content,
-          });
-
-          this.categories = updatedCategories;
-          return;
-        }
-
-        this.categories = [
-          ...this.categories,
-          {
-            id: event.id,
-            name: subject[1],
-            description: event.content.substring(0, 100),
-            topics: [],
-          },
-        ];
-      });
-  }
-
-  override render() {
-    return html`
-      <arx-breadcrumbs
-        .items=${[{ text: 'Home', href: '/' }, { text: 'Phora' }]}
-      >
-      </arx-breadcrumbs>
-
-      <arx-phora-button href="/phora/new-category">
-        New Category
-      </arx-phora-button>
-
-      ${this.categories.map(
-        (category) => html`
-          <arx-phora-forum-category id=${category.id} title=${category.name}>
-            ${category.topics.map(
-              (topic) => html`
-                <arx-phora-forum-topic
-                  id=${topic.id}
-                  title=${topic.title}
-                  description=${topic.description}
-                  lastPostBy=${topic.author}
-                  lastPostTime=${topic.created_at}
-                >
-                </arx-phora-forum-topic>
-              `,
-            )}
-          </arx-phora-forum-category>
-        `,
-      )}
-    `;
-  }
-}
diff --git a/src/routes/Phora/NewCategory.ts b/src/routes/Phora/NewCategory.ts
deleted file mode 100644
index f95bfef..0000000
--- a/src/routes/Phora/NewCategory.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { getSigner, ndk } from '@/ndk';
-import { NDKEvent } from '@nostr-dev-kit/ndk';
-import { LitElement, css, html } from 'lit';
-import { customElement, state } from 'lit/decorators.js';
-
-@customElement('arx-phora-category-creator')
-export class PhoraCategoryCreator extends LitElement {
-  @state()
-  private newCategory = '';
-
-  @state()
-  private categoryDescription = '';
-
-  static override styles = css`
-    :host {
-      display: block;
-    }
-
-    input,
-    textarea {
-      width: 100%;
-      margin-bottom: 1rem;
-      padding: 0.5rem;
-      border: 1px solid #ccc;
-      border-radius: 4px;
-    }
-
-    textarea {
-      min-height: 100px;
-      resize: vertical;
-    }
-  `;
-
-  private async doCreateCategory() {
-    if (this.newCategory.length < 3) {
-      alert('Category name must be at least 3 characters long');
-      return;
-    }
-
-    if (this.categoryDescription.length < 10) {
-      alert('Category description must be at least 10 characters long');
-      return;
-    }
-
-    try {
-      await getSigner();
-      const event = new NDKEvent(ndk);
-      event.kind = 11;
-      event.tags = [['subject', this.newCategory]];
-      event.content = this.categoryDescription;
-      await event.sign();
-      await event.publish();
-
-      this.dispatchEvent(
-        new CustomEvent('category-created', {
-          bubbles: true,
-          composed: true,
-        }),
-      );
-
-      this.newCategory = '';
-      this.categoryDescription = '';
-    } catch (error) {
-      console.error('Failed to create category:', error);
-      alert('Failed to create category');
-    }
-  }
-
-  private handleCategoryInput(e: InputEvent) {
-    this.newCategory = (e.target as HTMLInputElement).value;
-  }
-
-  private handleDescriptionInput(e: InputEvent) {
-    this.categoryDescription = (e.target as HTMLTextAreaElement).value;
-  }
-
-  override render() {
-    return html`
-      <input
-        type="text"
-        placeholder="New Category"
-        .value=${this.newCategory}
-        @input=${this.handleCategoryInput}
-      />
-
-      <textarea
-        placeholder="Category Description"
-        .value=${this.categoryDescription}
-        @input=${this.handleDescriptionInput}
-      ></textarea>
-
-      <arx-phora-button @click=${this.doCreateCategory}>
-        Create
-      </arx-phora-button>
-    `;
-  }
-}
diff --git a/src/routes/Phora/TopicView.ts b/src/routes/Phora/TopicView.ts
deleted file mode 100644
index 9b2052a..0000000
--- a/src/routes/Phora/TopicView.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import { getSigner, ndk } from '@/ndk';
-import type { NDKSubscription } from '@nostr-dev-kit/ndk';
-import { LitElement, css, html } from 'lit';
-import { customElement, property, state } from 'lit/decorators.js';
-
-import '@components/Breadcrumbs';
-import '@components/ForumPost';
-import '@components/PhoraButton';
-
-interface ForumPost {
-  id: string;
-  npub: string;
-  date: Date;
-  content: string;
-}
-
-@customElement('arx-phora-topic-view')
-export class PhoraTopicView extends LitElement {
-  @property({ type: String })
-  topicId = '';
-
-  @state()
-  override title = '';
-
-  @state()
-  private posts: ForumPost[] = [];
-
-  private subscription: NDKSubscription | undefined;
-
-  static override styles = css`
-    :host {
-      display: block;
-    }
-
-    .topic {
-      max-width: 1200px;
-      padding: 1em;
-      background: rgba(255, 255, 255, 0.8);
-      border-radius: 0.5rem;
-      display: grid;
-      gap: 1rem;
-      grid-template-columns: [column-1] auto [column-2] 1fr;
-    }
-
-    .header {
-      background: var(--primary);
-      color: oklch(100% 0 0);
-      padding: 1rem 1.5rem;
-      border-radius: 0.5rem 0.5rem 0 0;
-      font-weight: 500;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-    }
-
-    .posts-container {
-      display: flex;
-      flex-direction: column;
-      gap: 1rem;
-    }
-
-    .actions {
-      margin-top: 1rem;
-    }
-  `;
-
-  override async connectedCallback() {
-    super.connectedCallback();
-    await this.loadTopic();
-  }
-
-  override disconnectedCallback() {
-    super.disconnectedCallback();
-    if (this.subscription) {
-      this.subscription.stop();
-    }
-  }
-
-  private async loadTopic() {
-    try {
-      await getSigner();
-      const event = await ndk.fetchEvent(this.topicId);
-
-      if (!event) {
-        throw new Error('Could not load topic');
-      }
-
-      this.title = event.tags.find((tag) => tag[0] === 'subject')?.[1] || '';
-
-      this.posts = [
-        {
-          id: event.id,
-          npub: event.pubkey,
-          date: new Date((event.created_at || 0) * 1000),
-          content: event.content,
-        },
-      ];
-
-      // Subscribe to new posts
-      this.subscription = ndk
-        .subscribe({
-          kinds: [1111],
-          '#e': [this.topicId],
-        })
-        .on('event', (event) => {
-          this.posts = [
-            ...this.posts,
-            {
-              id: event.id,
-              npub: event.pubkey,
-              date: new Date((event.created_at || 0) * 1000),
-              content: event.content,
-            },
-          ];
-        });
-    } catch (error) {
-      console.error('Failed to load topic:', error);
-      alert('Could not load topic');
-    }
-  }
-
-  override render() {
-    const breadcrumbItems = [{ text: 'Home', href: '/' }, { text: 'Phora', href: '/phora' }, { text: this.title }];
-
-    return html`
-      <arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
-
-      <div class="header">
-        <span>${this.title}</span>
-      </div>
-
-      <div class="posts-container">
-        ${this.posts.map(
-          (post) => html`
-            <arx-forum-post
-              class="topic"
-              id=${post.id}
-              .topicId=${this.topicId}
-              .npub=${post.npub}
-              .date=${post.date}
-              .content=${post.content}
-            ></arx-forum-post>
-          `,
-        )}
-      </div>
-
-      <div class="actions">
-        <arx-phora-button href="/phora/new-post/${this.topicId}">
-          New Post
-        </arx-phora-button>
-      </div>
-    `;
-  }
-}
diff --git a/src/routes/router.ts b/src/routes/router.ts
index 7bc3fb7..7b3fb0b 100644
--- a/src/routes/router.ts
+++ b/src/routes/router.ts
@@ -1,11 +1,10 @@
 import '@routes/404Page';
 import '@routes/Home';
 import '@routes/Profile';
-import '@routes/Phora/Home';
-import '@routes/Phora/NewCategory';
-import '@routes/Phora/NewTopic';
-import '@routes/Phora/TopicView';
-import '@routes/Phora/NewPost';
+import '@routes/Arbor/Home';
+import '@routes/Arbor/NewTopic';
+import '@routes/Arbor/TopicView';
+import '@routes/Arbor/NewPost';
 import '@components/InitialSetup';
 
 import { spread } from '@open-wc/lit-helpers';
@@ -40,29 +39,24 @@ export default class EveRouter extends LitElement {
       component: literal`arx-profile-route`,
     },
     {
-      pattern: 'phora',
+      pattern: 'arbor',
       params: {},
-      component: literal`arx-phora-home`,
+      component: literal`arx-arbor-home`,
     },
     {
-      pattern: 'phora/new-category',
+      pattern: 'arbor/new-topic/:categoryId',
       params: {},
-      component: literal`arx-phora-category-creator`,
+      component: literal`arx-arbor-topic-creator`,
     },
     {
-      pattern: 'phora/new-topic/:categoryId',
+      pattern: 'arbor/topics/:topicId',
       params: {},
-      component: literal`arx-phora-topic-creator`,
+      component: literal`arx-arbor-topic-view`,
     },
     {
-      pattern: 'phora/topics/:topicId',
+      pattern: 'arbor/new-post/:topicId',
       params: {},
-      component: literal`arx-phora-topic-view`,
-    },
-    {
-      pattern: 'phora/new-post/:topicId',
-      params: {},
-      component: literal`arx-phora-post-creator`,
+      component: literal`arx-arbor-post-creator`,
     },
     {
       pattern: '404',