← All commits
AI · claude-opus-4-7radosukala0baef2dOur-One/our-one

Homepage Phase 1: living organism over SaaS landing — operator anchor, products, gloss

+562 / 1337 filesscreenshot pending

Files changed

Diff

Lines reveal in sequence as you scroll. First 20 lines per file shown — expand for the rest.

new filesrc/app/(marketing)/products/[slug]/page.tsx+232 / 0tsx
@@ -0,0 +1,232 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import type { Metadata } from "next";
+import { getProductBySlug, type ProductEntry } from "@/shared/products/registry";
+import { listPublishedComparisonsByIncumbent } from "@/shared/comparisons/queries";
+import { findIncumbentBySlug } from "@/shared/comparisons/queries";
+ 
+export const dynamic = "force-dynamic";
+ 
+interface PageProps {
+ params: Promise<{ slug: string }>;
+}
+ 
+export async function generateMetadata({
+ params,
+}: PageProps): Promise<Metadata> {
+ const { slug } = await params;
+ const product = getProductBySlug(slug);
+ if (!product) return { title: "Product — Our.one" };
modifiedsrc/marketing/components/homepage.tsx+106 / 95tsx
@@ -20,36 +20,29 @@ import { HomeActions } from "./home-actions";
import { NamedBetrayalBeat } from "./named-betrayal-beat";
import { RealHumanBeat } from "./real-human-beat";
import { OfferBeat } from "./offer-beat";
+import { ProductsStrip } from "./products-strip";
 
// COMMITMENTS rendered with their canonical numbers from the constitution.
+// ROLES section was removed from homepage in the living-organism rewrite —
+// the role economics are now on /join (content/en/join.md) where they belong.
 
const ROLES: { name: string; what: string; how: string }[] = [
{
name: "User",
what: "Subscribe, use, retain.",
how: "40% of every flagship's net revenue flows back to Users. The product pays you to stay.",
},
{
name: "Ambassador",
what: "Bring anyone onto a product — consumer signups or enterprise deals.",
how: "25% of net revenue, recurring share of what your recruits generate, single-level attribution.",
modifiedsrc/marketing/components/named-betrayal-beat.tsx+3 / 3tsx
@@ -45,7 +45,7 @@ export async function NamedBetrayalBeat() {
 
if (!feature) {
return (
<BeatShell label="Beat 1 · What we refuse">
+ <BeatShell label="What we constitutionally refuse">
<div className="flex flex-col gap-6 rounded-2xl border-2 border-dashed border-stone-300 bg-stone-50 px-6 py-10 md:px-8 md:py-12">
<p className="font-sans text-[11px] font-medium uppercase tracking-[0.25em] text-stone-500">
Refusals being drafted now
@@ -111,8 +111,8 @@ export async function NamedBetrayalBeat() {
 
const noSplit = isRefusal ? splitNoTitle(feature.title) : null;
const sectionLabel = isRefusal
? "Beat 1 · What we refuse"
: "Beat 1 · What we ship";
+ ? "What we constitutionally refuse"
+ : "What we ship this week";
const scopeTag = isRefusal
? `Constitutional refusal · ${feature.productScope ?? "portfolio-wide"}`
: `Our.one ships${feature.productScope ? ` · ${feature.productScope}` : " · portfolio-wide"}`;
modifiedsrc/marketing/components/offer-beat.tsx+1 / 1tsx
@@ -34,7 +34,7 @@ export async function OfferBeat({ stripeCheckoutEnabled }: Props) {
return (
<section>
<p className="font-sans text-[10px] font-medium uppercase tracking-[0.3em] text-stone-400">
Beat 3 · The offer with mechanical urgency
+ $100 once. Lifetime.
</p>
 
<div className="mt-4 rounded-2xl border-2 border-stone-900 bg-stone-900 px-6 py-10 text-[#FDFBF7] md:px-10 md:py-12">
new filesrc/marketing/components/products-strip.tsx+115 / 0tsx
@@ -0,0 +1,115 @@
+import Link from "next/link";
+import { PRODUCTS, type ProductEntry } from "@/shared/products/registry";
+ 
+const STATUS_LABEL: Record<ProductEntry["status"], string> = {
+ live: "Live",
+ "in-build": "In build",
+ planning: "Planning",
+};
+ 
+const STATUS_TAG_CLASS: Record<ProductEntry["status"], string> = {
+ live: "bg-emerald-50 text-emerald-700 border-emerald-200",
+ "in-build": "bg-amber-50 text-amber-800 border-amber-200",
+ planning: "bg-stone-100 text-stone-700 border-stone-300",
+};
+ 
+function ProductCard({ product }: { product: ProductEntry }) {
+ const isLive = product.status === "live";
+ const isInBuild = product.status === "in-build";
+ const isPlanning = product.status === "planning";
modifiedsrc/marketing/components/real-human-beat.tsx+10 / 34tsx
@@ -97,45 +97,22 @@ const dateFmt = new Intl.DateTimeFormat("en-US", {
export async function RealHumanBeat() {
const human = await pickRotatedHuman().catch(() => null);
 
+ // Empty state: hide entirely. The previous "First Patron lands at Stripe
+ // go-live" placeholder broadcast emptiness. Better to render nothing than
+ // tell visitors no one has joined yet. Once Rado completes Stripe checkout
+ // as Patron #1, this surface activates with his name.
+ if (!human) {
+ return null;
+ }
+ 
return (
<section>
<p className="font-sans text-[10px] font-medium uppercase tracking-[0.3em] text-stone-400">
Beat 2 · The real human with a name
+ Real humans, named
</p>
 
<div className="mt-4">
new filesrc/shared/products/registry.ts+95 / 0typescript
@@ -0,0 +1,95 @@
+/**
+ * Product registry — source of truth for the products-strip on the homepage
+ * and the per-product collection pages at /products/<slug>.
+ *
+ * Each product is one of three statuses:
+ * - "live" — has a working route, members can use it now
+ * - "in-build" — code exists but isn't shippable; visitors can see current state
+ * - "planning" — no code yet; collecting NOs (refusals against the named
+ * incumbent) and feature wishes from members. Becomes a
+ * collection page that builds the case for what gets coded
+ * next.
+ *
+ * Operator: rename, reorder, or add entries here. The homepage updates on the
+ * next render. No DB write required for product metadata — it lives in this
+ * file because the operator is the source of truth for "what are we building".
+ *
+ * The `incumbentSlug` field links to a row in the `incumbents` table seeded
+ * by scripts/seed/incumbents.ts. When set, the product's collection page can
+ * surface refusals confirmed against that incumbent, and member wishes can be