Launch Phase
A single-screen onboarding finale rendered as a vertical poster: header block (eyebrow / title / subtitle / detail) → seed node + animated timeline rail with milestone rows → dashed future-branch hint → CTA. The component drives a staged reveal across all three zones on mount and respects prefers-reduced-motion. Content is fully agnostic: pass copy, milestones, future-branches chips, and either a ctaLabel + onAdvance pair or a custom cta slot.
Final step
25 patterns. Your path starts here.
Master one pattern to unlock the next.
You are not getting dumped into all 25 at once. Start with one live pattern, then let the tree branch under your feet.
Start here
One live pattern opens the rest of the path.
Array
Two Pointers
Motion and elimination collapse the search space.
Tree
BFS / DFS
Branch outward without losing the shape of the graph.
Search
Binary Search
Cut giant answer spaces down with confident proof.
Math
Dynamic Programming
Turn repeated subproblems into a table that finally moves.
Then the rest of the tree starts branching open.
Installation
npx shadcn@latest add https://craftbits.dev/r/launch-phase.jsonUsage
import { LaunchPhase, type LaunchPhaseMilestone } from "@craft-bits/core";
const MILESTONES: LaunchPhaseMilestone[] = [
{ id: "two-pointers", trackLabel: "Array", name: "Two Pointers", summary: "Motion + elimination collapse the search space." },
{ id: "bfs-dfs", trackLabel: "Tree", name: "BFS / DFS", summary: "Branch outward without losing the shape." },
{ id: "binary", trackLabel: "Search", name: "Binary Search", summary: "Cut answer spaces down with confident proof." },
{ id: "dp", trackLabel: "Math", name: "Dynamic Programming", summary: "Turn repeated subproblems into a table." },
];
<LaunchPhase
eyebrow="Final step"
title="25 patterns. Your path starts here."
subtitle="Master one pattern to unlock the next."
detail="You are not getting dumped into all 25 at once."
milestones={MILESTONES}
seedBody="One live pattern opens the rest of the path."
futureBranchCaption="Then the rest of the tree starts branching open."
futureBranches={["Greedy", "Trie", "Intervals", "And more"]}
ctaLabel="Begin"
onAdvance={() => router.push("/learn")}
/>To supply your own action — a link, a form submit, a custom button with state — pass the cta slot instead of ctaLabel + onAdvance:
<LaunchPhase
title="Ready to begin?"
milestones={MILESTONES}
cta={
<a
href="/learn"
className="rounded-cb-md bg-cb-accent px-6 py-2 text-sm font-medium text-cb-accent-fg"
>
Take me there
</a>
}
/>Understanding the component
- Three vertical zones. Header (eyebrow / title / subtitle / detail) sits on a centered max-w-2xl column. The timeline rail sits on the same column underneath. The CTA sits below the rail. Single column at every breakpoint; only padding flexes.
- Staged reveal. A
stageinteger advances through scheduledsetTimeoutticks on mount: seed at ~180ms, the rail draws at ~420ms, each milestone row reveals at ~280ms intervals, the future-branch row follows, and the CTA fades in last. Every animation isopacity+transformonly. - One animated SVG path. The rail is a single
<motion.line>measured once viauseLayoutEffectfrom the seed node center to the terminal-dot center.pathLengthanimates 0 → 1 withSPRINGS.smooth. The line lives behind every row and connects them visually. - Per-milestone accent. Each
milestone.accentColorpaints the row's left border, the track label, and the numbered node ring. The component-levelaccentColorpaints only the seed node and the rail line. - Reduced motion. When
prefers-reduced-motion: reduceis set, the initialstagejumps straight to the final value, everyinitial={...}collapses tofalse, and the SVG line renders fully drawn — nothing animates, nothing pops in. - CTA slot vs default. Pass
ctawhen you need a link, a form-submit button, or any element with its own state. PassctaLabel+onAdvancefor the default 44px-tall accent button.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
eyebrow | ReactNode | — | Small accent line above the title. |
title | ReactNode | required | Primary headline. Rendered as <h2>. |
subtitle | ReactNode | — | Secondary line under the headline. |
detail | ReactNode | — | Smaller supporting line under the subtitle. |
milestones | readonly LaunchPhaseMilestone[] | required | Ordered rows on the timeline rail. |
seedCaption | ReactNode | "Start here" | Small uppercase label next to the seed node. |
seedBody | ReactNode | — | Body text next to the seed node. |
futureBranches | readonly ReactNode[] | — | Chips under the dashed terminal-dot. |
futureBranchCaption | ReactNode | — | Line above the chip row. |
ctaLabel | ReactNode | "Begin" | Default CTA button label. |
onAdvance | () => void | — | Fires when the default CTA is clicked. |
cta | ReactNode | — | When set, replaces the default CTA entirely. |
accentColor | string | var(--cb-accent) | CSS color for the seed node and the rail line. |
className | string | — | Merged onto the outer <section> via cn(). |
LaunchPhaseMilestone
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier — used as the row's React key. |
trackLabel | string | Uppercase track / category label rendered above the headline. |
name | string | Row headline. Rendered as <h3>. |
summary | string | Supporting sentence under the headline. |
accentColor | string | CSS color for this row's left border, label, and node ring. Defaults to var(--cb-accent). |
illustration | ReactNode | Optional 56×56 slot on the right side of the row. |
Accessibility
- Render order matches reading order: header → seed → milestones → future → CTA. Each row is a real DOM element in source order; no
position: absoluteoverlap to confuse a screen reader. - The reveal sequence respects
prefers-reduced-motion: the component jumps straight to the final layout and skips everyinitialanimation. - The default CTA is a real
<button type="button">withmin-height: 2.75rem(44px) hit area, anaria-labelmirroringctaLabelwhen it's a string, and a visible focus ring (focus-visible:ring-cb-accent). - Decorative artefacts — the seed node, the milestone nodes, the SVG rail, the dashed terminal-dot, and milestone illustrations — are marked
aria-hidden. Headings and body text carry the semantics. - The component does not announce reveal stages over
aria-live. The flow is a one-shot reveal, not a queue of state changes; the static text is read whole once the page settles.
Credits
- Extracted from:
algoflashcards(src/platform/ui/onboarding/LaunchPhase.tsx). The source baked in a fixed 4-pattern algorithm curriculum (Two Pointers / BFS / Binary Search / DP), aTRACKStrack-color map, project-specificButton/GlowNode/SectionIllustrationprimitives, anIllustrationKeyenum, theuseTimershook, and inlineSPRING/ELEMENT_ENTER/PHASE_TRANSITIONmotion presets. craft-bits' version strips every project-specific dependency, turns the milestone list into a prop, swaps the inline springs forSPRINGS.smooth/SPRINGS.snapfrom@craft-bits/core/motion, swapsGlowNode/Buttonfor in-file CSS-only primitives, and exposes actaslot so callers can plug in their own action element.