Vertical-slice. Copy-first. One dispatcher across N templates. Subdomain demos. No fork.
Each of the 8 website templates gets a portfolio-quality demo URL via wildcard DNS + a host-header rewriter in proxy.ts. Zero forked repos. All 8 subdomains serve the same Next.js build — edit once, push, all 8 reflect on next request.
Request → demo-konsultan.rahmanef.com/admin/admin-panel/users
│
▼
proxy.ts (root, Next 16)
│
├── Host header → resolveDemoSlug("demo-konsultan") → "konsultan-os"
└── Path rewrite → /preview/konsultan-os/dashboard/admin/admin-panel/users
│
▼
AdminFeatureStubPage(segment="users")
│
└─► <UsersBlockView />Each template's admin panel feature route is a 1-line stub that calls a shared dispatcher. Adding a new real block = one switch case + one BlockView file — propagates to all 8 templates' routes simultaneously.
// components/templates/_shared/admin-panel/AdminFeatureStubPage.tsx
//
// One file, 6 dispatch cases. 8 templates × 6 admin-panel blocks = 48
// routes. Every template's /admin/admin-panel/<segment>/page.tsx just
// calls <AdminFeatureStubPage segment="X" /> — zero per-template
// duplication.
export function AdminFeatureStubPage({ segment }: { segment: string }) {
const block = ADMIN_PANEL_BLOCKS.find((b) => b.segment === segment);
if (!block) notFound();
if (segment === "users") return <UsersBlockView />;
if (segment === "audit-log") return <AuditLogBlockView />;
if (segment === "ai-config") return <AiConfigBlockView />;
if (segment === "analytics") return <AnalyticsBlockView />;
if (segment === "webhooks") return <WebhooksBlockView />;
if (segment === "settings") return <SettingsBlockView />;
return <AdminFeatureCard block={block} />; // fallback
} users audit-log ai-config analytics webhooks settings
konsultan ✅ ✅ ✅ ✅ ✅ ✅
personal-brand ✅ ✅ ✅ ✅ ✅ ✅
kreator ✅ ✅ ✅ ✅ ✅ ✅
wirausaha ✅ ✅ ✅ ✅ ✅ ✅
riset ✅ ✅ ✅ ✅ ✅ ✅
agency ✅ ✅ ✅ ✅ ✅ ✅
saas ✅ ✅ ✅ ✅ ✅ ✅
notion-clone ✅ ✅ ✅ ✅ ✅ ✅
8 templates × 6 blocks = 48 routes served by 1 dispatcherEvery block follows the same shape — view orchestrator + extracted sub-components, all ≤200 LOC. Shared chrome (BlockHeader, SectionHeader, EmptyState) lives in _shared/admin-panel/ui/. Semantic tones (success / warn / danger / neutral / info / accent / elevated) come from a single SSOT — every status badge across all 6 blocks resolves through it.
_shared/admin-panel/
├── AdminFeatureStubPage.tsx # dispatcher (6 cases + fallback)
├── AdminFeatureCard.tsx # placeholder for future segments
├── feature-blocks.ts # registry (id, segment, icon, label, poweredBy)
├── ui/ # shared chrome (BY-wave)
│ ├── tones.ts # semantic palette SSOT (success/warn/danger/…)
│ ├── block-header.tsx
│ ├── section-header.tsx
│ └── empty-state.tsx
└── blocks/<segment>/ # 1 dir per real block
├── types.ts
├── seed.ts # demo data (resets on browser reload)
├── <Segment>BlockView.tsx # orchestrator, ≤200 LOC
└── <…>.tsx # sub-components@convex-dev/auth.<button> / <dialog> / native date+file inputs. Use ResponsiveDialog, DateField, FileUpload.audit-file-size across 1.4K+ files.h-dvh full-bleed (BZ-wave) — workspace = the product, not a landing about it.