{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marker",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "marker.tsx",
      "content": "\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { forwardRef, type ReactNode, useId } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst DRAW_EASE = [0.65, 0, 0.35, 1] as const;\nconst DEFAULT_DURATION = 0.7;\nconst STAGGER_SECONDS = 0.16;\n\ntype StrokeLinecap = \"butt\" | \"round\" | \"square\";\ntype StrokeLinejoin = \"bevel\" | \"miter\" | \"round\";\n\nfunction DrawPath({\n  animate,\n  d,\n  delay,\n  duration,\n  fill,\n  filter,\n  opacity = 1,\n  reduceMotion,\n  stroke,\n  strokeLinecap,\n  strokeLinejoin,\n  strokeWidth,\n}: {\n  animate: boolean;\n  d: string;\n  delay: number;\n  duration: number;\n  fill?: string;\n  filter?: string;\n  opacity?: number;\n  reduceMotion: boolean | null;\n  stroke?: string;\n  strokeLinecap?: StrokeLinecap;\n  strokeLinejoin?: StrokeLinejoin;\n  strokeWidth?: number;\n}) {\n  const pathProps = {\n    d,\n    fill,\n    filter,\n    stroke,\n    strokeLinecap,\n    strokeLinejoin,\n    strokeWidth,\n  };\n\n  if (!(animate && !reduceMotion)) {\n    return <path {...pathProps} opacity={opacity} />;\n  }\n\n  return (\n    <motion.path\n      {...pathProps}\n      initial={{ opacity: 0, pathLength: 0 }}\n      transition={{\n        opacity: { delay, duration: Math.min(duration, 0.3) },\n        pathLength: { delay, duration, ease: DRAW_EASE },\n      }}\n      viewport={{ once: true }}\n      whileInView={{ opacity, pathLength: 1 }}\n    />\n  );\n}\n\nfunction RoughFilters({ id }: Readonly<{ id: string }>) {\n  return (\n    <svg aria-hidden=\"true\" className=\"absolute h-0 w-0\">\n      <defs>\n        <filter\n          height=\"160%\"\n          id={`hd-rough-${id}`}\n          width=\"160%\"\n          x=\"-30%\"\n          y=\"-30%\"\n        >\n          <feTurbulence\n            baseFrequency={0.045}\n            numOctaves={2}\n            result=\"n\"\n            seed={4}\n            type=\"fractalNoise\"\n          />\n          <feDisplacementMap\n            in=\"SourceGraphic\"\n            in2=\"n\"\n            scale={2.6}\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n          />\n        </filter>\n        <filter\n          height=\"160%\"\n          id={`hd-rough-soft-${id}`}\n          width=\"160%\"\n          x=\"-30%\"\n          y=\"-30%\"\n        >\n          <feTurbulence\n            baseFrequency={0.035}\n            numOctaves={2}\n            result=\"n2\"\n            seed={11}\n            type=\"fractalNoise\"\n          />\n          <feDisplacementMap\n            in=\"SourceGraphic\"\n            in2=\"n2\"\n            scale={1.5}\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n          />\n        </filter>\n      </defs>\n    </svg>\n  );\n}\n\ntype DecorationProps = Readonly<{\n  animate: boolean;\n  className?: string;\n  delay: number;\n  duration: number;\n  filterId: string;\n  filterSoftId: string;\n  reduceMotion: boolean | null;\n}>;\n\nfunction WavyDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 140 14\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M2,6 Q5.5,3 9,6 T17,6 T25,6 T33,6 T41,6 T49,6 T57,6 T65,6 T73,6 T81,6 T89,6 T97,6 T105,6 T113,6 T121,6 T129,6 T137,6\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.2}\n      />\n    </svg>\n  );\n}\n\nfunction CircleDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterId,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 220 64\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M40,40 C20,23 53,7 102,5 C153,3 207,11 211,29 C215,47 167,60 109,60\n           C59,60 15,53 19,35 C21,27 27,22 37,20\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={3}\n      />\n      <DrawPath\n        animate={animate}\n        d=\"M43,37 C28,25 58,9 105,7 C151,6 199,14 206,29 C212,45 167,57 110,58\"\n        delay={delay + STAGGER_SECONDS}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        opacity={0.55}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={1.5}\n      />\n    </svg>\n  );\n}\n\nfunction HighlightDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  const shouldAnimate = animate && !reduceMotion;\n\n  return (\n    <motion.svg\n      aria-hidden=\"true\"\n      className={className}\n      initial={shouldAnimate ? { scaleX: 0 } : undefined}\n      preserveAspectRatio=\"none\"\n      style={shouldAnimate ? { transformOrigin: \"0% 50%\" } : undefined}\n      transition={\n        shouldAnimate\n          ? { delay, duration: duration * 1.1, ease: DRAW_EASE }\n          : undefined\n      }\n      viewBox=\"0 0 170 26\"\n      viewport={shouldAnimate ? { once: true } : undefined}\n      whileInView={shouldAnimate ? { scaleX: 1 } : undefined}\n    >\n      <path\n        d=\"M4,17 C2,11 5,7 12,6 C45,2 95,2 138,4 C152,5 164,7 166,13\n           C167,18 163,21 155,22 C112,24 60,24 16,22 C8,21.5 4,20 4,17 Z\"\n        fill=\"currentColor\"\n        filter={`url(#${filterSoftId})`}\n      />\n    </motion.svg>\n  );\n}\n\nfunction UnderlineDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 140 10\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M3,6 C40,3 100,3 137,5\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.4}\n      />\n    </svg>\n  );\n}\n\nfunction LineDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  reduceMotion,\n}: DecorationProps) {\n  const shouldAnimate = animate && !reduceMotion;\n\n  return (\n    <motion.span\n      aria-hidden=\"true\"\n      className={cn(\"block rounded-full bg-current\", className)}\n      initial={shouldAnimate ? { scaleX: 0 } : undefined}\n      style={shouldAnimate ? { transformOrigin: \"0% 50%\" } : undefined}\n      transition={\n        shouldAnimate ? { delay, duration, ease: DRAW_EASE } : undefined\n      }\n      viewport={shouldAnimate ? { once: true } : undefined}\n      whileInView={shouldAnimate ? { scaleX: 1 } : undefined}\n    />\n  );\n}\n\nfunction DottedUnderlineDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  reduceMotion,\n}: DecorationProps) {\n  const shouldAnimate = animate && !reduceMotion;\n\n  return (\n    <motion.span\n      aria-hidden=\"true\"\n      className={cn(\n        \"bg-[radial-gradient(circle,currentColor_1.5px,transparent_1.5px)]\",\n        \"bg-position-[0_100%] bg-size-[0.5em_100%] bg-repeat-x\",\n        className\n      )}\n      initial={shouldAnimate ? { scaleX: 0 } : undefined}\n      style={shouldAnimate ? { transformOrigin: \"0% 50%\" } : undefined}\n      transition={\n        shouldAnimate ? { delay, duration, ease: DRAW_EASE } : undefined\n      }\n      viewport={shouldAnimate ? { once: true } : undefined}\n      whileInView={shouldAnimate ? { scaleX: 1 } : undefined}\n    />\n  );\n}\n\nfunction DoubleUnderlineDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 140 16\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M3,5 C40,2 100,2 137,4\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.2}\n      />\n      <DrawPath\n        animate={animate}\n        d=\"M5,12 C42,9 98,10 135,11\"\n        delay={delay + STAGGER_SECONDS}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        opacity={0.75}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={1.8}\n      />\n    </svg>\n  );\n}\n\nfunction StrikethroughDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 140 10\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M3,5 C40,7 100,3 137,5\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.4}\n      />\n    </svg>\n  );\n}\n\nfunction CrossOutDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterId,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 140 40\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M4,32 C40,10 96,30 136,8\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.4}\n      />\n      <DrawPath\n        animate={animate}\n        d=\"M6,10 C44,30 92,12 134,30\"\n        delay={delay + STAGGER_SECONDS}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        opacity={0.7}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2}\n      />\n    </svg>\n  );\n}\n\nfunction ArrowUnderlineDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterSoftId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 150 18\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M3,7 C45,3 105,4 140,8\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.4}\n      />\n      <DrawPath\n        animate={animate}\n        d=\"M132,3 L142,8 L131,13\"\n        delay={delay + STAGGER_SECONDS}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterSoftId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth={2.4}\n      />\n    </svg>\n  );\n}\n\nfunction BracketDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 160 60\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M18,6 C9,7 6,12 6,30 C6,48 9,53 18,54\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.6}\n      />\n      <DrawPath\n        animate={animate}\n        d=\"M142,6 C151,7 154,12 154,30 C154,48 151,53 142,54\"\n        delay={delay + STAGGER_SECONDS}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth={2.6}\n      />\n    </svg>\n  );\n}\n\nfunction BoxDecoration({\n  animate,\n  className,\n  delay,\n  duration,\n  filterId,\n  reduceMotion,\n}: DecorationProps) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      fill=\"none\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 200 64\"\n    >\n      <DrawPath\n        animate={animate}\n        d=\"M12,10 C60,6 140,6 188,10 C193,26 193,40 188,54\n           C140,58 60,58 12,54 C7,40 7,26 12,10 Z\"\n        delay={delay}\n        duration={duration}\n        fill=\"none\"\n        filter={`url(#${filterId})`}\n        reduceMotion={reduceMotion}\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth={2.6}\n      />\n    </svg>\n  );\n}\n\nconst annotationStyles = {\n  wavy: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: WavyDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute bottom-[-0.4em] left-[-2%] h-[0.7em] w-[104%]\",\n    defaultColor: \"text-purple-300\",\n  },\n  circle: {\n    wrapper: \"relative inline-block px-1 whitespace-nowrap\",\n    Decoration: CircleDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute inset-[-0.6em_-0.55em] h-[calc(100%+1.2em)] w-[calc(100%+1.1em)]\",\n    defaultColor: \"text-cyan-200\",\n  },\n  highlight: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: HighlightDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute inset-x-[-4%] bottom-[-0.08em] z-0 h-[1.15em] w-[108%]\",\n    defaultColor: \"text-yellow-300/60\",\n  },\n  underline: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: UnderlineDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute bottom-[-0.32em] left-[-1%] h-[0.5em] w-[102%]\",\n    defaultColor: \"text-purple-400\",\n  },\n  line: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: LineDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute bottom-[-0.18em] left-[-1%] block h-[2px] w-[102%]\",\n    defaultColor: \"text-neutral-500\",\n  },\n  dottedUnderline: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: DottedUnderlineDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute bottom-[-0.35em] left-[-1%] block h-[0.55em] w-[102%]\",\n    defaultColor: \"text-neutral-400\",\n  },\n  doubleUnderline: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: DoubleUnderlineDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute bottom-[-0.5em] left-[-1%] h-[0.7em] w-[102%]\",\n    defaultColor: \"text-emerald-400\",\n  },\n  strikethrough: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: StrikethroughDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute top-1/2 left-[-1%] h-[0.5em] w-[102%] -translate-y-1/2\",\n    defaultColor: \"text-red-400\",\n  },\n  crossOut: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: CrossOutDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute top-1/2 left-[-2%] h-[1.4em] w-[104%] -translate-y-1/2\",\n    defaultColor: \"text-red-400\",\n  },\n  arrow: {\n    wrapper: \"relative inline-block whitespace-nowrap\",\n    Decoration: ArrowUnderlineDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute bottom-[-0.45em] left-[-1%] h-[0.8em] w-[106%]\",\n    defaultColor: \"text-blue-400\",\n  },\n  bracket: {\n    wrapper: \"relative inline-block px-[0.35em] whitespace-nowrap\",\n    Decoration: BracketDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute inset-y-[-0.25em] left-[-0.15em] h-[calc(100%+0.5em)] w-[calc(100%+0.3em)]\",\n    defaultColor: \"text-neutral-500\",\n  },\n  box: {\n    wrapper: \"relative inline-block px-[0.4em] whitespace-nowrap\",\n    Decoration: BoxDecoration,\n    decorationClassName:\n      \"pointer-events-none absolute inset-[-0.4em_-0.4em] h-[calc(100%+0.8em)] w-[calc(100%+0.8em)]\",\n    defaultColor: \"text-orange-400\",\n  },\n} as const;\n\ntype AnnotationVariant = keyof typeof annotationStyles;\n\nexport type MarkerProps = Readonly<{\n  /** Text (or inline content) to annotate. */\n  children: ReactNode;\n  /** Hand-drawn annotation style. @default \"wavy\" */\n  variant?: AnnotationVariant;\n  /** Overrides the variant's default ink color. Accepts a Tailwind text-color class. */\n  color?: string;\n  /** Whether the decoration draws itself in when scrolled into view. @default true */\n  animate?: boolean;\n  /** Draw duration in seconds for a single stroke. @default 0.7 */\n  duration?: number;\n  /** Delay in seconds before the draw-in starts. @default 0 */\n  delay?: number;\n  className?: string;\n}>;\n\n// Hand-drawn text annotations — wavy, circle, highlight, underline, and more\n// — that draw themselves in like ink as they scroll into view, or render\n// fully static when animate is off.\nexport const Marker = forwardRef<HTMLSpanElement, MarkerProps>(\n  (\n    {\n      children,\n      variant = \"wavy\",\n      color,\n      animate = true,\n      duration = DEFAULT_DURATION,\n      delay = 0,\n      className,\n    },\n    ref\n  ) => {\n    // React's useId() wraps the id in colons (e.g. \":r0:\"), which some\n    // browsers fail to resolve inside an SVG url(#...) reference.\n    const id = useId().replace(/:/g, \"\");\n    const reduceMotion = useReducedMotion();\n    const style = annotationStyles[variant];\n    const Decoration = style.Decoration;\n    const decorationClass = cn(\n      style.decorationClassName,\n      color ?? style.defaultColor\n    );\n    const filterId = `hd-rough-${id}`;\n    const filterSoftId = `hd-rough-soft-${id}`;\n\n    // The highlight fill sits behind the glyphs so it doesn't cover them.\n    const isBehindText = variant === \"highlight\";\n\n    const decoration = (\n      <Decoration\n        animate={animate}\n        className={decorationClass}\n        delay={delay}\n        duration={duration}\n        filterId={filterId}\n        filterSoftId={filterSoftId}\n        reduceMotion={reduceMotion}\n      />\n    );\n\n    if (isBehindText) {\n      return (\n        <>\n          <RoughFilters id={id} />\n          <span className={cn(style.wrapper, className)} ref={ref}>\n            <span className=\"relative z-10\">{children}</span>\n            {decoration}\n          </span>\n        </>\n      );\n    }\n\n    return (\n      <>\n        <RoughFilters id={id} />\n        <span className={cn(style.wrapper, className)} ref={ref}>\n          {children}\n          {decoration}\n        </span>\n      </>\n    );\n  }\n);\n\nMarker.displayName = \"Marker\";\n",
      "type": "registry:ui"
    }
  ],
  "title": "Marker",
  "description": "Hand-drawn text annotations — wavy, circle, highlight, underline, and more — that draw themselves in like ink as they scroll into view, with an optional animate prop for a fully static render."
}
