diff --git a/AGENTS.md b/AGENTS.md
index 7e97ff8..7d16ef9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -344,6 +344,17 @@ session with **no other memory of this project**. So:
burger menu (`MOBILE_MENU_LISTED_HREFS`) now **appends catalog items that
aren't hard-coded**, so Marketplace/Mints (and any future section) are always
reachable on mobile — don't re-hardcode only a subset.
+- **App Shop** (`/app-shop`, `src/pages/AppShopPage.tsx`): SkateSpots merch, to be
+ paid in **sats only (Lightning or Cashu)** — no cards/accounts. Registered in
+ `NAV_CATALOG` as id `app-shop` (label "App Shop", `ShoppingBag`), which is what
+ makes it appear in **both** the desktop "More" menu and the mobile burger menu
+ (the mobile menu appends catalog items not in `MOBILE_MENU_LISTED_HREFS`). It is
+ **intentionally empty**: no stock, no prices, no pre-orders and nothing for
+ sale — the copy states that plainly ("an empty shop with good lighting") and
+ only previews what's coming. **Do not add prices/fake urgency/buy buttons until
+ there is real stock**; when items exist, model them as listings and take
+ Lightning/Cashu payment. The page reads the live stock strip as 0/None/Nothing
+ — keep it honest if that changes.
- **Mints directory**: `/mints` page lists Cashu mints from the community
**MintRadar** project. MintRadar has **no CORS**, so it's fetched same-origin
at `/api/mints/*` — prod proxies via `public/_redirects` (`/api/mints/* →
diff --git a/release-notes.txt b/release-notes.txt
index 3cc1b29..a68dd81 100644
--- a/release-notes.txt
+++ b/release-notes.txt
@@ -1,5 +1,12 @@
v1.5.4
+🛍 App Shop — a new section (empty on purpose)
+- Merch is coming, and it'll be paid in sats: Lightning or Cashu, your choice.
+ No cards, no accounts, no processor skimming the drop.
+- To be clear: we hold no stock and nothing is for sale yet. No pre-orders, no
+ countdown timers, no fake scarcity — the page says so, and previews what's in
+ the works. Subscribe to the newsletter to get the drop first.
+
✉️ Direct messages actually arrive now
- Your DM inbox relays were being forgotten every time the app loaded, so
messages were sent to your normal relays instead of the ones the other person
diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs
index eeebce0..cf9ac8c 100755
--- a/scripts/smoke-test.mjs
+++ b/scripts/smoke-test.mjs
@@ -140,6 +140,7 @@ const ROUTES = [
'/collections',
'/stats',
'/mints',
+ '/app-shop',
'/messages/group/abc123',
];
const problems = [];
diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx
index 0864140..174e560 100644
--- a/src/AppRouter.tsx
+++ b/src/AppRouter.tsx
@@ -17,6 +17,7 @@ const EventsPage = lazyWithRetry(() => import("./pages/EventsPage"));
const LabPage = lazyWithRetry(() => import("./pages/LabPage"));
const WalletPage = lazyWithRetry(() => import("./pages/WalletPage"));
const MintsPage = lazyWithRetry(() => import("./pages/MintsPage"));
+const AppShopPage = lazyWithRetry(() => import("./pages/AppShopPage"));
const SettingsPage = lazyWithRetry(() => import("./pages/SettingsPage"));
const CreateSpotPage = lazyWithRetry(() => import("./pages/CreateSpotPage"));
const CreateShopPage = lazyWithRetry(() => import("./pages/CreateShopPage"));
@@ -134,6 +135,7 @@ export function AppRouter() {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/hooks/useNavPreferences.ts b/src/hooks/useNavPreferences.ts
index 3e6a5d5..7e78056 100644
--- a/src/hooks/useNavPreferences.ts
+++ b/src/hooks/useNavPreferences.ts
@@ -11,6 +11,7 @@ import {
BarChart3,
Coins,
Handshake,
+ ShoppingBag,
type LucideIcon,
} from 'lucide-react';
import { useLocalStorage } from '@/hooks/useLocalStorage';
@@ -60,6 +61,7 @@ export const NAV_CATALOG: NavTab[] = [
{ id: 'crews', href: '/crews', labelKey: 'nav.crews', label: 'Crews', icon: Users },
{ id: 'wallet', href: '/wallet', labelKey: 'nav.wallet', label: 'Wallet', icon: Wallet, authOnly: true },
{ id: 'mints', href: '/mints', label: 'Mints', icon: Coins },
+ { id: 'app-shop', href: '/app-shop', label: 'App Shop', icon: ShoppingBag },
{ id: 'trades', href: '/marketplace', label: 'Marketplace', icon: Handshake },
{ id: 'stash', href: '/stash', label: 'Stash', icon: Package, authOnly: true },
{ id: 'collections', href: '/collections', label: 'Collections', icon: Bookmark, authOnly: true },
diff --git a/src/pages/AppShopPage.tsx b/src/pages/AppShopPage.tsx
new file mode 100644
index 0000000..5d2d03c
--- /dev/null
+++ b/src/pages/AppShopPage.tsx
@@ -0,0 +1,233 @@
+import { useSeoMeta } from '@unhead/react';
+import { Link } from 'react-router-dom';
+import {
+ ShoppingBag,
+ Zap,
+ Coins,
+ Truck,
+ Shirt,
+ Sticker,
+ Package,
+ Hammer,
+ Sparkles,
+ BadgeCheck,
+ ArrowRight,
+} from 'lucide-react';
+import { PageHeader } from '@/components/PageHeader';
+import { NewsletterSubscribeButton } from '@/components/NewsletterSubscribeButton';
+import { Button } from '@/components/ui/button';
+
+/**
+ * App Shop — SkateSpots merch.
+ *
+ * Deliberately a STOREFRONT WITH NOTHING IN IT: there is no stock, no
+ * pre-order, no price and nothing to buy. It exists so the section, the design
+ * language and the payment story (sats only — Lightning or Cashu) are in place
+ * before the first drop. When items land we can model them as listings and take
+ * Lightning/Cashu payment; until then this page must never imply a sale.
+ */
+
+/** What we're working on. Not products, not prices — just intent. */
+const PLANNED = [
+ { icon: Shirt, label: 'Tees & hoodies', note: 'Heavyweight, printed somewhere that pays people properly.' },
+ { icon: Package, label: 'Decks', note: 'Small numbered runs. Ride it or hang it, your call.' },
+ { icon: Sticker, label: 'Sticker packs', note: 'For the box, the rail, the laptop lid.' },
+ { icon: Hammer, label: 'Shop tools', note: 'The hardware the homies actually wear out.' },
+];
+
+const PAY_CHIPS = [
+ { icon: Zap, label: 'Lightning' },
+ { icon: Coins, label: 'Cashu' },
+ { icon: Truck, label: 'Shipped worldwide' },
+ { icon: BadgeCheck, label: 'At cost, no markup theatre' },
+];
+
+export default function AppShopPage() {
+ useSeoMeta({
+ title: 'App Shop — SkateSpots',
+ description:
+ 'SkateSpots merch, paid in sats over Lightning or Cashu. Nothing in stock yet — here is what is coming.',
+ });
+
+ return (
+
+ Merch for the people who keep this thing rolling. Paid in{' '}
+ sats — Lightning or Cashu,
+ whichever you already keep. No cards, no accounts, no middleman skimming the drop.
+
+
+
+ {/* ── How it'll work ───────────────────────────────────────────────── */}
+
+
+
+ How it'll work
+
+
+
+
+
+ {[
+ { n: '1', t: 'Pick your thing', d: 'One page, no logged-in pricing games, no fake urgency.' },
+ { n: '2', t: 'Pay in sats', d: 'Lightning or Cashu. Straight to us — no processor in the middle.' },
+ { n: '3', t: 'It ships', d: 'Posted to your door with an honest tracking number.' },
+ ].map((s) => (
+
+ The honest bit: we hold no stock and
+ nothing here is for sale. There are no pre-orders, no countdown timers and no
+ fake scarcity — when there is something real to buy, this page will say so and
+ take your sats. Until then it is just an empty shop with good lighting.
+