apex-landing
A marketing landing page that composes its content from other features:
the hero copy comes from text-content, the prices and plan caps come from
tier-engine. renderApexPage turns the assembled page into one static,
cacheable HTML string — zero React, one HTTP response.
The point of the recipe: a landing page is not a place to hard-code copy and prices. Pull them from the features that already own that data and the page can never drift from the product.
The two seams
Section titled “The two seams”// hero copy ← text-content: block body if seeded, else a baked-in fallbacktitle: block(input.blocks, "index:hero.title", "Ship your roadmap, …"),
// prices/caps ← tier-engine: one plan from the config → one pricing cardtiers: input.plans.map(toPricingTier),block(blocks, slug, fallback) reads the text-content projection; omit a slug and
the fallback renders, so the page is complete before anything is seeded.
toPricingTier(plan) maps a tier-engine plan (price, cap, benefits) onto an
ApexPricingTier. See src/feature.ts for the full assembly.
Screenshot renderer
Section titled “Screenshot renderer”bun run screenshot renders the sample page and shoots a full-page PNG via
Playwright (page.setContent — no server, because the renderer is pure). The
docs guide embeds that PNG, so the image is always the real output.
bun test # both seams: block fallback + price/cap formattingbun run screenshot # → screenshots/landing.pngSource code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// Apex landing page — composed from OTHER features' data.//// `renderApexPage(page)` is a pure function: a typed `ApexPage` in, one HTML// string out. The interesting part is where that page's CONTENT comes from. An// app does not hard-code its landing copy or prices — it pulls them from the// features it already runs, so the marketing page can never drift from the// product://// • hero headline / tagline ← text-content (editable blocks, keyed by slug)// • prices and plan caps ← tier-engine (the app's plan config)//// `buildLandingPage` below shows both seams. It takes the data those features// expose and assembles an `ApexPage`; the app serves `renderApexPage(page)` as// one static, cacheable response.
import { type ApexPage, type ApexPricingTier, renderApexPage,} from "@cosmicdrift/kumiko-headless/apex";
// --- Inputs: shapes the surrounding features hand you -----------------------
/** Editable copy as the `text-content` feature projects it: a body string per * stable slug. A real app fills this Map from the text-content read model. */export type ContentBlocks = ReadonlyMap<string, string>;
/** One plan as the app's `tier-engine` config exposes it. */export type PlanInfo = { readonly key: string; readonly name: string; readonly tagline: string; /** `null` = free or on-request; the renderer just shows the `amount` text. */ readonly monthlyEur: number | null; /** Usage cap from the tier config; `Infinity` = unlimited. */ readonly maxProjects: number; readonly benefits: readonly string[]; readonly featured?: boolean;};
export type LandingInput = { /** From `text-content`. Omit a slug and the baked-in fallback is used, so the * page renders fully even before anything is seeded. */ readonly blocks?: ContentBlocks; /** From `tier-engine`. */ readonly plans: readonly PlanInfo[];};
// --- The two feature seams --------------------------------------------------
/** text-content seam: block body if seeded, else the fallback baked in here. */function block(blocks: ContentBlocks | undefined, slug: string, fallback: string): string { return blocks?.get(slug) ?? fallback;}
function formatEuro(n: number): string { return `${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} €`;}
function planAmount(plan: PlanInfo): string { if (plan.monthlyEur === null) return plan.key === "enterprise" ? "Let's talk" : "0 €"; return formatEuro(plan.monthlyEur);}
function planCap(plan: PlanInfo): string { return Number.isFinite(plan.maxProjects) ? `${plan.maxProjects.toLocaleString("en-US")} projects` : "Unlimited projects";}
/** tier-engine seam: one plan from the config → one Apex pricing card. */function toPricingTier(plan: PlanInfo): ApexPricingTier { const paid = plan.monthlyEur !== null; return { name: plan.name, tagline: plan.tagline, amount: planAmount(plan), priceSuffix: paid ? "/month" : undefined, featured: plan.featured, badge: plan.featured ? "Popular" : undefined, capLine: planCap(plan), benefits: plan.benefits, cta: { label: plan.key === "enterprise" ? "Contact us" : `Choose ${plan.name}`, href: plan.key === "enterprise" ? "/contact" : "/signup", variant: plan.featured ? "primary" : "secondary", }, };}
// --- Assembly ---------------------------------------------------------------
const FEATURE_ICON = { bolt: '<path d="M13 2 4 14h6l-1 8 9-12h-6z"/>', shield: '<path d="M12 3 5 6v6c0 4 3 7 7 9 4-2 7-5 7-9V6z"/><path d="m9 12 2 2 4-4"/>', layers: '<path d="m12 3 9 5-9 5-9-5z"/><path d="m3 13 9 5 9-5"/>',} as const;
const BRAND_TOKENS = `:root{ --bg:#ffffff; --bg-card:#ffffff; --bg-muted:#f6f7f9; --border:#e6e8ec; --fg:#0f1729; --fg-muted:#475067; --fg-subtle:#6b7280; --primary:#4f46e5; --primary-hover:#4338ca; --primary-fg:#ffffff; --accent:#4f46e5; --accent-fg:#ffffff; --accent-hover:#6366f1; --status-ok:#16a34a; --shadow:0 12px 30px -12px rgba(15,23,42,.25); --footer-cols:3; --font-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; --font-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;}`;
export function buildLandingPage(input: LandingInput): ApexPage { return { theme: "light", brand: { tokensCss: BRAND_TOKENS }, head: { lang: "en", title: block(input.blocks, "index:meta.title", "Tasklane — ship your roadmap"), description: block( input.blocks, "index:meta.description", "Plan, track and ship product work in one place. Free to start, no credit card.", ), canonicalUrl: "https://tasklane.example/", }, header: { brand: { href: "/", label: "Tasklane" }, navLinks: [ { label: "Features", href: "#features" }, { label: "Pricing", href: "#pricing" }, ], actions: [{ label: "Sign in", href: "/login", variant: "link" }], }, sections: [ { kind: "hero", title: block(input.blocks, "index:hero.title", "Ship your roadmap, not your spreadsheet"), tagline: block( input.blocks, "index:hero.tagline", "Plan, track and ship product work in one place — free to start, no credit card.", ), ctas: [ { label: "Start free", href: "/signup", variant: "primary" }, { label: "See pricing", href: "#pricing", variant: "secondary" }, ], metaHtml: "<strong>Free forever plan.</strong> No credit card required.", }, { kind: "feature-grid", id: "features", eyebrow: "Features", heading: "Everything your team needs to ship", sub: "From a single backlog to the whole portfolio — without tab-juggling.", items: [ { icon: FEATURE_ICON.bolt, title: "Live planning", desc: "Reorder, estimate and assign in one board that updates as you type.", }, { icon: FEATURE_ICON.layers, title: "Portfolio view", desc: "Every project on one screen: progress, owners and ship dates at a glance.", }, { icon: FEATURE_ICON.shield, title: "Your data, yours", desc: "EU-hosted, no tracking, export any time. Privacy is the baseline, not a tier.", }, ], }, { kind: "pricing-grid", id: "pricing", eyebrow: "Pricing", heading: "Fair prices, clear limits", sub: "Start free. Upgrade when your portfolio grows. Cancel any time.", tiers: input.plans.map(toPricingTier), }, { kind: "final-cta", heading: "Your first board in two minutes", sub: "Start free — no credit card, no install.", cta: { label: "Start free", href: "/signup", variant: "primary" }, }, ], footer: { brand: { label: "Tasklane" }, tagline: "Ship your roadmap.", columns: [ { heading: "Product", links: [ { label: "Features", href: "#features" }, { label: "Pricing", href: "#pricing" }, ], }, { heading: "Company", links: [ { label: "About", href: "/about" }, { label: "Contact", href: "/contact" }, ], }, { heading: "Legal", links: [ { label: "Privacy", href: "/legal/privacy" }, { label: "Imprint", href: "/legal/imprint" }, ], }, ], bottomLeft: "© 2026 Tasklane", bottomRight: "Made with Kumiko", }, };}
/** Convenience: input → final HTML string an app serves as its landing page. */export function renderLanding(input: LandingInput): string { return renderApexPage(buildLandingPage(input));}
/** Sample plan config, as a `tier-engine`-backed app would expose it. * Used by the test and the screenshot runner. */export const SAMPLE_PLANS: readonly PlanInfo[] = [ { key: "free", name: "Free", tagline: "For your first project", monthlyEur: null, maxProjects: 3, benefits: ["Live planning board", "Up to 5 collaborators", "CSV export"], }, { key: "pro", name: "Pro", tagline: "For a growing team", monthlyEur: 12, maxProjects: 50, benefits: ["Everything in Free", "Portfolio view", "Custom fields", "Priority support"], featured: true, }, { key: "enterprise", name: "Enterprise", tagline: "For the whole org", monthlyEur: null, maxProjects: Number.POSITIVE_INFINITY, benefits: ["Everything in Pro", "SSO & SCIM", "Dedicated instance", "SLA"], },];📄 On GitHub: samples/recipes/apex-landing/src/feature.ts