{
  "name": "block-transition",
  "type": "registry:component",
  "title": "Block Transition",
  "description": "Horizontal split-block route transition with row-based stagger and optional content shift blur using GSAP.",
  "dependencies": [
    "gsap",
    "next-transition-router"
  ],
  "registryDependencies": [],
  "exportName": "BlockTransition",
  "exportKind": "default",
  "tier": "free",
  "version": "1.1.0",
  "changelog": [
    {
      "version": "1.1.0",
      "date": "2026-07-21",
      "summary": "Added reduced-motion route fallback with short opacity fades.",
      "breaking": false
    },
    {
      "version": "1.0.0",
      "date": "2026-02-10",
      "summary": "Initial release",
      "breaking": false
    }
  ],
  "files": [
    {
      "path": "index.jsx",
      "type": "registry:component",
      "target": "src/components/effects/block-transition/index.jsx",
      "content": "// Built using Hyperiux Vault: https://vault.hyperiux.com\n\n\"use client\";\n\nimport { TransitionRouter } from \"next-transition-router\";\nimport React, { useLayoutEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\nimport { prefersReducedMotion } from \"@/lib/motion\";\n\n\nconst ROWS = 5;\nconst COLOR = \"#000000\";\n\nconst STAGGER_DELAY = 0.12;\nconst COVER_DURATION = 0.72;\nconst REVEAL_DURATION = 0.68;\nconst BLOCK_OFFSET_PERCENT = 101;\n\nconst SHIFT_SCALE_OUT = 0.98;\nconst SHIFT_SCALE_IN = 1.02;\nconst SHIFT_BLUR = \"4px\";\nconst SHIFT_DURATION_OUT = 0.7;\nconst SHIFT_DURATION_IN = 0.8;\n\n\nexport default function BlockTransition({\n  children,\n  enableContentShift = false,\n  rows = ROWS,\n  color = COLOR,\n}) {\n  const wrapperRef = useRef(null);\n  const rowRefs    = useRef([]);\n\n  const getRows = () => rowRefs.current.filter(Boolean);\n\n\n\n  const buildRowsAnimation = (timeline, direction) => {\n    const rows = getRows();\n    const orderedRows = direction === \"cover\" ? rows : [...rows].reverse();\n\n    orderedRows.forEach((row, index) => {\n      const [leftBlock, rightBlock] = row.children;\n      const delay = index * STAGGER_DELAY;\n\n      if (direction === \"cover\") {\n        timeline.set([leftBlock, rightBlock], { autoAlpha: 1 }, delay);\n        timeline.to(\n          [leftBlock, rightBlock],\n          {\n            xPercent: 0,\n            duration: COVER_DURATION,\n            ease: \"power3.inOut\",\n          },\n          delay\n        );\n      } else {\n        timeline.to(\n          leftBlock,\n          {\n            xPercent: -BLOCK_OFFSET_PERCENT,\n            duration: REVEAL_DURATION,\n            ease: \"power3.inOut\",\n          },\n          delay\n        );\n        timeline.set(leftBlock, { autoAlpha: 0 }, delay + REVEAL_DURATION);\n\n        timeline.to(\n          rightBlock,\n          {\n            xPercent: BLOCK_OFFSET_PERCENT,\n            duration: REVEAL_DURATION,\n            ease: \"power3.inOut\",\n          },\n          delay\n        );\n        timeline.set(rightBlock, { autoAlpha: 0 }, delay + REVEAL_DURATION);\n      }\n    });\n  };\n\n\n  useLayoutEffect(() => {\n    getRows().forEach((row) => {\n      const [leftBlock, rightBlock] = row.children;\n      if (!leftBlock || !rightBlock) return;\n\n      gsap.set(leftBlock, {\n        xPercent: -BLOCK_OFFSET_PERCENT,\n        autoAlpha: 0,\n      });\n\n      gsap.set(rightBlock, {\n        xPercent: BLOCK_OFFSET_PERCENT,\n        autoAlpha: 0,\n      });\n    });\n  }, []);\n\n\n  return (\n    <TransitionRouter\n      auto\n      leave={(next) => {\n        const timeline = gsap.timeline({ onComplete: next });\n\n        if (prefersReducedMotion()) {\n          timeline.to(wrapperRef.current, { opacity: 0, duration: 0.2, ease: \"power1.out\" }, 0);\n          return () => timeline.kill();\n        }\n\n        if (enableContentShift) {\n          timeline.fromTo(\n            wrapperRef.current,\n            { scale: 1, filter: \"blur(0px)\", opacity: 1 },\n            {\n              scale: SHIFT_SCALE_OUT,\n              filter: `blur(${SHIFT_BLUR})`,\n              opacity: 0.85,\n              duration: SHIFT_DURATION_OUT,\n              ease: \"power2.inOut\",\n            },\n            0\n          );\n        }\n\n        buildRowsAnimation(timeline, \"cover\");\n\n        return () => timeline.kill();\n      }}\n      enter={(next) => {\n        const timeline = gsap.timeline({ onComplete: next });\n\n        if (prefersReducedMotion()) {\n          timeline.to(wrapperRef.current, { opacity: 1, duration: 0.2, ease: \"power1.out\", clearProps: \"all\" }, 0);\n          return () => timeline.kill();\n        }\n\n        if (enableContentShift) {\n          timeline.fromTo(\n            wrapperRef.current,\n            { scale: SHIFT_SCALE_IN, filter: `blur(${SHIFT_BLUR})`, opacity: 0.85 },\n            {\n              scale: 1,\n              filter: \"blur(0px)\",\n              opacity: 1,\n              duration: SHIFT_DURATION_IN,\n              ease: \"power2.out\",\n            },\n            0.08\n          );\n        }\n\n        buildRowsAnimation(timeline, \"reveal\");\n\n        return () => timeline.kill();\n      }}\n    >\n      <div className=\"pointer-events-none fixed inset-0 z-999 overflow-hidden\">\n        {Array.from({ length: rows }).map((_, index) => (\n          <div\n            key={index}\n            ref={(node) => {\n              rowRefs.current[index] = node;\n            }}\n            className=\"absolute left-0 w-full overflow-hidden\"\n            style={{\n              top: `${(index * 100) / rows}%`,\n              height: `calc(${100 / rows}% + 2px)`,\n            }}\n          >\n            <span\n              className=\"absolute top-0 left-0 h-full w-[51%] will-change-transform\"\n              style={{\n                backgroundColor: color,\n                opacity: 0,\n                visibility: \"hidden\",\n              }}\n            />\n\n            <span\n              className=\"absolute top-0 right-0 h-full w-[51%] will-change-transform\"\n              style={{\n                backgroundColor: color,\n                opacity: 0,\n                visibility: \"hidden\",\n              }}\n            />\n          </div>\n        ))}\n      </div>\n\n      <div className=\"relative h-full w-full\">\n        <div ref={wrapperRef} className=\"h-full w-full will-change-transform\">\n          {children}\n        </div>\n      </div>\n    </TransitionRouter>\n  );\n}\n"
    }
  ]
}