{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marquee",
  "title": "Marquee",
  "description": "Pure-CSS infinite scroll; pauses on hover/focus and wraps statically under reduced motion.",
  "dependencies": [],
  "registryDependencies": [
    "@hjconsult/lib",
    "@hjconsult/styles"
  ],
  "files": [
    {
      "path": "src/components/marquee/index.tsx",
      "content": "import type { ComponentPropsWithoutRef, CSSProperties, Ref } from 'react'\nimport { cn, focusRing } from '../../lib'\n\nexport type MarqueeGap = 'sm' | 'md' | 'lg'\n\n/** Gap in spacing-scale steps; resolved through `--spacing` so it stays on the token grid. */\nconst marqueeGaps: Record<MarqueeGap, number> = { sm: 4, md: 8, lg: 12 }\n\nexport interface MarqueeProps extends ComponentPropsWithoutRef<'div'> {\n  /** Speed multiplier over the `--hj-motion-duration-marquee` cycle. `2` is twice as fast. */\n  speed?: number\n  direction?: 'left' | 'right'\n  /** Pause while hovered. Always pauses while any child has focus. */\n  pauseOnHover?: boolean\n  /**\n   * Fade the edges with a mask. While the strip moves, both edges; as the row it becomes under\n   * reduced motion, only the edge with more to scroll to.\n   */\n  fade?: boolean\n  gap?: MarqueeGap\n  ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Pure-CSS infinite scroll. The content is rendered twice; the clone is `aria-hidden`.\n *\n * Under reduced motion the clone is removed and the strip stops as one row the reader can scroll.\n * Stopped in place, it showed two names of forty-six on a phone and cut off the rest; wrapped into\n * a list, the same forty-six filled 762 px of a 390 px screen. As a row it keeps the height it has\n * while it moves, and swiping, a trackpad or the arrow keys reach every item.\n *\n * So the strip is a group a keyboard can reach: it scrolls under reduced motion, and focusing it\n * pauses it while it moves. Name it with `aria-label`, which the group role makes a real name\n * rather than an attribute a screen reader may ignore.\n * Requires `@hjconsult/ui/styles.css` (keyframes and pause rules live there).\n */\nexport function Marquee({\n  speed = 1,\n  direction = 'left',\n  pauseOnHover = true,\n  fade = true,\n  gap = 'md',\n  className,\n  style,\n  children,\n  ...props\n}: MarqueeProps) {\n  const vars = {\n    '--hj-marquee-speed': speed,\n    '--hj-marquee-gap': `calc(var(--spacing) * ${marqueeGaps[gap]})`,\n  } as CSSProperties\n  const track = cn(\n    'hj-marquee-track flex min-w-full shrink-0 items-center justify-around gap-[var(--hj-marquee-gap)]',\n    'motion-reduce:justify-start',\n  )\n  return (\n    // A named group, not a region: a ticker is not a landmark worth listing.\n    // biome-ignore lint/a11y/useSemanticElements: the element it suggests, a fieldset, is for form controls\n    <div\n      role=\"group\"\n      // Keyboard access to the row it becomes under reduced motion (WCAG 2.1.1, axe\n      // `scrollable-region-focusable`), and a way to pause it while it moves (WCAG 2.2.2).\n      // biome-ignore lint/a11y/noNoninteractiveTabindex: the strip scrolls, and focus pauses it\n      tabIndex={0}\n      data-direction={direction}\n      data-pause-on-hover={pauseOnHover || undefined}\n      data-fade={fade || undefined}\n      className={cn(\n        'hj-marquee group/marquee relative flex w-full overflow-hidden',\n        'motion-reduce:overflow-x-auto motion-reduce:overscroll-x-contain motion-reduce:[scrollbar-width:thin]',\n        focusRing,\n        fade &&\n          'motion-safe:[mask-image:linear-gradient(to_right,transparent,black_12%,black_88%,transparent)]',\n        className,\n      )}\n      style={{ ...vars, ...style }}\n      {...props}\n    >\n      <div className={track}>{children}</div>\n      <div aria-hidden=\"true\" className={cn(track, 'motion-reduce:hidden')}>\n        {children}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "hj/components/marquee/index.tsx"
    }
  ],
  "cssVars": {},
  "type": "registry:ui"
}