{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "roadmap-timeline",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "roadmap-timeline.tsx",
      "target": "components/ui/roadmap-timeline.tsx",
      "type": "registry:ui",
      "content": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type RoadmapTone = \"done\" | \"current\" | \"planned\";\n\nexport interface RoadmapItem {\n  label: React.ReactNode;\n  /** Lifts the item out of the list and onto its own accent card. */\n  featured?: boolean;\n}\n\nexport interface RoadmapMilestone {\n  /** Mono chip in the top left. A version, a quarter, a date. */\n  version?: React.ReactNode;\n  /** Pill in the top right. The word for where this milestone stands. */\n  status?: React.ReactNode;\n  title: React.ReactNode;\n  body?: React.ReactNode;\n  items?: RoadmapItem[];\n  tone?: RoadmapTone;\n}\n\nexport interface RoadmapTimelineProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  milestones: RoadmapMilestone[];\n  /** Replaces the tick drawn inside a shipped item's marker. */\n  markerIcon?: React.ReactNode;\n  headingLevel?: \"h2\" | \"h3\" | \"h4\";\n  /** Hides the connector track above the cards. */\n  hideTrack?: boolean;\n}\n\nconst cardTones: Record<RoadmapTone, string> = {\n  done: \"border-border bg-ink/[0.03]\",\n  current:\n    \"border-accent/25 bg-accent/[0.05] shadow-[0_24px_60px_-40px_var(--color-accent)]\",\n  planned: \"border-border/60 bg-ink/[0.02]\",\n};\n\nconst statusTones: Record<RoadmapTone, string> = {\n  done: \"bg-ink/[0.06] text-muted before:bg-muted\",\n  current: \"bg-accent/15 text-accent before:bg-accent\",\n  planned: \"bg-ink/[0.04] text-muted before:bg-faint\",\n};\n\nconst nodeTones: Record<RoadmapTone, string> = {\n  done: \"border-accent/40 bg-accent/30\",\n  current:\n    \"border-accent bg-accent shadow-[0_0_0_4px_color-mix(in_oklab,var(--color-accent)_18%,transparent)]\",\n  planned: \"border-border-strong bg-sunken\",\n};\n\nconst titleTones: Record<RoadmapTone, string> = {\n  done: \"text-ink\",\n  current: \"text-ink\",\n  planned: \"text-muted\",\n};\n\nconst bodyTones: Record<RoadmapTone, string> = {\n  done: \"text-muted\",\n  current: \"text-muted\",\n  planned: \"text-muted\",\n};\n\nconst itemTones: Record<RoadmapTone, string> = {\n  done: \"text-muted\",\n  current: \"text-muted\",\n  planned: \"text-muted\",\n};\n\nconst markerTones: Record<RoadmapTone, string> = {\n  done: \"border-accent/40 text-accent\",\n  current: \"border-accent/40 text-accent\",\n  planned: \"border-border-strong text-transparent\",\n};\n\n/** The node the filled part of the track runs to: the current one, else the last shipped one. */\nfunction progressIndex(milestones: RoadmapMilestone[]): number {\n  const current = milestones.findIndex((m) => m.tone === \"current\");\n  if (current !== -1) return current;\n  return milestones.reduce(\n    (last, milestone, index) => (milestone.tone === \"done\" ? index : last),\n    -1,\n  );\n}\n\nconst center = (index: number, count: number): string =>\n  `${((index + 0.5) / count) * 100}%`;\n\nexport const RoadmapTimeline = React.forwardRef<\n  HTMLDivElement,\n  RoadmapTimelineProps\n>(\n  (\n    {\n      className,\n      milestones,\n      markerIcon,\n      headingLevel: Heading = \"h3\",\n      hideTrack = false,\n      style,\n      ...props\n    },\n    ref,\n  ) => {\n    const count = milestones.length;\n    const reached = progressIndex(milestones);\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"@container relative flex flex-col\", className)}\n        style={{ \"--roadmap-columns\": count, ...style } as React.CSSProperties}\n        {...props}\n      >\n        {!hideTrack && count > 1 && (\n          <div\n            data-roadmap-track=\"\"\n            aria-hidden=\"true\"\n            className=\"relative mb-3 hidden h-3 w-full @2xl:block\"\n          >\n            <div\n              className=\"absolute top-1/2 h-0.5 -translate-y-1/2 bg-[repeating-linear-gradient(90deg,var(--color-border-strong)_0,var(--color-border-strong)_7px,transparent_7px,transparent_16px)]\"\n              style={{ left: center(0, count), right: center(0, count) }}\n            />\n            {reached > 0 && (\n              <div\n                data-roadmap-track-fill=\"\"\n                className=\"absolute top-1/2 h-0.5 -translate-y-1/2 bg-[linear-gradient(90deg,color-mix(in_oklab,var(--color-accent)_25%,transparent),var(--color-accent))]\"\n                style={{\n                  left: center(0, count),\n                  right: `calc(100% - ${center(reached, count)})`,\n                }}\n              />\n            )}\n            {milestones.map((milestone, index) => (\n              <span\n                // biome-ignore lint/suspicious/noArrayIndexKey: nodes are positional, and the list is static\n                key={index}\n                className={cn(\n                  \"-translate-x-1/2 -translate-y-1/2 absolute top-1/2 size-3 rounded-full border\",\n                  nodeTones[milestone.tone ?? \"planned\"],\n                )}\n                style={{ left: center(index, count) }}\n              />\n            ))}\n          </div>\n        )}\n\n        <ol className=\"grid grid-cols-1 gap-4 @2xl:grid-cols-[repeat(var(--roadmap-columns),minmax(0,1fr))]\">\n          {milestones.map((milestone, index) => {\n            const tone = milestone.tone ?? \"planned\";\n            return (\n              <li\n                // biome-ignore lint/suspicious/noArrayIndexKey: milestones are positional, and the list is static\n                key={index}\n                className={cn(\n                  \"flex flex-col rounded-2xl border p-6 transition-control duration-fast ease-soft\",\n                  \"hover:-translate-y-1 motion-reduce:transform-none motion-reduce:transition-none\",\n                  cardTones[tone],\n                )}\n              >\n                {(milestone.version != null || milestone.status != null) && (\n                  <div className=\"mb-5 flex items-center justify-between gap-4\">\n                    {milestone.version != null && (\n                      <span className=\"inline-flex rounded-lg border border-accent/25 bg-accent/[0.08] px-3 py-2 font-mono font-extrabold text-ink text-xs\">\n                        {milestone.version}\n                      </span>\n                    )}\n                    {milestone.status != null && (\n                      <span\n                        className={cn(\n                          \"ml-auto inline-flex items-center gap-2 rounded-full px-3 py-1 font-bold text-[0.72rem] before:size-1.5 before:rounded-full before:content-['']\",\n                          statusTones[tone],\n                        )}\n                      >\n                        {milestone.status}\n                      </span>\n                    )}\n                  </div>\n                )}\n\n                <Heading\n                  className={cn(\n                    \"font-bold leading-tight\",\n                    titleTones[tone],\n                    tone === \"current\"\n                      ? \"text-[clamp(1.35rem,2.4cqi,1.8rem)]\"\n                      : \"text-[clamp(1.22rem,2cqi,1.55rem)]\",\n                  )}\n                >\n                  {milestone.title}\n                </Heading>\n\n                {milestone.body != null && (\n                  <p className={cn(\"mt-2 text-sm leading-6\", bodyTones[tone])}>\n                    {milestone.body}\n                  </p>\n                )}\n\n                {milestone.items != null && milestone.items.length > 0 && (\n                  <ul className=\"mt-5 grid gap-3 text-[0.92rem]\">\n                    {milestone.items.map((item, itemIndex) => (\n                      <li\n                        // biome-ignore lint/suspicious/noArrayIndexKey: items are positional, and the list is static\n                        key={itemIndex}\n                        className={cn(\n                          \"flex items-start gap-2 leading-6\",\n                          item.featured\n                            ? \"rounded-[0.62rem] border border-accent/30 bg-accent/[0.12] px-3 py-2 font-extrabold text-ink\"\n                            : itemTones[tone],\n                        )}\n                      >\n                        {!item.featured && (\n                          <span\n                            aria-hidden=\"true\"\n                            className={cn(\n                              \"mt-1 grid size-[1.1rem] shrink-0 place-items-center rounded-[0.28rem] border-[1.5px] [&_svg]:size-3\",\n                              markerTones[tone],\n                            )}\n                          >\n                            {tone !== \"planned\" &&\n                              (markerIcon ?? <CheckIcon />)}\n                          </span>\n                        )}\n                        {item.label}\n                      </li>\n                    ))}\n                  </ul>\n                )}\n              </li>\n            );\n          })}\n        </ol>\n      </div>\n    );\n  },\n);\nRoadmapTimeline.displayName = \"RoadmapTimeline\";\n\nfunction CheckIcon() {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"3\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"M20 6 9 17l-5-5\" />\n    </svg>\n  );\n}\n"
    }
  ]
}