{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sticky-scroll-stack-motion",
  "title": "Sticky Scroll Stack (Motion)",
  "author": "AlgorizeTech",
  "description": "Ask the user to pick one of 6 scroll stack variants: https://avaraui.com/view/scroll-stack. This is the Sticky scroll stack. Skip the question if the user already named one. A sticky section whose panels swap content as you scroll, supporting list, image, split and custom layouts. Use it for feature sections with varied content per step.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/motion/StickyScrollStackBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { motion, useScroll, useTransform } from \"motion/react\";\nimport {\n  ArrowRight,\n  CheckCircle2,\n  Code2,\n  Layers,\n  Palette,\n  Smartphone,\n  Sparkles,\n  TrendingUp,\n} from \"lucide-react\";\nimport React, { useRef } from \"react\";\n\nexport interface ScrollStackItem {\n  id: string;\n  title: string;\n  badge?: string;\n  icon?: React.ReactNode;\n  contentType: \"list\" | \"image\" | \"split\" | \"custom\" | (string & {});\n  description?: string;\n  listItems?: string[];\n  imageUrl?: string;\n  imageAlt?: string;\n  buttonText?: string;\n  onButtonClick?: () => void;\n}\n\nexport interface StickyScrollStackProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  headerBadge?: string;\n  headerTitle?: string;\n  headerSubtitle?: string;\n  headerButtonText?: string;\n  onHeaderButtonClick?: () => void;\n  headerContentType?: \"text-only\" | \"text-button\" | \"image-button\" | \"custom\";\n  headerImageUrl?: string;\n  headerImageAlt?: string;\n  headerCustomContent?: React.ReactNode;\n  items?: ScrollStackItem[];\n  stackOffsetStep?: number;\n  scaleStep?: number;\n  enableGlow?: boolean;\n}\n\nconst defaultStackItems: ScrollStackItem[] = [\n  {\n    id: \"ui-design\",\n    title: \"UI Design\",\n    badge: \"Visual Identity\",\n    icon: <Palette className=\"w-5 h-5 text-[var(--theme-color,var(--primary))]\" />,\n    contentType: \"list\",\n    description:\n      \"Crafting intuitive, pixel-perfect interfaces designed to engage and convert real audiences.\",\n    listItems: [\n      \"Website / Landing Pages\",\n      \"Mobile Apps (iOS & Android)\",\n      \"Web App / SaaS Dashboards\",\n      \"Comprehensive Design Systems\",\n    ],\n    buttonText: \"View UI Showcase\",\n  },\n  {\n    id: \"ux-design\",\n    title: \"UX Design\",\n    badge: \"User Research\",\n    icon: <Layers className=\"w-5 h-5 text-[var(--theme-color,var(--primary))]\" />,\n    contentType: \"list\",\n    description:\n      \"Data-driven architecture mapping out frictionless customer journeys.\",\n    listItems: [\n      \"In-depth User Research\",\n      \"Userflow & Wireframing\",\n      \"Interactive Prototyping\",\n      \"UX Audit & Heuristic Evaluation\",\n    ],\n  },\n  {\n    id: \"branding\",\n    title: \"Branding & Identity\",\n    badge: \"Brand Strategy\",\n    icon: <Sparkles className=\"w-5 h-5 text-[var(--theme-color,var(--primary))]\" />,\n    contentType: \"split\",\n    description:\n      \"Building memorable, iconic brand identities that stand out in crowded markets.\",\n    imageUrl:\n      \"https://res.cloudinary.com/dodndg7ai/image/upload/f_webp,q_auto,w_800,c_limit/v1790146209/photo-1470071459604-3b5ec3a7fe05-1790146212205.jpg\",\n    imageAlt: \"Misty mountain forest nature landscape\",\n    buttonText: \"Explore Branding\",\n  },\n  {\n    id: \"webflow-dev\",\n    title: \"Webflow Development\",\n    badge: \"No-Code / Low-Code\",\n    icon: <Code2 className=\"w-5 h-5 text-[var(--theme-color,var(--primary))]\" />,\n    contentType: \"list\",\n    description:\n      \"High-performance, enterprise-grade Webflow websites built for marketing speed.\",\n    listItems: [\n      \"Custom Figma to Webflow Conversion\",\n      \"CMS & Dynamic Content Architecture\",\n      \"E-Commerce Solutions\",\n      \"Technical SEO Optimization\",\n    ],\n  },\n  {\n    id: \"fullstack-dev\",\n    title: \"Full-Stack Development\",\n    badge: \"Engineering\",\n    icon: <Smartphone className=\"w-5 h-5 text-[var(--theme-color,var(--primary))]\" />,\n    contentType: \"image\",\n    description:\n      \"Scalable, secure web applications built with modern React and Next.js frameworks.\",\n    imageUrl:\n      \"https://res.cloudinary.com/dodndg7ai/image/upload/f_webp,q_auto,w_800,c_limit/v1790146209/photo-1469474968028-56623f02e42e-1790146212205.jpg\",\n    imageAlt: \"Golden mountain valley sunset nature landscape\",\n    buttonText: \"Tech Stack Specs\",\n  },\n  {\n    id: \"digital-marketing\",\n    title: \"Digital Marketing\",\n    badge: \"Growth\",\n    icon: <TrendingUp className=\"w-5 h-5 text-[var(--theme-color,var(--primary))]\" />,\n    contentType: \"list\",\n    description:\n      \"Strategic acquisition and retention campaigns tailored for sustainable ROI.\",\n    listItems: [\n      \"Search Engine Optimization (SEO)\",\n      \"Content Marketing & Strategy\",\n      \"Paid Advertising (PPC & Social)\",\n      \"Conversion Rate Optimization (CRO)\",\n    ],\n    buttonText: \"Get Growth Audit\",\n  },\n];\n\nconst StackCard: React.FC<{\n  item: ScrollStackItem;\n  index: number;\n  total: number;\n  stackOffsetStep: number;\n  scaleStep: number;\n}> = ({ item, index, total, stackOffsetStep, scaleStep }) => {\n  const cardRef = useRef<HTMLDivElement>(null);\n\n  // PRECISION FIX: Track scroll progress relative to this specific card's top edge hitting the sticky boundary.\n  // This guarantees the card NEVER scales down or dims while moving up the screen before pinning.\n  const { scrollYProgress } = useScroll({\n    target: cardRef,\n    offset: [\"start start\", \"end start\"],\n  });\n\n  const isLast = index === total - 1;\n  const targetScale = isLast ? 1 : 1 - (total - index - 1) * scaleStep;\n  const targetOpacity = isLast ? 1 : 0.4;\n\n  const scale = useTransform(\n    scrollYProgress,\n    [0, 1],\n    [1, Math.max(0.85, targetScale)],\n  );\n  const opacity = useTransform(scrollYProgress, [0, 1], [1, targetOpacity]);\n\n  return (\n    // The wrapper is row-height (see the grid below) and the face fills it,\n    // so every card is as tall as the tallest: the card on top always covers\n    // the ones behind, leaving only their headers peeking out.\n    <div\n      ref={cardRef}\n      className=\"sticky w-full flex flex-col\"\n      style={{ top: `calc(5rem + ${index * stackOffsetStep}px)` }}\n    >\n      <motion.div\n        style={{ scale, opacity }}\n        className=\"flex-1 flex flex-col w-full origin-top rounded-2xl md:rounded-3xl bg-card border border-border/80 p-5 sm:p-6 md:p-7 shadow-xl shadow-zinc-200/50 dark:shadow-black/50 transition-colors duration-300 mt-5\"\n      >\n        <div className=\"flex items-center justify-between gap-3 mb-4 pb-4 border-b border-border\">\n          <div className=\"flex items-center gap-3\">\n            {item.icon && (\n              <div className=\"p-2 rounded-xl bg-muted border border-border/60\">\n                {item.icon}\n              </div>\n            )}\n            <div>\n              <h3 className=\"text-lg sm:text-xl md:text-2xl font-semibold tracking-tight text-foreground\">\n                {item.title}\n              </h3>\n              {item.badge && (\n                <span className=\"inline-block text-xs font-semibold uppercase tracking-wider text-[var(--theme-color,var(--primary))]\">\n                  {item.badge}\n                </span>\n              )}\n            </div>\n          </div>\n\n          <span className=\"font-mono text-xs font-semibold text-muted-foreground\">\n            0{index + 1} / 0{total}\n          </span>\n        </div>\n\n        {item.contentType === \"list\" && (\n          <div className=\"space-y-4\">\n            {item.description && (\n              <p className=\"text-xs sm:text-sm md:text-base text-muted-foreground leading-relaxed\">\n                {item.description}\n              </p>\n            )}\n            {item.listItems && (\n              <ul className=\"grid grid-cols-1 sm:grid-cols-2 gap-2.5 pt-1\">\n                {item.listItems.map((listItem, i) => (\n                  <li\n                    key={i}\n                    className=\"flex items-center gap-2.5 text-xs sm:text-sm font-medium text-foreground\"\n                  >\n                    <CheckCircle2 className=\"w-4 h-4 text-emerald-500 shrink-0\" />\n                    <span>{listItem}</span>\n                  </li>\n                ))}\n              </ul>\n            )}\n          </div>\n        )}\n\n        {item.contentType === \"image\" && (\n          <div className=\"space-y-4\">\n            {item.description && (\n              <p className=\"text-xs sm:text-sm md:text-base text-muted-foreground leading-relaxed\">\n                {item.description}\n              </p>\n            )}\n            {item.imageUrl && (\n              <div className=\"relative w-full h-44 sm:h-52 rounded-xl overflow-hidden border border-border/60 bg-muted\">\n                <img\n                  src={item.imageUrl}\n                  alt={item.imageAlt || item.title}\n                  className=\"w-full h-full object-cover transform hover:scale-105 transition-transform duration-500\"\n                  loading=\"lazy\"\n                />\n              </div>\n            )}\n          </div>\n        )}\n\n        {item.contentType === \"split\" && (\n          <div className=\"grid grid-cols-1 md:grid-cols-2 gap-5 items-center\">\n            <div className=\"space-y-3\">\n              {item.description && (\n                <p className=\"text-xs sm:text-sm md:text-base text-muted-foreground leading-relaxed\">\n                  {item.description}\n                </p>\n              )}\n              {item.listItems && (\n                <ul className=\"space-y-2 pt-1\">\n                  {item.listItems.map((listItem, i) => (\n                    <li\n                      key={i}\n                      className=\"flex items-center gap-2 text-xs sm:text-sm text-foreground\"\n                    >\n                      <CheckCircle2 className=\"w-4 h-4 text-[var(--theme-color,var(--primary))] shrink-0\" />\n                      <span>{listItem}</span>\n                    </li>\n                  ))}\n                </ul>\n              )}\n            </div>\n            {item.imageUrl && (\n              <div className=\"relative w-full h-40 sm:h-48 md:h-full min-h-[160px] rounded-xl overflow-hidden border border-border/60\">\n                <img\n                  src={item.imageUrl}\n                  alt={item.imageAlt || item.title}\n                  className=\"w-full h-full object-cover\"\n                  loading=\"lazy\"\n                />\n              </div>\n            )}\n          </div>\n        )}\n\n        {item.contentType === \"custom\" && (\n          <div className=\"space-y-4\">\n            <p className=\"text-xs sm:text-sm md:text-base text-muted-foreground leading-relaxed\">\n              {item.description ||\n                \"Custom content layout section tailored for specialized workflows.\"}\n            </p>\n          </div>\n        )}\n\n        {/* Holds the old 24px gap above the button row; mt-auto on the row\n            then pins it to the bottom of a card stretched to the tallest. */}\n        {item.buttonText && <div aria-hidden className=\"h-6 shrink-0\" />}\n        {item.buttonText && (\n          <div className=\"mt-auto pt-4 border-t border-border flex justify-end\">\n            <button\n              onClick={item.onButtonClick}\n              type=\"button\"\n              className=\"inline-flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-semibold text-primary-foreground bg-[var(--theme-color,var(--primary))] hover:opacity-90 shadow-md transition-all duration-200 cursor-pointer active:scale-95\"\n            >\n              <span>{item.buttonText}</span>\n              <ArrowRight className=\"w-3.5 h-3.5\" />\n            </button>\n          </div>\n        )}\n      </motion.div>\n    </div>\n  );\n};\n\nexport const StickyScrollStack = React.forwardRef<\n  HTMLDivElement,\n  StickyScrollStackProps\n>(\n  (\n    {\n      headerBadge = \"Expertise & Solutions\",\n      headerTitle = \"Our Core Capabilities\",\n      headerSubtitle = \"We emphasize user-centric design, ensuring that every project we undertake prioritizes the needs and goals of your real audience.\",\n      headerButtonText = \"Request free audit\",\n      onHeaderButtonClick,\n      headerContentType = \"text-button\",\n      headerImageUrl,\n      headerImageAlt,\n      headerCustomContent,\n      items = defaultStackItems,\n      stackOffsetStep = 12,\n      scaleStep = 0.03,\n      enableGlow = false,\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const containerRef = useRef<HTMLDivElement>(null);\n\n    React.useImperativeHandle(\n      ref,\n      () => containerRef.current as HTMLDivElement,\n    );\n\n    return (\n      <div\n        ref={containerRef}\n        className={cn(\n          \"relative w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-20\",\n          className,\n        )}\n        {...props}\n      >\n        {enableGlow && (\n          <div className=\"pointer-events-none absolute top-1/3 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-gradient-to-tr from-[var(--theme-color,var(--primary))]/10 via-[var(--theme-color,var(--primary))]/10 to-transparent blur-[120px] rounded-full -z-10\" />\n        )}\n\n        <div className=\"grid grid-cols-1 lg:grid-cols-12 gap-10 lg:gap-14 items-start\">\n          <div className=\"lg:col-span-5 lg:sticky lg:top-28 flex flex-col justify-center text-left z-10\">\n            {headerBadge && (\n              <div className=\"inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[var(--theme-color,var(--primary))]/10 border border-[var(--theme-color,var(--primary))]/30 text-[var(--theme-color,var(--primary))] text-xs font-semibold uppercase tracking-wider w-fit mb-5\">\n                <Sparkles className=\"w-3.5 h-3.5\" />\n                <span>{headerBadge}</span>\n              </div>\n            )}\n\n            <h2 className=\"text-2xl sm:text-3xl lg:text-4xl font-extrabold tracking-tight text-foreground leading-[1.15] mb-4\">\n              {headerTitle}\n            </h2>\n\n            <p className=\"text-sm sm:text-base text-muted-foreground leading-relaxed mb-6 max-w-md\">\n              {headerSubtitle}\n            </p>\n\n            {headerContentType === \"image-button\" && headerImageUrl && (\n              <div className=\"mb-6 relative w-full max-w-md h-48 sm:h-56 rounded-2xl overflow-hidden border border-border/80 shadow-md bg-muted\">\n                <img\n                  src={headerImageUrl}\n                  alt={headerImageAlt || headerTitle}\n                  className=\"w-full h-full object-cover transform hover:scale-105 transition-transform duration-500\"\n                  loading=\"lazy\"\n                />\n              </div>\n            )}\n\n            {headerContentType === \"custom\" && headerCustomContent && (\n              <div className=\"mb-6 w-full max-w-md\">{headerCustomContent}</div>\n            )}\n\n            {(headerContentType === \"text-button\" ||\n              headerContentType === \"image-button\") &&\n              headerButtonText && (\n                <div>\n                  <button\n                    onClick={onHeaderButtonClick}\n                    type=\"button\"\n                    className=\"inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-2xl font-semibold text-sm text-primary-foreground bg-[var(--theme-color,var(--primary))] hover:opacity-90 shadow-lg hover:shadow-xl transition-all duration-200 cursor-pointer active:scale-95\"\n                  >\n                    <span>{headerButtonText}</span>\n                    <ArrowRight className=\"w-4 h-4\" />\n                  </button>\n                </div>\n              )}\n          </div>\n\n          {/* One 1fr row per card sizes every row to the tallest card, so each\n              sticky wrapper is the same height. A sticky card only stays pinned\n              while its bottom is inside this column — with uneven heights (a card\n              whose image stacks under its text on a phone) the tallest one hit the\n              column's end first and slid out of the stack as the last card\n              landed. Equal wrappers release in order, last card first.\n              The trailing auto row is a real spacer: sticky ignores the column's\n              padding, so the old pb-[20vh] never held the finished stack. */}\n          <div\n            className=\"lg:col-span-7 grid grid-cols-1 gap-6\"\n            style={\n              items && items.length > 0\n                ? { gridTemplateRows: `repeat(${items.length}, 1fr) auto` }\n                : undefined\n            }\n          >\n            {items &&\n              items.map((item, index) => (\n                <StackCard\n                  key={item.id}\n                  item={item}\n                  index={index}\n                  total={items.length}\n                  stackOffsetStep={stackOffsetStep}\n                  scaleStep={scaleStep}\n                />\n              ))}\n            {/* The auto row: holds the finished stack before it scrolls away. */}\n            {items && items.length > 0 && (\n              <div aria-hidden className=\"h-[10vh] sm:h-[20vh]\" />\n            )}\n          </div>\n        </div>\n      </div>\n    );\n  },\n);\n\nStickyScrollStack.displayName = \"StickyScrollStack\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/StickyScrollStackBlock.tsx"
    }
  ],
  "docs": "Motion build of `sticky-scroll-stack` — same props, markup and install path; install one or the other. Named export: StickyScrollStack. Each item sets `contentType` (list | image | split | custom) so steps need not share a shape. Uses next/image, so it needs edits outside Next.js.",
  "categories": [
    "scroll",
    "marketing"
  ],
  "type": "registry:component"
}
