diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx
index 9ac131df..14f254a6 100644
--- a/src/AppRouter.tsx
+++ b/src/AppRouter.tsx
@@ -6,7 +6,7 @@ import { MinimizedAudioBar } from "@/components/MinimizedAudioBar";
import { AudioPlayerProvider } from "@/contexts/AudioPlayerContext";
import { sidebarItemIcon } from "@/lib/sidebarItems";
import { Toaster } from "./components/ui/toaster";
-import { MainLayout } from "./components/MainLayout";
+import { FundraiserLayout } from "./components/FundraiserLayout";
import { ScrollToTop } from "./components/ScrollToTop";
import { VersionCheck } from "./components/VersionCheck";
import { useCurrentUser } from "./hooks/useCurrentUser";
@@ -24,11 +24,9 @@ const ReplyComposeModal = lazy(() => import("@/components/ReplyComposeModal").th
// Lazy-loaded emoji pack dialog
const EmojiPackDialog = lazy(() => import("@/components/EmojiPackDialog").then(m => ({ default: m.EmojiPackDialog })));
-// HomePage eagerly imported all page components; now lazy-loaded
-const HomePage = lazy(() => import("./pages/HomePage").then(m => ({ default: m.HomePage })));
-
-// Campaigns: lazy-loaded list + create pages. (Detail page is dispatched from
-// NIP19Page when an naddr resolves to kind 30223.)
+// Campaigns: home + create. (Campaign detail is dispatched from NIP19Page
+// when an naddr resolves to kind 30223.) The campaigns list IS the homepage;
+// the configurable HomePage delegation from the Twitter-era app is gone.
const CampaignsPage = lazy(() => import("./pages/CampaignsPage").then(m => ({ default: m.CampaignsPage })));
const CreateCampaignPage = lazy(() => import("./pages/CreateCampaignPage").then(m => ({ default: m.CreateCampaignPage })));
@@ -159,11 +157,11 @@ export function AppRouter() {
{/* Auto-follow deep link: fullscreen immersive (no sidebars/nav) */}
} />
- {/* All routes share the persistent MainLayout (sidebar + nav) */}
- }>
- } />
+ {/* All routes share the persistent FundraiserLayout (top nav + footer) */}
+ }>
+ } />
} />
- } />
+ } />
} />
} />
} />
diff --git a/src/components/FundraiserLayout.tsx b/src/components/FundraiserLayout.tsx
new file mode 100644
index 00000000..bf209e0d
--- /dev/null
+++ b/src/components/FundraiserLayout.tsx
@@ -0,0 +1,105 @@
+import { Suspense, useCallback, useMemo, useRef, useState } from 'react';
+import { Outlet } from 'react-router-dom';
+
+import { TopNav } from '@/components/TopNav';
+import { Skeleton } from '@/components/ui/skeleton';
+import {
+ CenterColumnContext,
+ DrawerContext,
+ LayoutStore,
+ LayoutStoreContext,
+ NavHiddenContext,
+} from '@/contexts/LayoutContext';
+import { cn } from '@/lib/utils';
+
+/**
+ * Persistent app shell for the fundraising-platform overhaul.
+ *
+ * Replaces the previous Twitter-style three-column `MainLayout` with a
+ * GoFundMe-style top-nav-only chrome. Routes render in a single full-width
+ * content area below the {@link TopNav}.
+ *
+ * Compatibility surface:
+ * - We still provide `LayoutStoreContext`, so pages that call
+ * `useLayoutOptions(...)` keep working. Most options (FAB, sidebars,
+ * mobile arc) are intentionally ignored here because the new shell has
+ * no FAB and no sidebars. The store still drives the
+ * `wrapperClassName` escape hatch for pages that need to widen.
+ * - `CenterColumnContext` exposes the content `
` so legacy components
+ * (e.g. nsite preview overlay) can still portal into it.
+ * - `DrawerContext` and `NavHiddenContext` are kept as no-op providers so
+ * pages that read them don't crash.
+ */
+
+function PageSkeleton() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+function FundraiserLayoutInner() {
+ const centerColumnRef = useRef(null);
+ const [centerColumnEl, setCenterColumnEl] = useState(null);
+
+ // Mobile drawer is owned by TopNav now, so consumers of `useOpenDrawer`
+ // become no-ops. Keeping the context shape avoids touching every page that
+ // pulls the hook.
+ const openDrawer = useCallback(() => {}, []);
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+function SiteFooter() {
+ return (
+
+ );
+}
+
+export function FundraiserLayout() {
+ const store = useMemo(() => new LayoutStore(), []);
+ return (
+
+
+
+ );
+}
+
+export default FundraiserLayout;
diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx
new file mode 100644
index 00000000..00dd94b3
--- /dev/null
+++ b/src/components/TopNav.tsx
@@ -0,0 +1,196 @@
+import { useState } from 'react';
+import { Link, NavLink } from 'react-router-dom';
+import { Menu, PlusCircle, X } from 'lucide-react';
+
+import { LoginArea } from '@/components/auth/LoginArea';
+import { Button } from '@/components/ui/button';
+import { LogoIcon } from '@/components/icons/LogoIcon';
+import { Sheet, SheetContent } from '@/components/ui/sheet';
+import { useAppContext } from '@/hooks/useAppContext';
+import { useCurrentUser } from '@/hooks/useCurrentUser';
+import { cn } from '@/lib/utils';
+
+interface NavItem {
+ label: string;
+ to: string;
+ /** If true, this link is treated as active only on an exact match. */
+ exact?: boolean;
+}
+
+const NAV_ITEMS: NavItem[] = [
+ { label: 'Discover', to: '/', exact: true },
+ { label: 'Start a campaign', to: '/campaigns/new' },
+ { label: 'About', to: '/help' },
+];
+
+/**
+ * Persistent top navigation bar rendered by {@link FundraiserLayout}. Mirrors
+ * the GoFundMe-style chrome: brand mark on the left, primary nav links in the
+ * middle, "Sign in" / account avatar plus a "Start a campaign" pill on the
+ * right. Collapses to a hamburger menu below the `md` breakpoint.
+ */
+export function TopNav() {
+ const { config } = useAppContext();
+ const { user } = useCurrentUser();
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ return (
+
+
+ {/* Mobile menu trigger */}
+
+
+ {/* Brand */}
+
+
+ {config.appName}
+
+
+ {/* Desktop nav */}
+
+
+
+
+ {/* Right cluster */}
+
+ {/* Primary CTA pill — hidden on small screens to keep the bar uncluttered;
+ the same action lives at the top of the mobile menu and as a FAB-style
+ button in the homepage hero. */}
+
+
+ {/* LoginArea handles both logged-in (account avatar dropdown) and
+ logged-out (Log in / Sign up) states. We render it inline-flex
+ and let it style its own children. */}
+
+