{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "split-stepper",
  "title": "Split Stepper",
  "author": "AlgorizeTech",
  "description": "A two-column stepper with a sticky side panel and stacked cards that advance as you scroll. Use it for onboarding and feature walkthroughs on a landing page.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/SplitStepperBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ArrowRight, CheckCircle2 } from \"lucide-react\";\nimport React, { useRef, useState } from \"react\";\n\n// ==========================================\n// CARD DECK MOTION (CSS transitions)\n// ==========================================\n/** GSAP's power3.out, sampled into a CSS `linear()` curve. */\nconst EASE_POWER3_OUT =\n  \"linear(0, .0963, .1855, .2679, .3439, .4138, .478, .5367, .5904, .6392, .6836, .7237, .7599, .7924, .8215, .8474, .8704, .8907, .9085, .924, .9375, .9491, .959, .9674, .9744, .9802, .985, .9888, .9919, .9943, .9961, .9974, .9984, .9991, .9995, .9998, .9999, 1, 1, 1, 1)\";\n\n/** The card also carries `transition-shadow`; its transition is kept alongside the deck motion. */\nconst CARD_CLASS_TRANSITION = [\"box-shadow\"];\n\n/**\n * Where a card sits in the deck — stacked behind the active card, at the\n * front, or waiting below — as a style that transitions there from wherever\n * the card is now.\n */\nfunction deckCardStyle(\n  idx: number,\n  active: number,\n  count: number,\n  cardOffset: number,\n  cardDepthScale: number,\n  upcomingY: number,\n): React.CSSProperties {\n  let y = 0;\n  let scale = 1;\n  let opacity = 1;\n  let zIndex = count + 10;\n  let duration = 0.6;\n\n  if (idx < active) {\n    // Stacked behind in the deck\n    const depth = active - idx;\n    y = -depth * cardOffset;\n    scale = 1 - depth * cardDepthScale;\n    opacity = Math.max(0.2, 1 - depth * 0.25);\n    zIndex = idx;\n  } else if (idx > active) {\n    // Upcoming, hidden below\n    y = upcomingY;\n    scale = 0.95;\n    opacity = 0;\n    zIndex = idx;\n    duration = 0.5;\n  }\n\n  const deck = [\"transform\", \"opacity\", \"z-index\"];\n  return {\n    transform: `translateY(${y}px) scale(${scale})`,\n    opacity,\n    zIndex,\n    transitionProperty: [...deck, ...CARD_CLASS_TRANSITION].join(\", \"),\n    transitionDuration: [\n      ...deck.map(() => `${duration}s`),\n      ...CARD_CLASS_TRANSITION.map(\n        () => \"var(--tw-duration, var(--default-transition-duration, 150ms))\",\n      ),\n    ].join(\", \"),\n    transitionTimingFunction: [\n      ...deck.map(() => EASE_POWER3_OUT),\n      ...CARD_CLASS_TRANSITION.map(\n        () =>\n          \"var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1)))\",\n      ),\n    ].join(\", \"),\n  };\n}\n\nexport interface SplitStep {\n  id: string;\n  stepNumber: string;\n  title: string;\n  description: string;\n  tags?: string[];\n  cardContent: React.ReactNode;\n  color?: string;\n}\n\nexport interface SplitStepperProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  steps: SplitStep[];\n  stickySide?: \"left\" | \"right\";\n  cardDepthScale?: number;\n  cardOffset?: number;\n  /** Hex accent for the active step. Defaults to the theme's primary. */\n  color?: string;\n  onStepChange?: (index: number) => void;\n}\n\nexport const SplitStepper = React.forwardRef<HTMLDivElement, SplitStepperProps>(\n  (\n    {\n      steps,\n      stickySide = \"right\",\n      cardDepthScale = 0.05,\n      cardOffset = 16,\n      color,\n      className,\n      onStepChange,\n      ...props\n    },\n    ref,\n  ) => {\n    const [activeStep, setActiveStep] = useState(0);\n    const listRefs = useRef<(HTMLDivElement | null)[]>([]);\n    const accent = color ?? \"var(--primary)\";\n    const isHex = !color || color.startsWith(\"#\");\n\n    const handleStepSelect = (index: number) => {\n      setActiveStep(index);\n      onStepChange?.(index);\n      listRefs.current[index]?.scrollIntoView({\n        behavior: \"smooth\",\n        block: \"nearest\",\n      });\n    };\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-12 w-full relative items-start\",\n          className,\n        )}\n        {...props}\n      >\n        {/* NARRATIVE LIST - Full width on Mobile */}\n        <div\n          className={cn(\n            \"flex flex-col gap-4 relative z-10\",\n            stickySide === \"left\"\n              ? \"md:order-2 md:col-span-6 lg:col-span-7\"\n              : \"md:order-1 md:col-span-6 lg:col-span-7\",\n          )}\n        >\n          {steps.map((step, index) => {\n            const isActive = activeStep === index;\n            const isCompleted = activeStep > index;\n\n            return (\n              <div\n                key={step.id}\n                ref={(el) => {\n                  listRefs.current[index] = el;\n                }}\n                onClick={() => handleStepSelect(index)}\n                className={cn(\n                  \"group relative rounded-3xl p-5 sm:p-8 border transition-all duration-300 cursor-pointer select-none overflow-hidden\",\n                  isActive\n                    ? \"bg-card shadow-xl border-transparent\"\n                    : \"bg-muted/60 border-border/80 hover:bg-card hover:border-border\",\n                )}\n                style={{\n                  borderColor: isActive && isHex ? accent : undefined,\n                }}\n              >\n                {isActive && (\n                  <div\n                    className=\"absolute top-0 bottom-0 left-0 w-1.5 transition-colors duration-300\"\n                    style={{ backgroundColor: isHex ? accent : undefined }}\n                  />\n                )}\n\n                <div className=\"flex items-start justify-between gap-4\">\n                  <div className=\"flex items-center gap-3.5\">\n                    <span\n                      className={cn(\n                        \"w-9 h-9 sm:w-10 sm:h-10 rounded-xl flex items-center justify-center font-mono font-semibold text-sm border transition-colors duration-300\",\n                        isActive\n                          ? cn(color ? \"text-white\" : \"text-primary-foreground\", \"border-transparent shadow-md\")\n                          : isCompleted\n                            ? \"bg-muted text-foreground border-transparent\"\n                            : \"bg-muted text-muted-foreground border-border\",\n                      )}\n                      style={{\n                        backgroundColor: isActive && isHex ? accent : undefined,\n                      }}\n                    >\n                      {isCompleted ? (\n                        <CheckCircle2 className=\"w-4 h-4 sm:w-5 sm:h-5\" />\n                      ) : (\n                        step.stepNumber\n                      )}\n                    </span>\n\n                    <h3\n                      className={cn(\n                        \"text-lg sm:text-2xl font-semibold tracking-tight transition-colors duration-300\",\n                        isActive\n                          ? \"text-foreground\"\n                          : \"text-muted-foreground group-hover:text-foreground\",\n                      )}\n                    >\n                      {step.title}\n                    </h3>\n                  </div>\n\n                  <div\n                    className={cn(\n                      \"w-8 h-8 sm:w-9 sm:h-9 rounded-full flex items-center justify-center transition-all duration-300 flex-shrink-0\",\n                      isActive\n                        ? \"translate-x-0 opacity-100 shadow-sm\"\n                        : \"-translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-40\",\n                    )}\n                    style={{\n                      backgroundColor: isActive ? `color-mix(in oklab, ${accent} 8%, transparent)` : \"transparent\",\n                      color: isActive ? accent : \"currentColor\",\n                    }}\n                  >\n                    <ArrowRight className=\"w-4 h-4 sm:w-5 sm:h-5\" />\n                  </div>\n                </div>\n\n                <p\n                  className={cn(\n                    \"mt-4 text-sm sm:text-base leading-relaxed transition-colors duration-300 pl-[48px] sm:pl-[54px]\",\n                    isActive\n                      ? \"text-muted-foreground\"\n                      : \"text-muted-foreground\",\n                  )}\n                >\n                  {step.description}\n                </p>\n\n                {step.tags && step.tags.length > 0 && (\n                  <div className=\"flex flex-wrap gap-2 mt-6 pl-[48px] sm:pl-[54px]\">\n                    {step.tags.map((tag, tIdx) => (\n                      <span\n                        key={tIdx}\n                        className={cn(\n                          \"px-3 py-1 rounded-lg text-[11px] sm:text-xs font-semibold uppercase tracking-wider border transition-all duration-300\",\n                          isActive\n                            ? \"border-transparent shadow-xs\"\n                            : \"bg-transparent border-border text-muted-foreground/70\",\n                        )}\n                        style={{\n                          backgroundColor: isActive ? `color-mix(in oklab, ${accent} 7%, transparent)` : undefined,\n                          color: isActive ? accent : undefined,\n                        }}\n                      >\n                        {tag}\n                      </span>\n                    ))}\n                  </div>\n                )}\n              </div>\n            );\n          })}\n        </div>\n\n        {/* VISUAL STACK - HIDDEN ON MOBILE (hidden md:block) */}\n        <div\n          className={cn(\n            \"hidden md:block w-full sticky z-20 top-8 h-[450px] lg:h-[500px]\",\n            stickySide === \"left\"\n              ? \"md:order-1 md:col-span-6 lg:col-span-5\"\n              : \"md:order-2 md:col-span-6 lg:col-span-5\",\n          )}\n        >\n          <div className=\"relative w-full h-full max-w-lg mx-auto\">\n            {steps.map((step, index) => {\n              return (\n                <div\n                  key={step.id}\n                  className={cn(\n                    \"absolute inset-0 w-full h-full rounded-[2.5rem] p-8 border overflow-hidden shadow-2xl flex flex-col justify-between origin-top transition-shadow\",\n                    step.color ||\n                      \"bg-zinc-900 text-white border-white/10 dark:border-zinc-800\",\n                  )}\n                  style={deckCardStyle(\n                    index,\n                    activeStep,\n                    steps.length,\n                    cardOffset,\n                    cardDepthScale,\n                    120,\n                  )}\n                >\n                  <div className=\"absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/30 to-transparent pointer-events-none\" />\n\n                  <div className=\"flex items-center justify-between relative z-10 mb-4\">\n                    <span className=\"px-3 py-1 rounded-full bg-white/10 backdrop-blur-md text-[11px] font-mono font-semibold tracking-widest uppercase border border-white/15\">\n                      STEP // {step.stepNumber}\n                    </span>\n                    <div\n                      className=\"w-2.5 h-2.5 rounded-full animate-ping\"\n                      style={{ backgroundColor: accent }}\n                    />\n                  </div>\n\n                  <div className=\"relative z-10 flex-1 flex flex-col justify-center my-2 overflow-hidden\">\n                    {step.cardContent}\n                  </div>\n                </div>\n              );\n            })}\n          </div>\n        </div>\n      </div>\n    );\n  },\n);\nSplitStepper.displayName = \"SplitStepper\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/SplitStepperBlock.tsx"
    }
  ],
  "docs": "Named export: SplitStepper. `stickySide` places the pinned column left or right; `cardDepthScale` and `cardOffset` tune the stacked-card depth effect. Needs real page height to scroll through — it will look inert inside a short container.",
  "categories": [
    "navigation",
    "scroll",
    "marketing"
  ],
  "type": "registry:component"
}
