{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"path-follow-scroll","type":"registry:component","title":"Path Follow Scroll","description":"A marker that rides an SVG curve on scroll, banking through the bends.","dependencies":["framer-motion"],"registryDependencies":[],"files":[{"path":"components/viberdy/path-follow-scroll.tsx","content":"\"use client\";\n\nimport { useRef } from \"react\";\nimport { motion, useScroll, useSpring, useTransform } from \"framer-motion\";\n\nexport function PathFollowScroll({\n  d,\n  viewBox = \"0 0 340 170\",\n  markerSize = 14,\n  drawPath = true,\n  autoRotate = true,\n}: {\n  /** Any SVG path string. The marker rides this exact curve. */\n  d: string;\n  viewBox?: string;\n  markerSize?: number;\n  /** Draw the trail in behind the marker as it travels. */\n  drawPath?: boolean;\n  /** Let the browser bank the marker to the curve's tangent. */\n  autoRotate?: boolean;\n}) {\n  const sectionRef = useRef<HTMLDivElement>(null);\n  const { scrollYProgress } = useScroll({\n    target: sectionRef,\n    offset: [\"start end\", \"end start\"],\n  });\n  const progress = useSpring(scrollYProgress, { stiffness: 220, damping: 40 });\n\n  const offsetDistance = useTransform(progress, (v) => v * 100 + \"%\");\n  // Hoisted: feeding this inline into JSX that may be conditional would make\n  // a hook run conditionally.\n  const dashOffset = useTransform(progress, (v) => 1 - v);\n\n  return (\n    <div ref={sectionRef} className=\"relative h-[200vh]\">\n      <div className=\"sticky top-1/3\">\n        <div className=\"relative mx-auto w-full max-w-3xl\">\n          <svg aria-hidden viewBox={viewBox} className=\"w-full overflow-visible\">\n            <path d={d} fill=\"none\" stroke=\"currentColor\" strokeOpacity={0.14} strokeWidth={1.5} />\n            {drawPath && (\n              <motion.path\n                d={d}\n                fill=\"none\"\n                stroke=\"#3d7bff\"\n                strokeWidth={1.5}\n                strokeLinecap=\"round\"\n              /*\n               * pathLength={1} normalises the path so dasharray/dashoffset are\n               * plain 0-1 fractions — no getTotalLength(), and it keeps working\n               * at any rendered size.\n               */\n                pathLength={1}\n                strokeDasharray=\"1 1\"\n                style={{ strokeDashoffset: dashOffset }}\n              />\n            )}\n          </svg>\n\n          {/*\n           * The marker rides CSS offset-path / offset-distance rather than\n           * interpolated x/y. The browser solves position AND tangent angle\n           * along the curve, so offsetRotate:\"auto\" banks it through the bends\n           * for free — doing that manually means sampling and differentiating\n           * the path yourself.\n           */}\n          <motion.div\n            aria-hidden\n            className=\"absolute left-0 top-0 rounded-full bg-blue-500\"\n            style={{\n              width: markerSize,\n              height: markerSize,\n              offsetPath: 'path(\"' + d + '\")',\n              offsetDistance,\n              offsetRotate: autoRotate ? \"auto\" : \"0deg\",\n            }}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n","type":"registry:component","target":"components/viberdy/path-follow-scroll.tsx"}],"meta":{"tier":"free","tags":["scroll","svg","offset-path","framer-motion"],"href":"https://viberdy.dev/library/path-follow-scroll","aiPrompt":"Create a React + Tailwind + Framer Motion component named PathFollowScroll — a marker that travels along an SVG path as the user scrolls, with the path drawing in behind it.\n\nBehavior:\n- A tall section (~200vh) with a sticky SVG. Scrolling moves a marker from the start of the path to the end while an accent stroke draws in behind it.\n- Progress comes from useScroll({ target, offset: [\"start end\", \"end start\"] }) and is smoothed with useSpring (stiffness 220, damping 40).\n\nTwo implementation requirements that are the point of the component:\n1. Move the marker with CSS offset-path / offset-distance — set offsetPath to path(\"<d>\") and drive offsetDistance from 0% to 100%. Do NOT interpolate x/y manually. The browser solves both the position and the TANGENT ANGLE along the curve, so offsetRotate: \"auto\" banks the marker through the bends for free; doing that by hand means sampling the path and differentiating it.\n2. Draw the trail with stroke-dasharray/stroke-dashoffset and set pathLength={1} on the path so the dash values are plain 0-1 fractions. No getTotalLength() call, and it keeps working at any rendered size.\n\nAlso: hoist every useTransform to the component's top level. Calling one inline inside conditionally-rendered JSX makes a hook run conditionally, which breaks the rules of hooks.\n\nProps: d (SVG path string), viewBox (string), markerSize (number).\n\nOutput a single self-contained \"use client\" component.","license":"https://viberdy.dev/license"}}