From f9fa3a36986917f2c49b924cff723f31dd7ae9d2 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Fri, 17 Jul 2026 08:38:26 -0400 Subject: [PATCH 1/5] feat: add Rockets product vision page --- components/ConceptaHome.tsx | 38 +- components/FloatingNavbar.tsx | 14 +- components/ProductFooter.tsx | 25 +- components/constants.ts | 1 + components/landing/LandingButton.tsx | 15 + components/landing/LandingRoot.tsx | 1 - components/landing/code-analysis/content.tsx | 24 +- .../readiness/ReadinessLanding.tsx | 7 +- components/landing/landing.css | 329 +++++++++++++++++- components/landing/rockets/HeroWindow.tsx | 223 ++++++++++++ components/landing/rockets/OutputsBento.tsx | 195 +++++++++++ .../landing/rockets/ProviderSpotlights.tsx | 319 +++++++++++++++++ components/landing/rockets/RocketsLanding.tsx | 8 + components/landing/rockets/content.tsx | 233 +++++++++++++ components/landing/rockets/snippets.ts | 47 +++ components/landing/sections/ClosingCta.tsx | 15 +- components/landing/sections/Hero.tsx | 15 +- components/landing/sections/Marquee.tsx | 6 +- components/landing/sections/TrustSplit.tsx | 11 +- components/landing/stargate/content.tsx | 24 +- components/landing/types.ts | 30 +- globals.css | 11 + public/assets/logo_rockets_mark.svg | 1 + site-tests/naked-ui-integration.test.mjs | 2 +- site-tests/rockets-integration.test.mjs | 89 +++++ src/app/layout.jsx | 4 +- src/app/rockets/icon.svg | 1 + src/app/rockets/page.tsx | 28 ++ src/content/_meta.js | 5 + src/content/index.mdx | 2 +- 30 files changed, 1685 insertions(+), 38 deletions(-) create mode 100644 components/landing/rockets/HeroWindow.tsx create mode 100644 components/landing/rockets/OutputsBento.tsx create mode 100644 components/landing/rockets/ProviderSpotlights.tsx create mode 100644 components/landing/rockets/RocketsLanding.tsx create mode 100644 components/landing/rockets/content.tsx create mode 100644 components/landing/rockets/snippets.ts create mode 100644 public/assets/logo_rockets_mark.svg create mode 100644 site-tests/rockets-integration.test.mjs create mode 100644 src/app/rockets/icon.svg create mode 100644 src/app/rockets/page.tsx diff --git a/components/ConceptaHome.tsx b/components/ConceptaHome.tsx index 51329d13..b0ca35e4 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 +
+
runtime planner · no generated files
+
+ + Nest API + /me · /pets · /api +
+
+ + guard · CRUD · hooks · Swagger +
+
+ ); +} + 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 NestJS configuration wires auth, dynamic repositories, generated CRUD, hooks, owner scoping, and Swagger at runtime.", + href: "/rockets", + accent: "#FF5906", + status: "Open source", + windowLabel: "app.module.ts", + Visual: RocketsVisual, + }, { name: "Stargate", tagline: "Complex workflows for the enterprise.", diff --git a/components/FloatingNavbar.tsx b/components/FloatingNavbar.tsx index 9e60c9b1..dfa6081b 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' +type ProductId = 'concepta' | 'mix' | 'remix' | 'naked-ui' | 'ack' | 'rockets' | 'stargate' | 'code-analysis' 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' return 'mix' @@ -63,6 +65,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 { @@ -70,6 +73,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 } @@ -79,6 +83,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 } @@ -125,6 +130,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 69d48c09..4a6c4c8a 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/') @@ -36,6 +45,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 } @@ -75,6 +85,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/LandingRoot.tsx b/components/landing/LandingRoot.tsx index 41215815..0acff0dc 100644 --- a/components/landing/LandingRoot.tsx +++ b/components/landing/LandingRoot.tsx @@ -30,7 +30,6 @@ export function LandingRoot({ content }: { content: LandingContent }) { diff --git a/components/landing/code-analysis/content.tsx b/components/landing/code-analysis/content.tsx index d2c423ce..85d4481a 100644 --- a/components/landing/code-analysis/content.tsx +++ b/components/landing/code-analysis/content.tsx @@ -24,6 +24,16 @@ export const codeAnalysisContent: LandingContent = { you can put in front of a client. ), + primaryCta: { + label: "Join the waitlist", + href: "#waitlist", + arrow: "right", + }, + secondaryCta: { + label: "See how it works", + href: "#how", + variant: "secondary", + }, }, HeroWindow, stats: [ @@ -105,6 +115,12 @@ export const codeAnalysisContent: LandingContent = { "Flows", "Coverage", ], + cta: { + label: "Get early access", + href: "#waitlist", + variant: "ghost", + arrow: "right", + }, }, trustSplit: { eyebrow: "Determinism", @@ -115,7 +131,12 @@ export const codeAnalysisContent: LandingContent = { "Budget caps, resumable runs, content-hash caching", "Deterministic scoring — reruns are byte-equal", ], - ctaLabel: "How scoring works", + cta: { + label: "How scoring works", + href: "#faq", + variant: "ghost", + arrow: "right", + }, snippet: CODE_HEALTH_JSON, snippetFile: "code-health.json", }, @@ -164,5 +185,6 @@ export const codeAnalysisContent: LandingContent = { title: "Be first in line for the audit.", lead: "Code Analysis is launching soon. Join the waitlist for early access, launch pricing, and progress updates.", finePrint: "© 2026 · macOS · Linux · Windows", + action: { kind: "waitlist", product: "code-analysis" }, }, }; diff --git a/components/landing/code-analysis/readiness/ReadinessLanding.tsx b/components/landing/code-analysis/readiness/ReadinessLanding.tsx index 2c18bbe2..0d80e961 100644 --- a/components/landing/code-analysis/readiness/ReadinessLanding.tsx +++ b/components/landing/code-analysis/readiness/ReadinessLanding.tsx @@ -98,7 +98,12 @@ const TRUST_SPLIT: LandingContent["trustSplit"] = { "Deterministic evidence — rerun it, get the same numbers", "Policy-backed ceilings — an unrotated secret caps the grade, no matter what else is right", ], - ctaLabel: "Questions, answered", + cta: { + label: "Questions, answered", + href: "#faq", + variant: "ghost", + arrow: "right", + }, snippet: METHODOLOGY_JSON, snippetFile: "methodology.json", }; diff --git a/components/landing/landing.css b/components/landing/landing.css index 30383fbd..6b1ef8d4 100644 --- a/components/landing/landing.css +++ b/components/landing/landing.css @@ -43,6 +43,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); @@ -55,7 +65,8 @@ html[data-product='naked-ui'] { @media (prefers-reduced-motion: no-preference) { html[data-product='stargate'], html[data-product='code-analysis'], - html[data-product='naked-ui'] { + html[data-product='naked-ui'], + html[data-product='rockets'] { scroll-behavior: smooth; } } @@ -918,12 +929,328 @@ html[data-product='naked-ui'] { border-top: 1px solid var(--lp-border-card); } +/* ═══ Rockets provider spotlights ═══════════════════════ */ +.lp-provider-demo { + display: flex; + min-width: 0; + flex-direction: column; + gap: 16px; +} +.lp-vision-card, +.lp-provider-card { + overflow: hidden; + padding: 20px; + border-radius: 18px; + box-shadow: 0 32px 80px -52px var(--lp-accent-glow); +} +.lp-vision-card { + border: 1px solid color-mix(in srgb, var(--lp-accent) 26%, transparent); + background: + radial-gradient( + circle at 18% 0%, + color-mix(in srgb, var(--lp-accent) 10%, transparent), + transparent 42% + ), + linear-gradient(180deg, rgba(22, 20, 33, 0.92), rgba(9, 8, 14, 0.96)); +} +.lp-vision-status { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + margin-top: 16px; +} +.lp-vision-status > div { + display: flex; + align-items: center; + gap: 9px; + min-width: 0; + padding: 9px 10px; + border: 1px solid var(--lp-border-card); + border-radius: 10px; + background: rgba(3, 3, 8, 0.32); +} +.lp-vision-status .lp-severity-chip { + flex: 0 0 auto; + color: var(--lp-accent); + background: var(--lp-accent-low); +} +.lp-vision-status .lp-vision-direction { + color: var(--lp-accent-2); + background: color-mix(in srgb, var(--lp-accent-2) 10%, transparent); +} +.lp-vision-status p { + overflow: hidden; + font-size: 11px; + color: #d9d7e0; + text-overflow: ellipsis; + white-space: nowrap; +} +.lp-vision-pipeline { + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr); + align-items: center; + gap: 8px; + margin-top: 12px; +} +.lp-vision-node { + display: flex; + align-items: flex-start; + gap: 9px; + min-width: 0; + min-height: 82px; + padding: 13px; + border: 1px solid var(--lp-border-card); + border-radius: 12px; + background: rgba(3, 3, 8, 0.42); +} +.lp-vision-node > svg { + flex: 0 0 auto; + margin-top: 1px; + color: var(--lp-accent); +} +.lp-vision-node p, +.lp-vision-control p { + font-size: 12px; + font-weight: 600; + color: #fff; +} +.lp-vision-node span, +.lp-vision-control span { + display: block; + margin-top: 4px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 9px; + line-height: 1.45; + color: var(--lp-text-muted); +} +.lp-vision-arrow { + flex: 0 0 auto; + color: color-mix(in srgb, var(--lp-accent) 72%, white); +} +.lp-vision-fleet { + display: grid; + grid-template-columns: minmax(0, 0.9fr) auto minmax(0, 1.1fr); + align-items: center; + gap: 10px; + margin-top: 12px; + padding: 12px; + border: 1px dashed color-mix(in srgb, var(--lp-accent) 24%, transparent); + border-radius: 12px; + background: color-mix(in srgb, var(--lp-accent) 4%, transparent); +} +.lp-vision-control { + display: flex; + align-items: flex-start; + gap: 9px; + min-width: 0; +} +.lp-vision-control > svg { + flex: 0 0 auto; + color: var(--lp-accent-2); +} +.lp-vision-apps { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; +} +.lp-vision-apps span { + overflow: hidden; + padding: 8px 6px; + border: 1px solid var(--lp-border-card); + border-radius: 9px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 9px; + color: #e9e7ef; + text-align: center; + text-overflow: ellipsis; + background: rgba(3, 3, 8, 0.46); +} +.lp-vision-identity { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-top: 10px; + padding: 10px 12px; + border-radius: 10px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 10px; + color: #dfdde6; + background: var(--lp-accent-low); +} +.lp-vision-identity svg { + flex: 0 0 auto; + color: var(--lp-accent); +} +.lp-provider-card { + 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: 680px) { + .lp-vision-status, + .lp-vision-pipeline, + .lp-vision-fleet { + grid-template-columns: 1fr; + } + .lp-vision-pipeline > .lp-vision-arrow, + .lp-vision-fleet > .lp-vision-arrow { + justify-self: center; + transform: rotate(90deg); + } + .lp-vision-node { + min-height: 0; + } +} +@media (max-width: 480px) { + .lp-vision-card, + .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; align-items: center; gap: 12px; } +.lp-mock-key { + flex: 0 0 68px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 9px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--lp-accent); +} +.lp-bento-flow { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + min-width: 0; +} +.lp-bento-flow-step { + display: flex; + align-items: center; + gap: 7px; + min-width: 0; +} +.lp-bento-flow-step > span:first-child { + overflow: hidden; + padding: 8px 9px; + border: 1px solid var(--lp-border-card); + border-radius: 9px; + font-family: var(--font-jetbrains-mono), ui-monospace, monospace; + font-size: 9px; + color: #dedce5; + text-overflow: ellipsis; + white-space: nowrap; + background: rgba(3, 3, 8, 0.36); +} +.lp-bento-flow-arrow { + color: var(--lp-accent); +} +@media (max-width: 480px) { + .lp-bento-flow { + align-items: stretch; + flex-direction: column; + } + .lp-bento-flow-step { + flex-direction: column; + } + .lp-bento-flow-step > span:first-child { + width: 100%; + text-align: center; + } + .lp-bento-flow-arrow { + transform: rotate(90deg); + } +} .lp-mock-path { flex: 0 0 auto; width: 46%; diff --git a/components/landing/rockets/HeroWindow.tsx b/components/landing/rockets/HeroWindow.tsx new file mode 100644 index 00000000..198e0c48 --- /dev/null +++ b/components/landing/rockets/HeroWindow.tsx @@ -0,0 +1,223 @@ +"use client"; + +import { useState } 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, + DEFINE_ROCKETS_TS, + STORAGE_PROVIDERS_TS, +} from "./snippets"; + +const PLAN_ITEMS = [ + "Domain resources resolved", + "Shared identity boundary attached", + "Storage routes bound", + "HTTP + OpenAPI materialized", +] as const; + +function SpecStage() { + return ( +
+ {PLAN_ITEMS.map((item) => ( +
+ + {item} +
+ ))} +
+ one backend definition → one domain micro app +
+
+ ); +} + +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: "spec", + name: "Spec", + code: DEFINE_ROCKETS_TS, + lang: "typescript" as const, + stageLabel: "Backend definition · contract to running API", + Stage: SpecStage, + }, + { + 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 tab = TABS[activeTab]; + + return ( + + @@ -53,7 +51,10 @@ export function TrustSplit({ {trustSplit.snippetFile}
- +
diff --git a/components/landing/stargate/content.tsx b/components/landing/stargate/content.tsx index 1421596d..450ed2cf 100644 --- a/components/landing/stargate/content.tsx +++ b/components/landing/stargate/content.tsx @@ -20,6 +20,16 @@ export const stargateContent: LandingContent = { WorkflowSpec before and after each handler runs. ), + primaryCta: { + label: "Join the waitlist", + href: "#waitlist", + arrow: "right", + }, + secondaryCta: { + label: "See how it works", + href: "#how", + variant: "secondary", + }, }, HeroWindow, stats: [ @@ -103,6 +113,12 @@ export const stargateContent: LandingContent = { "Human-in-the-loop", "Workflow evals", ], + cta: { + label: "Get early access", + href: "#waitlist", + variant: "ghost", + arrow: "right", + }, }, trustSplit: { eyebrow: "Portability", @@ -113,7 +129,12 @@ export const stargateContent: LandingContent = { "No handlers, no executable strings in the artifact", "Validated at authoring, at load, and at every node boundary", ], - ctaLabel: "How validation works", + cta: { + label: "How validation works", + href: "#faq", + variant: "ghost", + arrow: "right", + }, snippet: WORKFLOW_SPEC_JSON, snippetFile: "ask-the-agent.json", }, @@ -147,6 +168,7 @@ export const stargateContent: LandingContent = { title: "Be first through the gate.", lead: "Stargate is launching soon. Join the waitlist for early access and progress updates.", finePrint: "© 2026 · Studio · CLI · API · MCP", + action: { kind: "waitlist", product: "stargate" }, }, ClosingVisual: StudioPrototype, }; diff --git a/components/landing/types.ts b/components/landing/types.ts index 8abcb27b..4d1b0336 100644 --- a/components/landing/types.ts +++ b/components/landing/types.ts @@ -5,13 +5,26 @@ 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 type LandingProduct = Exclude | "rockets"; + +export interface LandingCta { + label: string; + href: string; + variant?: "primary" | "secondary" | "ghost"; + arrow?: "right"; + external?: boolean; +} + export interface LandingContent { - product: ProductSlug; + product: LandingProduct; wordmarkName: string; + showWordmarkByline?: boolean; hero: { titleTop: ReactNode; titleGradient: ReactNode; lead: ReactNode; + primaryCta: LandingCta; + secondaryCta: LandingCta; }; /** Per-product hero visual rendered below the CTA row. */ HeroWindow: ComponentType; @@ -28,18 +41,27 @@ 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 }; + closingCta: { + title: string; + lead: string; + finePrint: string; + anchor?: string; + action: + | { kind: "waitlist"; product: ProductSlug } + | { kind: "links"; links: LandingCta[] }; + }; /** Optional visual rendered inside the closing CTA card, below the waitlist form. */ ClosingVisual?: ComponentType; } diff --git a/globals.css b/globals.css index 6bc9f975..acd7a5ac 100644 --- a/globals.css +++ b/globals.css @@ -89,6 +89,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..9c76dcfe --- /dev/null +++ b/site-tests/rockets-integration.test.mjs @@ -0,0 +1,89 @@ +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(globals, /data-product="rockets"/) + assert.match(landingStyles, /data-product='rockets'/) + assert.equal( + fs.existsSync(path.join(root, 'public/assets/logo_rockets_mark.svg')), + true, + ) +}) + +test('presents the repository-backed Rockets vision', () => { + 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 outputs = read('components/landing/rockets/OutputsBento.tsx') + const snippets = read('components/landing/rockets/snippets.ts') + + assert.match(content, /Your backend should be a spec/) + assert.match(content, /showWordmarkByline: false/) + assert.match(content, /The definition is the product/) + assert.match(content, /One bounded domain, one micro app/) + assert.match(content, /One identity across the product/) + assert.match(content, /Infrastructure stays at the edge/) + assert.match(content, /The framework stops at the boundary/) + assert.match(content, /AuthAdapterInterface/) + assert.match(content, /RepositoryInterface/) + assert.match(content, /first-class backend artifact is the direction/) + assert.match(content, /not a language-neutral serialized artifact/) + assert.match(content, /How does Rockets relate to Stargate\?/) + assert.match(content, /Do I need Stargate to use Rockets\?/) + assert.match(content, /pre-1\.0/) + assert.match(hero, /role="tablist"/) + assert.match(hero, /role="tab"/) + assert.match(hero, /aria-selected=/) + assert.match(hero, /name: "Spec"/) + assert.match(hero, /name: "Auth"/) + assert.match(hero, /name: "Storage"/) + assert.match(hero, /one backend definition → one domain micro app/) + assert.match(providers, /Vision · The definition is the product/) + assert.match(providers, /open-source product model/) + assert.match(providers, /DIRECTION/) + assert.match(providers, /unmatched → try next · invalid match → stop/) + assert.match(providers, /one RepositoryInterface/) + assert.match(outputs, /One spec\. A complete domain surface\./) + assert.match(outputs, /Stargate orchestrates · micro apps execute/) + assert.match(snippets, /defineRockets/) + assert.match(snippets, /defineFirebaseAuth/) + assert.match(snippets, /defineFirestoreRepository/) + assert.doesNotMatch(snippets, /RocketsModule\.forRoot/) +}) + +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 65907725..fa494385 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 }) {