diff --git a/AGENTS.md b/AGENTS.md index 7058917..8fe1798 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -806,6 +806,18 @@ 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. +- **DIY resources (`/diy`, 2026-10-05):** a curated hub for making your own + boards/ramps/concrete. Content is a seed list in **`src/lib/diyGuides.ts`** + (`DIY_CATEGORIES` boards/ramps/concrete/repairs/tools + `DIY_CARDS` + + `DIY_ACTIONS`) — vetted external links (Open Source Skateboards / SK8CAD / + Instructables / YouTube) and in-app action cards; rendered by + `src/pages/DiyPage.tsx`. Registered as a nav-catalog item (id `diy`, Hammer) + so it shows in the desktop "More" menu **and** the mobile burger, and linked + from the **site footer** + the **map links strip**. Cross-links OUT to + `/lab?craft=building` (Builder), `/crews`, `/create-spot`, `/create-listing`, + `/lab?craft=coaching`. ⚠️ **Link out, never rehost** — credit the source; + `sanitizeUrl` on every external URL. In-app tools (cost calculator) are the + next step; community guides would be NIP-23 `30023` with `t=diy`. - **App Shop** (`/app-shop`, `src/pages/AppShopPage.tsx`): SkateSpots merch, to be paid in **sats only (Lightning or Cashu)** — no cards/accounts — sold **locally, on request**: there is **no cart, no checkout, no shipping**. Buying diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index de142dc..064771f 100755 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -145,6 +145,7 @@ const ROUTES = [ '/stats', '/mints', '/app-shop', + '/diy', '/review', '/card', '/card/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx', diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 664bc05..b253b88 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -18,6 +18,7 @@ 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 DiyPage = lazyWithRetry(() => import("./pages/DiyPage")); const AdminReviewPage = lazyWithRetry(() => import("./pages/AdminReviewPage")); const SettingsPage = lazyWithRetry(() => import("./pages/SettingsPage")); const CreateSpotPage = lazyWithRetry(() => import("./pages/CreateSpotPage")); @@ -141,6 +142,7 @@ export function AppRouter() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 938f0b7..3e32f1d 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1071,6 +1071,12 @@ export function Layout({ children, noPadding }: LayoutProps) {
+ + {t('nav.diy')} + + {card.emoji} + + {t(card.titleKey)} + {t(card.blurbKey)} + {card.source && ( + {t('diyUi.source')}: {card.source} + )} + + {external + ? + : } + + ); + + const cls = cn( + 'group flex items-start gap-3 rounded-2xl border border-white/10 bg-white/[0.03] p-4 transition-colors', + 'hover:border-[#ccff00]/40 hover:bg-white/[0.06]', + ); + + if (external) { + return ( + + {body} + + ); + } + return {body}; +} + +export default function DiyPage() { + const { t } = useTranslation(); + + useSeoMeta({ + title: 'DIY — Make Your Own | SkateSpots', + description: 'Guides, open tools and plans to make your own skateboards, ramps and concrete spots — and the people and places to do it with.', + }); + + return ( +
+ {/* Header */} +
+

+ + {t('diyUi.title')} +

+

{t('diyUi.tagline')}

+
+ + {/* Category chips */} + + + {/* Resource sections */} + {DIY_CATEGORIES.map((cat) => { + const cards = DIY_CARDS.filter((c) => c.category === cat.id); + if (cards.length === 0) return null; + return ( +
+

+ {cat.emoji} + {t(cat.titleKey)} +

+
+ {cards.map((card) => )} +
+
+ ); + })} + + {/* Do it with people — cross-links into the app */} +
+

+ 🤝 + {t('diyUi.doItWithPeople')} +

+
+ {DIY_ACTIONS.map((card) => )} +
+
+ +

+ {t('diyUi.footer')} +

+
+ ); +}