{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "project-planner",
  "title": "Project Planner",
  "description": "Multi-step project scoping planner UI.",
  "dependencies": [
    "lucide-react",
    "motion",
    "next"
  ],
  "registryDependencies": [
    "@atroui/utils"
  ],
  "files": [
    {
      "path": "registry/default/blocks/project-planner.tsx",
      "content": "\"use client\";\n\nimport { ArrowLeft, ArrowRight, CheckCircle2 } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useMemo, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype PlannerState = {\n  projectType: \"mvp\" | \"full\" | \"\";\n  features: string[];\n  aiRequired: boolean | null;\n  aiType: string;\n  budget: string;\n  timeline: string;\n  name: string;\n  email: string;\n};\n\nconst STEPS = [\n  \"Project type\",\n  \"Features\",\n  \"AI needs\",\n  \"Budget\",\n  \"Contact\",\n  \"Estimate\",\n] as const;\n\nconst FEATURE_OPTIONS = [\n  \"User authentication\",\n  \"Payments / billing\",\n  \"Admin dashboard\",\n  \"Real-time features\",\n  \"AI / ML feature\",\n  \"Mobile-responsive\",\n  \"Third-party integrations\",\n];\n\nconst AI_TYPES = [\n  \"Document processing\",\n  \"Chat / conversational\",\n  \"Content generation\",\n  \"Search / RAG\",\n  \"Classification / tagging\",\n];\n\nconst BUDGETS = [\n  { id: \"<2k\", label: \"Under $2k\" },\n  { id: \"2k-5k\", label: \"$2k - $5k\" },\n  { id: \"5k-10k\", label: \"$5k - $10k\" },\n  { id: \"10k+\", label: \"$10k+\" },\n];\n\nfunction estimateProject(state: PlannerState): {\n  service: string;\n  serviceId: string;\n  priceRange: string;\n  timeline: string;\n  summary: string;\n} {\n  const featureCount = state.features.length;\n  const hasAI =\n    state.aiRequired === true || state.features.includes(\"AI / ML feature\");\n\n  if (state.projectType === \"mvp\" && featureCount <= 3 && !hasAI) {\n    return {\n      service: \"7-Day MVP Sprint\",\n      serviceId: \"mvp-sprint\",\n      priceRange: \"$4,800\",\n      timeline: \"7 days\",\n      summary: \"A focused sprint to ship your core workflow fast.\",\n    };\n  }\n\n  if (hasAI && featureCount <= 4) {\n    return {\n      service: \"AI Feature Integration\",\n      serviceId: \"ai-integration\",\n      priceRange: \"$2,400 - $4,000\",\n      timeline: \"1-2 weeks\",\n      summary:\n        \"Add a well-designed AI feature to your existing or new product.\",\n    };\n  }\n\n  if (state.features.includes(\"Admin dashboard\") && featureCount >= 4) {\n    return {\n      service: \"Full-Stack Product Build\",\n      serviceId: \"full-stack-build\",\n      priceRange: \"$8,000 - $15,000\",\n      timeline: \"4-8 weeks\",\n      summary: \"End-to-end product development with multiple features.\",\n    };\n  }\n\n  if (state.projectType === \"mvp\") {\n    return {\n      service: \"7-Day MVP Sprint\",\n      serviceId: \"mvp-sprint\",\n      priceRange: \"$4,800 - $6,000\",\n      timeline: \"7-10 days\",\n      summary:\n        \"MVP sprint with scoped features - we'll narrow scope on the intro call.\",\n    };\n  }\n\n  return {\n    service: \"Full-Stack Product Build\",\n    serviceId: \"full-stack-build\",\n    priceRange: \"$8,000+\",\n    timeline: \"4-8 weeks\",\n    summary: \"A complete product build tailored to your requirements.\",\n  };\n}\n\nconst fieldInput = cn(\n  \"w-full border border-border-subtle bg-background px-3.5 py-2.5 text-base text-foreground sm:text-sm\",\n  \"placeholder:text-muted-foreground/60\",\n  \"transition-[border-color,box-shadow] duration-200\",\n  \"focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/20\",\n);\n\nfunction ChoiceButton({\n  pressed,\n  onClick,\n  children,\n  className,\n}: {\n  pressed: boolean;\n  onClick: () => void;\n  children: React.ReactNode;\n  className?: string;\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      aria-pressed={pressed}\n      className={cn(\n        \"border px-4 py-3 text-left transition-colors active:scale-[0.99]\",\n        pressed\n          ? \"border-brand bg-brand/8 text-foreground\"\n          : \"border-border-subtle bg-background text-muted-foreground hover:border-border hover:text-foreground\",\n        className,\n      )}\n    >\n      {children}\n    </button>\n  );\n}\n\nexport function ProjectPlanner() {\n  const router = useRouter();\n  const reduce = useReducedMotion();\n  const [step, setStep] = useState(0);\n  const [state, setState] = useState<PlannerState>({\n    projectType: \"\",\n    features: [],\n    aiRequired: null,\n    aiType: \"\",\n    budget: \"\",\n    timeline: \"\",\n    name: \"\",\n    email: \"\",\n  });\n\n  const estimate = useMemo(\n    () => (step === 5 ? estimateProject(state) : null),\n    [step, state],\n  );\n\n  const progress = ((step + 1) / STEPS.length) * 100;\n\n  const canNext = () => {\n    switch (step) {\n      case 0:\n        return !!state.projectType;\n      case 1:\n        return state.features.length > 0;\n      case 2:\n        return state.aiRequired !== null;\n      case 3:\n        return !!state.budget;\n      case 4:\n        return !!state.name.trim() && !!state.email.trim();\n      default:\n        return true;\n    }\n  };\n\n  const toggleFeature = (f: string) => {\n    setState((s) => ({\n      ...s,\n      features: s.features.includes(f)\n        ? s.features.filter((x) => x !== f)\n        : [...s.features, f],\n    }));\n  };\n\n  const goToContact = () => {\n    const params = new URLSearchParams({\n      service: estimate?.serviceId ?? \"\",\n      planner: \"1\",\n    });\n    router.push(`/contact?${params.toString()}`);\n  };\n\n  return (\n    <div className=\"grid grid-cols-1 lg:grid-cols-12\">\n      {/* Step rail */}\n      <aside className=\"hidden border-r border-border-subtle p-8 lg:col-span-4 lg:block lg:p-10\">\n        <p className=\"ms-stamp\">Steps</p>\n        <ol className=\"mt-6 space-y-1\">\n          {STEPS.map((label, i) => {\n            const done = i < step;\n            const active = i === step;\n            return (\n              <li\n                key={label}\n                className={cn(\n                  \"flex items-center gap-3 border-l-2 py-2.5 pl-4 text-sm\",\n                  active\n                    ? \"border-brand text-foreground\"\n                    : done\n                      ? \"border-brand/40 text-muted-foreground\"\n                      : \"border-transparent text-muted-foreground/50\",\n                )}\n              >\n                <span className=\"font-mono text-[11px] tabular-nums\">\n                  {String(i + 1).padStart(2, \"0\")}\n                </span>\n                <span className={cn(active && \"font-medium\")}>{label}</span>\n              </li>\n            );\n          })}\n        </ol>\n      </aside>\n\n      {/* Wizard */}\n      <div className=\"flex min-h-0 flex-col lg:col-span-8 lg:min-h-105\">\n        <div className=\"border-b border-border-subtle ms-shell-pad py-4\">\n          <div className=\"flex items-center justify-between gap-3\">\n            <p className=\"ds-mono-label\">\n              Step {step + 1} / {STEPS.length}\n            </p>\n            <p className=\"text-xs text-muted-foreground lg:hidden\">\n              {STEPS[step]}\n            </p>\n          </div>\n          <div\n            className=\"mt-3 h-px overflow-hidden bg-border-subtle\"\n            role=\"progressbar\"\n            aria-valuenow={Math.round(progress)}\n            aria-valuemin={0}\n            aria-valuemax={100}\n            aria-label={`Project planner progress: step ${step + 1} of ${STEPS.length}`}\n          >\n            <div\n              className=\"h-full bg-brand transition-[width] duration-300\"\n              style={{ width: `${progress}%` }}\n            />\n          </div>\n        </div>\n\n        <div className=\"flex flex-1 flex-col p-6 sm:p-8 lg:p-10\">\n          <AnimatePresence mode=\"wait\" initial={false}>\n            <motion.div\n              key={step}\n              className=\"flex flex-1 flex-col\"\n              initial={reduce ? false : { opacity: 0, y: 12 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={reduce ? undefined : { opacity: 0, y: -8 }}\n              transition={{ duration: 0.22, ease: [0.32, 0.72, 0, 1] }}\n            >\n          {step === 0 ? (\n            <div className=\"space-y-4\">\n              <div>\n                <h2 className=\"ds-headline text-2xl text-foreground\">\n                  What are you building?\n                </h2>\n                <p className=\"mt-2 text-sm text-muted-foreground\">\n                  MVP to validate fast, or a fuller product build?\n                </p>\n              </div>\n              <div className=\"grid gap-3 sm:grid-cols-2\">\n                {(\n                  [\n                    {\n                      id: \"mvp\" as const,\n                      label: \"MVP Sprint\",\n                      desc: \"Validate in 7-14 days\",\n                    },\n                    {\n                      id: \"full\" as const,\n                      label: \"Full Build\",\n                      desc: \"Complete product in 4-8 weeks\",\n                    },\n                  ] as const\n                ).map((opt) => (\n                  <ChoiceButton\n                    key={opt.id}\n                    pressed={state.projectType === opt.id}\n                    onClick={() =>\n                      setState((s) => ({ ...s, projectType: opt.id }))\n                    }\n                    className=\"p-5\"\n                  >\n                    <div className=\"font-medium text-foreground\">\n                      {opt.label}\n                    </div>\n                    <div className=\"mt-1 text-xs text-muted-foreground\">\n                      {opt.desc}\n                    </div>\n                  </ChoiceButton>\n                ))}\n              </div>\n            </div>\n          ) : null}\n\n          {step === 1 ? (\n            <div className=\"space-y-4\">\n              <div>\n                <h2 className=\"ds-headline text-2xl text-foreground\">\n                  Key features needed\n                </h2>\n                <p className=\"mt-2 text-sm text-muted-foreground\">\n                  Select all that apply.\n                </p>\n              </div>\n              <div className=\"flex flex-wrap gap-2\">\n                {FEATURE_OPTIONS.map((f) => (\n                  <ChoiceButton\n                    key={f}\n                    pressed={state.features.includes(f)}\n                    onClick={() => toggleFeature(f)}\n                    className=\"rounded-md px-3.5 py-2 text-sm\"\n                  >\n                    {f}\n                  </ChoiceButton>\n                ))}\n              </div>\n            </div>\n          ) : null}\n\n          {step === 2 ? (\n            <div className=\"space-y-4\">\n              <div>\n                <h2 className=\"ds-headline text-2xl text-foreground\">\n                  AI features required?\n                </h2>\n                <p className=\"mt-2 text-sm text-muted-foreground\">\n                  Streaming chat, document AI, RAG - or none for now.\n                </p>\n              </div>\n              <div className=\"grid grid-cols-2 gap-3\">\n                {[true, false].map((val) => (\n                  <ChoiceButton\n                    key={String(val)}\n                    pressed={state.aiRequired === val}\n                    onClick={() =>\n                      setState((s) => ({ ...s, aiRequired: val }))\n                    }\n                    className=\"p-4 text-center text-sm font-medium\"\n                  >\n                    {val ? \"Yes\" : \"No\"}\n                  </ChoiceButton>\n                ))}\n              </div>\n              {state.aiRequired ? (\n                <div className=\"border-t border-border-subtle pt-4\">\n                  <p className=\"text-xs font-medium text-foreground\">\n                    AI integration type\n                  </p>\n                  <div className=\"mt-2 flex flex-wrap gap-2\">\n                    {AI_TYPES.map((t) => (\n                      <ChoiceButton\n                        key={t}\n                        pressed={state.aiType === t}\n                        onClick={() => setState((s) => ({ ...s, aiType: t }))}\n                        className=\"min-h-10 rounded-md px-3.5 py-2 text-xs\"\n                      >\n                        {t}\n                      </ChoiceButton>\n                    ))}\n                  </div>\n                </div>\n              ) : null}\n            </div>\n          ) : null}\n\n          {step === 3 ? (\n            <div className=\"space-y-4\">\n              <div>\n                <h2 className=\"ds-headline text-2xl text-foreground\">\n                  Budget range\n                </h2>\n                <p className=\"mt-2 text-sm text-muted-foreground\">\n                  Ballpark only - we confirm on the call.\n                </p>\n              </div>\n              <div className=\"divide-y divide-border-subtle border-y border-border-subtle\">\n                {BUDGETS.map((b) => (\n                  <button\n                    key={b.id}\n                    type=\"button\"\n                    onClick={() => setState((s) => ({ ...s, budget: b.id }))}\n                    aria-pressed={state.budget === b.id}\n                    className={cn(\n                      \"flex w-full items-center justify-between px-1 py-4 text-left text-sm transition-colors active:scale-[0.99]\",\n                      state.budget === b.id\n                        ? \"font-medium text-foreground\"\n                        : \"text-muted-foreground hover:text-foreground\",\n                    )}\n                  >\n                    <span>{b.label}</span>\n                    {state.budget === b.id ? (\n                      <span className=\"size-1.5 rounded-full bg-brand\" aria-hidden />\n                    ) : null}\n                  </button>\n                ))}\n              </div>\n            </div>\n          ) : null}\n\n          {step === 4 ? (\n            <div className=\"space-y-5\">\n              <div>\n                <h2 className=\"ds-headline text-2xl text-foreground\">\n                  Almost there\n                </h2>\n                <p className=\"mt-2 text-sm text-muted-foreground\">\n                  So we can send the estimate and follow up.\n                </p>\n              </div>\n              <div className=\"flex flex-col gap-2\">\n                <label\n                  htmlFor=\"planner-name\"\n                  className=\"text-xs font-medium text-foreground\"\n                >\n                  Name\n                </label>\n                <input\n                  id=\"planner-name\"\n                  type=\"text\"\n                  value={state.name}\n                  onChange={(e) =>\n                    setState((s) => ({ ...s, name: e.target.value }))\n                  }\n                  className={fieldInput}\n                  placeholder=\"Your name\"\n                  autoComplete=\"name\"\n                  required\n                />\n              </div>\n              <div className=\"flex flex-col gap-2\">\n                <label\n                  htmlFor=\"planner-email\"\n                  className=\"text-xs font-medium text-foreground\"\n                >\n                  Email\n                </label>\n                <input\n                  id=\"planner-email\"\n                  type=\"email\"\n                  value={state.email}\n                  onChange={(e) =>\n                    setState((s) => ({ ...s, email: e.target.value }))\n                  }\n                  className={fieldInput}\n                  placeholder=\"you@company.com\"\n                  autoComplete=\"email\"\n                  required\n                />\n              </div>\n            </div>\n          ) : null}\n\n          {step === 5 && estimate ? (\n            <div className=\"flex flex-col gap-8\">\n              <div>\n                <div className=\"mb-4 flex size-10 items-center justify-center rounded-lg border border-border-subtle bg-background text-brand\">\n                  <CheckCircle2 className=\"size-5\" aria-hidden />\n                </div>\n                <p className=\"ms-stamp\">Recommendation</p>\n                <h2 className=\"ds-headline mt-3 text-2xl text-foreground sm:text-3xl\">\n                  {estimate.service}\n                </h2>\n                <p className=\"mt-3 max-w-md text-sm leading-relaxed text-muted-foreground\">\n                  {estimate.summary}\n                </p>\n              </div>\n\n              <dl className=\"grid grid-cols-1 divide-y divide-border-subtle border-y border-border-subtle sm:grid-cols-2 sm:divide-x sm:divide-y-0\">\n                <div className=\"py-5 sm:pr-6\">\n                  <dt className=\"text-xs text-muted-foreground\">\n                    Estimated price\n                  </dt>\n                  <dd className=\"ds-display mt-2 text-xl break-words text-foreground sm:text-2xl md:text-3xl\">\n                    {estimate.priceRange}\n                  </dd>\n                </div>\n                <div className=\"py-5 sm:pl-6\">\n                  <dt className=\"text-xs text-muted-foreground\">Timeline</dt>\n                  <dd className=\"ds-display mt-2 text-xl text-foreground sm:text-2xl md:text-3xl\">\n                    {estimate.timeline}\n                  </dd>\n                </div>\n              </dl>\n\n              <p className=\"text-xs text-muted-foreground\">\n                Ballpark only - final scope and price confirmed on the intro\n                call.\n              </p>\n\n              <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n                <button type=\"button\" onClick={goToContact} className=\"ms-cta\">\n                  Continue to contact\n                  <ArrowRight className=\"size-4\" aria-hidden />\n                </button>\n                <Link\n                  href={`/services/${estimate.serviceId}`}\n                  className=\"ms-cta-ghost\"\n                >\n                  View service details\n                </Link>\n              </div>\n            </div>\n          ) : null}\n\n          {step < 5 ? (\n            <div className=\"mt-auto flex items-center justify-between gap-3 border-t border-border-subtle pt-6\">\n              <button\n                type=\"button\"\n                onClick={() => setStep((s) => Math.max(0, s - 1))}\n                disabled={step === 0}\n                className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground disabled:opacity-40\"\n              >\n                <ArrowLeft className=\"size-3.5\" aria-hidden />\n                Back\n              </button>\n              <button\n                type=\"button\"\n                onClick={() => setStep((s) => s + 1)}\n                disabled={!canNext()}\n                className=\"ms-cta min-w-[9.5rem] justify-center disabled:pointer-events-none disabled:opacity-50\"\n              >\n                <AnimatePresence mode=\"wait\" initial={false}>\n                  <motion.span\n                    key={step === 4 ? \"estimate\" : \"continue\"}\n                    className=\"inline-flex items-center gap-1.5\"\n                    initial={reduce ? false : { opacity: 0, y: 4 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    exit={reduce ? undefined : { opacity: 0, y: -4 }}\n                    transition={{ duration: 0.16 }}\n                  >\n                    {step === 4 ? \"See estimate\" : \"Continue\"}\n                    <ArrowRight className=\"size-4\" aria-hidden />\n                  </motion.span>\n                </AnimatePresence>\n              </button>\n            </div>\n          ) : null}\n            </motion.div>\n          </AnimatePresence>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/blocks/project-planner.tsx"
    }
  ],
  "type": "registry:block"
}