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.

GreedyTrieIntervalsAnd more
Customize
Header copy
Timeline

Installation

npx shadcn@latest add https://craftbits.dev/r/launch-phase.json

Usage

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

  1. 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.
  2. Staged reveal. A stage integer advances through scheduled setTimeout ticks 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 is opacity + transform only.
  3. One animated SVG path. The rail is a single <motion.line> measured once via useLayoutEffect from the seed node center to the terminal-dot center. pathLength animates 0 → 1 with SPRINGS.smooth. The line lives behind every row and connects them visually.
  4. Per-milestone accent. Each milestone.accentColor paints the row's left border, the track label, and the numbered node ring. The component-level accentColor paints only the seed node and the rail line.
  5. Reduced motion. When prefers-reduced-motion: reduce is set, the initial stage jumps straight to the final value, every initial={...} collapses to false, and the SVG line renders fully drawn — nothing animates, nothing pops in.
  6. CTA slot vs default. Pass cta when you need a link, a form-submit button, or any element with its own state. Pass ctaLabel + onAdvance for the default 44px-tall accent button.

Props

PropTypeDefaultDescription
eyebrowReactNodeSmall accent line above the title.
titleReactNoderequiredPrimary headline. Rendered as <h2>.
subtitleReactNodeSecondary line under the headline.
detailReactNodeSmaller supporting line under the subtitle.
milestonesreadonly LaunchPhaseMilestone[]requiredOrdered rows on the timeline rail.
seedCaptionReactNode"Start here"Small uppercase label next to the seed node.
seedBodyReactNodeBody text next to the seed node.
futureBranchesreadonly ReactNode[]Chips under the dashed terminal-dot.
futureBranchCaptionReactNodeLine above the chip row.
ctaLabelReactNode"Begin"Default CTA button label.
onAdvance() => voidFires when the default CTA is clicked.
ctaReactNodeWhen set, replaces the default CTA entirely.
accentColorstringvar(--cb-accent)CSS color for the seed node and the rail line.
classNamestringMerged onto the outer <section> via cn().

LaunchPhaseMilestone

FieldTypeDescription
idstringStable identifier — used as the row's React key.
trackLabelstringUppercase track / category label rendered above the headline.
namestringRow headline. Rendered as <h3>.
summarystringSupporting sentence under the headline.
accentColorstringCSS color for this row's left border, label, and node ring. Defaults to var(--cb-accent).
illustrationReactNodeOptional 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: absolute overlap to confuse a screen reader.
  • The reveal sequence respects prefers-reduced-motion: the component jumps straight to the final layout and skips every initial animation.
  • The default CTA is a real <button type="button"> with min-height: 2.75rem (44px) hit area, an aria-label mirroring ctaLabel when 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), a TRACKS track-color map, project-specific Button / GlowNode / SectionIllustration primitives, an IllustrationKey enum, the useTimers hook, and inline SPRING / ELEMENT_ENTER / PHASE_TRANSITION motion presets. craft-bits' version strips every project-specific dependency, turns the milestone list into a prop, swaps the inline springs for SPRINGS.smooth / SPRINGS.snap from @craft-bits/core/motion, swaps GlowNode / Button for in-file CSS-only primitives, and exposes a cta slot so callers can plug in their own action element.