diff --git a/components/ConceptaHome.tsx b/components/ConceptaHome.tsx index 751c2325..1fffd925 100644 --- a/components/ConceptaHome.tsx +++ b/components/ConceptaHome.tsx @@ -94,8 +94,9 @@ const PILLARS = [ /* ── Per-project window visuals ────────────────────────────────────── Each block echoes the visual signature of its product page: Mix shows a style snippet, Remix its component catalog, Naked UI its observable state, - Ack a validation result, FVM a pinned SDK, Stargate a governed workflow, and - Code Analysis a scorecard. Same chrome, different content. */ + Ack a validation result, FVM a pinned SDK, Rockets its configuration plan, + Stargate a governed workflow, and Code Analysis a scorecard. Same chrome, + different content. */ const MIX_SNIPPET = `final cardStyle = BoxStyler() .color(Colors.blue) @@ -198,6 +199,28 @@ function FvmVisual() { ); } +function RocketsVisual() { + return ( +
+
+ + options + auth · resources · repository +
+
createServer · no generated files
+
+ + server + /me · /pets · /api +
+
+ + identity · CRUD · hooks · OpenAPI +
+
+ ); +} + function StargateVisual() { return (
@@ -324,6 +347,17 @@ const PROJECTS: Project[] = [ external: true, Visual: FvmVisual, }, + { + name: "Rockets", + tagline: "Describe the backend. Ship the domain.", + description: + "One typed backend definition wires identity, storage, resources, access, hooks, and OpenAPI at runtime.", + href: "/rockets", + accent: "#FF5906", + status: "Open source", + windowLabel: "server.ts", + Visual: RocketsVisual, + }, { name: "Stargate", tagline: "Complex workflows for the enterprise.", diff --git a/components/FloatingNavbar.tsx b/components/FloatingNavbar.tsx index d73eca22..89c5cdc1 100644 --- a/components/FloatingNavbar.tsx +++ b/components/FloatingNavbar.tsx @@ -19,6 +19,7 @@ import { MIX_GITHUB_URL, NAKED_UI_GITHUB_URL, REMIX_GITHUB_URL, + ROCKETS_GITHUB_URL, } from './constants' function DiscordIcon(props: React.ComponentPropsWithoutRef<'svg'>) { @@ -29,7 +30,7 @@ function DiscordIcon(props: React.ComponentPropsWithoutRef<'svg'>) { ) } -type ProductId = 'concepta' | 'mix' | 'remix' | 'naked-ui' | 'ack' | 'stargate' | 'code-analysis' | 'voyager' +type ProductId = 'concepta' | 'mix' | 'remix' | 'naked-ui' | 'ack' | 'rockets' | 'stargate' | 'code-analysis' | 'voyager' function getActiveProduct(pathname: string | null): ProductId { if (!pathname) return 'concepta' @@ -41,6 +42,7 @@ function getActiveProduct(pathname: string | null): ProductId { if (pathname === '/naked-ui' || pathname.startsWith('/naked-ui/')) return 'naked-ui' if (pathname.startsWith('/documentation/ack')) return 'ack' if (pathname === '/ack' || pathname.startsWith('/ack/')) return 'ack' + if (pathname === '/rockets' || pathname.startsWith('/rockets/')) return 'rockets' if (pathname === '/stargate' || pathname.startsWith('/stargate/')) return 'stargate' if (pathname === '/code-analysis' || pathname.startsWith('/code-analysis/')) return 'code-analysis' if (pathname === '/voyager' || pathname.startsWith('/voyager/')) return 'voyager' @@ -64,6 +66,7 @@ const MIX_DOCS_URL = '/documentation/mix/overview/introduction' const REMIX_DOCS_URL = '/documentation/remix' const NAKED_UI_DOCS_URL = 'https://docs.page/btwld/naked_ui' const ACK_DOCS_URL = '/documentation/ack/getting-started/overview' +const ROCKETS_DOCS_URL = 'https://github.com/btwld/rockets#readme' // Stargate and Code Analysis have no public docs or repos yet. function getDocsHref(product: ProductId): string | null { @@ -71,6 +74,7 @@ function getDocsHref(product: ProductId): string | null { if (product === 'remix') return REMIX_DOCS_URL if (product === 'naked-ui') return NAKED_UI_DOCS_URL if (product === 'ack') return ACK_DOCS_URL + if (product === 'rockets') return ROCKETS_DOCS_URL return null } @@ -80,6 +84,7 @@ function getGithubHref(product: ProductId): string | null { if (product === 'remix') return REMIX_GITHUB_URL if (product === 'naked-ui') return NAKED_UI_GITHUB_URL if (product === 'ack') return ACK_GITHUB_URL + if (product === 'rockets') return ROCKETS_GITHUB_URL return null } @@ -126,6 +131,13 @@ const PRODUCTS: Product[] = [ logo: '/assets/logo_ack_sidebar.svg', showLabel: true, }, + { + id: 'rockets' as const, + label: 'Rockets', + href: '/rockets', + logo: '/assets/logo_rockets_mark.svg', + showLabel: true, + }, { id: 'stargate' as const, label: 'Stargate', diff --git a/components/ProductFooter.tsx b/components/ProductFooter.tsx index d508bee7..9a977d42 100644 --- a/components/ProductFooter.tsx +++ b/components/ProductFooter.tsx @@ -2,7 +2,12 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' -import { CONCEPTA_GITHUB_URL, MIX_GITHUB_URL, REMIX_GITHUB_URL } from './constants' +import { + CONCEPTA_GITHUB_URL, + MIX_GITHUB_URL, + REMIX_GITHUB_URL, + ROCKETS_GITHUB_URL, +} from './constants' function isAckPath(pathname: string | null) { return pathname === '/ack' @@ -14,6 +19,10 @@ function isNakedUiPath(pathname: string | null) { return pathname === '/naked-ui' || pathname?.startsWith('/naked-ui/') } +function isRocketsPath(pathname: string | null) { + return pathname === '/rockets' || pathname?.startsWith('/rockets/') +} + function isRemixPath(pathname: string | null) { return pathname === '/remix' || pathname?.startsWith('/remix/') @@ -42,6 +51,7 @@ function isProductWithoutPublicRepo(pathname: string | null) { function getGithubHref(pathname: string | null) { if (isRemixPath(pathname)) return REMIX_GITHUB_URL if (isMixPath(pathname)) return MIX_GITHUB_URL + if (isRocketsPath(pathname)) return ROCKETS_GITHUB_URL if (isProductWithoutPublicRepo(pathname)) return null return CONCEPTA_GITHUB_URL } @@ -82,6 +92,19 @@ export default function ProductFooter() { ) } + if (isRocketsPath(pathname)) { + return ( +
+ Describe the backend. Ship the domain. +
+ GitHub + npm + Guide +
+
+ ) + } + // Public products link their own repo/package. Concepta pages link the org, // while products without a public repo omit GitHub, matching the navbar. const githubHref = getGithubHref(pathname) diff --git a/components/constants.ts b/components/constants.ts index 62633b86..53442d9f 100644 --- a/components/constants.ts +++ b/components/constants.ts @@ -4,3 +4,4 @@ export const MIX_GITHUB_URL = 'https://github.com/btwld/mix' export const REMIX_GITHUB_URL = 'https://github.com/btwld/remix' export const NAKED_UI_GITHUB_URL = 'https://github.com/btwld/naked_ui' export const ACK_GITHUB_URL = 'https://github.com/btwld/ack' +export const ROCKETS_GITHUB_URL = 'https://github.com/btwld/rockets' diff --git a/components/landing/LandingButton.tsx b/components/landing/LandingButton.tsx index 97d63a4a..5b1ab25f 100644 --- a/components/landing/LandingButton.tsx +++ b/components/landing/LandingButton.tsx @@ -3,6 +3,7 @@ import clsx from "clsx"; import Link from "next/link"; import React from "react"; +import type { LandingCta } from "./types"; function ArrowIcon(props: React.ComponentPropsWithoutRef<"svg">) { return ( @@ -72,3 +73,17 @@ export function LandingButton({ ); } + +export function LandingCtaButton({ cta }: { cta: LandingCta }) { + return ( + + {cta.label} + + ); +} diff --git a/components/landing/landing.css b/components/landing/landing.css index 492f0a70..e5ba27f3 100644 --- a/components/landing/landing.css +++ b/components/landing/landing.css @@ -56,6 +56,16 @@ html[data-product='naked-ui'] { --lp-aurora-b-glow: rgba(37, 99, 235, 0.17); } +html[data-product='rockets'] { + --lp-accent: #ff5906; + --lp-accent-hover: #ff7433; + --lp-accent-low: rgba(255, 89, 6, 0.1); + --lp-accent-glow: rgba(255, 89, 6, 0.2); + --lp-accent-2: #ffad66; + --lp-gradient-mid: #ffad7a; + --lp-aurora-b-glow: rgba(234, 75, 6, 0.16); +} + /* Behaviors the standalone sites applied globally, scoped to landing pages */ .lp-root { color: var(--lp-text-main); @@ -69,7 +79,8 @@ html[data-product='naked-ui'] { html[data-product='stargate'], html[data-product='code-analysis'], html[data-product='voyager'], - html[data-product='naked-ui'] { + html[data-product='naked-ui'], + html[data-product='rockets'] { scroll-behavior: smooth; } } @@ -951,6 +962,265 @@ html[data-product='voyager'] .lp-aurora::after { border-top: 1px solid var(--lp-border-card); } +/* ═══ Rockets provider spotlights ═══════════════════════ */ +.lp-rockets-root { + padding-bottom: 112px; +} +.lp-rockets-root .lp-gap { + margin-top: clamp(88px, 10vw, 144px); +} +.lp-rockets-surface .lp-head, +.lp-providers .lp-head, +.lp-ownership .lp-head { + margin-bottom: 40px; +} +.lp-server-capabilities { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} +.lp-server-capability { + min-width: 0; + padding: 22px; + border: 1px solid var(--lp-border-card); + border-radius: 16px; + background: linear-gradient( + 180deg, + color-mix(in srgb, var(--lp-accent) 5%, rgba(22, 20, 33, 0.78)), + rgba(10, 9, 15, 0.9) + ); + transition: border-color 0.2s ease, transform 0.2s ease; +} +.lp-server-capability:last-child { + grid-column: 1 / -1; +} +@media (hover: hover) and (pointer: fine) { + .lp-server-capability:hover { + border-color: color-mix(in srgb, var(--lp-accent) 34%, transparent); + transform: translateY(-2px); + } +} +.lp-server-capability-head { + display: flex; + align-items: center; + gap: 9px; + color: var(--lp-accent); +} +.lp-server-capability-head code { + overflow: hidden; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.08em; + text-overflow: ellipsis; +} +.lp-server-capability h3 { + margin-top: 20px; + font-size: 1rem; + font-weight: 600; + color: #fff; +} +.lp-server-capability p { + margin-top: 7px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 10px; + line-height: 1.55; + color: var(--lp-text-muted); +} +.lp-provider-grid, +.lp-ownership-grid { + display: grid; + grid-template-columns: 1fr; + gap: 18px; +} +.lp-provider-column { + display: flex; + min-width: 0; + flex-direction: column; + gap: 14px; +} +.lp-provider-column .lp-code-card { + flex: 1 1 auto; +} +.lp-boundary-card { + padding: clamp(24px, 4vw, 34px); + border: 1px solid var(--lp-border-card); + border-radius: 18px; + background: rgba(13, 11, 20, 0.68); +} +.lp-boundary-card.is-accent { + border-color: color-mix(in srgb, var(--lp-accent) 27%, transparent); + background: + radial-gradient( + circle at 15% 0%, + color-mix(in srgb, var(--lp-accent) 10%, transparent), + transparent 48% + ), + rgba(13, 11, 20, 0.74); +} +.lp-boundary-card-head { + display: flex; + align-items: center; + gap: 10px; + color: var(--lp-accent); +} +.lp-boundary-card-head h3 { + font-size: 1.05rem; + font-weight: 600; + color: #fff; +} +.lp-boundary-card ul { + display: flex; + flex-direction: column; + gap: 10px; + margin: 22px 0 0; + padding: 0; + list-style: none; +} +.lp-boundary-card li { + position: relative; + padding-left: 16px; + font-size: 0.94rem; + line-height: 1.55; + color: var(--lp-text-muted); +} +.lp-boundary-card li::before { + position: absolute; + top: 0.65em; + left: 0; + width: 4px; + height: 4px; + border-radius: 999px; + background: var(--lp-accent); + content: ""; +} +@media (min-width: 720px) { + .lp-server-capabilities { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .lp-server-capability:last-child { + grid-column: auto; + } + .lp-provider-grid, + .lp-ownership-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +@media (max-width: 480px) { + .lp-rockets-root .lp-seg { + display: inline-flex; + align-items: center; + min-height: 44px; + } + .lp-server-capabilities { + grid-template-columns: 1fr; + } + .lp-server-capability:last-child { + grid-column: auto; + } + .lp-server-capability { + padding: 18px; + } +} +.lp-provider-card { + overflow: hidden; + padding: 20px; + border-radius: 18px; + box-shadow: 0 32px 80px -52px var(--lp-accent-glow); + border: 1px solid color-mix(in srgb, var(--lp-accent) 24%, transparent); + background: linear-gradient( + 180deg, + color-mix(in srgb, var(--lp-accent) 6%, rgba(22, 20, 33, 0.9)), + rgba(13, 11, 20, 0.92) + ); +} +.lp-provider-head { + display: flex; + align-items: center; + gap: 9px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.11em; + text-transform: uppercase; + color: var(--lp-accent); +} +.lp-provider-stack { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 16px; +} +.lp-provider-row { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: center; + gap: 12px; + padding: 12px 14px; + border: 1px solid var(--lp-border-card); + border-radius: 12px; + background: rgba(3, 3, 8, 0.36); +} +.lp-provider-row .lp-severity-chip { + min-width: 42px; + color: var(--lp-accent); + text-align: center; + background: var(--lp-accent-low); +} +.lp-provider-name { + font-size: 13px; + font-weight: 600; + color: #fff; +} +.lp-provider-detail { + margin-top: 2px; + overflow: hidden; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 10px; + color: var(--lp-text-muted); + text-overflow: ellipsis; + white-space: nowrap; +} +.lp-provider-result { + display: flex; + align-items: center; + gap: 9px; + margin-top: 12px; + padding: 11px 14px; + border: 1px solid color-mix(in srgb, var(--lp-accent) 20%, transparent); + border-radius: 12px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 11px; + color: #e9e7ef; + background: var(--lp-accent-low); +} +.lp-provider-result svg { + flex: 0 0 auto; + color: var(--lp-accent); +} +.lp-provider-card .lp-report-foot { + margin-top: 14px; +} +.lp-provider-code .lp-code-card-body { + padding: 16px; +} +.lp-provider-code .lp-code-card-body > div, +.lp-provider-code .lp-code-card-body code { + font-size: 12px !important; +} +@media (max-width: 480px) { + .lp-provider-card { + padding: 16px; + } + .lp-provider-row { + padding: 11px; + } + .lp-provider-code .lp-code-card-body > div, + .lp-provider-code .lp-code-card-body code { + font-size: 11px !important; + } +} + /* Bento tile mocks */ .lp-mock-row { display: flex; diff --git a/components/landing/rockets/HeroWindow.tsx b/components/landing/rockets/HeroWindow.tsx new file mode 100644 index 00000000..c45c79c6 --- /dev/null +++ b/components/landing/rockets/HeroWindow.tsx @@ -0,0 +1,263 @@ +"use client"; + +import { useRef, useState } from "react"; +import type { KeyboardEvent } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { Check } from "lucide-react"; +import { HighlightedCode } from "../HighlightedCode"; +import { EASE } from "../motion"; +import { + AUTH_PROVIDERS_TS, + CREATE_SERVER_TS, + STORAGE_PROVIDERS_TS, +} from "./snippets"; + +const SERVER_OUTPUTS = [ + ["resources", "routes · validation · OpenAPI"], + ["auth", "protected endpoints · AuthorizedUser"], + ["repository", "default storage · overrides"], + ["accessControl", "ownership · policy"], +] as const; + +function ServerStage() { + return ( +
+
+

Complete server surface

+

configuration → running capability

+
+ {SERVER_OUTPUTS.map(([option, output]) => ( +
+ +
+

{option}

+

{output}

+
+
+ ))} +
one definition → complete server
+
+ ); +} + +const AUTH_PROVIDERS = [ + ["01", "Firebase", "Bearer ID token"], + ["02", "API key", "X-API-Key header"], + ["ALT", "Built-in auth", "signup · login · OTP"], +] as const; + +function AuthStage() { + return ( +
+
+

Authentication providers

+

one guard · one AuthorizedUser

+
+ {AUTH_PROVIDERS.map(([order, provider, transport]) => ( +
+ + {order} + +
+

{provider}

+

{transport}

+
+
+ ))} +
unmatched → next · invalid match → stop
+
+ ); +} + +const STORAGE_ROUTES = [ + ["DEFAULT", "Pets + users", "TypeORM"], + ["OVERRIDE", "AnalyticsEvent", "Firestore"], + ["CUSTOM", "AuditEntry", "Your adapter"], +] as const; + +function StorageStage() { + return ( +
+
+

Storage routing

+

default once · override per entity

+
+ {STORAGE_ROUTES.map(([role, entity, provider]) => ( +
+ + {role} + +
+

{entity}

+

{provider}

+
+
+ ))} +
same RepositoryInterface<T>
+
+ ); +} + +const TABS = [ + { + key: "server", + name: "Server", + code: CREATE_SERVER_TS, + lang: "typescript" as const, + stageLabel: "createServer · definition to running API", + Stage: ServerStage, + }, + { + key: "auth", + name: "Auth", + code: AUTH_PROVIDERS_TS, + lang: "typescript" as const, + stageLabel: "Auth chain · ordered and transport-agnostic", + Stage: AuthStage, + }, + { + key: "storage", + name: "Storage", + code: STORAGE_PROVIDERS_TS, + lang: "typescript" as const, + stageLabel: "Repository routing · default plus overrides", + Stage: StorageStage, + }, +]; + +export function HeroWindow() { + const [activeTab, setActiveTab] = useState(0); + const tabRefs = useRef>([]); + const tab = TABS[activeTab]; + + function selectTabFromKey( + event: KeyboardEvent, + index: number, + ) { + let nextIndex: number | undefined; + + if (event.key === "ArrowRight") { + nextIndex = (index + 1) % TABS.length; + } else if (event.key === "ArrowLeft") { + nextIndex = (index - 1 + TABS.length) % TABS.length; + } else if (event.key === "Home") { + nextIndex = 0; + } else if (event.key === "End") { + nextIndex = TABS.length - 1; + } + + if (nextIndex === undefined) return; + + event.preventDefault(); + setActiveTab(nextIndex); + tabRefs.current[nextIndex]?.focus(); + } + + return ( + +
- +
diff --git a/components/landing/types.ts b/components/landing/types.ts index e786abf0..3f51136e 100644 --- a/components/landing/types.ts +++ b/components/landing/types.ts @@ -5,6 +5,14 @@ import type { LucideIcon } from "lucide-react"; waitlist/Resend mechanism but has no LandingContent page of its own. */ export type ProductSlug = "stargate" | "code-analysis" | "readiness"; +export interface LandingCta { + label: string; + href: string; + variant?: "primary" | "secondary" | "ghost"; + arrow?: "right"; + external?: boolean; +} + export interface LandingContent { product: ProductSlug; wordmarkName: string; @@ -14,6 +22,8 @@ export interface LandingContent { titleTop: ReactNode; titleGradient: ReactNode; lead: ReactNode; + primaryCta?: LandingCta; + secondaryCta?: LandingCta; }; /** Per-product hero visual rendered below the CTA row. */ HeroWindow: ComponentType; @@ -30,24 +40,35 @@ export interface LandingContent { OutputsBento: ComponentType; /** Optional per-product feature-spotlight sections, rendered after OutputsBento. */ Spotlights?: ComponentType; - marquee: { rowA: string[]; rowB: string[] }; + marquee: { rowA: string[]; rowB: string[]; cta?: LandingCta }; trustSplit: { eyebrow: string; title: ReactNode; lead?: ReactNode; bullets: string[]; ctaLabel: string; + cta?: LandingCta; snippet: string; snippetFile: string; + snippetLang?: "bash" | "json" | "typescript"; }; faq: { q: string; a: ReactNode }[]; closingCta: { title: string; lead: string; finePrint: string; + anchor?: string; + action?: + | { kind: "waitlist"; product: ProductSlug } + | { kind: "links"; links: LandingCta[] }; /** Defaults to true; set false for product-first pages without a parent-brand badge. */ showConceptaBrand?: boolean; }; /** Optional visual rendered inside the closing CTA card, below the waitlist form. */ ClosingVisual?: ComponentType; } + +export type LandingHeroContent = Pick< + LandingContent, + "wordmarkName" | "wordmarkShowByline" | "hero" | "HeroWindow" +>; diff --git a/docs/superpowers/plans/2026-07-17-rockets-landing-polish.md b/docs/superpowers/plans/2026-07-17-rockets-landing-polish.md new file mode 100644 index 00000000..63873658 --- /dev/null +++ b/docs/superpowers/plans/2026-07-17-rockets-landing-polish.md @@ -0,0 +1,97 @@ +# Plan: Rockets Standalone Landing Page Polish + +> Replace the overlong ecosystem narrative with a proof-first standalone Rockets page centered on the `createServer(...)` launch API. + +## Objective + +- Primary outcome: `/rockets` explains and demonstrates Rockets as a standalone backend product through one realistic `createServer(...)` definition. +- Constraints: no `NestJS`, `Stargate`, or `defineRockets(...)` in Rockets page content or metadata; do not change behavior on other product pages. +- Out of scope: implementing `createServer(...)` in the Rockets package repository, redesigning shared navigation/footer, or adding hosted-service flows. + +## Context + +- `components/landing/LandingRoot.tsx` currently renders every generic landing section in a fixed order, which forces Rockets through stats, statement, features, bento, spotlights, marquee, trust, FAQ, and closing CTA. +- `components/landing/rockets/content.tsx` repeats the product thesis across all of those sections and includes the framework and ecosystem terms now excluded from the page. +- `components/landing/rockets/snippets.ts` currently presents the invented `defineRockets(...)` facade; the approved launch-facing name is `createServer(...)` with the same options object as the existing alpha root registration. +- `components/landing/rockets/HeroWindow.tsx` already provides accessible Server/Auth/Storage-style tab mechanics and a useful two-column code/output window. +- `components/landing/sections/Hero.tsx`, `Faq.tsx`, `ClosingCta.tsx`, and `Aurora.tsx` are reusable primitives; only `Hero` is unnecessarily typed to the full `LandingContent` object. +- `site-tests/rockets-integration.test.mjs` is the existing content and shell contract for the route. +- Browser baseline: 9,782 px document height at 1440 × 900 and 14,192 px at 390 × 844, with no horizontal overflow. + +## Approach + +Give `RocketsLanding.tsx` a product-specific section composition while retaining the shared visual primitives and `.lp-root` shell. Narrow the `Hero` prop contract to only the fields it actually reads. Replace the generic grids and marquee with three Rockets-specific sections: server surface, compact provider model, and ownership boundary. + +- Alternative considered: make every `LandingContent` section optional in `LandingRoot` — rejected because it weakens the shared contract and spreads conditional rendering through unrelated product pages. +- Alternative considered: keep the current bento and only rewrite copy — rejected because the bento, stats, and marquee are the main sources of repetition and page length. + +## Compatibility + +- Breaking change: no public package or application API change. The only shared type refinement is narrowing `Hero` to the subset it already consumes. +- Migration: hard cut for `/rockets`; other product compositions remain on `LandingRoot` unchanged. +- Data migration: none. +- Rollback: revert the implementation commit; the existing Rockets page remains available in git history. + +## Work Breakdown + +1. Establish failing content contracts — `site-tests/rockets-integration.test.mjs` + - Require `createServer(` and the new standalone section names. + - Forbid `defineRockets(`, `NestJS`, and `Stargate` across Rockets content, snippets, custom components, and route metadata. + - Replace assertions tied to `OutputsBento` and the old vision/provider copy. + - Verification: `node --test site-tests/rockets-integration.test.mjs` fails for the expected missing/new content assertions. + +2. Replace the hero facade and launch sequence — `components/landing/rockets/snippets.ts`, `components/landing/rockets/HeroWindow.tsx` + - Rename the first tab to Server and make `createServer(...)` its default code example. + - Keep the authentication and storage examples grounded in current repository contracts. + - Map root configuration keys to concrete outcomes in the right-hand panel. + - Preserve semantic tabs, visible focus, reduced motion, and mobile-safe code presentation. + - Verification: the focused integration test passes its API-name and hero assertions. + +3. Build the concise product composition — `components/landing/rockets/RocketsLanding.tsx`, `components/landing/rockets/content.tsx`, `components/landing/rockets/ServerSurface.tsx`, `components/landing/rockets/ProviderSpotlights.tsx`, `components/landing/rockets/OwnershipBoundary.tsx`, `components/landing/sections/Hero.tsx`, `components/landing/types.ts` + - Define a Rockets content object containing only hero, FAQ, closing CTA, and copy used by the product-specific sections. + - Compose Aurora, Hero, ServerSurface, ProviderSpotlights, OwnershipBoundary, Faq, and ClosingCta under the existing MotionConfig and `.lp-root` shell. + - Narrow the shared Hero prop type to its actual content subset without changing runtime behavior for other pages. + - Merge identity and storage demonstrations into one compact provider section. + - Add a concrete Rockets-versus-team responsibility comparison. + - Remove `components/landing/rockets/OutputsBento.tsx` after its unique information is represented in ServerSurface. + - Verification: the focused integration test passes; `git diff` shows no behavioral edits to other product content. + +4. Tighten Rockets-specific styling — `components/landing/landing.css` + - Reuse the existing Night/Panel/Lift/Launch/Flame palette and Inter/JetBrains Mono roles. + - Add narrowly scoped layout styles for server surface and ownership boundary. + - Simplify obsolete Rockets vision/bento selectors and remove dead selectors associated only with deleted components. + - Ensure 44 px mobile tab targets, visible focus, reduced-motion behavior, and no document-level horizontal overflow. + - Verification: `git diff --check`; browser inspection at 1440 × 900 and 390 × 844. + +5. Align metadata, FAQ, and final content contracts — `src/app/rockets/page.tsx`, `components/landing/rockets/content.tsx`, `site-tests/rockets-integration.test.mjs` + - Remove implementation-framework naming from route metadata and all user-visible copy. + - Limit FAQ to six approved questions and distinguish launch facade from alpha behavior without introducing a competing facade name. + - Keep repository and guide CTAs external and standalone. + - Verification: `rg -n "NestJS|Stargate|\\bdefineRockets\\s*\\(" components/landing/rockets src/app/rockets` returns no matches; focused test passes. + +6. Verify, review, and publish + - Run `pnpm test:site`. + - Run `pnpm build`. + - Browser-test `/rockets` at desktop and mobile, exercise Server/Auth/Storage by pointer and keyboard, inspect console errors, confirm no horizontal overflow, and record document height. + - Review `git diff origin/main...HEAD` for unrelated files or regressions. + - Commit the implementation, push the current branch, open a PR against `main`, wait for required checks, and merge only when the PR diff and checks are clean. + +## Test Strategy + +- Integration contract: `site-tests/rockets-integration.test.mjs` validates the route, shell, API naming, standalone positioning, section composition, and forbidden terminology. +- Full site regression: `pnpm test:site` confirms shared product pages remain wired. +- Production compile: `pnpm build` catches TypeScript, Next.js, and static-rendering regressions. +- Browser: desktop/mobile responsive layout, tab interaction, focus behavior, overflow, console, reduced motion, and page-height targets. + +## Risks and Stop Conditions + +- Risk: the launch page can drift from the package API — mitigation: use only fields present in the current root options contract and avoid claiming that the facade is already published. +- Risk: shared CSS cleanup can affect other product pages — mitigation: remove only selectors proven Rockets-specific and inspect shared-page test/build results. +- Risk: Chrome extensions can create hydration noise — mitigation: distinguish extension-owned errors from application-owned errors and corroborate with a production build. +- Stop before merge if required checks fail, the PR includes unrelated files, other product-page behavior changes, mobile overflow appears, or the launch facade conflicts with current product direction. + +## Rollout + +- Feature flag: none; `/rockets` is already an isolated route on the feature branch. +- Deployment: normal merge to `main` after PR checks. +- Rollback: revert the implementation commit or PR merge. diff --git a/docs/superpowers/specs/2026-07-17-rockets-landing-polish-design.md b/docs/superpowers/specs/2026-07-17-rockets-landing-polish-design.md new file mode 100644 index 00000000..4bc815a3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-17-rockets-landing-polish-design.md @@ -0,0 +1,289 @@ +# Rockets Standalone Landing Page Polish + +**Date:** 2026-07-17 +**Status:** Approved direction, implementation pending +**Page:** `/rockets` + +## 1. Product Decision + +The Rockets landing page must present Rockets as a complete standalone product. The broader Concepta and Stargate strategy informs the page's focus—show one concrete capability before explaining a platform—but neither Stargate nor NestJS appears in the page copy. + +The page's single job is to convince a backend or platform engineer that one typed `createServer(...)` definition can establish the repeatable foundation of a production backend while leaving domain behavior under the team's control. + +### Core promise + +> Describe the backend once. Rockets makes it run. + +### Launch API + +The primary public API shown throughout the page is: + +```ts +export const billing = createServer({ + auth: sharedIdentity, + repository: postgres, + resources: [customers, invoices, ledger], + accessControl: billingPolicy, +}); +``` + +`createServer(...)` is the launch-facing facade for the same options object currently assembled through the alpha module-registration API. The facade changes the entry-point name, not the configuration contract. The landing page does not introduce a second aspirational name such as `defineRockets(...)`. + +### Explicit exclusions + +- Do not mention Stargate. +- Do not mention NestJS. +- Do not present Rockets as dependent on a hosted control plane. +- Do not describe a language-neutral or serialized backend artifact as a current capability. +- Do not imply that Rockets writes the product's domain logic. +- Do not show `defineRockets(...)`. + +## 2. Audience and Success Criteria + +### Primary audience + +Backend and platform engineers evaluating whether Rockets can remove repeated server infrastructure without hiding or owning their domain logic. + +### User story + +As an engineer evaluating Rockets, I want to see one realistic server definition and the concrete runtime surface it creates so that I can understand the product before reading its documentation. + +### Acceptance criteria + +- The hero communicates the product promise and shows `createServer(...)` before any conceptual product explanation. +- The default hero visual maps configuration fields to concrete server outcomes. +- The rendered Rockets page contains neither `NestJS` nor `Stargate`, including metadata and FAQ copy. +- The rendered page contains `createServer(` and does not contain `defineRockets(`. +- The page has no more than six major narrative sections after the shared navigation. +- Mobile document height at 390 × 844 is at least 25% shorter than the current 14,192 px baseline. +- Desktop document height at 1440 × 900 is at least 20% shorter than the current 9,782 px baseline. +- The page has no horizontal overflow at 390 px width. +- Tabs, links, and FAQ controls remain keyboard accessible with visible focus states. +- Motion respects `prefers-reduced-motion` through the existing `MotionConfig` behavior. +- Existing site tests, TypeScript checks, and the production build pass. + +## 3. Recommended Narrative + +The page uses a proof-first narrative. It shows the server definition, demonstrates the resulting surface, explains provider boundaries, and then states what remains the team's responsibility. + +### Section 1 — Hero: the thesis and proof + +**Headline:** Keep the existing thesis, “Your backend should be a spec. Rockets makes it run.” + +**Lead:** Replace framework and ecosystem language with a direct product explanation: + +> Describe your domain—resources, identity, storage, access, and APIs. Rockets turns that definition into a secure, documented server with the repeatable foundation already wired. + +**Primary CTA:** Explore on GitHub. +**Secondary CTA:** See `createServer`. + +The hero window defaults to a complete `createServer(...)` example. It retains the useful authentication and storage tabs, but the first tab is named **Server**, not **Spec**, and it is visually dominant. + +### Section 2 — One call, a complete server + +Show the direct mapping between the definition and the resulting surface: + +- `resources` → routes, validation, relationships, and OpenAPI +- `auth` → one authenticated-user contract and protected endpoints +- `repository` → default persistence with per-resource overrides +- `accessControl` → ownership and policy enforcement +- hooks and providers → extension points for domain behavior + +This replaces the current stats strip, statement, feature grid, and output bento, which repeat the same idea at different levels of abstraction. + +### Section 3 — Providers stay replaceable + +Use one compact split section with two provider cards: + +- **Identity:** Firebase, API keys, built-in identity, or a custom adapter resolve the same user contract. +- **Storage:** choose a default repository and override the exception without changing domain services. + +The section demonstrates both configuration fragments without becoming a package inventory. + +### Section 4 — Rockets builds the foundation; you build the product + +Draw a clear product boundary. + +**Rockets owns:** registration, authentication plumbing, persistence routing, resource controllers, validation, access-policy wiring, hooks, and API documentation. + +**Your team owns:** domain rules, schemas, services, integrations, workflows, events, and operational choices. + +This replaces the current repeated “definition is the product” and “framework stops at the boundary” explanations with one concrete comparison. + +### Section 5 — Focused FAQ + +Keep six questions at most: + +1. What is Rockets? +2. What does `createServer(...)` create? +3. Does Rockets generate source code? +4. Can identity and storage providers be replaced? +5. What application code do I still write? +6. Is Rockets stable? + +Answers must use product language, avoid implementation-framework naming, and distinguish launch direction from currently published alpha behavior where necessary. + +### Section 6 — Closing action + +**Title:** Define the backend. Launch the server. +**Lead:** Start with one domain definition, inspect the generated surface, and keep extending it with ordinary application code. +**Actions:** View the repository and read the guide. + +Remove the ecosystem marquee. It adds length and package vocabulary without helping the visitor understand the central product action. + +## 4. Visual Design + +### Design direction + +Preserve the current dark Rockets identity and refine it around a single signature interaction. The page should feel like a launch console whose input is a server definition and whose output is a live, inspectable backend surface. + +### Token system + +- **Night:** `#05040A` — page background +- **Panel:** `#0D0B14` — code and content surfaces +- **Lift:** `#161421` — elevated and selected surfaces +- **Launch:** `#FF5906` — primary action and active state +- **Flame:** `#FFAD7A` — secondary gradient and warm output highlight +- **Signal:** `#FFFFFF` — primary text; use opacity for muted copy + +### Typography + +- **Display and reading:** Inter, using tight tracking and strong weight contrast for the hero and section titles. +- **Utility and code:** JetBrains Mono for the wordmark, labels, configuration keys, file names, and runtime outputs. +- Use the mono face only where the content is genuinely technical. Do not turn ordinary prose into terminal decoration. + +### Layout + +Desktop: + +```text +┌──────────────────────────────────────────────────────────┐ +│ HERO THESIS + ACTIONS │ +│ ┌──────────────────────┬─────────────────────────────┐ │ +│ │ createServer(config) │ configuration → outcomes │ │ +│ └──────────────────────┴─────────────────────────────┘ │ +├──────────────────────────────────────────────────────────┤ +│ ONE CALL, A COMPLETE SERVER │ +│ resources auth storage policy API │ +├───────────────────────────┬──────────────────────────────┤ +│ IDENTITY PROVIDERS │ STORAGE PROVIDERS │ +├───────────────────────────┴──────────────────────────────┤ +│ ROCKETS BUILDS YOUR TEAM BUILDS │ +├──────────────────────────────────────────────────────────┤ +│ FAQ │ +├──────────────────────────────────────────────────────────┤ +│ CLOSING ACTION │ +└──────────────────────────────────────────────────────────┘ +``` + +Mobile: + +```text +HERO +createServer code +outcome sequence +configuration outcomes +identity providers +storage providers +Rockets / your team boundary +FAQ +closing action +``` + +Mobile sections stack in narrative order. Code may scroll inside its own panel only when it cannot wrap safely; the document itself must never overflow horizontally. + +### Signature interaction + +The memorable element is the **launch sequence** inside the hero window. Configuration keys on the left map to a restrained output checklist on the right. When the Server, Auth, or Storage tab changes, one coordinated transition updates both sides. The active `createServer` call and its resulting surface are the visual thesis of the page. + +Spend motion here and keep downstream animation quiet. Section reveals may remain subtle, but cards should not animate independently without communicating state. + +### Uniqueness review + +The current page relies on common landing-page devices: a numerical stats strip, a large philosophical statement, a generic feature grid, a bento grid, and a scrolling package marquee. Those devices are polished but not specific to Rockets. + +The revised design removes those generic structures. Its primary visual relationship—configuration keys becoming server capabilities—can only belong to this product. Orange remains a launch signal rather than decorative glow, and structural labels describe real configuration or runtime concepts. + +## 5. Component Architecture + +Rockets should no longer be forced through every section of the generic `LandingRoot` template. Preserve shared primitives but give the product a concise composition. + +### Recommended composition + +`RocketsLanding.tsx` becomes the page-level composition and renders: + +1. shared `Aurora` +2. shared `Hero` with the Rockets `HeroWindow` +3. new `ServerSurface` +4. revised `ProviderSpotlights` +5. new `OwnershipBoundary` +6. shared `Faq` +7. shared `ClosingCta` + +Keep `MotionConfig reducedMotion="user"` and the existing `.lp-root` shell. Do not make unrelated product fields optional merely to accommodate Rockets; a product-specific composition is clearer and avoids regressions to Stargate and Code Analysis pages. + +### File responsibilities + +- `RocketsLanding.tsx` — section order and page shell only +- `content.tsx` — user-facing copy, links, FAQ, and CTA data +- `snippets.ts` — `createServer`, authentication, and storage examples +- `HeroWindow.tsx` — tabs plus configuration-to-output launch sequence +- `ServerSurface.tsx` — compact configuration-to-capability mapping +- `ProviderSpotlights.tsx` — combined identity and storage provider section +- `OwnershipBoundary.tsx` — Rockets-versus-team responsibility comparison +- `landing.css` — shared primitives plus narrowly scoped Rockets selectors +- `rockets-integration.test.mjs` — positive and negative content contracts + +`OutputsBento.tsx` is removed once `ServerSurface.tsx` covers its unique information. Avoid leaving dead Rockets-specific selectors in `landing.css` after the component removal. + +## 6. Content and API Integrity + +The page may preview the `createServer(...)` launch facade, but it must not claim capabilities absent from the current planner and package contracts. + +Every output shown in the launch sequence must map to repository-backed behavior: + +- authentication adapter chain and authenticated-user contract +- repository abstraction and per-entity override +- declarative resources and generated CRUD surface +- access-control integration +- hooks and custom providers +- OpenAPI registration + +Copy must not claim that Rockets generates business logic, provisions infrastructure, or serializes a language-neutral specification. + +## 7. Responsive, Accessibility, and Failure States + +- Preserve semantic tabs with `role="tablist"`, `role="tab"`, and `aria-selected`. +- Add arrow-key tab navigation if it is absent during implementation. +- Decorative output panels remain hidden from assistive technology only when equivalent explanatory text is present. +- Interactive code tabs must retain visible focus and at least a 44 px touch target on mobile. +- Honor reduced motion by replacing cross-fades and transforms with immediate state changes. +- External links retain `rel="noopener noreferrer"`. +- The page has no form or remote data dependency; its primary failure risk is inaccurate or contradictory API copy. Tests enforce required and forbidden terminology. + +## 8. Verification Plan + +### Automated + +- Extend `site-tests/rockets-integration.test.mjs` to require `createServer` and forbid `defineRockets`, `NestJS`, and `Stargate` in Rockets page files and metadata. +- Verify the expected standalone sections and GitHub calls to action. +- Run `pnpm test:site`. +- Run the repository TypeScript check or production build used by this app. + +### Browser + +- Verify `/rockets` at 1440 × 900 and 390 × 844. +- Exercise Server, Auth, and Storage tabs with pointer and keyboard input. +- Confirm no horizontal overflow and measure document-height reduction against the recorded baselines. +- Inspect the browser console for application-owned errors. +- Confirm reduced-motion behavior. +- Capture hero and full-page screenshots for the final design critique. + +## 9. Non-Goals + +- Implementing `createServer(...)` in the Rockets package repository +- Rewriting the shared navigation or footer +- Redesigning other Concepta product landing pages +- Adding pricing, hosted services, a waitlist, or an account flow +- Adding Stargate or broader ecosystem positioning +- Changing the Rockets brand palette diff --git a/globals.css b/globals.css index 70a4e03d..8a267a90 100644 --- a/globals.css +++ b/globals.css @@ -110,6 +110,17 @@ html.dark[data-product="naked-ui"] { --mix-accent-glow: rgba(96, 165, 250, 0.2); } +/* Rockets product: official launch-orange palette. */ +html.dark[data-product="rockets"] { + --nextra-primary-hue: 20deg; + --nextra-primary-saturation: 100%; + --nextra-primary-lightness: 51%; + + --mix-accent: #FF5906; + --mix-accent-low: rgba(255, 89, 6, 0.1); + --mix-accent-glow: rgba(255, 89, 6, 0.2); +} + /* Ack product: blue/cyan validation palette */ html.dark[data-product="ack"] { --nextra-primary-hue: 228deg; diff --git a/public/assets/logo_rockets_mark.svg b/public/assets/logo_rockets_mark.svg new file mode 100644 index 00000000..eb2d9bf6 --- /dev/null +++ b/public/assets/logo_rockets_mark.svg @@ -0,0 +1 @@ + diff --git a/site-tests/naked-ui-integration.test.mjs b/site-tests/naked-ui-integration.test.mjs index 4eae0edc..df9d63e4 100644 --- a/site-tests/naked-ui-integration.test.mjs +++ b/site-tests/naked-ui-integration.test.mjs @@ -22,7 +22,7 @@ test('wires Naked UI into the shared product shell', () => { assert.match(route, //) assert.match(route, /Naked UI — Headless Flutter components/) assert.match(layout, /p==='\/naked-ui'/) - assert.match(layout, /Mix, Remix, Naked UI, Ack, FVM, Stargate, and Code Analysis/) + assert.match(layout, /Mix, Remix, Naked UI, Ack, FVM, Rockets, Stargate, and Code Analysis/) assert.match(navbar, /id: 'naked-ui'/) assert.match(navbar, /https:\/\/docs\.page\/btwld\/naked_ui/) assert.match(navbar, /NAKED_UI_GITHUB_URL/) diff --git a/site-tests/rockets-integration.test.mjs b/site-tests/rockets-integration.test.mjs new file mode 100644 index 00000000..c161ff99 --- /dev/null +++ b/site-tests/rockets-integration.test.mjs @@ -0,0 +1,114 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import test from 'node:test' + +const root = process.cwd() + +function read(relativePath) { + return fs.readFileSync(path.join(root, relativePath), 'utf8') +} + +test('wires Rockets into the shared product shell', () => { + const route = read('src/app/rockets/page.tsx') + const layout = read('src/app/layout.jsx') + const navbar = read('components/FloatingNavbar.tsx') + const constants = read('components/constants.ts') + const footer = read('components/ProductFooter.tsx') + const pageMap = read('src/content/_meta.js') + const conceptaHome = read('components/ConceptaHome.tsx') + const globals = read('globals.css') + const landingStyles = read('components/landing/landing.css') + + assert.match(route, //) + assert.match(route, /Rockets — Your backend should be a spec\./) + assert.match(layout, /p==='\/rockets'/) + assert.match(layout, /FVM, Rockets, Stargate/) + assert.match(navbar, /id: 'rockets'/) + assert.match(navbar, /ROCKETS_GITHUB_URL/) + assert.match(constants, /ROCKETS_GITHUB_URL = 'https:\/\/github\.com\/btwld\/rockets'/) + assert.match(footer, /isRocketsPath/) + assert.match(pageMap, /rockets:/) + assert.match(conceptaHome, /name: "Rockets"/) + assert.match(conceptaHome, /href: "\/rockets"/) + assert.match(conceptaHome, /createServer/) + assert.doesNotMatch(conceptaHome, /NestJS|Nest API/) + assert.match(globals, /data-product="rockets"/) + assert.match(landingStyles, /data-product='rockets'/) + assert.match(landingStyles, /\.lp-rockets-root \.lp-seg/) + assert.match(landingStyles, /min-height: 44px/) + assert.equal( + fs.existsSync(path.join(root, 'public/assets/logo_rockets_mark.svg')), + true, + ) +}) + +test('presents Rockets as a standalone createServer product', () => { + const route = read('src/app/rockets/page.tsx') + const landing = read('components/landing/rockets/RocketsLanding.tsx') + const content = read('components/landing/rockets/content.tsx') + const hero = read('components/landing/rockets/HeroWindow.tsx') + const providers = read('components/landing/rockets/ProviderSpotlights.tsx') + const snippets = read('components/landing/rockets/snippets.ts') + + assert.match(content, /Your backend should be a spec/) + assert.match(content, /wordmarkShowByline: false/) + assert.match(content, /Describe your domain/) + assert.match(content, /What does createServer\(\) create\?/) + assert.match(content, /pre-1\.0/) + assert.match(landing, //) + assert.match(landing, //) + assert.match(landing, //) + assert.doesNotMatch(landing, /LandingRoot/) + assert.match(hero, /role="tablist"/) + assert.match(hero, /role="tab"/) + assert.match(hero, /aria-selected=/) + assert.match(hero, /name: "Server"/) + assert.match(hero, /name: "Auth"/) + assert.match(hero, /name: "Storage"/) + assert.match(hero, /onKeyDown=/) + assert.match(hero, /ArrowRight/) + assert.match(hero, /ArrowLeft/) + assert.equal( + fs.existsSync(path.join(root, 'components/landing/rockets/ServerSurface.tsx')), + true, + ) + assert.equal( + fs.existsSync(path.join(root, 'components/landing/rockets/OwnershipBoundary.tsx')), + true, + ) + const surface = read('components/landing/rockets/ServerSurface.tsx') + const boundary = read('components/landing/rockets/OwnershipBoundary.tsx') + const rocketsPageSource = [ + route, + landing, + content, + hero, + providers, + snippets, + surface, + boundary, + ].join('\n') + assert.match(surface, /One call\. A complete server\./) + assert.match(surface, /resources/) + assert.match(surface, /accessControl/) + assert.match(providers, /Identity and storage/) + assert.match(providers, /unmatched → try next · invalid match → stop/) + assert.match(providers, /one RepositoryInterface/) + assert.match(boundary, /Rockets builds the foundation/) + assert.match(boundary, /Your team builds the product/) + assert.match(snippets, /createServer/) + assert.match(snippets, /defineFirebaseAuth/) + assert.match(snippets, /defineFirestoreRepository/) + assert.doesNotMatch(rocketsPageSource, /\bdefineRockets\s*\(/) + assert.doesNotMatch(rocketsPageSource, /NestJS/) + assert.doesNotMatch(rocketsPageSource, /Stargate/) +}) + +test('keeps the open-source calls to action out of the waitlist flow', () => { + const content = read('components/landing/rockets/content.tsx') + + assert.match(content, /kind: "links"/) + assert.match(content, /https:\/\/github\.com\/btwld\/rockets/) + assert.doesNotMatch(content, /kind: "waitlist"/) +}) diff --git a/src/app/layout.jsx b/src/app/layout.jsx index 8a53b77c..ae932bbe 100644 --- a/src/app/layout.jsx +++ b/src/app/layout.jsx @@ -20,7 +20,7 @@ const jetbrainsMono = JetBrains_Mono({ }) const description = - 'Concepta ships the systems your business runs on — and builds the open-source delivery foundation behind them: Mix, Remix, Naked UI, Ack, FVM, Stargate, and Code Analysis.' + 'Concepta ships the systems your business runs on — and builds the open-source delivery foundation behind them: Mix, Remix, Naked UI, Ack, FVM, Rockets, Stargate, and Code Analysis.' export const viewport = { themeColor: '#111111', @@ -72,7 +72,7 @@ export default async function RootLayout({ children }) {