{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "obsidian-ui",
  "homepage": "https://www.obsidianui.dev",
  "items": [
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "alert",
      "type": "registry:ui",
      "dependencies": [
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/alert.tsx",
          "target": "@ui/alert.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst alertVariants = cva(\n  \"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-card text-card-foreground\",\n        destructive:\n          \"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Alert({\n  className,\n  variant,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\n  return (\n    <div\n      data-slot=\"alert\"\n      role=\"alert\"\n      className={cn(alertVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-title\"\n      className={cn(\n        \"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-description\"\n      className={cn(\n        \"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Alert, AlertTitle, AlertDescription }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "apple-spotlight",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/apple-spotlight.tsx",
          "target": "@components/block/apple-spotlight.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport { AnimatePresence, motion } from 'motion/react';\nimport {\n    Activity,\n    Calendar,\n    ChevronRight,\n    Files,\n    Folder,\n    Globe,\n    Image as ImageIcon,\n    LayoutGrid,\n    Mail,\n    MessageSquare,\n    Music,\n    Search,\n    Settings,\n    StickyNote,\n    Terminal,\n    Twitter\n} from 'lucide-react';\nimport React, { useEffect, useRef, useState } from 'react';\n\ninterface Shortcut {\n    label: string;\n    icon: React.ReactNode;\n    link: string;\n}\n\ninterface SearchResult {\n    icon: React.ReactNode;\n    label: string;\n    description: string;\n    link: string;\n}\n\nconst SVGFilter = () => (\n    <svg width=\"0\" height=\"0\">\n        <filter id=\"blob\">\n            <feGaussianBlur stdDeviation=\"10\" in=\"SourceGraphic\" />\n            <feColorMatrix\n                values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -9\"\n                result=\"blob\"\n            />\n            <feBlend in=\"SourceGraphic\" in2=\"blob\" />\n        </filter>\n    </svg>\n);\n\nconst ShortcutButton = ({ icon, link, label }: { icon: React.ReactNode; link: string; label: string }) => (\n    <a href={link} target=\"_blank\" rel=\"noopener noreferrer\" aria-label={label}>\n        <div className=\"rounded-full cursor-pointer hover:shadow-lg opacity-30 hover:opacity-100 transition-[opacity,shadow] duration-200\">\n            <div className=\"size-16 aspect-square flex items-center justify-center\">{icon}</div>\n        </div>\n    </a>\n);\n\nconst SpotlightPlaceholder = ({ text, className }: { text: string; className?: string }) => (\n    <motion.div layout className={cn('absolute text-gray-500 flex items-center pointer-events-none z-10', className)}>\n        <AnimatePresence mode=\"popLayout\">\n            <motion.p\n                layoutId={`placeholder-${text}`}\n                key={`placeholder-${text}`}\n                initial={{ opacity: 0, y: 10, filter: 'blur(5px)' }}\n                animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}\n                exit={{ opacity: 0, y: -10, filter: 'blur(5px)' }}\n                transition={{ duration: 0.2, ease: 'easeOut' }}\n            >\n                {text}\n            </motion.p>\n        </AnimatePresence>\n    </motion.div>\n);\n\nconst SpotlightInput = ({\n    placeholder,\n    hidePlaceholder,\n    value,\n    onChange,\n    placeholderClassName\n}: {\n    placeholder: string;\n    hidePlaceholder: boolean;\n    value: string;\n    onChange: (value: string) => void;\n    placeholderClassName?: string;\n}) => {\n    const inputRef = useRef<HTMLInputElement>(null);\n\n    useEffect(() => {\n        inputRef.current?.focus();\n    }, []);\n\n    return (\n        <div className=\"flex items-center w-full justify-start gap-2 px-6 h-16\">\n            <motion.div layoutId=\"search-icon\"><Search /></motion.div>\n            <div className=\"flex-1 relative text-2xl\">\n                {!hidePlaceholder && <SpotlightPlaceholder text={placeholder} className={placeholderClassName} />}\n                <motion.input\n                    ref={inputRef}\n                    layout=\"position\"\n                    type=\"text\"\n                    aria-label=\"Search shortcuts\"\n                    value={value}\n                    onChange={(e) => onChange(e.target.value)}\n                    className=\"w-full bg-transparent outline-none ring-none\"\n                />\n            </div>\n        </div>\n    );\n};\n\nconst SearchResultCard = ({ icon, label, description, link, isLast }: SearchResult & { isLast: boolean }) => (\n    <a href={link} target=\"_blank\" className=\"overflow-hidden w-full group/card\">\n        <div className={cn(\n            'flex items-center text-black justify-start hover:bg-white gap-3 py-2 px-2 rounded-xl hover:shadow-md w-full',\n            isLast && 'rounded-b-3xl'\n        )}>\n            <div className=\"size-8 [&_svg]:stroke-[1.5] [&_svg]:size-6 aspect-square flex items-center justify-center\">{icon}</div>\n            <div className=\"flex flex-col\">\n                <p className=\"font-medium\">{label}</p>\n                <p className=\"text-xs opacity-50\">{description}</p>\n            </div>\n            <div className=\"flex-1 flex items-center justify-end opacity-0 group-hover/card:opacity-100 transition-opacity duration-200\">\n                <ChevronRight className=\"size-6\" />\n            </div>\n        </div>\n    </a>\n);\n\nconst SearchResultsContainer = ({ searchResults, onHover }: { searchResults: SearchResult[]; onHover: (index: number | null) => void }) => (\n    <motion.div layout onMouseLeave={() => onHover(null)} className=\"px-2 border-t flex flex-col bg-neutral-100 max-h-96 overflow-y-auto w-full py-2\">\n        {searchResults.map((result, index) => (\n            <motion.div\n                key={`search-result-${index}`}\n                onMouseEnter={() => onHover(index)}\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ delay: index * 0.1, duration: 0.2, ease: 'easeOut' }}\n            >\n                <SearchResultCard {...result} isLast={index === searchResults.length - 1} />\n            </motion.div>\n        ))}\n    </motion.div>\n);\n\ninterface AppleSpotlightProps {\n    shortcuts?: Shortcut[];\n    isOpen?: boolean;\n    handleClose?: () => void;\n}\n\nconst DEFAULT_SHORTCUTS: Shortcut[] = [\n    { label: 'Apps', icon: <LayoutGrid />, link: '#' },\n    { label: 'Files', icon: <Folder />, link: '#' },\n    { label: 'Actions', icon: <Activity />, link: '#' },\n    { label: 'Clipboard', icon: <Files />, link: '#' }\n];\n\nconst DEFAULT_SEARCH_RESULTS: SearchResult[] = [\n    { icon: <Twitter />, label: 'Twitter', description: 'Open Twitter', link: '#' },\n    { icon: <Globe />, label: 'Safari', description: 'Open web browser', link: '#' },\n    { icon: <Mail />, label: 'Mail', description: 'Open Mail', link: '#' },\n    { icon: <Calendar />, label: 'Calendar', description: 'View calendar', link: '#' },\n    { icon: <StickyNote />, label: 'Notes', description: 'Open Notes', link: '#' },\n    { icon: <ImageIcon />, label: 'Photos', description: 'Browse photos', link: '#' },\n    { icon: <Settings />, label: 'Settings', description: 'Open Settings', link: '#' },\n    { icon: <Terminal />, label: 'Terminal', description: 'Open Terminal', link: '#' },\n    { icon: <Folder />, label: 'Finder', description: 'Open Finder', link: '#' },\n    { icon: <MessageSquare />, label: 'Messages', description: 'Open Messages', link: '#' },\n    { icon: <Music />, label: 'Music', description: 'Open Music', link: '#' }\n];\n\nexport function AppleSpotlight({ shortcuts = DEFAULT_SHORTCUTS, isOpen = true, handleClose = () => { } }: AppleSpotlightProps) {\n    const [hovered, setHovered] = useState(false);\n    const [hoveredSearchResult, setHoveredSearchResult] = useState<number | null>(null);\n    const [hoveredShortcut, setHoveredShortcut] = useState<number | null>(null);\n    const [searchValue, setSearchValue] = useState('');\n\n    return (\n        <AnimatePresence mode=\"wait\">\n            {isOpen && (\n                <motion.div\n                    initial={{ opacity: 0, filter: 'blur(20px)', scaleX: 1.3, scaleY: 1.1, y: -10 }}\n                    animate={{ opacity: 1, filter: 'blur(0px)', scaleX: 1, scaleY: 1, y: 0 }}\n                    exit={{ opacity: 0, filter: 'blur(20px)', scaleX: 1.3, scaleY: 1.1, y: 10 }}\n                    transition={{ stiffness: 550, damping: 50, type: 'spring' }}\n                    className=\"fixed inset-0 z-50 flex flex-col items-center justify-center\"\n                    onClick={handleClose}\n                >\n                    <SVGFilter />\n                    <div\n                        onMouseEnter={() => setHovered(true)}\n                        onMouseLeave={() => { setHovered(false); setHoveredShortcut(null); }}\n                        onClick={(e) => e.stopPropagation()}\n                        className={cn(\n                            'w-full flex items-center justify-end gap-4 z-20 group',\n                            '[&>div]:bg-neutral-100 [&>div]:text-black [&>div]:rounded-full [&>div]:backdrop-blur-xl',\n                            '[&_svg]:size-7 [&_svg]:stroke-[1.4]',\n                            'max-w-3xl'\n                        )}\n                    >\n                        <AnimatePresence mode=\"popLayout\">\n                            <motion.div\n                                layoutId=\"search-input-container\"\n                                transition={{ layout: { duration: 0.5, type: 'spring', bounce: 0.2 } }}\n                                style={{ borderRadius: '30px' }}\n                                className=\"h-full w-full flex flex-col items-center justify-start z-10 relative shadow-lg overflow-hidden border\"\n                            >\n                                <SpotlightInput\n                                    placeholder={\n                                        hoveredShortcut !== null ? shortcuts[hoveredShortcut].label :\n                                            hoveredSearchResult !== null ? DEFAULT_SEARCH_RESULTS[hoveredSearchResult].label : 'Search'\n                                    }\n                                    placeholderClassName={hoveredSearchResult !== null ? 'text-black bg-white' : 'text-gray-500'}\n                                    hidePlaceholder={!(hoveredSearchResult !== null || !searchValue)}\n                                    value={searchValue}\n                                    onChange={setSearchValue}\n                                />\n                                {searchValue && <SearchResultsContainer searchResults={DEFAULT_SEARCH_RESULTS} onHover={setHoveredSearchResult} />}\n                            </motion.div>\n                            {hovered && !searchValue && shortcuts.map((shortcut, index) => (\n                                <motion.div\n                                    key={`shortcut-${index}`}\n                                    onMouseEnter={() => setHoveredShortcut(index)}\n                                    layout\n                                    initial={{ scale: 0.7, x: -1 * (64 * (index + 1)) }}\n                                    animate={{ scale: 1, x: 0 }}\n                                    exit={{ scale: 0.7, x: 1 * (16 * (shortcuts.length - index - 1) + 64 * (shortcuts.length - index - 1)) }}\n                                    transition={{ duration: 0.8, type: 'spring', bounce: 0.2, delay: index * 0.05 }}\n                                    className=\"rounded-full cursor-pointer\"\n                                >\n                                    <ShortcutButton icon={shortcut.icon} link={shortcut.link} label={shortcut.label} />\n                                </motion.div>\n                            ))}\n                        </AnimatePresence>\n                    </div>\n                </motion.div>\n            )}\n        </AnimatePresence>\n    );\n}\n\nexport default AppleSpotlight;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "arrow-fill-button",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/arrow-fill-button.css",
          "target": "@components/block/arrow-fill-button.css",
          "type": "registry:file",
          "content": ".obsidian-arrow-fill-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  height: 3rem;\n  padding-right: 3.5rem;\n  padding-left: 1.5rem;\n  position: relative;\n  width: fit-content;\n  border-radius: 1000px;\n  background: var(--btn-bg);\n  color: var(--btn-text);\n  font-size: 0.875rem;\n  font-weight: 500;\n  text-rendering: geometricPrecision;\n  white-space: nowrap;\n  overflow: hidden;\n  text-decoration: none;\n  cursor: pointer;\n  border: none;\n}\n\n.obsidian-arrow-fill-btn__text {\n  position: relative;\n  z-index: 1;\n}\n\n.obsidian-arrow-fill-btn__circle {\n  clip-path: inset(0.4rem 0.4rem 0.4rem calc(100% - 2.5rem) round 2rem);\n  position: absolute;\n  inset: -1px;\n  border-radius: 1000px;\n  display: flex;\n  align-items: center;\n  padding-right: 3.5rem;\n  padding-left: 1.5rem;\n  z-index: 2;\n  background-color: var(--btn-fill-bg);\n  color: var(--btn-fill-text);\n  transition: clip-path 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86), background-color 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86), color 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n\n.obsidian-arrow-fill-btn__circle-text {\n  padding: 0 1px 0 0;\n  display: flex;\n  align-items: center;\n  gap: 0.75rem;\n  width: 100%;\n  white-space: nowrap;\n}\n\n.obsidian-arrow-fill-btn__icon {\n  width: 0.75rem;\n  height: 0.75rem;\n  position: absolute;\n  right: 1rem;\n  overflow: hidden;\n  flex: 0 0 auto;\n  color: var(--btn-arrow);\n}\n\n.obsidian-arrow-fill-btn:is(:hover, :focus-visible):not(:disabled) .obsidian-arrow-fill-btn__icon {\n  color: var(--btn-arrow-hover);\n}\n\n.obsidian-arrow-fill-btn__path {\n  transition: transform 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n  transform-origin: center center;\n  fill: currentColor;\n}\n\n.obsidian-arrow-fill-btn__path:first-child {\n  transform: translateX(-120%) scale(0);\n}\n\n.obsidian-arrow-fill-btn:is(:hover, :focus-visible):not(:disabled) .obsidian-arrow-fill-btn__path:first-child {\n  transform: translateX(0) scale(1);\n}\n\n.obsidian-arrow-fill-btn:is(:hover, :focus-visible):not(:disabled) .obsidian-arrow-fill-btn__path:last-child {\n  transform: translateX(120%) scale(0);\n}\n\n.obsidian-arrow-fill-btn:is(:hover, :focus-visible):not(:disabled) .obsidian-arrow-fill-btn__circle {\n  clip-path: inset(0 round 2rem);\n  background-color: var(--btn-fill-bg-hover);\n  color: var(--btn-fill-text-hover);\n}\n.obsidian-arrow-fill-btn { font-family: var(--font-inter), var(--font-geist), sans-serif; }\n.obsidian-arrow-fill-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 4px; }\n.obsidian-arrow-fill-btn:disabled { cursor: not-allowed; opacity: 0.5; }\n@media (prefers-reduced-motion: reduce) { .obsidian-arrow-fill-btn__circle, .obsidian-arrow-fill-btn__path { transition: none; } }\n\n\n"
        },
        {
          "path": "components/block/arrow-fill-button.jsx",
          "target": "@components/block/arrow-fill-button.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport \"./arrow-fill-button.css\";\n\n/**\n * @param {import('react').ButtonHTMLAttributes<HTMLButtonElement> & import('react').AnchorHTMLAttributes<HTMLAnchorElement> & {\n *   as?: import('react').ElementType,\n *   bgColor?: string, textColor?: string, fillBgColor?: string, fillTextColor?: string,\n *   hoverFillBgColor?: string, hoverFillTextColor?: string,\n *   arrowColor?: string, hoverArrowColor?: string\n * }} props\n */\nexport function ArrowFillButton({\n  children = \"Explore components\",\n  className = \"\",\n  bgColor = \"#ff6b00\",\n  textColor = \"#ffffff\",\n  fillBgColor = \"#ffffff\",\n  fillTextColor = \"#ff6b00\",\n  hoverFillBgColor = \"#ffffff\",\n  hoverFillTextColor = \"#ff6b00\",\n  arrowColor,\n  hoverArrowColor,\n  as: Component = \"a\",\n  style,\n  ...props\n}) {\n  return (\n    <Component\n      type={Component === \"button\" ? \"button\" : undefined}\n      {...props}\n      className={cn(\"obsidian-arrow-fill-btn\", className)}\n      style={{\n        \"--btn-bg\": bgColor,\n        \"--btn-text\": textColor,\n        \"--btn-fill-bg\": fillBgColor,\n        \"--btn-fill-text\": fillTextColor,\n        \"--btn-fill-bg-hover\": hoverFillBgColor,\n        \"--btn-fill-text-hover\": hoverFillTextColor,\n        \"--btn-arrow\": arrowColor || fillTextColor,\n        \"--btn-arrow-hover\": hoverArrowColor || hoverFillTextColor,\n        ...style,\n      }}\n    >\n      <span className=\"obsidian-arrow-fill-btn__text\">{children}</span>\n\n      <div aria-hidden=\"true\" className=\"obsidian-arrow-fill-btn__circle\">\n        <span>{children}</span>\n\n        <div className=\"obsidian-arrow-fill-btn__circle-text\">\n          <svg\n            viewBox=\"0 0 10 10\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"obsidian-arrow-fill-btn__icon\"\n          >\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M3.82475e-07 5.625L7.625 5.625L4.125 9.125L5 10L10 5L5 -4.37114e-07L4.125 0.874999L7.625 4.375L4.91753e-07 4.375L3.82475e-07 5.625Z\"\n              className=\"obsidian-arrow-fill-btn__path\"\n            />\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M3.82475e-07 5.625L7.625 5.625L4.125 9.125L5 10L10 5L5 -4.37114e-07L4.125 0.874999L7.625 4.375L4.91753e-07 4.375L3.82475e-07 5.625Z\"\n              className=\"obsidian-arrow-fill-btn__path\"\n            />\n          </svg>\n        </div>\n      </div>\n    </Component>\n  );\n}\n\n\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "avatar",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-avatar",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/avatar.tsx",
          "target": "@ui/avatar.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Avatar({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Root>) {\n  return (\n    <AvatarPrimitive.Root\n      data-slot=\"avatar\"\n      className={cn(\n        \"relative flex size-8 shrink-0 overflow-hidden rounded-full\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarImage({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Image>) {\n  return (\n    <AvatarPrimitive.Image\n      data-slot=\"avatar-image\"\n      className={cn(\"aspect-square size-full\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {\n  return (\n    <AvatarPrimitive.Fallback\n      data-slot=\"avatar-fallback\"\n      className={cn(\n        \"bg-muted flex size-full items-center justify-center rounded-full\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Avatar, AvatarImage, AvatarFallback }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "badge",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/badge.tsx",
          "target": "@ui/badge.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n        secondary:\n          \"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n        destructive:\n          \"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Badge({\n  className,\n  variant,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"span\"> &\n  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : \"span\"\n\n  return (\n    <Comp\n      data-slot=\"badge\"\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nexport { Badge, badgeVariants }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "book-flip",
      "type": "registry:block",
      "dependencies": [
        "@react-three/drei",
        "@react-three/fiber",
        "clsx",
        "maath",
        "tailwind-merge",
        "three"
      ],
      "files": [
        {
          "path": "components/block/book-flip.jsx",
          "target": "@components/block/book-flip.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { Suspense, useEffect } from \"react\";\nimport { Canvas, useThree } from \"@react-three/fiber\";\nimport { Experience } from \"@/lib/effects/book-flip/Experience\";\nimport { PageProvider, usePage } from \"@/lib/effects/book-flip/PageContext\";\nimport { WebGLSurface, useEffectReducedMotion } from \"@/lib/effects/shared/webgl-surface\";\n\nconst defaultImages = Array.from({ length: 14 }, (_, index) => `book-flip-img${String(index + 1).padStart(2, \"0\")}`);\nconst defaultCameraDistance = { mobile: 5.5, desktop: 4 };\n\nfunction CameraFit({ cameraDistance }) {\n  const { camera, size } = useThree();\n  useEffect(() => {\n    camera.position.set(-0.5, 1, size.width < 480 ? cameraDistance.mobile : cameraDistance.desktop);\n    camera.updateProjectionMatrix();\n  }, [camera, size.width, cameraDistance]);\n  return null;\n}\n\nfunction BookNavigation({ images }) {\n  const { page, setPage } = usePage();\n  const count = Math.ceil(images.length / 2);\n  return <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex justify-center p-3\">\n    <div role=\"group\" aria-label=\"Book pages\" className=\"pointer-events-auto flex max-w-full gap-2 overflow-x-auto rounded-full bg-black/20 p-1\">\n      {Array.from({ length: count + 1 }, (_, index) => <button\n        key={index}\n        type=\"button\"\n        aria-pressed={index === page}\n        onClick={() => setPage(index)}\n        className={`shrink-0 rounded-full px-3 py-2 text-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white ${index === page ? \"bg-white/90 text-black\" : \"bg-black/30 text-white\"}`}\n      >{index === 0 ? \"Cover\" : index === count ? \"Back cover\" : `Page ${index}`}</button>)}\n    </div>\n  </div>;\n}\n\nfunction BookScene({ images, pathPattern, bgColor, cameraDistance, showUI }) {\n  const reducedMotion = useEffectReducedMotion();\n  return <PageProvider>\n    <Canvas\n      frameloop={reducedMotion ? \"demand\" : \"always\"}\n      dpr={[1, 2]}\n      style={{ position: \"absolute\", inset: 0, background: bgColor }}\n      camera={{ position: [-0.5, 1, cameraDistance.desktop], fov: 45 }}\n    >\n      <CameraFit cameraDistance={cameraDistance} />\n      <Suspense fallback={null}>\n        <Experience images={images} pathPattern={pathPattern} orbitControls={{ minAzimuthAngle: -Math.PI * 0.06, maxAzimuthAngle: Math.PI * 0.06, minPolarAngle: 1.07, maxPolarAngle: 1.58, rotateSpeed: 0.2, enableDamping: !reducedMotion }} />\n      </Suspense>\n    </Canvas>\n    {showUI && <BookNavigation images={images} />}\n  </PageProvider>;\n}\n\n/**\n * Images are PNG page names without their extension, resolved against pathPattern.\n * @param {{ images?: string[], pathPattern?: string, bgColor?: string, cameraDistance?: { mobile: number, desktop: number }, showUI?: boolean, className?: string, style?: import(\"react\").CSSProperties }} props\n */\nexport function BookFlip({ images = defaultImages, pathPattern = \"/effects/book-flip\", bgColor = \"#000000\", cameraDistance = defaultCameraDistance, showUI = true, className, style } = {}) {\n  return <WebGLSurface className={className} style={style} imageSrc={`${pathPattern}/${images[0] || \"book-flip-img01\"}.png`} label=\"ObsidianUI interactive nature book\">\n    <BookScene images={images} pathPattern={pathPattern} bgColor={bgColor} cameraDistance={cameraDistance} showUI={showUI} />\n  </WebGLSurface>;\n}\n"
        },
        {
          "path": "lib/effects/book-flip/Book.jsx",
          "target": "@lib/effects/book-flip/Book.jsx",
          "type": "registry:lib",
          "content": "'use client'\nimport { useTexture } from\"@react-three/drei\";\nimport { useFrame } from\"@react-three/fiber\";\nimport { useEffectReducedMotion } from \"@/lib/effects/shared/webgl-surface\";\nimport { easing } from\"maath\";\nimport { usePage } from\"./PageContext\";\nimport { useEffect, useMemo, useRef, useState } from\"react\";\nimport {\n Bone,\n BoxGeometry,\n Color,\n Float32BufferAttribute,\n MeshStandardMaterial,\n Skeleton,\n SkinnedMesh,\n SRGBColorSpace,\n Uint16BufferAttribute,\n Vector3,\n} from\"three\";\nimport { degToRad } from\"three/src/math/MathUtils.js\";\n\n/**\n * CONFIG CONSTANTS - Adjust these to customize the book appearance and animation\n */\nconst EASING_FACTOR = 0.5; // Controls the speed of page rotation easing (lower = slower)\nconst EASING_FACTOR_FOLD = 0.3; // Controls the speed of page fold animation\nconst INSIDE_CURVE_STRENGTH = 0.18; // How much the inside of the page curves when opened\nconst OUTSIDE_CURVE_STRENGTH = 0.05; // How much the outside edges curve\nconst TURNING_CURVE_STRENGTH = 0.09; // Curve strength during page turning animation\n\nconst PAGE_WIDTH = 1.28; // Width of a single page\nconst PAGE_HEIGHT = 1.71; // Height of a single page (4:3 aspect ratio)\nconst PAGE_DEPTH = 0.003; // Thickness of paper\nconst PAGE_SEGMENTS = 30; // Number of bone segments (more = more flexible, but slower)\nconst SEGMENT_WIDTH = PAGE_WIDTH / PAGE_SEGMENTS;\n\n/**\n * GEOMETRY SETUP\n * Creates a segmented geometry that can be deformed by skeleton bones\n * Each segment is controlled by 2 bones for smooth deformation\n */\nconst pageGeometry = new BoxGeometry(\n PAGE_WIDTH,\n PAGE_HEIGHT,\n PAGE_DEPTH,\n PAGE_SEGMENTS,\n 2\n);\n\n// Translate geometry so rotation happens from the left edge\npageGeometry.translate(PAGE_WIDTH / 2, 0, 0);\n\n// Setup skin weights and indices for skeletal animation\nconst position = pageGeometry.attributes.position;\nconst vertex = new Vector3();\nconst skinIndexes = [];\nconst skinWeights = [];\n\nfor (let i = 0; i < position.count; i++) {\n vertex.fromBufferAttribute(position, i);\n const x = vertex.x;\n\n // Each vertex is influenced by 2 adjacent bones\n const skinIndex = Math.max(0, Math.floor(x / SEGMENT_WIDTH));\n const skinWeight = (x % SEGMENT_WIDTH) / SEGMENT_WIDTH;\n\n // Store bone influences (up to 4 per vertex, we use 2)\n skinIndexes.push(skinIndex, skinIndex + 1, 0, 0);\n skinWeights.push(1 - skinWeight, skinWeight, 0, 0);\n}\n\npageGeometry.setAttribute(\n\"skinIndex\",\n new Uint16BufferAttribute(skinIndexes, 4)\n);\npageGeometry.setAttribute(\n\"skinWeight\",\n new Float32BufferAttribute(skinWeights, 4)\n);\n\n// Base materials for pages\nconst whiteColor = new Color(\"white\");\nconst emissiveColor = new Color(\"orange\");\n\nconst pageMaterials = [\n new MeshStandardMaterial({ color: whiteColor }),\n new MeshStandardMaterial({ color:\"#111\" }),\n new MeshStandardMaterial({ color: whiteColor }),\n new MeshStandardMaterial({ color: whiteColor }),\n];\n\n/**\n * Helper function to generate page pairs from image array\n * Creates front/back pairs: [0,1], [2,3], [4,5], etc.\n */\nconst generatePages = (imageArray) => {\n if (!imageArray || imageArray.length === 0) {\n return [];\n }\n\n const pages = [\n {\n front: imageArray[0],\n back: imageArray[1] || imageArray[0],\n },\n ];\n\n for (let i = 2; i < imageArray.length - 1; i += 2) {\n pages.push({\n front: imageArray[i],\n back: imageArray[i + 1],\n });\n }\n\n if (imageArray.length % 2 === 1) {\n pages.push({\n front: imageArray[imageArray.length - 1],\n back: imageArray[0],\n });\n }\n\n return pages;\n};\n\n/**\n * Preload textures for better performance\n */\nconst preloadTextures = (pages, pathPattern) => {\n pages.forEach((page) => {\n useTexture.preload(`${pathPattern}/${page.front}.png`);\n useTexture.preload(`${pathPattern}/${page.back}.png`);\n });\n};\n\n/**\n * PAGE COMPONENT\n * Represents a single page in the book with skinned mesh deformation\n *  * ANIMATION LOGIC:\n * - Uses skeleton with 31 bones along the page width\n * - Each bone rotates based on whether the page is opened\n * - Inner bones curve more (3D effect), outer bones curve less\n * - During turning, bones follow a sine wave for smooth animation\n */\nconst Page = ({  number,  front,  back,  page,  opened,  bookClosed,\n pathPattern,\n ...props }) => {\n const frontPath = `${pathPattern}/${front}.png`;\n const backPath = `${pathPattern}/${back}.png`;\n\n const pictures = useTexture([frontPath, backPath]);\n const [picture, picture2] = useMemo(() => pictures.map((original) => {\n   const texture = original.clone();\n   texture.colorSpace = SRGBColorSpace;\n   return texture;\n }), [pictures]);\n const reducedMotion = useEffectReducedMotion();\n\n const group = useRef();\n const turnedAt = useRef(0);\n const lastOpened = useRef(opened);\n const skinnedMeshRef = useRef();\n\n /**\n * Create the skeletal mesh for this page\n * - Creates 31 bones in a chain\n * - Attaches them hierarchically so rotation cascades\n * - Wraps geometry in skeleton for deformation\n */\n const manualSkinnedMesh = useMemo(() => {\n const bones = [];\n  // Create bone chain\n for (let i = 0; i <= PAGE_SEGMENTS; i++) {\n const bone = new Bone();\n bones.push(bone);\n  if (i === 0) {\n bone.position.x = 0; // Root bone at spine\n } else {\n bone.position.x = SEGMENT_WIDTH; // Each subsequent bone positioned relative to parent\n }\n  if (i > 0) {\n bones[i - 1].add(bone); // Attach to parent bone\n }\n }\n\n const skeleton = new Skeleton(bones);\n\n // Materials: 4 base materials + 2 textured materials (front and back)\n const materials = [\n ...pageMaterials.map((material) => material.clone()),\n new MeshStandardMaterial({\n color: whiteColor,\n map: picture,\n roughness: 0.1,\n emissive: emissiveColor,\n emissiveIntensity: 0,\n }),\n new MeshStandardMaterial({\n color: whiteColor,\n map: picture2,\n roughness: 0.1,\n emissive: emissiveColor,\n emissiveIntensity: 0,\n }),\n ];\n\n const mesh = new SkinnedMesh(pageGeometry.clone(), materials);\n mesh.frustumCulled = false;\n mesh.add(skeleton.bones[0]);\n mesh.bind(skeleton); // Bind skeleton to mesh\n  return mesh;\n }, [picture, picture2]);\n useEffect(() => () => {\n   manualSkinnedMesh.geometry.dispose();\n   manualSkinnedMesh.material.forEach((material) => material.dispose());\n   manualSkinnedMesh.skeleton.dispose();\n   picture.dispose();\n   picture2.dispose();\n }, [manualSkinnedMesh, picture, picture2]);\n\n /**\n * ANIMATION LOOP\n * Updates bone rotations each frame to create page flip effect\n */\n useFrame((_, delta) => {\n if (!skinnedMeshRef.current) return;\n\n // Highlight page on hover by increasing emissive intensity\n // const emissiveIntensity = highlighted ? 0.22 : 0;\n // skinnedMeshRef.current.material[4].emissiveIntensity =\n // skinnedMeshRef.current.material[5].emissiveIntensity = MathUtils.lerp(\n // skinnedMeshRef.current.material[4].emissiveIntensity,\n // emissiveIntensity,\n // 0.1\n // );\n\n // Track when page opened state changed for animation timing\n if (lastOpened.current !== opened) {\n turnedAt.current = +new Date();\n lastOpened.current = opened;\n }\n\n // Calculate animation progress (0 to 1, then sin for smooth curve)\n let turningTime = Math.min(400, new Date() - turnedAt.current) / 400;\n turningTime = reducedMotion ? 0 : Math.sin(turningTime * Math.PI);\n\n // Target rotation: opened = -90°, closed = +90°\n let targetRotation = opened ? -Math.PI / 2 : Math.PI / 2;\n  // Add slight extra rotation based on page number (pages fan out slightly)\n if (!bookClosed) {\n targetRotation += degToRad(number * 0.8);\n }\n\n /**\n * BONE ANIMATION CALCULATION\n * Each bone along the page rotates differently:\n * - Inside bones (0-8): Follow sin curve for inside curl effect\n * - Outside bones (8+): Follow cos curve for outside edge effect\n * - All bones smoothly interpolate to target rotation using dampAngle\n */\n const bones = skinnedMeshRef.current.skeleton.bones;\n for (let i = 0; i < bones.length; i++) {\n const target = i === 0 ? group.current : bones[i];\n\n // Inner curve: creates 3D curl effect on the inside\n const insideCurveIntensity = i < 8 ? Math.sin(i * 0.2 + 0.25) : 0;\n  // Outer curve: pages don't rotate as much at the edges\n const outsideCurveIntensity = i >= 8 ? Math.cos(i * 0.3 + 0.09) : 0;\n  // Turning animation: smooth wave during the page turn\n const turningIntensity =\n Math.sin(i * Math.PI * (1 / bones.length)) * turningTime;\n\n // Combine all rotation influences\n let rotationAngle =\n INSIDE_CURVE_STRENGTH * insideCurveIntensity * targetRotation -\n OUTSIDE_CURVE_STRENGTH * outsideCurveIntensity * targetRotation +\n TURNING_CURVE_STRENGTH * turningIntensity * targetRotation;\n\n // Fold rotation: subtle X-axis rotation for paper fold effect\n let foldRotationAngle = degToRad(Math.sign(targetRotation) * 2);\n\n // When book is closed, only root bone rotates\n if (bookClosed) {\n if (i === 0) {\n rotationAngle = targetRotation;\n foldRotationAngle = 0;\n } else {\n rotationAngle = 0;\n foldRotationAngle = 0;\n }\n }\n\n // Reduced motion applies the same final pose without the page-turn travel.\n if (reducedMotion) {\n   target.rotation.y = rotationAngle;\n   target.rotation.x = 0;\n   continue;\n }\n easing.dampAngle(\n target.rotation,\n\"y\",\n rotationAngle,\n EASING_FACTOR,\n delta\n );\n\n // Add fold effect to middle/end bones during turning\n const foldIntensity =\n i > 8\n ? Math.sin(i * Math.PI * (1 / bones.length) - 0.5) * turningTime\n : 0;\n easing.dampAngle(\n target.rotation,\n\"x\",\n foldRotationAngle * foldIntensity,\n EASING_FACTOR_FOLD,\n delta\n );\n }\n });\n\n const { setPage } = usePage();\n// const [highlighted, setHighlighted] = useState(false);\n// useCursor(highlighted);\n\n return (\n <group\n {...props}\n ref={group}\n // onPointerEnter={(e) => {\n // e.stopPropagation();\n // setHighlighted(true);\n // }}\n // onPointerLeave={(e) => {\n // e.stopPropagation();\n // setHighlighted(false);\n // }}\n onClick={(e) => {\n e.stopPropagation();\n setPage(opened ? number : number + 1);\n // setHighlighted(false);\n }}\n >\n <primitive\n object={manualSkinnedMesh}\n ref={skinnedMeshRef}\n // Stack pages with slight depth offset\n position-z={-number * PAGE_DEPTH + page * PAGE_DEPTH}\n />\n </group>\n );\n};\n\n/**\n * BOOK COMPONENT\n * Main book component that orchestrates page animation\n *  * ANIMATION FLOW:\n * 1. User clicks to change page number in PageContext\n * 2. delayedPage state animates one page at a time toward target\n * 3. Each page renders and receives updated page/opened props\n * 4. Pages animate their bones based on opened state\n */\nexport const Book = ({  images = [],\n pathPattern =\"/assets/nature\",\n ...props }) => {\n const { page } = usePage();\n const reducedMotion = useEffectReducedMotion();\n const [delayedPage, setDelayedPage] = useState(page);\n\n // Generate pages from image array\n const pages = useMemo(() => generatePages(images), [images]);\n\n // Preload textures\n useEffect(() => {\n if (pages.length > 0) {\n preloadTextures(pages, pathPattern);\n }\n }, [pages, pathPattern]);\n\n /**\n * PAGE ANIMATION SEQUENCING\n * Animates delayedPage toward target page one page at a time\n * This creates the sequential page-by-page animation\n */\n useEffect(() => {\n let timeout;\n const goToPage = () => {\n setDelayedPage((delayedPage) => {\n if (page === delayedPage) {\n return delayedPage;\n } else {\n // Faster animation for large jumps, slower for single pages\n timeout = setTimeout(\n () => {\n goToPage();\n },\n Math.abs(page - delayedPage) > 2 ? 50 : 150\n );\n  if (page > delayedPage) {\n return delayedPage + 1;\n }\n if (page < delayedPage) {\n return delayedPage - 1;\n }\n }\n });\n };\n if (!reducedMotion) timeout = setTimeout(goToPage, 0);\n return () => {\n clearTimeout(timeout);\n };\n }, [page, reducedMotion]);\n\n return (\n <group {...props} rotation-y={-Math.PI / 2} rotation-x={-0.7} >\n {pages.map((pageData, index) => (\n <Page\n key={index}\n page={reducedMotion ? page : delayedPage}\n number={index}\n opened={(reducedMotion ? page : delayedPage) > index}\n bookClosed={(reducedMotion ? page : delayedPage) === 0 || (reducedMotion ? page : delayedPage) === pages.length}\n pathPattern={pathPattern}\n {...pageData}\n />\n ))}\n </group>\n );\n};\n"
        },
        {
          "path": "lib/effects/book-flip/Experience.jsx",
          "target": "@lib/effects/book-flip/Experience.jsx",
          "type": "registry:lib",
          "content": "'use client'\nimport { Environment, OrbitControls } from\"@react-three/drei\";\nimport { Book } from\"./Book\";\n\nexport const Experience = ({\n images = [],\n pathPattern =\"/assets/nature\",\n orbitControls = {},\n ...props\n}) => {\n return (\n <>\n <OrbitControls\n enableDamping\n enablePan={false}\n enableZoom={false}\n target={[0, 0, 0]}\n {...orbitControls}\n />\n <Book images={images} pathPattern={pathPattern} {...props} />\n <Environment files=\"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/book-flip/studio.hdr\" />\n <directionalLight\n position={[2, 5, 2]}\n intensity={2.5}\n />\n </>\n );\n};\n"
        },
        {
          "path": "lib/effects/book-flip/PageContext.jsx",
          "target": "@lib/effects/book-flip/PageContext.jsx",
          "type": "registry:lib",
          "content": "'use client'\nimport { createContext, useContext, useState } from\"react\";\n\nconst PageContext = createContext();\n\nexport const PageProvider = ({ children }) => {\n const [page, setPage] = useState(0);\n\n return (\n <PageContext.Provider value={{ page, setPage }}>\n {children}\n </PageContext.Provider>\n );\n};\n\nexport const usePage = () => {\n const context = useContext(PageContext);\n if (!context) {\n throw new Error(\"usePage must be used within PageProvider\");\n }\n return context;\n};\n"
        },
        {
          "path": "lib/effects/shared/webgl-surface.jsx",
          "target": "@lib/effects/shared/webgl-surface.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\n\nimport { Component, useSyncExternalStore } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst subscribeMotion = (notify) => {\n  const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n  query.addEventListener(\"change\", notify);\n  return () => query.removeEventListener(\"change\", notify);\n};\n\nexport function useEffectReducedMotion() {\n  return useSyncExternalStore(subscribeMotion, () => window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches, () => true);\n}\n\nlet webglAvailable;\nfunction supportsWebGL() {\n  if (webglAvailable !== undefined) return webglAvailable;\n  try {\n    const canvas = document.createElement(\"canvas\");\n    const context = canvas.getContext(\"webgl2\");\n    webglAvailable = Boolean(context);\n    context?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n  } catch {\n    webglAvailable = false;\n  }\n  return webglAvailable;\n}\nconst subscribeAvailability = () => () => {};\n\nclass SurfaceBoundary extends Component {\n  state = { failed: false };\n  static getDerivedStateFromError() { return { failed: true }; }\n  render() { return this.state.failed ? this.props.fallback : this.props.children; }\n}\n\n/** @param {{ children?: import(\"react\").ReactNode, className?: string, style?: import(\"react\").CSSProperties, imageSrc?: string, label?: string }} props */\nexport function WebGLSurface({ children, className, style, imageSrc, label = \"ObsidianUI visual effect\" }) {\n  const supported = useSyncExternalStore(subscribeAvailability, supportsWebGL, () => false);\n  const fallback = <div role=\"img\" aria-label={label} className=\"absolute inset-0 bg-cover bg-center\" style={{ backgroundImage: imageSrc ? `url(${JSON.stringify(imageSrc)})` : undefined }} />;\n  return (\n    <div className={cn(\"relative isolate h-[28rem] w-full overflow-hidden bg-black\", className)} style={{ containerType: \"size\", ...style }}>\n      {fallback}\n      {supported && <SurfaceBoundary fallback={fallback}>{children}</SurfaceBoundary>}\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "breadcrumb",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/breadcrumb.tsx",
          "target": "@ui/breadcrumb.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Breadcrumb({ ...props }: React.ComponentProps<\"nav\">) {\n  return <nav aria-label=\"breadcrumb\" data-slot=\"breadcrumb\" {...props} />\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<\"ol\">) {\n  return (\n    <ol\n      data-slot=\"breadcrumb-list\"\n      className={cn(\n        \"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbItem({ className, ...props }: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"breadcrumb-item\"\n      className={cn(\"inline-flex items-center gap-1.5\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbLink({\n  asChild,\n  className,\n  ...props\n}: React.ComponentProps<\"a\"> & {\n  asChild?: boolean\n}) {\n  const Comp = asChild ? Slot : \"a\"\n\n  return (\n    <Comp\n      data-slot=\"breadcrumb-link\"\n      className={cn(\"hover:text-foreground transition-colors\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbPage({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"breadcrumb-page\"\n      role=\"link\"\n      aria-disabled=\"true\"\n      aria-current=\"page\"\n      className={cn(\"text-foreground font-normal\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"breadcrumb-separator\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn(\"[&>svg]:size-3.5\", className)}\n      {...props}\n    >\n      {children ?? <ChevronRight />}\n    </li>\n  )\n}\n\nfunction BreadcrumbEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"breadcrumb-ellipsis\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn(\"flex size-9 items-center justify-center\", className)}\n      {...props}\n    >\n      <MoreHorizontal className=\"size-4\" />\n      <span className=\"sr-only\">More</span>\n    </span>\n  )\n}\n\nexport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  BreadcrumbEllipsis,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "butterfly-trail-cursor",
      "type": "registry:block",
      "dependencies": [
        "@react-three/drei",
        "@react-three/fiber",
        "clsx",
        "motion",
        "tailwind-merge",
        "three"
      ],
      "files": [
        {
          "path": "components/block/butterfly-trail-cursor.jsx",
          "target": "@components/block/butterfly-trail-cursor.jsx",
          "type": "registry:block",
          "content": "'use client'\n/* eslint-disable react-hooks/immutability -- Three.js scene objects are mutable render resources owned by this component. */\n\nimport { Suspense, useEffect, useMemo, useRef } from 'react'\nimport { Canvas, useFrame, useLoader, useThree } from '@react-three/fiber'\nimport { useGLTF } from '@react-three/drei'\nimport * as THREE from 'three'\nimport { useReducedMotion } from 'motion/react'\nimport { cn } from '@/lib/utils'\nimport * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js'\n\nconst BUTTERFLY_LIFETIME = 2\nconst FADE_DURATION = 0.5\nconst MAX_BUTTERFLIES = 200\nconst SPAWN_THROTTLE = 120\n\nfunction ButterflyPool({ matcapMaterial, gltfScene, gltfAnimations, motionEnabled }) {\n  const poolRef = useRef([])\n  const lastSpawnTime = useRef(0)\n  const mixersRef = useRef([])\n  const { gl, viewport, clock } = useThree()\n\n  const poolGroup = useMemo(() => new THREE.Group(), [])\n\n  useEffect(() => {\n    const group = poolGroup\n\n    for (let index = 0;index < MAX_BUTTERFLIES;index += 1) {\n      const clone = SkeletonUtils.clone(gltfScene)\n      const instanceMaterial = matcapMaterial.clone()\n      instanceMaterial.transparent = true\n      instanceMaterial.opacity = 1\n\n      clone.traverse((child) => {\n        if (child.isMesh || child.isSkinnedMesh) child.material = instanceMaterial\n      })\n\n      clone.scale.setScalar(0.0012)\n      clone.visible = false\n      clone.userData = {\n        active: false,\n        direction: new THREE.Vector3(),\n        createdAt: 0,\n        initialRotation: new THREE.Euler(),\n        material: instanceMaterial,\n      }\n\n      const mixer = new THREE.AnimationMixer(clone)\n      if (gltfAnimations.length > 0) {\n        const action = mixer.clipAction(gltfAnimations[0])\n        action.play()\n        clone.userData.mixer = mixer\n        clone.userData.action = action\n      }\n\n      mixersRef.current.push(mixer)\n      group.add(clone)\n      poolRef.current.push(clone)\n    }\n\n    const pool = poolRef.current\n    const mixers = mixersRef.current\n    return () => {\n      for (const mixer of mixers) { mixer.stopAllAction(); mixer.uncacheRoot(mixer.getRoot()) }\n      for (const butterfly of pool) { butterfly.userData.material.dispose(); group.remove(butterfly) }\n      poolRef.current = []\n      mixersRef.current = []\n    }\n  }, [gltfAnimations, gltfScene, matcapMaterial, poolGroup])\n\n  useFrame((state, delta) => {\n    if (!motionEnabled) return\n    const now = state.clock.elapsedTime\n\n    for (const butterfly of poolRef.current) {\n      const data = butterfly.userData\n      if (!data.active) continue\n\n      const age = now - data.createdAt\n      const fadeStart = BUTTERFLY_LIFETIME - FADE_DURATION\n\n      if (age > fadeStart) {\n        const opacity = Math.max(0, 1 - (age - fadeStart) / FADE_DURATION)\n        data.material.opacity = opacity\n        if (data.action) data.action.timeScale = (1.6 + Math.random() * 0.8) * opacity\n      }\n\n      if (age > BUTTERFLY_LIFETIME) {\n        butterfly.visible = false\n        data.active = false\n        data.material.opacity = 1\n        continue\n      }\n\n      butterfly.position.x += data.direction.x * delta * 2\n      butterfly.position.y += data.direction.y * delta * 2\n      butterfly.position.z += data.direction.z * delta * 0.5\n      butterfly.rotation.x = data.initialRotation.x + Math.sin(now * 3 + butterfly.position.x) * 0.1\n      butterfly.rotation.y = data.initialRotation.y\n      butterfly.rotation.z = data.initialRotation.z\n      data.mixer?.update(delta)\n    }\n  })\n\n  useEffect(() => {\n    if (!motionEnabled) return\n    const surface = gl.domElement\n    const handleMouseMove = (event) => {\n      const now = Date.now()\n      if (now - lastSpawnTime.current < SPAWN_THROTTLE) return\n      lastSpawnTime.current = now\n\n      const rect = surface.getBoundingClientRect()\n      const x = ((event.clientX - rect.left) / rect.width - 0.5) * viewport.width\n      const y = (0.5 - (event.clientY - rect.top) / rect.height) * viewport.height\n      const count = 3 + Math.floor(Math.random() * 3)\n      const createdAt = clock.elapsedTime\n\n      let spawned = 0\n      for (const butterfly of poolRef.current) {\n        if (spawned >= count) break\n        const data = butterfly.userData\n        if (data.active) continue\n\n        const angle = Math.random() * Math.PI * 2\n        butterfly.position.set(\n          x + (Math.random() - 0.5) * 1.5,\n          y + (Math.random() - 0.5) * 1.5,\n          (Math.random() - 0.5) * 0.5\n        )\n        data.direction.set(\n          Math.cos(angle) * 0.75,\n          0.3 + Math.random() * 0.5,\n          (Math.random() - 0.5) * 0.3\n        )\n        data.initialRotation.set(\n          (Math.random() - 0.5) * Math.PI * 0.3,\n          Math.random() * Math.PI * 2,\n          (Math.random() - 0.5) * Math.PI * 0.2\n        )\n        butterfly.rotation.copy(data.initialRotation)\n        data.createdAt = createdAt\n        data.active = true\n        data.material.opacity = 1\n        butterfly.visible = true\n        if (data.action) {\n          data.action.timeScale = 1.6 + Math.random() * 0.8\n          data.action.time = Math.random() * 2\n        }\n        spawned += 1\n      }\n    }\n\n    surface.addEventListener('mousemove', handleMouseMove)\n    return () => surface.removeEventListener('mousemove', handleMouseMove)\n  }, [clock, gl, motionEnabled, viewport])\n\n  return <primitive object={poolGroup} />\n}\n\nfunction ButterflyTrail({ motionEnabled }) {\n  const matcapTexture = useLoader(THREE.TextureLoader, 'https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/butterfly-trail-cursor/butterfly-trail-cursor-matcap.webp')\n  const { scene, animations } = useGLTF('https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/butterfly-trail-cursor/butterfly3.glb')\n\n  const matcapMaterial = useMemo(\n    () =>\n      new THREE.MeshMatcapMaterial({\n        matcap: matcapTexture,\n        side: THREE.DoubleSide,\n      }),\n    [matcapTexture]\n  )\n\n  useEffect(() => () => matcapMaterial.dispose(), [matcapMaterial])\n  return <ButterflyPool matcapMaterial={matcapMaterial} gltfScene={scene} gltfAnimations={animations} motionEnabled={motionEnabled} />\n}\n\n\n/** @param {{ className?: string, height?: import(\"react\").CSSProperties[\"height\"], style?: import(\"react\").CSSProperties, text?: string }} props */\nexport function ButterflyTrailCursor({ className, height = 400, style, text = \"ObsidianUI, in flight.\" } = {}) {\n  const motionEnabled = !useReducedMotion()\n  return (\n    <div className={cn(\"relative isolate w-full overflow-hidden bg-[#EAEAE9]\", className)} style={{ height, containerType: \"inline-size\", ...style }}>\n      <Canvas frameloop={motionEnabled ? \"always\" : \"demand\"} camera={{ position: [0, 0, 5], fov: 75 }} gl={{ antialias: false, powerPreference: 'high-performance', alpha: false }} dpr={[1, 1]} performance={{ min: 0.5 }}>\n        <color attach=\"background\" args={['#EAEAE9']} />\n        <Suspense fallback={null}><ButterflyTrail motionEnabled={motionEnabled} /></Suspense>\n      </Canvas>\n      <p className=\"pointer-events-none absolute left-1/2 top-1/2 w-full -translate-x-1/2 -translate-y-1/2 text-center font-serif text-[clamp(22px,4cqw,44px)] text-zinc-600\">\n        {text}\n      </p>\n    </div>\n  )\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "button",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "button-group",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-separator",
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button-group.tsx",
          "target": "@ui/button-group.tsx",
          "type": "registry:ui",
          "content": "import { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Separator } from \"@/components/ui/separator\"\n\nconst buttonGroupVariants = cva(\n  \"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2\",\n  {\n    variants: {\n      orientation: {\n        horizontal:\n          \"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none\",\n        vertical:\n          \"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none\",\n      },\n    },\n    defaultVariants: {\n      orientation: \"horizontal\",\n    },\n  }\n)\n\nfunction ButtonGroup({\n  className,\n  orientation,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof buttonGroupVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"button-group\"\n      data-orientation={orientation}\n      className={cn(buttonGroupVariants({ orientation }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction ButtonGroupText({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  asChild?: boolean\n}) {\n  const Comp = asChild ? Slot : \"div\"\n\n  return (\n    <Comp\n      className={cn(\n        \"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ButtonGroupSeparator({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"button-group-separator\"\n      orientation={orientation}\n      className={cn(\n        \"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  ButtonGroup,\n  ButtonGroupSeparator,\n  ButtonGroupText,\n  buttonGroupVariants,\n}\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "target": "@ui/separator.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "calendar",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "lucide-react",
        "react-day-picker",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/calendar.tsx",
          "target": "@ui/calendar.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  ChevronDownIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n} from \"lucide-react\"\nimport { DayButton, DayPicker, getDefaultClassNames } from \"react-day-picker\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\n\nfunction Calendar({\n  className,\n  classNames,\n  showOutsideDays = true,\n  captionLayout = \"label\",\n  buttonVariant = \"ghost\",\n  formatters,\n  components,\n  ...props\n}: React.ComponentProps<typeof DayPicker> & {\n  buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"]\n}) {\n  const defaultClassNames = getDefaultClassNames()\n\n  return (\n    <DayPicker\n      showOutsideDays={showOutsideDays}\n      className={cn(\n        \"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent\",\n        String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n        String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n        className\n      )}\n      captionLayout={captionLayout}\n      formatters={{\n        formatMonthDropdown: (date) =>\n          date.toLocaleString(\"default\", { month: \"short\" }),\n        ...formatters,\n      }}\n      classNames={{\n        root: cn(\"w-fit\", defaultClassNames.root),\n        months: cn(\n          \"flex gap-4 flex-col md:flex-row relative\",\n          defaultClassNames.months\n        ),\n        month: cn(\"flex flex-col w-full gap-4\", defaultClassNames.month),\n        nav: cn(\n          \"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between\",\n          defaultClassNames.nav\n        ),\n        button_previous: cn(\n          buttonVariants({ variant: buttonVariant }),\n          \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n          defaultClassNames.button_previous\n        ),\n        button_next: cn(\n          buttonVariants({ variant: buttonVariant }),\n          \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n          defaultClassNames.button_next\n        ),\n        month_caption: cn(\n          \"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)\",\n          defaultClassNames.month_caption\n        ),\n        dropdowns: cn(\n          \"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5\",\n          defaultClassNames.dropdowns\n        ),\n        dropdown_root: cn(\n          \"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md\",\n          defaultClassNames.dropdown_root\n        ),\n        dropdown: cn(\n          \"absolute bg-popover inset-0 opacity-0\",\n          defaultClassNames.dropdown\n        ),\n        caption_label: cn(\n          \"select-none font-medium\",\n          captionLayout === \"label\"\n            ? \"text-sm\"\n            : \"rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5\",\n          defaultClassNames.caption_label\n        ),\n        table: \"w-full border-collapse\",\n        weekdays: cn(\"flex\", defaultClassNames.weekdays),\n        weekday: cn(\n          \"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none\",\n          defaultClassNames.weekday\n        ),\n        week: cn(\"flex w-full mt-2\", defaultClassNames.week),\n        week_number_header: cn(\n          \"select-none w-(--cell-size)\",\n          defaultClassNames.week_number_header\n        ),\n        week_number: cn(\n          \"text-[0.8rem] select-none text-muted-foreground\",\n          defaultClassNames.week_number\n        ),\n        day: cn(\n          \"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none\",\n          props.showWeekNumber\n            ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-md\"\n            : \"[&:first-child[data-selected=true]_button]:rounded-l-md\",\n          defaultClassNames.day\n        ),\n        range_start: cn(\n          \"rounded-l-md bg-accent\",\n          defaultClassNames.range_start\n        ),\n        range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\n        range_end: cn(\"rounded-r-md bg-accent\", defaultClassNames.range_end),\n        today: cn(\n          \"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none\",\n          defaultClassNames.today\n        ),\n        outside: cn(\n          \"text-muted-foreground aria-selected:text-muted-foreground\",\n          defaultClassNames.outside\n        ),\n        disabled: cn(\n          \"text-muted-foreground opacity-50\",\n          defaultClassNames.disabled\n        ),\n        hidden: cn(\"invisible\", defaultClassNames.hidden),\n        ...classNames,\n      }}\n      components={{\n        Root: ({ className, rootRef, ...props }) => {\n          return (\n            <div\n              data-slot=\"calendar\"\n              ref={rootRef}\n              className={cn(className)}\n              {...props}\n            />\n          )\n        },\n        Chevron: ({ className, orientation, ...props }) => {\n          if (orientation === \"left\") {\n            return (\n              <ChevronLeftIcon className={cn(\"size-4\", className)} {...props} />\n            )\n          }\n\n          if (orientation === \"right\") {\n            return (\n              <ChevronRightIcon\n                className={cn(\"size-4\", className)}\n                {...props}\n              />\n            )\n          }\n\n          return (\n            <ChevronDownIcon className={cn(\"size-4\", className)} {...props} />\n          )\n        },\n        DayButton: CalendarDayButton,\n        WeekNumber: ({ children, ...props }) => {\n          return (\n            <td {...props}>\n              <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\n                {children}\n              </div>\n            </td>\n          )\n        },\n        ...components,\n      }}\n      {...props}\n    />\n  )\n}\n\nfunction CalendarDayButton({\n  className,\n  day,\n  modifiers,\n  ...props\n}: React.ComponentProps<typeof DayButton>) {\n  const defaultClassNames = getDefaultClassNames()\n\n  const ref = React.useRef<HTMLButtonElement>(null)\n  React.useEffect(() => {\n    if (modifiers.focused) ref.current?.focus()\n  }, [modifiers.focused])\n\n  return (\n    <Button\n      ref={ref}\n      variant=\"ghost\"\n      size=\"icon\"\n      data-day={day.date.toLocaleDateString()}\n      data-selected-single={\n        modifiers.selected &&\n        !modifiers.range_start &&\n        !modifiers.range_end &&\n        !modifiers.range_middle\n      }\n      data-range-start={modifiers.range_start}\n      data-range-end={modifiers.range_end}\n      data-range-middle={modifiers.range_middle}\n      className={cn(\n        \"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70\",\n        defaultClassNames.day,\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Calendar, CalendarDayButton }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "card",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/card.tsx",
          "target": "@ui/card.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card\"\n      className={cn(\n        \"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\"leading-none font-semibold\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-6\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\"flex items-center px-6 [.border-t]:pt-6\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "carousel",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "embla-carousel-react",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/carousel.tsx",
          "target": "@ui/carousel.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from \"embla-carousel-react\"\nimport { ArrowLeft, ArrowRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ntype CarouselProps = {\n  opts?: CarouselOptions\n  plugins?: CarouselPlugin\n  orientation?: \"horizontal\" | \"vertical\"\n  setApi?: (api: CarouselApi) => void\n}\n\ntype CarouselContextProps = {\n  carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n  api: ReturnType<typeof useEmblaCarousel>[1]\n  scrollPrev: () => void\n  scrollNext: () => void\n  canScrollPrev: boolean\n  canScrollNext: boolean\n} & CarouselProps\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n  const context = React.useContext(CarouselContext)\n\n  if (!context) {\n    throw new Error(\"useCarousel must be used within a <Carousel />\")\n  }\n\n  return context\n}\n\nfunction Carousel({\n  orientation = \"horizontal\",\n  opts,\n  setApi,\n  plugins,\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & CarouselProps) {\n  const [carouselRef, api] = useEmblaCarousel(\n    {\n      ...opts,\n      axis: orientation === \"horizontal\" ? \"x\" : \"y\",\n    },\n    plugins\n  )\n  const subscribe = React.useCallback((onChange: () => void) => {\n    if (!api) return () => {}\n    api.on(\"reInit\", onChange)\n    api.on(\"select\", onChange)\n    return () => {\n      api.off(\"reInit\", onChange)\n      api.off(\"select\", onChange)\n    }\n  }, [api])\n  const getSnapshot = React.useCallback(\n    () => (api?.canScrollPrev() ? 1 : 0) | (api?.canScrollNext() ? 2 : 0),\n    [api],\n  )\n  const scrollState = React.useSyncExternalStore(subscribe, getSnapshot, () => 0)\n  const canScrollPrev = (scrollState & 1) !== 0\n  const canScrollNext = (scrollState & 2) !== 0\n\n  const scrollPrev = React.useCallback(() => {\n    api?.scrollPrev()\n  }, [api])\n\n  const scrollNext = React.useCallback(() => {\n    api?.scrollNext()\n  }, [api])\n\n  const handleKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (event.target instanceof HTMLElement && event.target.closest('input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])')) return\n      if (event.key === (orientation === \"horizontal\" ? \"ArrowLeft\" : \"ArrowUp\")) {\n        event.preventDefault()\n        scrollPrev()\n      } else if (event.key === (orientation === \"horizontal\" ? \"ArrowRight\" : \"ArrowDown\")) {\n        event.preventDefault()\n        scrollNext()\n      }\n    },\n    [scrollPrev, scrollNext, orientation]\n  )\n\n  React.useEffect(() => {\n    if (!api || !setApi) return\n    setApi(api)\n  }, [api, setApi])\n\n  return (\n    <CarouselContext.Provider\n      value={{\n        carouselRef,\n        api: api,\n        opts,\n        orientation:\n          orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\n        scrollPrev,\n        scrollNext,\n        canScrollPrev,\n        canScrollNext,\n      }}\n    >\n      <div\n        onKeyDownCapture={handleKeyDown}\n        className={cn(\"relative\", className)}\n        role=\"region\"\n        aria-roledescription=\"carousel\"\n        data-slot=\"carousel\"\n        {...props}\n      >\n        {children}\n      </div>\n    </CarouselContext.Provider>\n  )\n}\n\nfunction CarouselContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { carouselRef, orientation } = useCarousel()\n\n  return (\n    <div\n      ref={carouselRef}\n      className=\"overflow-hidden\"\n      data-slot=\"carousel-content\"\n    >\n      <div\n        className={cn(\n          \"flex\",\n          orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { orientation } = useCarousel()\n\n  return (\n    <div\n      role=\"group\"\n      aria-roledescription=\"slide\"\n      data-slot=\"carousel-item\"\n      className={cn(\n        \"min-w-0 shrink-0 grow-0 basis-full\",\n        orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CarouselPrevious({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n\n  return (\n    <Button\n      data-slot=\"carousel-previous\"\n      variant={variant}\n      size={size}\n      className={cn(\n        \"absolute size-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"top-1/2 -left-12 -translate-y-1/2\"\n          : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className\n      )}\n      disabled={!canScrollPrev}\n      onClick={scrollPrev}\n      {...props}\n    >\n      <ArrowLeft />\n      <span className=\"sr-only\">Previous slide</span>\n    </Button>\n  )\n}\n\nfunction CarouselNext({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { orientation, scrollNext, canScrollNext } = useCarousel()\n\n  return (\n    <Button\n      data-slot=\"carousel-next\"\n      variant={variant}\n      size={size}\n      className={cn(\n        \"absolute size-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"top-1/2 -right-12 -translate-y-1/2\"\n          : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className\n      )}\n      disabled={!canScrollNext}\n      onClick={scrollNext}\n      {...props}\n    >\n      <ArrowRight />\n      <span className=\"sr-only\">Next slide</span>\n    </Button>\n  )\n}\n\nexport {\n  type CarouselApi,\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselPrevious,\n  CarouselNext,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "chart",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "recharts",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/chart.tsx",
          "target": "@ui/chart.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RechartsPrimitive from \"recharts\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const\n\nexport type ChartConfig = {\n  [k in string]: {\n    label?: React.ReactNode\n    icon?: React.ComponentType\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n}\n\ntype ChartContextProps = {\n  config: ChartConfig\n}\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n  const context = React.useContext(ChartContext)\n\n  if (!context) {\n    throw new Error(\"useChart must be used within a <ChartContainer />\")\n  }\n\n  return context\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  config: ChartConfig\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >[\"children\"]\n}) {\n  const uniqueId = React.useId()\n  const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer>\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  )\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme || config.color\n  )\n\n  if (!colorConfig.length) {\n    return null\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||\n      itemConfig.color\n    return color ? `  --color-${key}: ${color};` : null\n  })\n  .join(\"\\n\")}\n}\n`\n          )\n          .join(\"\\n\"),\n      }}\n    />\n  )\n}\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = \"dot\",\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<\"div\"> & {\n    hideLabel?: boolean\n    hideIndicator?: boolean\n    indicator?: \"line\" | \"dot\" | \"dashed\"\n    nameKey?: string\n    labelKey?: string\n  }) {\n  const { config } = useChart()\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null\n    }\n\n    const [item] = payload\n    const key = `${labelKey || item?.dataKey || item?.name || \"value\"}`\n    const itemConfig = getPayloadConfigFromPayload(config, item, key)\n    const value =\n      !labelKey && typeof label === \"string\"\n        ? config[label as keyof typeof config]?.label || label\n        : itemConfig?.label\n\n    if (labelFormatter) {\n      return (\n        <div className={cn(\"font-medium\", labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      )\n    }\n\n    if (!value) {\n      return null\n    }\n\n    return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ])\n\n  if (!active || !payload?.length) {\n    return null\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== \"dot\"\n\n  return (\n    <div\n      className={cn(\n        \"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl\",\n        className\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== \"none\")\n          .map((item, index) => {\n            const key = `${nameKey || item.name || item.dataKey || \"value\"}`\n            const itemConfig = getPayloadConfigFromPayload(config, item, key)\n            const indicatorColor = color || item.payload.fill || item.color\n\n            return (\n              <div\n                key={item.dataKey}\n                className={cn(\n                  \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\n                  indicator === \"dot\" && \"items-center\"\n                )}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            \"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\",\n                            {\n                              \"h-2.5 w-2.5\": indicator === \"dot\",\n                              \"w-1\": indicator === \"line\",\n                              \"w-0 border-[1.5px] border-dashed bg-transparent\":\n                                indicator === \"dashed\",\n                              \"my-0.5\": nestLabel && indicator === \"dashed\",\n                            }\n                          )}\n                          style={\n                            {\n                              \"--color-bg\": indicatorColor,\n                              \"--color-border\": indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        \"flex flex-1 justify-between leading-none\",\n                        nestLabel ? \"items-end\" : \"items-center\"\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label || item.name}\n                        </span>\n                      </div>\n                      {item.value && (\n                        <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                          {item.value.toLocaleString()}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            )\n          })}\n      </div>\n    </div>\n  )\n}\n\nconst ChartLegend = RechartsPrimitive.Legend\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = \"bottom\",\n  nameKey,\n}: React.ComponentProps<\"div\"> &\n  Pick<RechartsPrimitive.LegendProps, \"payload\" | \"verticalAlign\"> & {\n    hideIcon?: boolean\n    nameKey?: string\n  }) {\n  const { config } = useChart()\n\n  if (!payload?.length) {\n    return null\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center justify-center gap-4\",\n        verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== \"none\")\n        .map((item) => {\n          const key = `${nameKey || item.dataKey || \"value\"}`\n          const itemConfig = getPayloadConfigFromPayload(config, item, key)\n\n          return (\n            <div\n              key={item.value}\n              className={cn(\n                \"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3\"\n              )}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          )\n        })}\n    </div>\n  )\n}\n\n// Helper to extract item config from a payload.\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== \"object\" || payload === null) {\n    return undefined\n  }\n\n  const payloadPayload =\n    \"payload\" in payload &&\n    typeof payload.payload === \"object\" &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined\n\n  let configLabelKey: string = key\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === \"string\"\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string\n  }\n\n  return configLabelKey in config\n    ? config[configLabelKey]\n    : config[key as keyof typeof config]\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "checkbox",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-checkbox",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/checkbox.tsx",
          "target": "@ui/checkbox.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { CheckIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Checkbox({\n  className,\n  ...props\n}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        \"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"grid place-content-center text-current transition-none\"\n      >\n        <CheckIcon className=\"size-3.5\" />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "circle-menu",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/circle-menu.tsx",
          "target": "@components/block/circle-menu.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { AnimatePresence, motion, useAnimationControls } from 'motion/react';\nimport { Menu, X } from 'lucide-react';\nimport React, { useState } from 'react';\nimport { cn } from '@/lib/utils';\n\nconst CONSTANTS = {\n    itemSize: 48,\n    containerSize: 250,\n    openStagger: 0.02,\n    closeStagger: 0.07\n};\n\nconst STYLES: Record<string, Record<string, string>> = {\n    trigger: {\n        container:\n            'rounded-full flex items-center bg-[#27272A] justify-center border border-white/10 cursor-pointer outline-none ring-0 hover:brightness-125 transition-all duration-100 z-50',\n        active: 'bg-[#27272A]'\n    },\n    item: {\n        container:\n            'rounded-full flex items-center justify-center absolute bg-zinc-100 text-black hover:bg-white cursor-pointer',\n        label: 'text-xs text-black absolute top-full left-1/2 -translate-x-1/2 text-white mt-1'\n    }\n};\n\nconst pointOnCircle = (i: number, n: number, r: number, cx = 0, cy = 0) => {\n    const theta = (2 * Math.PI * i) / n - Math.PI / 2;\n    const x = cx + r * Math.cos(theta);\n    const y = cy + r * Math.sin(theta) + 0;\n    return { x, y };\n};\n\ninterface MenuItemProps {\n    icon: React.ReactNode;\n    label: string;\n    onClick?: () => void;\n    index: number;\n    totalItems: number;\n    isOpen: boolean;\n}\n\nconst MenuItem = ({ icon, label, onClick, index, totalItems, isOpen }: MenuItemProps) => {\n    const { x, y } = pointOnCircle(index, totalItems, CONSTANTS.containerSize / 2);\n    const [hovering, setHovering] = useState(false);\n\n    return (\n        <motion.button\n            onClick={onClick}\n            type=\"button\"\n            aria-label={label}\n            disabled={!isOpen}\n            tabIndex={isOpen ? 0 : -1}\n            animate={{\n                x: isOpen ? x : 0,\n                y: isOpen ? y : 0\n            }}\n            whileHover={{\n                scale: 1.1,\n                transition: {\n                    duration: 0.1,\n                    delay: 0\n                }\n            }}\n            transition={{\n                delay: isOpen ? index * CONSTANTS.openStagger : index * CONSTANTS.closeStagger,\n                type: 'spring',\n                stiffness: 300,\n                damping: 30\n            }}\n            style={{\n                height: CONSTANTS.itemSize - 2,\n                width: CONSTANTS.itemSize - 2\n            }}\n            className={STYLES.item.container}\n            onMouseEnter={() => setHovering(true)}\n            onMouseLeave={() => setHovering(false)}\n        >\n            {icon}\n            {hovering && <p className={STYLES.item.label}>{label}</p>}\n        </motion.button>\n    );\n};\n\ninterface MenuTriggerProps {\n    setIsOpen: (isOpen: boolean) => void;\n    isOpen: boolean;\n    itemsLength: number;\n    closeAnimationCallback: () => void;\n    openIcon?: React.ReactNode;\n    closeIcon?: React.ReactNode;\n}\n\nconst MenuTrigger = ({\n    setIsOpen,\n    isOpen,\n    itemsLength,\n    closeAnimationCallback,\n    openIcon,\n    closeIcon\n}: MenuTriggerProps) => {\n    const animate = useAnimationControls();\n    const shakeAnimation = useAnimationControls();\n\n    const scaleTransition = Array.from({ length: itemsLength - 1 })\n        .map((_, index) => index + 1)\n        .reduce((acc, _, index) => {\n            const increasedValue = index * 0.15;\n            acc.push(1 + increasedValue);\n            return acc;\n        }, [] as number[]);\n\n    const closeAnimation = async () => {\n        shakeAnimation.start({\n            translateX: [0, 2, -2, 0, 2, -2, 0],\n            transition: {\n                duration: CONSTANTS.closeStagger,\n                ease: 'linear',\n                repeat: Infinity,\n                repeatType: 'loop'\n            }\n        });\n        for (let i = 0; i < scaleTransition.length; i++) {\n            await animate.start({\n                height: Math.min(\n                    CONSTANTS.itemSize * scaleTransition[i],\n                    CONSTANTS.itemSize + CONSTANTS.itemSize / 2\n                ),\n                width: Math.min(\n                    CONSTANTS.itemSize * scaleTransition[i],\n                    CONSTANTS.itemSize + CONSTANTS.itemSize / 2\n                ),\n                backgroundColor: `color-mix(in srgb, #27272A ${Math.max(100 - i * 10, 40)}%, white)`,\n                transition: {\n                    duration: CONSTANTS.closeStagger / 2,\n                    ease: 'linear'\n                }\n            });\n            if (i !== scaleTransition.length - 1) {\n                await new Promise((resolve) => setTimeout(resolve, CONSTANTS.closeStagger * 1000));\n            }\n        }\n\n        shakeAnimation.stop();\n        shakeAnimation.start({\n            translateX: 0,\n            transition: {\n                duration: 0\n            }\n        });\n\n        animate.start({\n            height: CONSTANTS.itemSize,\n            width: CONSTANTS.itemSize,\n            backgroundColor: '#27272A',\n            transition: {\n                duration: 0.1,\n                ease: 'backInOut'\n            }\n        });\n    };\n\n    return (\n        <motion.div animate={shakeAnimation} className=\"z-50\">\n            <motion.button\n                type=\"button\"\n                aria-label={isOpen ? 'Close menu' : 'Open menu'}\n                aria-expanded={isOpen}\n                animate={animate}\n                style={{\n                    height: CONSTANTS.itemSize,\n                    width: CONSTANTS.itemSize\n                }}\n                className={cn(STYLES.trigger.container, isOpen && STYLES.trigger.active)}\n                onClick={() => {\n                    if (isOpen) {\n                        setIsOpen(false);\n                        closeAnimationCallback();\n                        closeAnimation();\n                    } else {\n                        setIsOpen(true);\n                    }\n                }}\n            >\n                <AnimatePresence mode=\"popLayout\">\n                    {isOpen ? (\n                        <motion.span\n                            key=\"menu-close\"\n                            initial={{ opacity: 0, filter: 'blur(10px)' }}\n                            animate={{ opacity: 1, filter: 'blur(0px)' }}\n                            exit={{ opacity: 0, filter: 'blur(10px)' }}\n                            transition={{ duration: 0.2 }}\n                        >\n                            {closeIcon}\n                        </motion.span>\n                    ) : (\n                        <motion.span\n                            key=\"menu-open\"\n                            initial={{ opacity: 0, filter: 'blur(10px)' }}\n                            animate={{ opacity: 1, filter: 'blur(0px)' }}\n                            exit={{ opacity: 0, filter: 'blur(10px)' }}\n                            transition={{ duration: 0.2 }}\n                        >\n                            {openIcon}\n                        </motion.span>\n                    )}\n                </AnimatePresence>\n            </motion.button>\n        </motion.div>\n    );\n};\n\nexport interface CircleMenuProps {\n    items: Array<{ label: string; icon: React.ReactNode; onClick?: () => void }>;\n    openIcon?: React.ReactNode;\n    closeIcon?: React.ReactNode;\n}\n\nexport function CircleMenu({\n    items,\n    openIcon = <Menu size={18} className=\"text-white\" />,\n    closeIcon = <X size={18} className=\"text-white\" />\n}: CircleMenuProps) {\n    const [isOpen, setIsOpen] = useState(false);\n    const animate = useAnimationControls();\n\n    const closeAnimationCallback = async () => {\n        await animate.start({\n            rotate: -360,\n            filter: 'blur(1px)',\n            transition: {\n                duration: CONSTANTS.closeStagger * (items.length + 2),\n                ease: 'linear'\n            }\n        });\n        await animate.start({\n            rotate: 0,\n            filter: 'blur(0px)',\n            transition: {\n                duration: 0\n            }\n        });\n    };\n\n    return (\n        <div\n            style={{\n                width: CONSTANTS.containerSize,\n                height: CONSTANTS.containerSize\n            }}\n            className=\"relative flex items-center justify-center place-self-center\"\n        >\n            <MenuTrigger\n                setIsOpen={setIsOpen}\n                isOpen={isOpen}\n                itemsLength={items.length}\n                closeAnimationCallback={closeAnimationCallback}\n                openIcon={openIcon}\n                closeIcon={closeIcon}\n            />\n            <motion.div\n                animate={animate}\n                className={cn('absolute inset-0 z-0 flex items-center justify-center')}\n            >\n                {items.map((item, index) => {\n                    return (\n                        <MenuItem\n                            key={`menu-item-${index}`}\n                            icon={item.icon}\n                            label={item.label}\n                            onClick={item.onClick}\n                            index={index}\n                            totalItems={items.length}\n                            isOpen={isOpen}\n                        />\n                    );\n                })}\n            </motion.div>\n        </div>\n    );\n}\n\nexport default CircleMenu;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "click-spark",
      "type": "registry:block",
      "dependencies": [
        "motion",
        "next-themes"
      ],
      "files": [
        {
          "path": "components/block/click-spark.tsx",
          "target": "@components/block/click-spark.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport React, { useRef, useEffect, useCallback } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport { useReducedMotion } from \"motion/react\";\n\ninterface Spark {\n  x: number;\n  y: number;\n  angle: number;\n  startTime: number;\n}\n\ninterface ClickSparkProps {\n  sparkColor?: string;\n  sparkSize?: number;\n  sparkRadius?: number;\n  sparkCount?: number;\n  duration?: number;\n  easing?: \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\";\n  extraScale?: number;\n}\n\nexport const ClickSpark = ({\n  sparkColor,\n  sparkSize = 10,\n  sparkRadius = 15,\n  sparkCount = 8,\n  duration = 400,\n  easing = \"ease-out\",\n  extraScale = 1,\n}: ClickSparkProps) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const reduceMotion = useReducedMotion();\n  const sparksRef = useRef<Spark[]>([]);\n  const startTimeRef = useRef<number | null>(null);\n  const { resolvedTheme } = useTheme();\n\n  const effectiveColor = sparkColor || (resolvedTheme === \"dark\" ? \"#fff\" : \"#000\");\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    let resizeTimeout: NodeJS.Timeout;\n\n    const resizeCanvas = () => {\n      if (typeof window !== \"undefined\") {\n        const dpr = window.devicePixelRatio || 1;\n        canvas.width = window.innerWidth * dpr;\n        canvas.height = window.innerHeight * dpr;\n        canvas.style.width = \"100%\";\n        canvas.style.height = \"100%\";\n        \n        const ctx = canvas.getContext(\"2d\");\n        if (ctx) {\n          ctx.scale(dpr, dpr);\n        }\n      }\n    };\n\n    const handleResize = () => {\n      if (typeof window !== \"undefined\") {\n        clearTimeout(resizeTimeout);\n        resizeTimeout = setTimeout(resizeCanvas, 100);\n      }\n    };\n\n    if (typeof window !== \"undefined\") {\n      resizeCanvas();\n      window.addEventListener(\"resize\", handleResize);\n\n      return () => {\n        window.removeEventListener(\"resize\", handleResize);\n        clearTimeout(resizeTimeout);\n      };\n    }\n  }, []);\n\n  const easeFunc = useCallback(\n    (t: number) => {\n      switch (easing) {\n        case \"linear\":\n          return t;\n        case \"ease-in\":\n          return t * t;\n        case \"ease-in-out\":\n          return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n        case \"ease-out\":\n        default:\n          return t * (2 - t);\n      }\n    },\n    [easing]\n  );\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n    if (reduceMotion) {\n      sparksRef.current = [];\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      return;\n    }\n\n    let animationId: number;\n\n    const draw = (timestamp: number) => {\n      if (!startTimeRef.current) {\n        startTimeRef.current = timestamp;\n      }\n\n      const dpr = window.devicePixelRatio || 1;\n      ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);\n\n      sparksRef.current = sparksRef.current.filter((spark) => {\n        const elapsed = timestamp - spark.startTime;\n        if (elapsed >= duration) {\n          return false;\n        }\n\n        const progress = elapsed / duration;\n        const eased = easeFunc(progress);\n\n        const distance = eased * sparkRadius * extraScale;\n        const lineLength = sparkSize * (1 - eased);\n\n        const x1 = spark.x + distance * Math.cos(spark.angle);\n        const y1 = spark.y + distance * Math.sin(spark.angle);\n        const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);\n        const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);\n\n        ctx.strokeStyle = effectiveColor;\n        ctx.lineWidth = 2;\n        ctx.beginPath();\n        ctx.moveTo(x1, y1);\n        ctx.lineTo(x2, y2);\n        ctx.stroke();\n\n        return true;\n      });\n\n      animationId = requestAnimationFrame(draw);\n    };\n\n    animationId = requestAnimationFrame(draw);\n\n    return () => {\n      cancelAnimationFrame(animationId);\n    };\n  }, [sparkColor, effectiveColor, sparkSize, sparkRadius, sparkCount, duration, easeFunc, extraScale, reduceMotion]);\n\n  const createSparks = useCallback((x: number, y: number) => {\n    if (reduceMotion) return;\n    const now = performance.now();\n    const newSparks: Spark[] = Array.from({ length: sparkCount }, (_, i) => ({\n      x,\n      y,\n      angle: (2 * Math.PI * i) / sparkCount,\n      startTime: now,\n    }));\n\n    sparksRef.current.push(...newSparks);\n  }, [sparkCount, reduceMotion]);\n\n  const handleClick = useCallback((e: MouseEvent) => {\n    createSparks(e.clientX, e.clientY);\n  }, [createSparks]);\n\n  const handleTouchStart = useCallback((e: TouchEvent) => {\n    // Handle all touch points for multi-touch support\n    Array.from(e.changedTouches).forEach((touch) => {\n      createSparks(touch.clientX, touch.clientY);\n    });\n  }, [createSparks]);\n\n  useEffect(() => {\n    if (typeof window === \"undefined\") return;\n    \n    document.addEventListener(\"click\", handleClick);\n    document.addEventListener(\"touchstart\", handleTouchStart, { passive: true });\n    \n    return () => {\n      document.removeEventListener(\"click\", handleClick);\n      document.removeEventListener(\"touchstart\", handleTouchStart);\n    };\n  }, [handleClick, handleTouchStart]);\n\n  return (\n    <canvas\n      ref={canvasRef}\n      style={{\n        width: \"100%\",\n        height: \"100%\",\n        position: \"fixed\",\n        top: 0,\n        left: 0,\n        pointerEvents: \"none\",\n        zIndex: 9999,\n      }}\n    />\n  );\n};\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "collapsible",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-collapsible"
      ],
      "files": [
        {
          "path": "components/ui/collapsible.tsx",
          "target": "@ui/collapsible.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\"\n\nfunction Collapsible({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />\n}\n\nfunction CollapsibleTrigger({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleTrigger\n      data-slot=\"collapsible-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction CollapsibleContent({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleContent\n      data-slot=\"collapsible-content\"\n      {...props}\n    />\n  )\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent }\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "colorful-cursor-aura",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/colorful-cursor-aura.jsx",
          "target": "@components/block/colorful-cursor-aura.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\nimport { useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\n\n\n/** @param {{ text?: string, colors?: {color1: string, color2: string, color3: string}, enableEntryAnimation?: boolean, textColor?: string, className?: string, height?: import(\"react\").CSSProperties[\"height\"], style?: import(\"react\").CSSProperties }} props */\nexport function ColorfulCursorAura({\n  text = \"ObsidianUI, in full color.\",\n  colors = {\n    color1: \"#7f7de4\",\n    color2: \"#f79694\",\n    color3: \"#f5dd94\",\n  },\n  enableEntryAnimation = false,\n  textColor = \"#000000\",\n  className = \"\",\n  height = 400,\n  style,\n} = {}) {\n  const container = useRef(null);\n  const auraText = useRef(null);\n  const maskedText = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const circleTrackers = useRef([\n    { x: 0, y: 0 },\n    { x: 0, y: 0 },\n    { x: 0, y: 0 },\n  ]);\n\n\n  useEffect(() => {\n    const el = container.current;\n    const maskEl = maskedText.current;\n    const trackers = circleTrackers.current;\n    if (!el || !maskEl || !motionEnabled) {\n      gsap.killTweensOf(trackers);\n      return;\n    }\n\n    const syncMaskVars = () => {\n      maskEl.style.setProperty(\"--x-color1\", `${circleTrackers.current[0].x}px`);\n      maskEl.style.setProperty(\"--y-color1\", `${circleTrackers.current[0].y}px`);\n      maskEl.style.setProperty(\"--x-color2\", `${circleTrackers.current[1].x}px`);\n      maskEl.style.setProperty(\"--y-color2\", `${circleTrackers.current[1].y}px`);\n      maskEl.style.setProperty(\"--x-color3\", `${circleTrackers.current[2].x}px`);\n      maskEl.style.setProperty(\"--y-color3\", `${circleTrackers.current[2].y}px`);\n    };\n\n    const rect = maskEl.getBoundingClientRect();\n    const cx = rect.width / 2;\n    const cy = rect.height / 2;\n\n    circleTrackers.current.forEach((item) => {\n      item.x = cx;\n      item.y = cy;\n    });\n    syncMaskVars();\n\n    const onMove = (event) => {\n      const maskRect = maskEl.getBoundingClientRect();\n      const localX = event.clientX - maskRect.left;\n      const localY = event.clientY - maskRect.top;\n\n      gsap.to(circleTrackers.current, {\n        x: localX,\n        y: localY,\n        duration: 0.5,\n        ease: \"power1.out\",\n        stagger: -0.1,\n        overwrite: \"auto\",\n        onUpdate: syncMaskVars,\n      });\n    };\n\n    el.addEventListener(\"mousemove\", onMove);\n    return () => {\n      el.removeEventListener(\"mousemove\", onMove);\n      gsap.killTweensOf(trackers);\n    };\n  }, [motionEnabled]);\n\n  useEffect(() => {\n    if (!enableEntryAnimation || !motionEnabled) return;\n\n    const ctx = gsap.context(() => {\n      gsap.from(auraText.current, {\n        opacity: 0,\n        yPercent: 320,\n        skewY: 30,\n        duration: 3,\n        ease: \"expo.out\",\n      });\n    }, container);\n\n    return () => ctx.revert();\n  }, [enableEntryAnimation, motionEnabled]);\n\n  return (\n    <section\n      ref={container}\n      className={cn(\"relative isolate w-full overflow-hidden bg-[#ececec]\", className)}\n      style={{ height, containerType: \"inline-size\", ...style }}\n    >\n      <div className=\"relative flex h-full w-full items-center justify-center overflow-hidden\">\n        <div ref={auraText} className=\"relative w-[70%] max-lg:w-[80%]\">\n          <p\n            className=\"text-center text-[clamp(24px,6cqw,72px)] font-medium leading-none\"\n            style={{ color: textColor }}\n          >\n            {text}\n          </p>\n\n          {motionEnabled && (\n            <p\n              ref={maskedText}\n              aria-hidden\n              className=\"pointer-events-none absolute inset-0 text-center text-[clamp(24px,6cqw,72px)] font-medium leading-none text-transparent [background-clip:text] [-webkit-background-clip:text] [-webkit-text-fill-color:transparent]\"\n              style={{\n                \"--x-color1\": \"50%\",\n                \"--y-color1\": \"50%\",\n                \"--x-color2\": \"50%\",\n                \"--y-color2\": \"50%\",\n                \"--x-color3\": \"50%\",\n                \"--y-color3\": \"50%\",\n                backgroundImage: `\n                  radial-gradient(circle min(135px, 14cqw) at var(--x-color3) var(--y-color3), ${colors.color3} 0 99%, transparent 100%),\n                  radial-gradient(circle min(220px, 23cqw) at var(--x-color2) var(--y-color2), ${colors.color2} 0 99%, transparent 100%),\n                  radial-gradient(circle min(325px, 34cqw) at var(--x-color1) var(--y-color1), ${colors.color1} 0 99%, transparent 100%)\n                `,\n              }}\n            >\n              {text}\n            </p>\n          )}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "command",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-dialog",
        "clsx",
        "cmdk",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/command.tsx",
          "target": "@ui/command.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Command as CommandPrimitive } from \"cmdk\"\nimport { SearchIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      data-slot=\"command\"\n      className={cn(\n        \"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandDialog({\n  title = \"Command Palette\",\n  description = \"Search for a command to run...\",\n  children,\n  className,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof Dialog> & {\n  title?: string\n  description?: string\n  className?: string\n  showCloseButton?: boolean\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\"overflow-hidden p-0\", className)}\n        showCloseButton={showCloseButton}\n      >\n        <Command className=\"[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n          {children}\n        </Command>\n      </DialogContent>\n    </Dialog>\n  )\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div\n      data-slot=\"command-input-wrapper\"\n      className=\"flex h-9 items-center gap-2 border-b px-3\"\n    >\n      <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n      <CommandPrimitive.Input\n        data-slot=\"command-input\"\n        className={cn(\n          \"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      data-slot=\"command-list\"\n      className={cn(\n        \"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandEmpty({\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      data-slot=\"command-empty\"\n      className=\"py-6 text-center text-sm\"\n      {...props}\n    />\n  )\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      data-slot=\"command-group\"\n      className={cn(\n        \"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      data-slot=\"command-separator\"\n      className={cn(\"bg-border -mx-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CommandItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      data-slot=\"command-item\"\n      className={cn(\n        \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"command-shortcut\"\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n}\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "target": "@ui/dialog.tsx",
          "type": "registry:ui",
          "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Dialog({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal data-slot=\"dialog-portal\">\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\n        className={cn(\n          \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Content>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"text-lg leading-none font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "context-menu",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-context-menu",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/context-menu.tsx",
          "target": "@ui/context-menu.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ContextMenu({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {\n  return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />\n}\n\nfunction ContextMenuTrigger({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n  return (\n    <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" {...props} />\n  )\n}\n\nfunction ContextMenuGroup({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n  return (\n    <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />\n  )\n}\n\nfunction ContextMenuPortal({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {\n  return (\n    <ContextMenuPrimitive.Portal data-slot=\"context-menu-portal\" {...props} />\n  )\n}\n\nfunction ContextMenuSub({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {\n  return <ContextMenuPrimitive.Sub data-slot=\"context-menu-sub\" {...props} />\n}\n\nfunction ContextMenuRadioGroup({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {\n  return (\n    <ContextMenuPrimitive.RadioGroup\n      data-slot=\"context-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.SubTrigger\n      data-slot=\"context-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </ContextMenuPrimitive.SubTrigger>\n  )\n}\n\nfunction ContextMenuSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {\n  return (\n    <ContextMenuPrimitive.SubContent\n      data-slot=\"context-menu-sub-content\"\n      className={cn(\n        \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.Content\n        data-slot=\"context-menu-content\"\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md\",\n          className\n        )}\n        {...props}\n      />\n    </ContextMenuPrimitive.Portal>\n  )\n}\n\nfunction ContextMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <ContextMenuPrimitive.Item\n      data-slot=\"context-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {\n  return (\n    <ContextMenuPrimitive.CheckboxItem\n      data-slot=\"context-menu-checkbox-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction ContextMenuRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {\n  return (\n    <ContextMenuPrimitive.RadioItem\n      data-slot=\"context-menu-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.RadioItem>\n  )\n}\n\nfunction ContextMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.Label\n      data-slot=\"context-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n  return (\n    <ContextMenuPrimitive.Separator\n      data-slot=\"context-menu-separator\"\n      className={cn(\"bg-border -mx-1 my-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"context-menu-shortcut\"\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "curved-plane",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "tailwind-merge",
        "three"
      ],
      "files": [
        {
          "path": "components/block/curved-plane.jsx",
          "target": "@components/block/curved-plane.jsx",
          "type": "registry:block",
          "content": "\"use client\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport gsap from \"gsap\";\nimport { WebGLSurface, useEffectReducedMotion } from \"@/lib/effects/shared/webgl-surface\";\n\nconst vertexShader = `\nuniform vec2 uOffset;\nvarying vec2 vUv;\n\n#define M_PI 3.1415926535897932384626433832795\n\nvoid main() {\n   vUv = uv;\n   vec3 newPosition = position;\n\n   float edgeIntensity = abs(uv.x - 0.5) * 2.0;\n   newPosition.x += sin(uv.y * M_PI) * uOffset.x * edgeIntensity;\n\n   gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);\n}\n`;\n\nconst fragmentShader = `\nuniform sampler2D uTexture;\nuniform float uAlpha;\nuniform vec2 uTextureSize;\nuniform vec2 uMeshSize;\nvarying vec2 vUv;\n\nvec2 coverUv(vec2 uv, vec2 textureSize, vec2 meshSize) {\n    float rs = meshSize.x / meshSize.y;\n    float rt = textureSize.x / textureSize.y;\n\n    vec2 newUv = uv;\n\n    if (rs > rt) {\n        float scale = rs / rt;\n        newUv.x = uv.x * scale - (scale - 1.0) * 0.5;\n    } else {\n        float scale = rt / rs;\n        newUv.y = uv.y * scale - (scale - 1.0) * 0.5;\n    }\n\n    return newUv;\n}\n\nvoid main() {\n   vec2 coveredUv = coverUv(vUv, uTextureSize, uMeshSize);\n\n   vec2 center = vec2(0.5, 0.5);\n   float scaleAmount = 1.5;\n   coveredUv = center + (coveredUv - center) / scaleAmount;\n\n   vec4 texColor = texture2D(uTexture, coveredUv);\n   gl_FragColor = vec4(texColor.rgb, texColor.a * uAlpha);\n}\n`;\n\nconst defaultImages = [\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/curved-plane/curved-plane-img01.webp\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/curved-plane/curved-plane-img02.webp\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/curved-plane/curved-plane-img03.webp\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/curved-plane/curved-plane-img04.png\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/curved-plane/curved-plane-img05.png\",\n];\n\nconst SWIPER_VISIBLE_IMAGES = 3;\nconst FIXED_IMAGE_WIDTH = 320;\nconst DEFORMATION_INTENSITY = 8;\nconst DEFORMATION_SENSITIVITY = 0.02;\nconst DEFORMATION_SMOOTHNESS = 0.15;\nconst DRAG_SENSITIVITY = 2.0;\nconst MOMENTUM_FRICTION = 0.94;\nconst SCROLL_SMOOTHNESS = 0.12;\nconst MAX_SCROLL_VELOCITY = 0.5;\nconst MAX_DEFORMATION = 0.05;\nconst MAX_SCROLL_DEFORMATION = 0.02;\n\nfunction lerp(a, b, t) {\n  return a * (1 - t) + b * t;\n}\n\nfunction mod(n, m) {\n  return ((n % m) + m) % m;\n}\n\nfunction CurvedPlaneScene({ images }) {\n  const reducedMotion = useEffectReducedMotion();\n  const containerRef = useRef(null);\n  const meshesRef = useRef([]);\n  const sceneRef = useRef();\n  const cameraRef = useRef();\n  const rendererRef = useRef();\n  const texturesRef = useRef([]);\n\n  const offsetRef = useRef(0);\n  const targetOffsetRef = useRef(0);\n  const velocityRef = useRef(0);\n  const deformationRef = useRef(0);\n  const targetDeformationRef = useRef(0);\n  const scrollVelocityRef = useRef(0);\n  const scrollIntensityRef = useRef(0);\n  const dragging = useRef(false);\n  const lastX = useRef(0);\n  const lastTime = useRef(0);\n\n  const followerRef = useRef(null);\n  const isInsideBounds = useRef(false);\n\n  const [loadedImages, setLoadedImages] = useState(null);\n  const imagesLoaded = loadedImages === images;\n\n  useEffect(() => {\n    const loader = new THREE.TextureLoader();\n    loader.setCrossOrigin(\"anonymous\");\n    let loaded = 0;\n    let cancelled = false;\n    const textures = [];\n    texturesRef.current = textures;\n\n    images.forEach((src, idx) => {\n      loader.load(\n        src,\n        (tex) => {\n          if (cancelled) { tex.dispose(); return; }\n          textures[idx] = tex;\n          if (++loaded === images.length) setLoadedImages(images);\n        },\n        undefined,\n        () => {\n          if (cancelled) return;\n          textures[idx] = null;\n          if (++loaded === images.length) setLoadedImages(images);\n        }\n      );\n    });\n    return () => { cancelled = true; textures.forEach((texture) => texture?.dispose()); };\n  }, [images]);\n\n  useEffect(() => {\n    if (!imagesLoaded) return;\n\n    const container = containerRef.current;\n    let viewHeight = Math.max(1, container.clientHeight);\n    let viewWidth = Math.max(1, container.clientWidth);\n    const imageSize = Math.min(FIXED_IMAGE_WIDTH, viewHeight * 0.8, viewWidth * 0.65);\n    let frame = 0;\n\n    const scene = new THREE.Scene();\n    const camera = new THREE.PerspectiveCamera(\n      (180 * (2 * Math.atan(viewHeight / 2 / 1000))) / Math.PI,\n      viewWidth / viewHeight,\n      1,\n      3000\n    );\n    camera.position.z = 1000;\n\n    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });\n    renderer.setSize(viewWidth, viewHeight);\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n    container.appendChild(renderer.domElement);\n\n    sceneRef.current = scene;\n    cameraRef.current = camera;\n    rendererRef.current = renderer;\n\n    const geometry = new THREE.PlaneGeometry(1, 1, 50, 50);\n    const meshes = [];\n    const totalMeshes = SWIPER_VISIBLE_IMAGES + 4;\n\n    for (let i = 0; i < totalMeshes; i++) {\n      const mat = new THREE.ShaderMaterial({\n        uniforms: {\n          uTexture: { value: null },\n          uOffset: { value: new THREE.Vector2(0, 0) },\n          uAlpha: { value: 1 },\n          uTextureSize: { value: new THREE.Vector2(1, 1) },\n          uMeshSize: { value: new THREE.Vector2(imageSize, imageSize) },\n        },\n        vertexShader,\n        fragmentShader,\n        transparent: true,\n      });\n\n      const mesh = new THREE.Mesh(geometry, mat);\n      mesh.scale.set(imageSize, imageSize, 1);\n      scene.add(mesh);\n      meshes.push(mesh);\n    }\n\n    meshesRef.current = meshes;\n\n    const spacing = imageSize * 1.1875;\n\n    const updateMeshes = () => {\n      const offset = offsetRef.current;\n      const baseIndex = Math.floor(offset);\n      const fractional = offset - baseIndex;\n\n      meshes.forEach((mesh, i) => {\n        const meshIndex = baseIndex + i - 2;\n        const imgIndex = mod(meshIndex, images.length);\n        const texture = texturesRef.current[imgIndex];\n\n        if (texture) {\n          mesh.material.uniforms.uTexture.value = texture;\n          mesh.material.uniforms.uTextureSize.value = new THREE.Vector2(\n            texture.image.width,\n            texture.image.height\n          );\n        }\n\n        const centerOffset = i - totalMeshes / 2 + 0.5;\n        const xPos = (centerOffset - fractional) * spacing;\n        mesh.position.x = xPos;\n\n        const distFromCenter = Math.abs(xPos) / (viewWidth / 2);\n        mesh.material.uniforms.uAlpha.value = Math.max(0.2, 1 - distFromCenter * 1.0);\n\n        const deform = deformationRef.current;\n        mesh.material.uniforms.uOffset.value.x = deform * DEFORMATION_INTENSITY;\n      });\n    };\n\n    const animate = () => {\n      if (Math.abs(scrollVelocityRef.current) > 0.0001) {\n        targetOffsetRef.current += scrollVelocityRef.current;\n        scrollVelocityRef.current = lerp(scrollVelocityRef.current, 0, 0.05);\n      }\n\n      scrollIntensityRef.current = lerp(scrollIntensityRef.current, 0, 0.02);\n\n      if (!dragging.current && Math.abs(velocityRef.current) > 0.001) {\n        targetOffsetRef.current += velocityRef.current;\n        velocityRef.current *= MOMENTUM_FRICTION;\n      }\n\n      if (!dragging.current) {\n        targetDeformationRef.current = lerp(targetDeformationRef.current, 0, 0.05);\n      }\n\n      offsetRef.current = lerp(offsetRef.current, targetOffsetRef.current, SCROLL_SMOOTHNESS);\n      deformationRef.current = lerp(deformationRef.current, targetDeformationRef.current, DEFORMATION_SMOOTHNESS);\n\n      updateMeshes();\n      renderer.render(scene, camera);\n      if (!reducedMotion) frame = requestAnimationFrame(animate);\n    };\n    animate();\n\n    const onResize = () => {\n      const vh = viewHeight = Math.max(1, container.clientHeight);\n      const vw = viewWidth = Math.max(1, container.clientWidth);\n      camera.fov = (180 * (2 * Math.atan(vh / 2 / 1000))) / Math.PI;\n      camera.aspect = vw / vh;\n      camera.updateProjectionMatrix();\n      renderer.setSize(vw, vh);\n      if (reducedMotion) animate();\n    };\n    const observer = new ResizeObserver(onResize);\n    observer.observe(container);\n\n    const getPointerX = (e) => (e.touches ? e.touches[0].clientX : e.clientX);\n\n    const onPointerDown = (e) => {\n      dragging.current = true;\n      e.currentTarget.setPointerCapture?.(e.pointerId);\n      lastX.current = getPointerX(e);\n      lastTime.current = Date.now();\n      velocityRef.current = 0;\n      container.style.userSelect = \"none\";\n      if (reducedMotion) { offsetRef.current = targetOffsetRef.current; deformationRef.current = 0; updateMeshes(); renderer.render(scene, camera); }\n      e.preventDefault();\n    };\n\n    const onPointerMove = (e) => {\n      if (!dragging.current) return;\n      const currentX = getPointerX(e);\n      const currentTime = Date.now();\n      const dx = currentX - lastX.current;\n      const dt = Math.max(currentTime - lastTime.current, 1);\n      const delta = (dx / spacing) * DRAG_SENSITIVITY;\n      targetOffsetRef.current -= delta;\n      const instantVelocity = dx / dt;\n      velocityRef.current = Math.max(-MAX_SCROLL_VELOCITY, Math.min(MAX_SCROLL_VELOCITY, -delta / (dt / 16)));\n      const deformIntensity = Math.max(-MAX_DEFORMATION, Math.min(MAX_DEFORMATION, instantVelocity * DEFORMATION_SENSITIVITY));\n      targetDeformationRef.current = deformIntensity;\n      lastX.current = currentX;\n      lastTime.current = currentTime;\n      if (reducedMotion) { offsetRef.current = targetOffsetRef.current; deformationRef.current = 0; updateMeshes(); renderer.render(scene, camera); }\n      e.preventDefault();\n    };\n\n    const onPointerUp = () => {\n      dragging.current = false;\n      container.style.userSelect = \"\";\n    };\n\n    const onWheel = (e) => {\n      if (reducedMotion) { offsetRef.current = targetOffsetRef.current; deformationRef.current = 0; updateMeshes(); renderer.render(scene, camera); }\n      e.preventDefault();\n      const rawScrollDelta = e.deltaY;\n      const scrollIntensity = Math.abs(rawScrollDelta) * 0.002;\n      scrollIntensityRef.current = Math.min(1.0, scrollIntensityRef.current + scrollIntensity);\n      const scrollDelta = rawScrollDelta * 0.0008;\n      scrollVelocityRef.current += scrollDelta;\n      scrollVelocityRef.current = Math.max(-MAX_SCROLL_VELOCITY * 0.3, Math.min(MAX_SCROLL_VELOCITY * 0.3, scrollVelocityRef.current));\n      const intensityBasedDeformation = scrollIntensityRef.current * 0.03;\n      const deformIntensity = Math.max(-MAX_SCROLL_DEFORMATION, Math.min(MAX_SCROLL_DEFORMATION, Math.sign(rawScrollDelta) * intensityBasedDeformation));\n      targetDeformationRef.current = deformIntensity;\n      if (reducedMotion) { targetOffsetRef.current += scrollVelocityRef.current; offsetRef.current = targetOffsetRef.current; scrollVelocityRef.current = 0; deformationRef.current = 0; updateMeshes(); renderer.render(scene, camera); }\n    };\n\n    const dom = renderer.domElement;\n    dom.addEventListener(\"pointerdown\", onPointerDown);\n    dom.addEventListener(\"pointermove\", onPointerMove);\n    dom.addEventListener(\"pointerup\", onPointerUp);\n    dom.addEventListener(\"pointercancel\", onPointerUp);\n    dom.addEventListener(\"wheel\", onWheel, { passive: false });\n\n    return () => {\n      observer.disconnect();\n      cancelAnimationFrame(frame);\n      dom.removeEventListener(\"pointerdown\", onPointerDown);\n      dom.removeEventListener(\"pointermove\", onPointerMove);\n      dom.removeEventListener(\"pointerup\", onPointerUp);\n      dom.removeEventListener(\"pointercancel\", onPointerUp);\n      dom.removeEventListener(\"wheel\", onWheel);\n      geometry.dispose();\n      meshes.forEach((mesh) => mesh.material.dispose());\n      container.style.userSelect = \"\";\n      dragging.current = false;\n      renderer.dispose();\n      if (container.contains(renderer.domElement)) container.removeChild(renderer.domElement);\n    };\n  }, [imagesLoaded, images, reducedMotion]);\n\n  useEffect(() => {\n    if (reducedMotion) return;\n    const surface = containerRef.current;\n    const follower = followerRef.current;\n    const handleMouseMove = (e) => {\n      const container = containerRef.current;\n      if (!container) return;\n      const rect = container.getBoundingClientRect();\n      const isInside = (\n        e.clientX >= rect.left && e.clientX <= rect.right &&\n        e.clientY >= rect.top + 100 && e.clientY <= rect.bottom - 100\n      );\n\n      if (isInside && !isInsideBounds.current) {\n        isInsideBounds.current = true;\n        gsap.to(followerRef.current, { opacity: 1, scale: 1, duration: 0.3, ease: \"power2.out\" });\n      } else if (!isInside && isInsideBounds.current) {\n        isInsideBounds.current = false;\n        gsap.to(followerRef.current, { opacity: 0, scale: 0.8, duration: 0.3, ease: \"power2.out\" });\n      }\n\n      if (isInside) {\n        gsap.to(followerRef.current, { x: e.clientX - rect.left - 30, y: e.clientY - rect.top - 30, duration: 0.35, ease: \"power2.out\" });\n      }\n    };\n\n    surface.addEventListener(\"pointermove\", handleMouseMove);\n    const leave = () => gsap.to(follower, { opacity: 0, duration: 0.2 });\n    surface.addEventListener(\"pointerleave\", leave);\n    return () => { surface.removeEventListener(\"pointermove\", handleMouseMove); surface.removeEventListener(\"pointerleave\", leave); gsap.killTweensOf(follower); };\n  }, [reducedMotion]);\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"w-full h-full relative overflow-hidden flex items-center justify-center\"\n      style={{ background: \"#f5f5f5\", touchAction: \"none\" }}\n    >\n      <p className=\"text-black absolute top-[15%] hidden mobile:block left-1/2 -translate-x-1/2 text-center text-xl font-medium\">\n        Drag &amp; Swipe\n      </p>\n      <div\n        ref={followerRef}\n        className=\"h-16 w-16 mobile:hidden rounded-full bg-[#ff5f00] text-white flex items-center justify-center\"\n        style={{\n          position: \"absolute\",\n          top: 0,\n          left: 0,\n          opacity: 0,\n          scale: 0.8,\n          pointerEvents: \"none\",\n          zIndex: 10000,\n          transform: \"translate3d(0,0,0)\",\n        }}\n      >\n        <p className=\"text-[10px] w-3/5 text-center font-medium\">Drag or Scroll</p>\n      </div>\n      {!imagesLoaded && (\n        <div className=\"absolute text-gray-800 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-xl font-light\">\n          Loading...\n        </div>\n      )}\n    </div>\n  );\n}\n\n/** @param {{ images?: string[], className?: string, style?: import(\"react\").CSSProperties }} props */\nexport function CurvedPlane({ images = defaultImages, className, style } = {}) {\n  return <WebGLSurface className={className} style={style} imageSrc={images[0]} label=\"ObsidianUI curved image gallery\">\n    {images.length > 0 && <CurvedPlaneScene images={images} />}\n  </WebGLSurface>;\n}\n"
        },
        {
          "path": "lib/effects/shared/webgl-surface.jsx",
          "target": "@lib/effects/shared/webgl-surface.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\n\nimport { Component, useSyncExternalStore } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst subscribeMotion = (notify) => {\n  const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n  query.addEventListener(\"change\", notify);\n  return () => query.removeEventListener(\"change\", notify);\n};\n\nexport function useEffectReducedMotion() {\n  return useSyncExternalStore(subscribeMotion, () => window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches, () => true);\n}\n\nlet webglAvailable;\nfunction supportsWebGL() {\n  if (webglAvailable !== undefined) return webglAvailable;\n  try {\n    const canvas = document.createElement(\"canvas\");\n    const context = canvas.getContext(\"webgl2\");\n    webglAvailable = Boolean(context);\n    context?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n  } catch {\n    webglAvailable = false;\n  }\n  return webglAvailable;\n}\nconst subscribeAvailability = () => () => {};\n\nclass SurfaceBoundary extends Component {\n  state = { failed: false };\n  static getDerivedStateFromError() { return { failed: true }; }\n  render() { return this.state.failed ? this.props.fallback : this.props.children; }\n}\n\n/** @param {{ children?: import(\"react\").ReactNode, className?: string, style?: import(\"react\").CSSProperties, imageSrc?: string, label?: string }} props */\nexport function WebGLSurface({ children, className, style, imageSrc, label = \"ObsidianUI visual effect\" }) {\n  const supported = useSyncExternalStore(subscribeAvailability, supportsWebGL, () => false);\n  const fallback = <div role=\"img\" aria-label={label} className=\"absolute inset-0 bg-cover bg-center\" style={{ backgroundImage: imageSrc ? `url(${JSON.stringify(imageSrc)})` : undefined }} />;\n  return (\n    <div className={cn(\"relative isolate h-[28rem] w-full overflow-hidden bg-black\", className)} style={{ containerType: \"size\", ...style }}>\n      {fallback}\n      {supported && <SurfaceBoundary fallback={fallback}>{children}</SurfaceBoundary>}\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "dialog",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-dialog",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/dialog.tsx",
          "target": "@ui/dialog.tsx",
          "type": "registry:ui",
          "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Dialog({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal data-slot=\"dialog-portal\">\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\n        className={cn(\n          \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Content>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"text-lg leading-none font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "dither-canvas",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/dither-canvas.jsx",
          "target": "@components/block/dither-canvas.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst FC = 80;\nconst FR = 60;\nconst FN = FC * FR;\nconst CC = 110;\nconst EDGE_LO = 36;\nconst EDGE_HI = 130;\nconst EDGES = [\".\", \",\", \"=\", \"+\", \"-\"];\nconst BRIGHTS = [...\"OBSIDIANUI\"];\nconst ALL_CHARS = [...EDGES, ...BRIGHTS];\nconst BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];\nconst TL = 320;\nconst TS = 10;\nconst TM = 72;\nconst TRAIL_CFG = { fb: 0.08, fss: 18, ffm: 0.15, fir: 0.8, firl: 1.0 };\n\nconst VS = `#version 300 es\nin vec2 a_pos;\nvoid main(){ gl_Position = vec4(a_pos, 0, 1); }`;\n\nconst FS = `#version 300 es\nprecision highp float;\nuniform sampler2D uVideo, uFluid, uAtlas;\nuniform vec2 uRes;\nuniform int uPhase, uTrailN;\nuniform vec4 uTP[${TM}];\nuniform float uTL[${TM}];\nout vec4 O;\nconst float CC = ${CC}.0, FC = ${FC}.0, FR = ${FR}.0;\nconst float EL = ${EDGE_LO}.0, EH = ${EDGE_HI}.0;\nconst int BAYER[16] = int[16](${BAYER.map((v) => Math.round((v / 16) * 255)).join(\",\")});\nconst int CHAR_N = ${ALL_CHARS.length};\nvoid main(){\n  float cw = uRes.x / CC;\n  float rows = ceil(uRes.y / cw) + 1.0;\n  float gx = floor(gl_FragCoord.x / cw);\n  float gy = floor((uRes.y - gl_FragCoord.y) / cw);\n  if(gx >= CC || gy >= rows) discard;\n  vec2 cp = vec2(fract(gl_FragCoord.x / cw), fract((uRes.y - gl_FragCoord.y) / cw));\n  vec2 bp = vec2((gx + 0.5) * cw, (gy + 0.5) * cw);\n  ivec2 fc = ivec2(gx / CC * FC, gy / rows * FR);\n  fc = clamp(fc, ivec2(0), ivec2(int(FC)-1, int(FR)-1));\n  vec2 flow = texelFetch(uFluid, fc, 0).rg;\n  vec2 disp = vec2(0.0);\n  for(int i = 0; i < uTrailN; i++){\n    float life = uTL[i];\n    if(life <= 0.0) continue;\n    vec2 d = bp - uTP[i].xy;\n    float dist = length(d);\n    float r = 5.0 + life * 3.0;\n    if(dist == 0.0 || dist > r) continue;\n    float f = pow(1.0 - dist / r, 2.0);\n    disp += (d / dist) * f * life * 3.0 + uTP[i].zw * f * 0.04;\n  }\n  vec2 sp = bp + disp + flow * 6.0;\n  vec2 uv = clamp(sp / uRes, 0.0, 1.0);\n  vec3 vc = texture(uVideo, uv).rgb;\n  // Retain the detail in saturated footage without turning dark frames into a solid field.\n  float signal = max(vc.r, max(vc.g, vc.b));\n  float bg = smoothstep(0.035, 0.7, signal) * 255.0;\n  float hm = min(1.0, length(flow) * 1.1);\n  float gray = bg * (1.0 - hm) + (255.0 - bg) * hm;\n  float thr = float(BAYER[(int(gy) & 3) * 4 + (int(gx) & 3)]);\n  bool invDark = hm > 0.05 && bg > thr && gray <= thr;\n  bool lit = gray > thr;\n  if(!lit && !invDark) discard;\n  float pg = invDark ? bg : gray;\n  int ci;\n  if(pg >= EL && pg <= EH) ci = uPhase % 5;\n  else if(pg > EH) ci = 5 + uPhase % ${BRIGHTS.length};\n  else discard;\n  float au = (float(ci) + cp.x) / float(CHAR_N);\n  float ca = texture(uAtlas, vec2(au, cp.y)).a;\n  if(ca < 0.05) discard;\n  vec3 blue = vec3(0.145, 0.388, 0.922);\n  vec3 cyan = vec3(0.02, 0.64, 0.88);\n  vec3 violet = vec3(0.36, 0.29, 0.95);\n  float tint = smoothstep(0.15, 0.9, uv.x * 0.6 + uv.y * 0.4);\n  vec3 col = mix(blue, cyan, tint);\n  col = mix(col, violet, hm * 0.65);\n  float a = (invDark ? 0.85 : 1.0) * ca;\n  O = vec4(col * a, a);\n}`;\n\nfunction createFluid() {\n  const vx = new Float32Array(FN);\n  const vy = new Float32Array(FN);\n  const vx0 = new Float32Array(FN);\n  const vy0 = new Float32Array(FN);\n  const p = new Float32Array(FN);\n  const div = new Float32Array(FN);\n\n  const fi = (x, y) => Math.max(0, Math.min(FR - 1, y)) * FC + Math.max(0, Math.min(FC - 1, x));\n  const bnd = (b, a) => {\n    for (let x = 1; x < FC - 1; x++) {\n      a[fi(x, 0)] = b === 2 ? -a[fi(x, 1)] : a[fi(x, 1)];\n      a[fi(x, FR - 1)] = b === 2 ? -a[fi(x, FR - 2)] : a[fi(x, FR - 2)];\n    }\n    for (let y = 1; y < FR - 1; y++) {\n      a[fi(0, y)] = b === 1 ? -a[fi(1, y)] : a[fi(1, y)];\n      a[fi(FC - 1, y)] = b === 1 ? -a[fi(FC - 2, y)] : a[fi(FC - 2, y)];\n    }\n  };\n  const diffuse = (b, d, s, diff, dt) => {\n    const a = dt * diff * FN;\n    for (let k = 0; k < 4; k++) {\n      for (let y = 1; y < FR - 1; y++) for (let x = 1; x < FC - 1; x++) {\n        d[fi(x, y)] = (s[fi(x, y)] + a * (d[fi(x - 1, y)] + d[fi(x + 1, y)] + d[fi(x, y - 1)] + d[fi(x, y + 1)])) / (1 + 4 * a);\n      }\n      bnd(b, d);\n    }\n  };\n  const advect = (b, d, d0, ux, uy, dt) => {\n    const dtx = dt * FC * 1.4;\n    const dty = dt * FR * 1.4;\n    for (let y = 1; y < FR - 1; y++) for (let x = 1; x < FC - 1; x++) {\n      const px = Math.max(0.5, Math.min(FC - 1.5, x - dtx * ux[fi(x, y)]));\n      const py = Math.max(0.5, Math.min(FR - 1.5, y - dty * uy[fi(x, y)]));\n      const x0 = Math.floor(px);\n      const y0 = Math.floor(py);\n      const s1 = px - x0;\n      const s0 = 1 - s1;\n      const t1 = py - y0;\n      const t0 = 1 - t1;\n      d[fi(x, y)] = s0 * (t0 * d0[fi(x0, y0)] + t1 * d0[fi(x0, y0 + 1)]) + s1 * (t0 * d0[fi(x0 + 1, y0)] + t1 * d0[fi(x0 + 1, y0 + 1)]);\n    }\n    bnd(b, d);\n  };\n  const project = (ux, uy) => {\n    const hx = 1 / FC;\n    const hy = 1 / FR;\n    for (let y = 1; y < FR - 1; y++) for (let x = 1; x < FC - 1; x++) {\n      div[fi(x, y)] = -0.5 * (hx * (ux[fi(x + 1, y)] - ux[fi(x - 1, y)]) + hy * (uy[fi(x, y + 1)] - uy[fi(x, y - 1)]));\n      p[fi(x, y)] = 0;\n    }\n    bnd(0, div);\n    bnd(0, p);\n    for (let k = 0; k < 4; k++) {\n      for (let y = 1; y < FR - 1; y++) for (let x = 1; x < FC - 1; x++) {\n        p[fi(x, y)] = (div[fi(x, y)] + p[fi(x - 1, y)] + p[fi(x + 1, y)] + p[fi(x, y - 1)] + p[fi(x, y + 1)]) / 4;\n      }\n      bnd(0, p);\n    }\n    for (let y = 1; y < FR - 1; y++) for (let x = 1; x < FC - 1; x++) {\n      ux[fi(x, y)] -= 0.5 * (p[fi(x + 1, y)] - p[fi(x - 1, y)]) / hx;\n      uy[fi(x, y)] -= 0.5 * (p[fi(x, y + 1)] - p[fi(x, y - 1)]) / hy;\n    }\n    bnd(1, ux);\n    bnd(2, uy);\n  };\n  return {\n    vx,\n    vy,\n    fi,\n    step() {\n      diffuse(1, vx0, vx, 0.00002, 0.016);\n      diffuse(2, vy0, vy, 0.00002, 0.016);\n      project(vx0, vy0);\n      advect(1, vx, vx0, vx0, vy0, 0.016);\n      advect(2, vy, vy0, vx0, vy0, 0.016);\n      project(vx, vy);\n      for (let i = 0; i < FN; i++) {\n        vx[i] *= 0.94;\n        vy[i] *= 0.94;\n      }\n    },\n  };\n}\n\n/**\n * The original video dither and fluid simulation, scoped to its own surface.\n * @param {{ videoSrc?: string, imageSrc?: string, className?: string, style?: import(\"react\").CSSProperties, paused?: boolean }} props\n */\nexport function DitherCanvas({\n  videoSrc = \"https://www.obsidianui.dev/effects/dither-canvas/dither-canvas-video.mp4\",\n  imageSrc = \"https://www.obsidianui.dev/effects/dither-canvas/dither-canvas-poster.webp\",\n  className,\n  style,\n  paused = false,\n} = {}) {\n  const ref = useRef(null);\n  const videoRef = useRef(null);\n\n  useEffect(() => {\n    const canvas = ref.current;\n    const video = videoRef.current;\n    if (!canvas || !video) return;\n    canvas.style.opacity = \"0\";\n    canvas.style.pointerEvents = \"none\";\n    let gl;\n    try {\n      gl = canvas.getContext(\"webgl2\", { alpha: false, antialias: false });\n    } catch {\n      return;\n    }\n    if (!gl) return;\n\n    const textures = [];\n    const shaders = [];\n    const buffers = [];\n    let prog;\n    let rafId = 0;\n    let disposed = false;\n    let observer;\n    const listeners = [];\n    const motion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const isStatic = () => paused || motion.matches;\n    const listen = (target, event, handler) => {\n      target.addEventListener(event, handler);\n      listeners.push(() => target.removeEventListener(event, handler));\n    };\n    const cleanup = () => {\n      if (disposed) return;\n      disposed = true;\n      cancelAnimationFrame(rafId);\n      observer?.disconnect();\n      listeners.forEach((remove) => remove());\n      video.pause();\n      textures.forEach((texture) => gl.deleteTexture(texture));\n      buffers.forEach((buffer) => gl.deleteBuffer(buffer));\n      shaders.forEach((shader) => gl.deleteShader(shader));\n      if (prog) gl.deleteProgram(prog);\n    };\n    const fallback = () => {\n      if (disposed) return;\n      canvas.style.opacity = \"0\";\n      canvas.style.pointerEvents = \"none\";\n      cleanup();\n    };\n\n    try {\n      const mkShader = (type, source) => {\n        const shader = gl.createShader(type);\n        if (!shader) throw new Error(\"Shader allocation failed.\");\n        shaders.push(shader);\n        gl.shaderSource(shader, source);\n        gl.compileShader(shader);\n        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n          throw new Error(\"Shader compilation failed.\");\n        }\n        return shader;\n      };\n      const mkTex = (unit) => {\n        const tex = gl.createTexture();\n        if (!tex) throw new Error(\"Texture allocation failed.\");\n        textures.push(tex);\n        gl.activeTexture(gl.TEXTURE0 + unit);\n        gl.bindTexture(gl.TEXTURE_2D, tex);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n        return tex;\n      };\n      prog = gl.createProgram();\n      if (!prog) throw new Error(\"Program allocation failed.\");\n      gl.attachShader(prog, mkShader(gl.VERTEX_SHADER, VS));\n      gl.attachShader(prog, mkShader(gl.FRAGMENT_SHADER, FS));\n      gl.linkProgram(prog);\n      if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n        throw new Error(\"Shader linking failed.\");\n      }\n      gl.useProgram(prog);\n      const loc = (name) => gl.getUniformLocation(prog, name);\n      const buf = gl.createBuffer();\n      if (!buf) throw new Error(\"Buffer allocation failed.\");\n      buffers.push(buf);\n      gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n      const aPos = gl.getAttribLocation(prog, \"a_pos\");\n      gl.enableVertexAttribArray(aPos);\n      gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);\n\n      const videoTex = mkTex(0);\n      const fluidTex = mkTex(1);\n      gl.activeTexture(gl.TEXTURE0);\n      gl.bindTexture(gl.TEXTURE_2D, videoTex);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\n      const atlasCanvas = document.createElement(\"canvas\");\n      const CELL = 64;\n      atlasCanvas.width = CELL * ALL_CHARS.length;\n      atlasCanvas.height = CELL;\n      const actx = atlasCanvas.getContext(\"2d\");\n      if (!actx) throw new Error(\"Character atlas unavailable.\");\n      actx.font = `${CELL * 0.92}px monospace`;\n      actx.textAlign = \"center\";\n      actx.textBaseline = \"middle\";\n      actx.fillStyle = \"#fff\";\n      ALL_CHARS.forEach((char, index) => actx.fillText(char, CELL * (index + 0.5), CELL * 0.5));\n      mkTex(2);\n      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlasCanvas);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n      gl.uniform1i(loc(\"uVideo\"), 0);\n      gl.uniform1i(loc(\"uFluid\"), 1);\n      gl.uniform1i(loc(\"uAtlas\"), 2);\n\n      const fluid = createFluid();\n      const fluidData = new Float32Array(FN * 2);\n      const mouse = { x: -9999, y: -9999, vx: 0, vy: 0 };\n      const trail = [];\n      const now = () => performance.now();\n      const onMove = (event) => {\n        if (isStatic()) return;\n        const rect = canvas.getBoundingClientRect();\n        const px = mouse.x;\n        const py = mouse.y;\n        mouse.x = event.clientX - rect.left;\n        mouse.y = event.clientY - rect.top;\n        mouse.vx = mouse.x - px;\n        mouse.vy = mouse.y - py;\n        if (px < 0 || py < 0) {\n          trail.unshift({ x: mouse.x, y: mouse.y, vx: 0, vy: 0, b: now() });\n          if (trail.length > TM) trail.length = TM;\n          return;\n        }\n        const d = Math.hypot(mouse.vx, mouse.vy);\n        if (d < 0.5) return;\n        const steps = Math.max(1, Math.ceil(d / TS));\n        const birth = now();\n        for (let s = 1; s <= steps; s++) {\n          const t = s / steps;\n          trail.unshift({ x: px + mouse.vx * t, y: py + mouse.vy * t, vx: mouse.vx / steps, vy: mouse.vy / steps, b: birth });\n          if (trail.length > TM) trail.length = TM;\n        }\n      };\n      listen(canvas, \"pointermove\", onMove);\n      listen(canvas, \"pointerleave\", () => { mouse.x = mouse.y = -9999; });\n      listen(canvas, \"webglcontextlost\", (event) => {\n        event.preventDefault();\n        fallback();\n      });\n      let W = 1;\n      let H = 1;\n      const resize = () => {\n        const rect = canvas.getBoundingClientRect();\n        W = canvas.width = Math.max(1, Math.round(rect.width));\n        H = canvas.height = Math.max(1, Math.round(rect.height));\n        gl.viewport(0, 0, W, H);\n      };\n      resize();\n      gl.enable(gl.BLEND);\n      gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n      const uTP = loc(\"uTP\");\n      const uTLoc = loc(\"uTL\");\n      const uRes = loc(\"uRes\");\n      const uPhase = loc(\"uPhase\");\n      const uTrailN = loc(\"uTrailN\");\n      const tpBuf = new Float32Array(TM * 4);\n      const tlBuf = new Float32Array(TM);\n      let phase = 0;\n      let frame = 0;\n\n      const draw = () => {\n        if (video.readyState < 2 || disposed) return;\n        const ts = now();\n        const { fb, fss, ffm, fir, firl } = TRAIL_CFG;\n        for (let i = trail.length - 1; i >= 0; i--) {\n          const pt = trail[i];\n          const age = ts - pt.b;\n          if (age >= TL) {\n            trail.splice(i, 1);\n            continue;\n          }\n          const life = 1 - age / TL;\n          const radius = fir + life * firl;\n          const gr = Math.ceil(radius);\n          const speed = Math.hypot(pt.vx, pt.vy);\n          const force = (fb + Math.min(speed, fss) / fss) * life;\n          const cx = ((pt.x / W) * FC) | 0;\n          const cy = ((pt.y / H) * FR) | 0;\n          for (let dy = -gr; dy <= gr; dy++) for (let dx = -gr; dx <= gr; dx++) {\n            const dist = Math.hypot(dx, dy);\n            if (dist > radius) continue;\n            const f = (1 - dist / radius) ** 2;\n            fluid.vx[fluid.fi(cx + dx, cy + dy)] += pt.vx * f * force * ffm;\n            fluid.vy[fluid.fi(cx + dx, cy + dy)] += pt.vy * f * force * ffm;\n          }\n        }\n        fluid.step();\n        if (!isStatic() && frame++ % 8 === 0) phase = (phase + 1) % 255;\n        gl.activeTexture(gl.TEXTURE0);\n        gl.bindTexture(gl.TEXTURE_2D, videoTex);\n        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);\n        for (let i = 0; i < FN; i++) {\n          fluidData[i * 2] = fluid.vx[i];\n          fluidData[i * 2 + 1] = fluid.vy[i];\n        }\n        gl.activeTexture(gl.TEXTURE1);\n        gl.bindTexture(gl.TEXTURE_2D, fluidTex);\n        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, FC, FR, 0, gl.RG, gl.FLOAT, fluidData);\n        tpBuf.fill(0);\n        tlBuf.fill(0);\n        for (let i = 0; i < trail.length; i++) {\n          const pt = trail[i];\n          tpBuf[i * 4] = pt.x;\n          tpBuf[i * 4 + 1] = pt.y;\n          tpBuf[i * 4 + 2] = pt.vx;\n          tpBuf[i * 4 + 3] = pt.vy;\n          tlBuf[i] = 1 - (ts - pt.b) / TL;\n        }\n        gl.uniform4fv(uTP, tpBuf);\n        gl.uniform1fv(uTLoc, tlBuf);\n        gl.uniform1i(uTrailN, trail.length);\n        gl.uniform2f(uRes, W, H);\n        gl.uniform1i(uPhase, phase);\n        gl.clearColor(1, 1, 1, 1);\n        gl.clear(gl.COLOR_BUFFER_BIT);\n        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n        canvas.style.opacity = \"1\";\n        canvas.style.pointerEvents = \"auto\";\n      };\n      const render = () => {\n        if (disposed) return;\n        try { draw(); } catch { fallback(); return; }\n        if (!isStatic()) rafId = requestAnimationFrame(render);\n      };\n      const syncMotion = () => {\n        cancelAnimationFrame(rafId);\n        trail.length = 0;\n        if (isStatic()) video.pause();\n        else video.play().catch(fallback);\n        render();\n      };\n      observer = new ResizeObserver(() => { resize(); if (isStatic()) render(); });\n      observer.observe(canvas.parentElement);\n      listen(video, \"loadeddata\", () => { if (isStatic()) render(); });\n      listen(video, \"error\", fallback);\n      listen(motion, \"change\", syncMotion);\n      syncMotion();\n    } catch {\n      fallback();\n    }\n    return cleanup;\n  }, [videoSrc, paused]);\n\n  return (\n    <div className={cn(\"relative h-[28rem] w-full overflow-hidden bg-white\", className)} style={style}>\n      <div\n        role=\"img\"\n        aria-label=\"Blue and cyan dither texture on white\"\n        className=\"pointer-events-none absolute inset-0\"\n        style={{\n          backgroundImage: \"linear-gradient(135deg, transparent 12%, #2563eb 35%, #0aa3e0 55%, #5c4af2 72%, transparent 90%)\",\n          maskImage: \"radial-gradient(circle, #000 1px, transparent 1.3px)\",\n          maskSize: \"7px 7px\",\n        }}\n      />\n      <video\n        ref={videoRef}\n        src={videoSrc}\n        poster={imageSrc}\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute inset-0 h-full w-full object-fill opacity-0\"\n        crossOrigin=\"anonymous\"\n        preload=\"auto\"\n        muted\n        loop\n        playsInline\n      />\n      <canvas ref={ref} aria-hidden=\"true\" className=\"absolute inset-0 h-full w-full opacity-0\" />\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ],
      "docs": "Default demo media loads from ObsidianUI. Replace these URLs with your own assets for offline use.",
      "meta": {
        "remoteAssets": [
          "https://www.obsidianui.dev/effects/dither-canvas/dither-canvas-poster.webp",
          "https://www.obsidianui.dev/effects/dither-canvas/dither-canvas-video.mp4"
        ]
      }
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "dotted-grid",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/dotted-grid.jsx",
          "target": "@components/block/dotted-grid.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst SPACING = 24;\nconst BASE_RADIUS = 7.2;\nconst MOUSE_RADIUS = 380; // wider head influence\nconst TRAIL_LENGTH = 456; // even more history\nconst TRAIL_RADIUS = 230; // wider trail reach\nconst TRAIL_FADE_MS = 1200;\n\nconst RANDOM_TIME = 0.6;\nconst COLLECT_TIME = 1.1;\nconst SHAPE_HOLD_TIME = 1.2;\nconst GRAY_DISPERSE_TIME = 0.9; // trail lingers ~2s\n\nconst TOTAL_CYCLE_TIME = RANDOM_TIME + COLLECT_TIME + SHAPE_HOLD_TIME + GRAY_DISPERSE_TIME;\nconst TOTAL_SHAPES = 5;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nconst lerp     = (a, b, t) => a + (b - a) * t;\nconst clamp01  = (v) => Math.max(0, Math.min(1, v));\nconst smoothstep = (e0, e1, v) => {\n  const t = clamp01((v - e0) / (e1 - e0));\n  return t * t * (3 - 2 * t);\n};\n\n// ─── Pure Shape Math ──────────────────────────────────────────────────────────\n\nconst getStarStrength = (x, y, width, height) => {\n  const cx = width / 2;\n  const cy = height / 2;\n  const scale = Math.min(width, height) * 0.28;\n\n  const nx = (x - cx) / scale;\n  const ny = (y - cy) / scale;\n\n  const r = Math.sqrt(nx * nx + ny * ny);\n  const angle = Math.atan2(ny, nx);\n\n  const spikes = 5;\n  const star = Math.cos(spikes * angle);\n  const radius = 0.55 + 0.25 * star;\n\n  return clamp01(1 - smoothstep(radius - 0.05, radius + 0.05, r));\n};\n\nconst getSquareStrength = (x, y, width, height) => {\n  const cx = width / 2;\n  const cy = height / 2;\n  const scale = Math.min(width, height) * 0.26;\n\n  const rx = (x - cx) / scale;\n  const ry = (y - cy) / scale;\n  const d  = Math.max(Math.abs(rx), Math.abs(ry));\n\n  return clamp01(1 - smoothstep(0.78, 0.82, d));\n};\n\nconst getCircleRingStrength = (x, y, width, height) => {\n  const cx = width / 2;\n  const cy = height / 2;\n  const scale = Math.min(width, height) * 0.28;\n\n  const r = Math.sqrt(((x - cx) / scale) ** 2 + ((y - cy) / scale) ** 2);\n\n  return clamp01(1 - smoothstep(0.13, 0.17, Math.abs(r - 0.72)));\n};\n\nconst getPlusStrength = (x, y, width, height) => {\n  const cx = width / 2;\n  const cy = height / 2;\n  const scale = Math.min(width, height) * 0.27;\n\n  const rx = (x - cx) / scale;\n  const ry = (y - cy) / scale;\n\n  const thickness = 0.18;\n  const length    = 0.75;\n\n  const vertical   = Math.abs(rx) < thickness && Math.abs(ry) < length;\n  const horizontal = Math.abs(ry) < thickness && Math.abs(rx) < length;\n\n  const d = Math.min(\n    Math.max(Math.abs(rx) - thickness, Math.abs(ry) - length),\n    Math.max(Math.abs(ry) - thickness, Math.abs(rx) - length)\n  );\n\n  return vertical || horizontal ? 1 : clamp01(1 - smoothstep(0, 0.06, d));\n};\n\nconst getTriangleStrength = (x, y, time, width, height) => {\n  const cx = width / 2;\n  const cy = height / 2;\n  const scale = Math.min(width, height) * 0.32;\n  const rotation = Math.sin(time * 0.3) * 0.12;\n\n  const cos = Math.cos(rotation);\n  const sin = Math.sin(rotation);\n\n  const rx = ((x - cx) * cos - (y - cy) * sin) / scale;\n  const ry = ((x - cx) * sin + (y - cy) * cos) / scale;\n\n  const a = Math.abs(rx) * 0.9 + ry * 0.52;\n  const b = -ry * 0.95;\n\n  return clamp01(1 - smoothstep(0.38, 0.48, Math.max(a, b)));\n};\n\nconst getRawShapeStrength = (shapeIndex, x, y, time, width, height) => {\n  const i = shapeIndex % TOTAL_SHAPES;\n  if (i === 0) return getStarStrength(x, y, width, height);\n  if (i === 1) return getSquareStrength(x, y, width, height);\n  if (i === 2) return getCircleRingStrength(x, y, width, height);\n  if (i === 3) return getPlusStrength(x, y, width, height);\n  return getTriangleStrength(x, y, time, width, height);\n};\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * @param {{ className?: string, style?: import(\"react\").CSSProperties, paused?: boolean }} props\n */\nexport function DottedGrid({ className, style, paused = false } = {}) {\n  const canvasRef  = useRef(null);\n  const patternRef = useRef({\n    currentShapeIndex: 0,\n    transitionStartTime: null,\n  });\n  const mouseRef = useRef({\n    x: 0,\n    y: 0,\n    targetX: 0,\n    targetY: 0,\n    active: false,\n    trail: [],\n  });\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx    = canvas.getContext(\"2d\", { alpha: false });\n    if (!ctx) return;\n    const surface = canvas.parentElement;\n    const motion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    let isStatic = paused || motion.matches;\n    if (isStatic) patternRef.current.transitionStartTime = null;\n\n    let width = 0;\n    let height = 0;\n    let dpr = window.devicePixelRatio || 1;\n    let animationId = 0;\n    let dots = [];\n\n    // ─── Dot Setup ─────────────────────────────────────────────────────────────\n\n    const createDots = () => {\n      dots = [];\n      for (let y = SPACING / 2; y < height; y += SPACING) {\n        for (let x = SPACING / 2; x < width; x += SPACING) {\n          dots.push({\n            x,\n            y,\n            phase: Math.random() * Math.PI * 2,\n            speed: 0.3 + Math.random() * 1.0,\n            randomOffset: Math.random() * 10,\n            currentShapeStrength: 0,\n            currentRandomStrength: 1,\n            currentMouseStrength: 0,\n            currentTrailStrength: 0,\n            currentGrayDisperseStrength: 0,\n          });\n        }\n      }\n    };\n\n    const resize = () => {\n      const rect = canvas.getBoundingClientRect();\n      width  = Math.max(1, rect.width);\n      height = Math.max(1, rect.height);\n      dpr    = Math.min(window.devicePixelRatio || 1, 2);\n      canvas.width  = Math.floor(width * dpr);\n      canvas.height = Math.floor(height * dpr);\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      createDots();\n      if (isStatic) animate(0);\n    };\n\n    // ─── Event Handlers ────────────────────────────────────────────────────────\n\n    const handlePointerMove = (e) => {\n      if (isStatic) return;\n      const rect = canvas.getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const y = e.clientY - rect.top;\n      mouseRef.current.targetX = x;\n      mouseRef.current.targetY = y;\n      mouseRef.current.active  = true;\n      mouseRef.current.trail.push({ x, y, t: performance.now() });\n\n      if (mouseRef.current.trail.length > TRAIL_LENGTH) {\n        mouseRef.current.trail.shift();\n      }\n    };\n\n    const handlePointerLeave = () => {\n      mouseRef.current.active = false;\n    };\n\n    const handleClick = () => {\n      patternRef.current.currentShapeIndex = (patternRef.current.currentShapeIndex + 1) % TOTAL_SHAPES;\n      patternRef.current.transitionStartTime = isStatic ? null : performance.now() * 0.001;\n      if (isStatic) animate(0);\n    };\n\n    // ─── Transition / Shape Interpolator ───────────────────────────────────────\n\n    const getShapeData = (x, y, time) => {\n      const { currentShapeIndex, transitionStartTime } = patternRef.current;\n      \n      if (transitionStartTime === null) {\n        return {\n          shapeStrength: getRawShapeStrength(currentShapeIndex, x, y, time, width, height),\n          randomStrength: 0,\n          grayDisperseStrength: 0,\n        };\n      }\n\n      const cyclePosition = time - transitionStartTime;\n\n      if (cyclePosition >= TOTAL_CYCLE_TIME) {\n        patternRef.current.transitionStartTime = null;\n        return {\n          shapeStrength: getRawShapeStrength(currentShapeIndex, x, y, time, width, height),\n          randomStrength: 0,\n          grayDisperseStrength: 0,\n        };\n      }\n\n      const shapeIndex = currentShapeIndex;\n      const shapeStrength = getRawShapeStrength(shapeIndex, x, y, time, width, height);\n\n      if (cyclePosition < RANDOM_TIME) {\n        return { shapeStrength: 0, randomStrength: 1, grayDisperseStrength: 0.35 };\n      }\n\n      if (cyclePosition < RANDOM_TIME + COLLECT_TIME) {\n        const eased = smoothstep(0, 1, (cyclePosition - RANDOM_TIME) / COLLECT_TIME);\n        return {\n          shapeStrength: shapeStrength * eased,\n          randomStrength: 1 - eased,\n          grayDisperseStrength: 0.35 * (1 - eased),\n        };\n      }\n\n      if (cyclePosition < RANDOM_TIME + COLLECT_TIME + SHAPE_HOLD_TIME) {\n        return { shapeStrength, randomStrength: 0, grayDisperseStrength: 0 };\n      }\n\n      const eased = smoothstep(0, 1, (cyclePosition - RANDOM_TIME - COLLECT_TIME - SHAPE_HOLD_TIME) / GRAY_DISPERSE_TIME);\n      return {\n        shapeStrength: shapeStrength * (1 - eased),\n        randomStrength: eased,\n        grayDisperseStrength: eased,\n      };\n    };\n\n    // ─── Drawing Utilities ─────────────────────────────────────────────────────\n\n    const drawDot = (x, y, radius, brightness, grayDisperseStrength, trailStrength, mouseStrength) => {\n      const mouseFade = mouseStrength * mouseStrength * 0.72;\n      const trailFade = trailStrength * 0.38;\n      const alpha = clamp01((0.28 + brightness * 0.72) - mouseFade - trailFade);\n\n      const normalL   = 16 + brightness * 78;\n      const disperseL = 12 + brightness * 58 + grayDisperseStrength * 22;\n      const lightness = lerp(normalL, disperseL, grayDisperseStrength);\n\n      const mouseLift = mouseStrength * (1 - mouseStrength) * 18;\n      const mouseDark = mouseStrength * mouseStrength * 38;\n      const trailLift = trailStrength * 38 * (1 - trailStrength * 0.55);\n\n      const finalLightness = clamp01((lightness - mouseDark + mouseLift + trailLift) / 100) * 100;\n      const saturation     = trailStrength * trailStrength * 16;\n\n      ctx.beginPath();\n      ctx.fillStyle = `hsla(210, ${saturation}%, ${finalLightness}%, ${alpha})`;\n      ctx.arc(x, y, radius, 0, Math.PI * 2);\n      ctx.fill();\n    };\n\n    // ─── Main Draw Loop ────────────────────────────────────────────────────────\n\n    const animate = (ms) => {\n      const time = ms * 0.001;\n      const mouse = mouseRef.current;\n      const now = performance.now();\n      mouse.trail = mouse.trail.filter((point) => now - point.t < TRAIL_FADE_MS);\n\n      // Lagging cursor effect\n      mouse.x = lerp(mouse.x, mouse.targetX, 0.12);\n      mouse.y = lerp(mouse.y, mouse.targetY, 0.12);\n\n      ctx.fillStyle = \"#000000\";\n      ctx.fillRect(0, 0, width, height);\n\n      for (const dot of dots) {\n        const { shapeStrength, randomStrength, grayDisperseStrength } = getShapeData(dot.x, dot.y, time);\n\n        dot.currentShapeStrength = isStatic ? shapeStrength : lerp(dot.currentShapeStrength, shapeStrength, 0.12);\n        dot.currentRandomStrength = isStatic ? 0 : lerp(dot.currentRandomStrength, randomStrength, 0.14);\n        dot.currentGrayDisperseStrength = lerp(dot.currentGrayDisperseStrength, grayDisperseStrength, 0.14);\n\n        // Cursor head influence\n        let targetMouseStrength = 0;\n        if (mouse.active) {\n          const dx = dot.x - mouse.x;\n          const dy = dot.y - mouse.y;\n          const dist = Math.sqrt(dx * dx + dy * dy);\n          if (dist < MOUSE_RADIUS) {\n            const norm = dist / MOUSE_RADIUS;\n            targetMouseStrength = (1 - norm) * (1 - norm) * (1 - norm);\n          }\n        }\n        dot.currentMouseStrength = isStatic ? 0 : lerp(dot.currentMouseStrength, targetMouseStrength, 0.12);\n\n        // Trail influence\n        let targetTrailStrength = 0;\n        for (let i = 0; i < mouse.trail.length; i++) {\n          const pt = mouse.trail[i];\n          const age = (now - pt.t) / TRAIL_FADE_MS;\n          if (age >= 1) continue;\n\n          const ageFade = (1 - age) * (1 - age) * (1 - age);\n          const positionFade = (i + 1) / mouse.trail.length;\n          const fade = ageFade * positionFade;\n\n          const dx = dot.x - pt.x;\n          const dy = dot.y - pt.y;\n          const dist = Math.sqrt(dx * dx + dy * dy);\n\n          if (dist < TRAIL_RADIUS) {\n            const proximity = 1 - smoothstep(0, 1, dist / TRAIL_RADIUS);\n            const softProximity = proximity * proximity * proximity;\n            targetTrailStrength = Math.max(targetTrailStrength, softProximity * fade);\n          }\n        }\n        dot.currentTrailStrength = isStatic ? 0 : lerp(dot.currentTrailStrength, targetTrailStrength, 0.08);\n\n        const randomBlink = Math.sin(\n          time * (1.2 + dot.speed * 1.2) + dot.phase + dot.randomOffset + dot.x * 0.02 + dot.y * 0.016\n        ) ** 2;\n\n        const softPulse = Math.sin(time * 1.4 + dot.phase + dot.x * 0.015) ** 2;\n\n        const stableBrightness = clamp01(0.16 + dot.currentShapeStrength * 0.84 + softPulse * 0.02);\n        const randomBrightness = clamp01(0.16 + randomBlink * 0.18);\n        const brightness = lerp(stableBrightness, randomBrightness, dot.currentRandomStrength);\n\n        const grayDisperseBlink = clamp01(dot.currentGrayDisperseStrength * (0.45 + randomBlink * 0.55));\n\n        // Scale modification based on mouse proximity and trail history\n        const mouseShrink = 1 - dot.currentMouseStrength * 0.75;\n        const trailShrink = 1 - dot.currentTrailStrength * 0.65;\n\n        const stableRadius = BASE_RADIUS + dot.currentShapeStrength * 1.25;\n        const randomRadius = BASE_RADIUS + randomBlink * 0.35;\n        const radius = lerp(stableRadius, randomRadius, dot.currentRandomStrength) * mouseShrink * trailShrink;\n\n        drawDot(dot.x, dot.y, radius, brightness, grayDisperseBlink, dot.currentTrailStrength, dot.currentMouseStrength);\n      }\n\n      if (!isStatic) animationId = requestAnimationFrame(animate);\n    };\n\n    // ─── Listeners & Boot ──────────────────────────────────────────────────────\n\n    resize();\n    const observer = new ResizeObserver(resize);\n    observer.observe(surface);\n    const handleMotionChange = () => {\n      isStatic = paused || motion.matches;\n      if (isStatic) patternRef.current.transitionStartTime = null;\n      cancelAnimationFrame(animationId);\n      mouseRef.current.active = false;\n      mouseRef.current.trail = [];\n      animate(isStatic ? 0 : performance.now());\n    };\n    motion.addEventListener(\"change\", handleMotionChange);\n    canvas.addEventListener(\"pointermove\", handlePointerMove);\n    canvas.addEventListener(\"pointerleave\", handlePointerLeave);\n    surface.addEventListener(\"click\", handleClick);\n    if (!isStatic) animationId = requestAnimationFrame(animate);\n\n    return () => {\n      observer.disconnect();\n      motion.removeEventListener(\"change\", handleMotionChange);\n      canvas.removeEventListener(\"pointermove\", handlePointerMove);\n      canvas.removeEventListener(\"pointerleave\", handlePointerLeave);\n      surface.removeEventListener(\"click\", handleClick);\n      cancelAnimationFrame(animationId);\n    };\n  }, [paused]);\n\n  return (\n    <button\n      type=\"button\"\n      aria-label=\"Change dotted grid shape\"\n      className={cn(\"relative block h-[28rem] w-full cursor-pointer overflow-hidden bg-black p-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\", className)}\n      style={style}\n    >\n      <canvas\n        ref={canvasRef}\n        aria-hidden=\"true\"\n        className=\"block h-full w-full bg-black\"\n      />\n      <span className=\"pointer-events-none absolute inset-0 bg-linear-to-b from-white/4 via-transparent to-black/40\" />\n      <span className=\"pointer-events-none absolute inset-0 shadow-[inset_0_0_140px_rgba(0,0,0,0.95)]\" />\n    </button>\n  );\n}\n\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "draggable-marquee",
      "type": "registry:block",
      "dependencies": [
        "gsap"
      ],
      "files": [
        {
          "path": "components/block/draggable-marquee.jsx",
          "target": "@components/block/draggable-marquee.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport Image from \"next/image\";\nimport gsap from \"gsap\";\nimport Draggable from \"gsap/Draggable\";\nimport \"@/lib/effects/draggable-marquee/styles.css\";\n\nif (typeof window !== \"undefined\") gsap.registerPlugin(Draggable);\n\n/**\n * @param {{items?: Array<{id?: string | number, src: string, alt?: string, width?: number, height?: number, imageClassName?: string}>,\n * speed?: number, repeatCount?: number, gapClassName?: string, className?: string, trackClassName?: string,\n * itemClassName?: string, pauseOnHover?: boolean, renderItem?: (item: {src: string, [key: string]: unknown}, index: number) => import('react').ReactNode,\n * throwMultiplier?: number, throwFriction?: number, maxThrowVelocity?: number, initialOffset?: number,\n * loopStart?: number, loopEndMultiplier?: number, label?: string}} props\n */\nconst DraggableMarquee = ({\n    items = [],\n    speed = 1,\n    repeatCount = 3,\n    gapClassName = \"gap-6\",\n    className = \"\",\n    trackClassName = \"\",\n    itemClassName = \"rounded-2xl\",\n    pauseOnHover = false,\n    renderItem,\n\n    // motion controls\n    throwMultiplier = 2.8,\n    throwFriction = 0.975,\n    maxThrowVelocity = 60,\n\n    // loop controls\n    initialOffset = 0,\n    loopStart = 0,\n    loopEndMultiplier = -1.02,\n    label = \"Image marquee. Drag or use the left and right arrow keys.\",\n}) => {\n    const rootRef = useRef(null);\n    const trackRef = useRef(null);\n    const dragRef = useRef(null);\n\n    const duplicatedItems = useMemo(() => {\n        return Array.from({ length: repeatCount }).flatMap(() => items);\n    }, [items, repeatCount]);\n\n    useEffect(() => {\n        if (!rootRef.current || !trackRef.current || !items.length) return;\n\n        const media = gsap.matchMedia();\n        media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n\n        const root = rootRef.current;\n        const track = trackRef.current;\n\n        let singleSetWidth = 0;\n        let x = initialOffset;\n        let throwVelocity = 0;\n        let isPointerOver = false;\n        let isDragging = false;\n        let lastDragX = 0;\n        let lastDragTime = 0;\n\n        let wrapValue = (value) => value;\n        let resizeRaf = null;\n\n        const observers = [];\n        const setX = gsap.quickSetter(track, \"x\", \"px\");\n\n        const getGap = () => {\n            const styles = window.getComputedStyle(track);\n            return parseFloat(styles.columnGap || styles.gap || 0);\n        };\n\n        const buildWrap = () => {\n            const min = singleSetWidth * loopEndMultiplier;\n            const max = loopStart;\n            wrapValue = gsap.utils.wrap(min, max);\n        };\n\n        const getProgressInLoop = () => {\n            if (!singleSetWidth) return 0;\n\n            const min = singleSetWidth * loopEndMultiplier;\n            const max = loopStart;\n            const range = max - min;\n\n            if (!range) return 0;\n\n            let wrapped = x;\n            while (wrapped < min) wrapped += range;\n            while (wrapped > max) wrapped -= range;\n\n            return (wrapped - min) / range;\n        };\n\n        const setProgressInLoop = (progress) => {\n            if (!singleSetWidth) return;\n\n            const min = singleSetWidth * loopEndMultiplier;\n            const max = loopStart;\n            const range = max - min;\n\n            x = min + range * progress;\n            x = wrapValue(x);\n            setX(x);\n\n            if (dragRef.current) {\n                dragRef.current.x = x;\n            }\n        };\n\n        const measure = () => {\n            const children = Array.from(track.children);\n            const setSize = Math.floor(children.length / repeatCount);\n            const firstSetChildren = children.slice(0, setSize);\n            const gap = getGap();\n\n            if (!firstSetChildren.length) return;\n\n            const prevProgress = getProgressInLoop();\n\n            const widths = firstSetChildren.reduce(\n                (sum, child) => sum + child.getBoundingClientRect().width,\n                0\n            );\n\n            singleSetWidth = widths + gap * Math.max(0, firstSetChildren.length - 1);\n\n            buildWrap();\n\n            if (!Number.isFinite(prevProgress)) {\n                x = wrapValue(initialOffset);\n                setX(x);\n            } else {\n                setProgressInLoop(prevProgress);\n            }\n        };\n\n        const scheduleMeasure = () => {\n            if (resizeRaf) cancelAnimationFrame(resizeRaf);\n            resizeRaf = requestAnimationFrame(() => {\n                measure();\n            });\n        };\n\n        const update = () => {\n            if (!isDragging) {\n                if (!(pauseOnHover && isPointerOver)) {\n                    x -= speed;\n                }\n\n                x += throwVelocity;\n                throwVelocity *= throwFriction;\n\n                if (Math.abs(throwVelocity) < 0.01) {\n                    throwVelocity = 0;\n                }\n            }\n\n            x = wrapValue(x);\n            setX(x);\n        };\n\n        measure();\n\n        dragRef.current = Draggable.create(track, {\n            type: \"x\",\n            allowContextMenu: true,\n            dragClickables: true,\n\n            onPress() {\n                isDragging = true;\n                throwVelocity = 0;\n                this.x = x;\n                lastDragX = this.x;\n                lastDragTime = performance.now();\n            },\n\n            onDrag() {\n                const now = performance.now();\n                const dx = this.x - lastDragX;\n                const dt = now - lastDragTime;\n\n                x = wrapValue(this.x);\n                setX(x);\n                this.x = x;\n\n                if (dt > 0) {\n                    const sampledVelocity = (dx / dt) * 36.67;\n\n                    throwVelocity = gsap.utils.clamp(\n                        -maxThrowVelocity,\n                        maxThrowVelocity,\n                        sampledVelocity * throwMultiplier\n                    );\n                }\n\n                lastDragX = this.x;\n                lastDragTime = now;\n            },\n\n            onRelease() {\n                isDragging = false;\n            },\n        })[0];\n\n        const handleMouseEnter = () => {\n            isPointerOver = true;\n        };\n\n        const handleMouseLeave = () => {\n            isPointerOver = false;\n        };\n\n        if (pauseOnHover) {\n            root.addEventListener(\"mouseenter\", handleMouseEnter);\n            root.addEventListener(\"mouseleave\", handleMouseLeave);\n        }\n\n        const handleResize = () => {\n            scheduleMeasure();\n        };\n        const handleKeyDown = (event) => {\n            if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return;\n            event.preventDefault();\n            x = wrapValue(x + (event.key === \"ArrowLeft\" ? 1 : -1) * root.clientWidth * 0.35);\n            throwVelocity = 0;\n            setX(x);\n        };\n        root.addEventListener(\"keydown\", handleKeyDown);\n\n        window.addEventListener(\"resize\", handleResize);\n\n        const children = Array.from(track.children);\n        const setSize = Math.floor(children.length / repeatCount);\n        const firstSetChildren = children.slice(0, setSize);\n\n        const trackObserver = new ResizeObserver(() => {\n            scheduleMeasure();\n        });\n\n        trackObserver.observe(track);\n        observers.push(trackObserver);\n\n        firstSetChildren.forEach((child) => {\n            const ro = new ResizeObserver(() => {\n                scheduleMeasure();\n            });\n\n            ro.observe(child);\n            observers.push(ro);\n\n            const imgs = child.querySelectorAll(\"img\");\n\n            imgs.forEach((img) => {\n                if (!img.complete) {\n                    img.addEventListener(\"load\", scheduleMeasure, { once: false });\n                }\n            });\n        });\n\n        gsap.ticker.add(update);\n\n        return () => {\n            root.removeEventListener(\"keydown\", handleKeyDown);\n            window.removeEventListener(\"resize\", handleResize);\n\n            if (pauseOnHover) {\n                root.removeEventListener(\"mouseenter\", handleMouseEnter);\n                root.removeEventListener(\"mouseleave\", handleMouseLeave);\n            }\n\n            gsap.ticker.remove(update);\n\n            if (resizeRaf) cancelAnimationFrame(resizeRaf);\n\n            observers.forEach((observer) => observer.disconnect());\n\n            const cleanupChildren = Array.from(track.children);\n\n            cleanupChildren.forEach((child) => {\n                const imgs = child.querySelectorAll(\"img\");\n\n                imgs.forEach((img) => {\n                    img.removeEventListener(\"load\", scheduleMeasure);\n                });\n            });\n\n            if (dragRef.current) {\n                dragRef.current.kill();\n                dragRef.current = null;\n            }\n        };\n        }, rootRef);\n        return () => media.revert();\n    }, [\n        items,\n        speed,\n        repeatCount,\n        pauseOnHover,\n        throwMultiplier,\n        throwFriction,\n        maxThrowVelocity,\n        initialOffset,\n        loopStart,\n        loopEndMultiplier,\n    ]);\n\n    if (!items.length) return null;\n\n    return (\n        <div\n            ref={rootRef}\n            tabIndex={0}\n            role=\"region\"\n            aria-label={label}\n            className={`obsidian-draggable-marquee relative w-full overflow-hidden cursor-grab outline-offset-4 focus-visible:outline-2 focus-visible:outline-ring active:cursor-grabbing ${className}`}\n        >\n            <div\n                ref={trackRef}\n                className={`flex w-max items-center ${gapClassName} ${trackClassName}`}\n            >\n                {duplicatedItems.map((item, index) => (\n                    <div\n                        key={`${item?.id || item?.src || \"item\"}-${index}`}\n                        className={`shrink-0 ${itemClassName}`}\n                        aria-hidden={index >= items.length}\n                        data-marquee-copy={index >= items.length ? \"duplicate\" : \"original\"}\n                    >\n                        {renderItem ? (\n                            renderItem(item, index % items.length)\n                        ) : (\n                            <Image\n                                src={item.src}\n                                alt={item.alt || \"marquee-item\"}\n                                width={item.width || 400}\n                                height={item.height || 500}\n                                className={item.imageClassName || \"h-auto w-auto object-cover max-sm:w-[320px] max-sm:h-105\"}\n                            />\n                        )}\n                    </div>\n                ))}\n            </div>\n        </div>\n    );\n};\n\nexport { DraggableMarquee };\n"
        },
        {
          "path": "lib/effects/draggable-marquee/styles.css",
          "target": "@lib/effects/draggable-marquee/styles.css",
          "type": "registry:file",
          "content": "@media (prefers-reduced-motion: reduce) {\n  .obsidian-draggable-marquee { overflow-x: auto; }\n  .obsidian-draggable-marquee > div { transform: none !important; }\n  .obsidian-draggable-marquee [data-marquee-copy=\"duplicate\"] { display: none; }\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "drawer",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge",
        "vaul"
      ],
      "files": [
        {
          "path": "components/ui/drawer.tsx",
          "target": "@ui/drawer.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Drawer as DrawerPrimitive } from \"vaul\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Drawer({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Root>) {\n  return <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />\n}\n\nfunction DrawerTrigger({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {\n  return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />\n}\n\nfunction DrawerPortal({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {\n  return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />\n}\n\nfunction DrawerClose({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Close>) {\n  return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />\n}\n\nfunction DrawerOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {\n  return (\n    <DrawerPrimitive.Overlay\n      data-slot=\"drawer-overlay\"\n      className={cn(\n        \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Content>) {\n  return (\n    <DrawerPortal data-slot=\"drawer-portal\">\n      <DrawerOverlay />\n      <DrawerPrimitive.Content\n        data-slot=\"drawer-content\"\n        className={cn(\n          \"group/drawer-content bg-background fixed z-50 flex h-auto flex-col\",\n          \"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b\",\n          \"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t\",\n          \"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm\",\n          \"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm\",\n          className\n        )}\n        {...props}\n      >\n        <div className=\"bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n        {children}\n      </DrawerPrimitive.Content>\n    </DrawerPortal>\n  )\n}\n\nfunction DrawerHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"drawer-header\"\n      className={cn(\n        \"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"drawer-footer\"\n      className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Title>) {\n  return (\n    <DrawerPrimitive.Title\n      data-slot=\"drawer-title\"\n      className={cn(\"text-foreground font-semibold\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Description>) {\n  return (\n    <DrawerPrimitive.Description\n      data-slot=\"drawer-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Drawer,\n  DrawerPortal,\n  DrawerOverlay,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerFooter,\n  DrawerTitle,\n  DrawerDescription,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "dropdown-menu",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-dropdown-menu",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/dropdown-menu.tsx",
          "target": "@ui/dropdown-menu.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction DropdownMenu({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n  return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n  return (\n    <DropdownMenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n  )\n}\n\nfunction DropdownMenuTrigger({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n  return (\n    <DropdownMenuPrimitive.Trigger\n      data-slot=\"dropdown-menu-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuContent({\n  className,\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n  return (\n    <DropdownMenuPrimitive.Portal>\n      <DropdownMenuPrimitive.Content\n        data-slot=\"dropdown-menu-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md\",\n          className\n        )}\n        {...props}\n      />\n    </DropdownMenuPrimitive.Portal>\n  )\n}\n\nfunction DropdownMenuGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n  return (\n    <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n  )\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <DropdownMenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n  return (\n    <DropdownMenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction DropdownMenuRadioGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n  return (\n    <DropdownMenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n  return (\n    <DropdownMenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.RadioItem>\n  )\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.Label\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n  return (\n    <DropdownMenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"bg-border -mx-1 my-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSub({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n  return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.SubTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto size-4\" />\n    </DropdownMenuPrimitive.SubTrigger>\n  )\n}\n\nfunction DropdownMenuSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n  return (\n    <DropdownMenuPrimitive.SubContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "empty",
      "type": "registry:ui",
      "dependencies": [
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/empty.tsx",
          "target": "@ui/empty.tsx",
          "type": "registry:ui",
          "content": "import { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Empty({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty\"\n      className={cn(\n        \"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-header\"\n      className={cn(\n        \"flex max-w-sm flex-col items-center gap-2 text-center\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst emptyMediaVariants = cva(\n  \"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        icon: \"bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction EmptyMedia({\n  className,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\n  return (\n    <div\n      data-slot=\"empty-icon\"\n      data-variant={variant}\n      className={cn(emptyMediaVariants({ variant, className }))}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-title\"\n      className={cn(\"text-lg font-medium tracking-tight\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <div\n      data-slot=\"empty-description\"\n      className={cn(\n        \"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-content\"\n      className={cn(\n        \"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Empty,\n  EmptyHeader,\n  EmptyTitle,\n  EmptyDescription,\n  EmptyContent,\n  EmptyMedia,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "field",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-label",
        "@radix-ui/react-separator",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/field.tsx",
          "target": "@ui/field.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport { useMemo } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\nimport { Separator } from \"@/components/ui/separator\"\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n  return (\n    <fieldset\n      data-slot=\"field-set\"\n      className={cn(\n        \"flex flex-col gap-6\",\n        \"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldLegend({\n  className,\n  variant = \"legend\",\n  ...props\n}: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\n        \"mb-3 font-medium\",\n        \"data-[variant=legend]:text-base\",\n        \"data-[variant=label]:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      className={cn(\n        \"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst fieldVariants = cva(\n  \"group/field flex w-full gap-3 data-[invalid=true]:text-destructive\",\n  {\n    variants: {\n      orientation: {\n        vertical: [\"flex-col [&>*]:w-full [&>.sr-only]:w-auto\"],\n        horizontal: [\n          \"flex-row items-center\",\n          \"[&>[data-slot=field-label]]:flex-auto\",\n          \"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n        ],\n        responsive: [\n          \"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto\",\n          \"@md/field-group:[&>[data-slot=field-label]]:flex-auto\",\n          \"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n        ],\n      },\n    },\n    defaultVariants: {\n      orientation: \"vertical\",\n    },\n  }\n)\n\nfunction Field({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"field\"\n      data-orientation={orientation}\n      className={cn(fieldVariants({ orientation }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      className={cn(\n        \"group/field-content flex flex-1 flex-col gap-1.5 leading-snug\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      data-slot=\"field-label\"\n      className={cn(\n        \"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50\",\n        \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4\",\n        \"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-label\"\n      className={cn(\n        \"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        \"text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance\",\n        \"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5\",\n        \"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  children?: React.ReactNode\n}) {\n  return (\n    <div\n      data-slot=\"field-separator\"\n      data-content={!!children}\n      className={cn(\n        \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\n        className\n      )}\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"bg-background text-muted-foreground relative mx-auto block w-fit px-2\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  )\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  errors?: Array<{ message?: string } | undefined>\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children\n    }\n\n    if (!errors?.length) {\n      return null\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ]\n\n    if (uniqueErrors?.length == 1) {\n      return uniqueErrors[0]?.message\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    )\n  }, [children, errors])\n\n  if (!content) {\n    return null\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      data-slot=\"field-error\"\n      className={cn(\"text-destructive text-sm font-normal\", className)}\n      {...props}\n    >\n      {content}\n    </div>\n  )\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n}\n"
        },
        {
          "path": "components/ui/label.tsx",
          "target": "@ui/label.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Label({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  return (\n    <LabelPrimitive.Root\n      data-slot=\"label\"\n      className={cn(\n        \"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "target": "@ui/separator.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "file-input",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/file-input.tsx",
          "target": "@components/block/file-input.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport { AnimatePresence, motion } from 'motion/react';\nimport {\n  CloudUpload,\n  File,\n  Globe,\n  MapPin,\n  FileText,\n  Image as ImageIcon,\n  Trash\n} from 'lucide-react';\nimport Image from 'next/image';\nimport React, { useEffect, useRef, useState } from 'react';\n\nconst validateFile = (\n  file: File,\n  accept?: string,\n  maxSizeInMB?: number\n): { success: boolean; message: string | null } => {\n  // Validate file type if accept is specified\n  if (accept) {\n    const acceptedTypes = accept.split(',').map((type) => type.trim().toLowerCase());\n    const fileType = file.type;\n    const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();\n\n    const isValidType = acceptedTypes.some((acceptedType) => {\n      if (acceptedType.startsWith('.')) {\n        return acceptedType === fileExtension;\n      }\n      if (acceptedType.endsWith('/*')) return fileType.startsWith(acceptedType.slice(0, -1));\n      return acceptedType === fileType;\n    });\n\n    if (!isValidType) {\n      return { success: false, message: 'File type not accepted.' };\n    }\n  }\n\n  // Validate file size\n  if (maxSizeInMB) {\n    const fileSizeInMB = file.size / (1024 * 1024);\n    if (fileSizeInMB > maxSizeInMB) {\n      return { success: false, message: `File size exceeds ${maxSizeInMB}MB limit.` };\n    }\n  }\n\n  return { success: true, message: null };\n};\n\nconst formatFileSize = (bytes: number): string => {\n  if (bytes === 0) return '0 Bytes';\n  const k = 1024;\n  const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n};\n\nconst getFileType = (file: File): 'image' | 'pdf' | 'text' | 'other' => {\n  if (file.type.startsWith('image/')) return 'image';\n  if (file.type === 'application/pdf') return 'pdf';\n  if (file.type.startsWith('text/')) return 'text';\n  return 'other';\n};\n\nconst getFileIcon = (fileType: 'image' | 'pdf' | 'text' | 'other') => {\n  switch (fileType) {\n    case 'image':\n      return <ImageIcon size={24} className=\"text-white\" />;\n    case 'pdf':\n      return <FileText size={24} className=\"text-white\" />;\n    case 'text':\n      return <FileText size={24} className=\"text-white\" />;\n    default:\n      return <File size={24} className=\"text-white\" />;\n  }\n};\n\ninterface IdleProps {\n  accept?: string;\n  maxSizeInMB?: number;\n  onClick: () => void;\n  onDrop: (e: React.DragEvent) => void;\n  error: string | null;\n}\n\nconst Idle = ({ accept, maxSizeInMB, onClick, onDrop, error }: IdleProps) => {\n  const [isDragOver, setIsDragOver] = useState(false);\n\n  const handleDragOver = (e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragOver(true);\n  };\n\n  const handleDragLeave = (e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragOver(false);\n  };\n\n  const handleDrop = (e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragOver(false);\n    onDrop(e);\n  };\n\n  return (\n    <div\n      className={cn(\n        `h-full w-full bg-black overflow-hidden relative border-[3px] border-dashed rounded-lg flex flex-col items-center justify-center select-none cursor-pointer transition-colors`,\n        isDragOver ? 'border-blue-500 bg-blue-950/20' : 'border-zinc-800 hover:border-zinc-700'\n      )}\n      onDragOver={handleDragOver}\n      onDragLeave={handleDragLeave}\n      onDrop={handleDrop}\n    >\n      <CloudUpload size={40} className=\"text-blue-500\" />\n      <button type=\"button\" onClick={onClick} className=\"text-sm text-zinc-200 font-medium after:absolute after:inset-0 focus-visible:outline-none focus-visible:after:ring-2 focus-visible:after:ring-ring focus-visible:after:ring-inset focus-visible:after:rounded-lg\">Upload a file</button>\n      <p className=\"text-xs mt-2 text-zinc-400\">Click to upload or drag and drop</p>\n      {accept && (\n        <p className=\"text-xs text-zinc-400\">\n          Accepts:{' '}\n          {accept\n            ?.split(',')\n            .map((item) => `.${item.split('/').pop()?.trim()}`)\n            .join(', ')}\n        </p>\n      )}\n      {maxSizeInMB && <p className=\"text-xs text-zinc-400\">Max size: {maxSizeInMB} MB</p>}\n      {error && (\n        <motion.p\n          animate={{\n            x: [0, 2, -2, 2, -2, 0, 2, -2, 2, -2, 0]\n          }}\n          transition={{\n            duration: 0.3,\n            delay: 0.15\n          }}\n          role=\"alert\"\n          className=\"text-xs text-red-500 absolute bottom-2\"\n        >\n          {error}\n        </motion.p>\n      )}\n\n      <AnimatePresence>\n        {isDragOver && (\n          <motion.div\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.2, ease: 'easeOut' }}\n            className=\"absolute inset-0 backdrop-blur-sm bg-black/50 z-10 flex items-center justify-center flex-col rounded-xl\"\n          >\n            <MapPin size={32} className=\"text-blue-100\" />\n            <p className=\"text-sm text-blue-100 font-medium\">Drop your file here</p>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n\nconst Loading = () => {\n  const fromRef = useRef<HTMLDivElement>(null);\n  const toRef = useRef<HTMLDivElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [path, setPath] = useState<string>('');\n  const [containerSize, setContainerSize] = useState<{ width: number; height: number }>({\n    width: 0,\n    height: 0\n  });\n\n  const gradientCoordinates = {\n    x1: ['10%', '110%'],\n    x2: ['0%', '100%'],\n    y1: ['0%', '0%'],\n    y2: ['0%', '0%']\n  };\n\n  useEffect(() => {\n    const updatePath = () => {\n      if (!fromRef.current || !toRef.current || !containerRef.current) return;\n\n      const containerRect = containerRef.current.getBoundingClientRect();\n      const fromRect = fromRef.current.getBoundingClientRect();\n      const toRect = toRef.current.getBoundingClientRect();\n\n      setContainerSize({ width: containerRect.width, height: containerRect.height });\n\n      const x1 = fromRect.left - containerRect.left + fromRect.width / 2;\n      const y1 = fromRect.top - containerRect.top + fromRect.height / 2;\n      const x2 = toRect.left - containerRect.left + toRect.width / 2;\n      const y2 = toRect.top - containerRect.top + toRect.height / 2;\n\n      const controlY = x1 - 50;\n      const d = `M ${x1},${y1} Q ${(x1 + x2) / 2},${controlY} ${x2},${y2}`;\n      setPath(d);\n    };\n\n    const resizeObserver = new ResizeObserver((entries) => {\n      entries.forEach(() => {\n        updatePath();\n      });\n    });\n\n    if (containerRef.current) {\n      resizeObserver.observe(containerRef.current);\n    }\n\n    updatePath();\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, []);\n\n  return (\n    <div className=\"h-full w-full bg-black relative border-[3px] border-zinc-900 rounded-xl flex flex-col items-center justify-center gap-4\">\n      <div ref={containerRef} className=\"w-full px-10 flex items-center justify-between relative\">\n        <motion.span\n          animate={{\n            scale: [1, 1.05, 1]\n          }}\n          transition={{\n            duration: 1.5,\n            repeat: Infinity,\n            ease: 'easeInOut'\n          }}\n          ref={fromRef}\n          className=\"bg-black rounded-full border border-blue-400 p-2 z-10 h-10 w-10 flex items-center justify-center shadow-sm shadow-blue-500\"\n        >\n          <File size={20} strokeWidth={1.5} className=\"text-zinc-300\" />\n        </motion.span>\n        <motion.span\n          animate={{\n            scale: [1, 1.05, 1]\n          }}\n          transition={{\n            delay: 0.5,\n            duration: 1.5,\n            repeat: Infinity,\n            ease: 'easeInOut'\n          }}\n          ref={toRef}\n          className=\"bg-black rounded-full border border-blue-400 p-2 z-10 h-10 w-10 flex items-center justify-center shadow-sm shadow-blue-500\"\n        >\n          <Globe size={32} strokeWidth={1.5} className=\"text-zinc-300\" />\n        </motion.span>\n        <svg\n          fill=\"none\"\n          width={containerSize.width}\n          height={containerSize.height}\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className={cn('pointer-events-none absolute left-0 top-0 transform-gpu stroke-2')}\n          viewBox={`0 0 ${containerSize.width} ${containerSize.height}`}\n        >\n          <path\n            d={path}\n            stroke=\"#3F3F47\"\n            strokeWidth={1}\n            strokeOpacity={0.8}\n            strokeLinecap=\"round\"\n          />\n          <path\n            d={path}\n            strokeWidth={3}\n            stroke={`url(#${'path'})`}\n            strokeOpacity=\"1\"\n            strokeLinecap=\"round\"\n          />\n          <defs>\n            <motion.linearGradient\n              className=\"transform-gpu\"\n              id={'path'}\n              gradientUnits={'userSpaceOnUse'}\n              initial={{\n                x1: '0%',\n                x2: '0%',\n                y1: '0%',\n                y2: '0%'\n              }}\n              animate={{\n                x1: gradientCoordinates.x1,\n                x2: gradientCoordinates.x2,\n                y1: gradientCoordinates.y1,\n                y2: gradientCoordinates.y2\n              }}\n              transition={{\n                duration: 1.5,\n                ease: 'easeInOut',\n                repeat: Infinity,\n                repeatType: 'loop'\n              }}\n            >\n              <stop stopColor={'#ffffff'} stopOpacity=\"0\"></stop>\n              <stop stopColor={'#ffffff'} stopOpacity=\"0.3\"></stop>\n              <stop offset=\"15%\" stopColor={'#00bfff'} stopOpacity=\"1\"></stop>\n              <stop offset=\"30%\" stopColor={'#0080ff'} stopOpacity=\"0.8\"></stop>\n              <stop offset=\"50%\" stopColor={'#0066cc'} stopOpacity=\"0.6\"></stop>\n              <stop offset=\"70%\" stopColor={'#004499'} stopOpacity=\"0.4\"></stop>\n              <stop offset=\"85%\" stopColor={'#002266'} stopOpacity=\"0.2\"></stop>\n              <stop offset=\"100%\" stopColor={'#001133'} stopOpacity=\"0\"></stop>\n            </motion.linearGradient>\n          </defs>\n        </svg>\n      </div>\n      <p className=\"text-xs text-zinc-300 font-light\">Uploading file...</p>\n    </div>\n  );\n};\n\nconst Preview = ({ file }: { file: File }) => {\n  const [imagePreview, setImagePreview] = useState<string | null>(null);\n  const [imageLoaded, setPreviewLoaded] = useState(false);\n  const fileType = getFileType(file);\n  const previewLoaded = fileType !== 'image' || imageLoaded;\n  const fileIcon = getFileIcon(fileType);\n\n  useEffect(() => {\n    if (fileType === 'image') {\n      const reader = new FileReader();\n      reader.onload = (e) => {\n        setImagePreview(e.target?.result as string);\n      };\n      reader.readAsDataURL(file);\n      return () => {\n        reader.onload = null;\n        if (reader.readyState === FileReader.LOADING) reader.abort();\n      };\n    }\n  }, [file, fileType]);\n\n  return (\n    <motion.div\n      initial={{ opacity: 0, scale: 0.8 }}\n      animate={{ opacity: previewLoaded ? 1 : 0, scale: previewLoaded ? 1 : 0.8 }}\n      transition={{ duration: 0.3, ease: 'easeInOut', delay: 0.2 }}\n      className=\"relative w-full h-full rounded-lg overflow-hidden border border-zinc-700 flex items-center justify-center\"\n    >\n      {fileType === 'image' && imagePreview ? (\n        <Image\n          onLoad={() => setPreviewLoaded(true)}\n          src={imagePreview}\n          alt=\"File preview\"\n          fill\n          className=\"w-full h-full object-cover\"\n        />\n      ) : (\n        <div className=\"w-full h-full bg-zinc-800 flex items-center justify-center\">{fileIcon}</div>\n      )}\n    </motion.div>\n  );\n};\n\ninterface SuccessProps {\n  files: FileList;\n  onRemove: () => void;\n}\n\nconst Success = ({ files, onRemove }: SuccessProps) => {\n  return (\n    <div className=\"h-full w-full bg-black relative border-[3px] border-zinc-900 rounded-xl flex flex-col items-center justify-center gap-4 overflow-hidden\">\n      <div className=\"w-full flex-1 flex flex-col items-center justify-center gap-3 relative h-full overflow-hidden\">\n        <div className=\"absolute top-0 left-0 w-full h-full\">\n          {files.length === 1 ? (\n            <Preview file={files[0]} />\n          ) : (\n            <div className=\"h-full w-full flex items-center justify-center\">\n              {Array.from(files)\n                .slice(0, 4)\n                .map((file, index) => {\n                  return (\n                    <motion.div\n                      key={`file-upload-preview-${index}-${file.name}`}\n                      style={{\n                        rotateZ: files.length <= 2 ? index * 15 : -(15 - index * 15),\n                        x: files.length <= 2 ? index * 10 : index * 15 - 15,\n                        y: index * 10\n                      }}\n                      className=\"h-3/4 w-3/4 absolute\"\n                    >\n                      <Preview file={file} />\n                    </motion.div>\n                  );\n                })}\n            </div>\n          )}\n        </div>\n        <div\n          style={{\n            // background: 'black',\n            backdropFilter: 'blur(10px)',\n            WebkitBackdropFilter: 'blur(10px)',\n            maskImage: 'linear-gradient(to bottom, transparent, black 80%)',\n            WebkitMaskImage: 'linear-gradient(to bottom, transparent, black 80%)'\n          }}\n          className=\"absolute inset-0\"\n        />\n\n        <div className=\"absolute inset-0 flex flex-col items-start justify-end p-3 bg-black/20\">\n          <div className=\"flex items-end justify-between w-full\">\n            <div className=\"flex flex-col items-start justify-end flex-1\">\n              <p className=\"text-sm text-white font-medium line-clamp-1\">\n                {files.length > 1 ? `${files.length} files` : files[0].name}\n              </p>\n              <p className=\"text-xs text-white line-clamp-1\">\n                {formatFileSize(Array.from(files).reduce((acc, file) => acc + file.size, 0))}\n              </p>\n            </div>\n            <button\n              type=\"button\"\n              aria-label=\"Remove uploaded files\"\n              onClick={onRemove}\n              className=\"p-2 rounded-full hover:bg-white/5 group\"\n            >\n              <Trash size={16} className=\"group-hover:text-red-600/90\" />\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\ninterface FileInputProps {\n  accept?: string; // e.g. 'image/png, image/jpeg, application/pdf\n  maxSizeInMB?: number;\n  onFileChange?: (files: FileList) => void | Promise<void>;\n  allowMultiple?: boolean;\n}\n\nconst FileInput = ({\n  accept = 'image/png, image/jpeg, application/pdf',\n  maxSizeInMB = 10,\n  onFileChange,\n  allowMultiple = false\n}: FileInputProps) => {\n  const [state, setState] = useState<'idle' | 'loading' | 'success'>('idle');\n  const [error, setError] = useState<string | null>(null);\n  const [files, setFiles] = useState<FileList | null>(null);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const pending = useRef(false);\n\n  const handleFileInputClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  const processFiles = async (files: FileList | null) => {\n    if (pending.current || !files?.length) return;\n    setError(null);\n    if (!allowMultiple && files.length > 1) {\n      setError('Choose only one file.');\n      return;\n    }\n\n    for (let i = 0; i < files.length; i++) {\n      const file = files[i];\n      const { success, message } = validateFile(file, accept, maxSizeInMB);\n      if (!success) {\n        setError(message || 'Something went wrong.');\n        return;\n      }\n    }\n\n    pending.current = true;\n    setState('loading');\n    try {\n      await onFileChange?.(files);\n    } catch (error) {\n      console.error(error);\n      setState('idle');\n      setError('Something went wrong.');\n      return;\n    } finally {\n      pending.current = false;\n    }\n    setFiles(files);\n    setState('success');\n  };\n\n  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    void processFiles(event.target.files);\n    event.target.value = '';\n  };\n\n  const handleDrop = (event: React.DragEvent) => {\n    void processFiles(event.dataTransfer.files);\n  };\n\n  const handleRemove = () => {\n    setFiles(null);\n    if (fileInputRef.current) {\n      fileInputRef.current.value = '';\n    }\n    setState('idle');\n  };\n\n  return (\n    <div className=\"w-[300px] h-[200px] relative overflow-hidden\">\n      <input\n        type=\"file\"\n        hidden\n        ref={fileInputRef}\n        className=\"hidden\"\n        accept={accept}\n        onChange={onChange}\n        multiple={allowMultiple}\n      />\n      <AnimatePresence mode=\"popLayout\" initial={false}>\n        <motion.div\n          key={state}\n          className=\"h-full w-full overflow-hidden rounded-xl\"\n          initial={{\n            y: state === 'loading' ? '-100%' : '100%',\n            filter: 'blur(10px)',\n            scale: 1,\n            borderRadius: '100%'\n          }}\n          animate={{ y: 0, filter: 'blur(0px)', scale: 1, borderRadius: '0%' }}\n          exit={{\n            y: ['loading', 'success'].includes(state) ? '-100%' : '100%',\n            filter: 'blur(10px)',\n            scale: 0.7,\n            borderRadius: '100%'\n          }}\n          transition={{\n            type: 'spring',\n            duration: 0.4,\n            bounce: 0\n          }}\n        >\n          {state === 'idle' && (\n            <Idle\n              accept={accept}\n              maxSizeInMB={maxSizeInMB}\n              onClick={handleFileInputClick}\n              onDrop={handleDrop}\n              error={error}\n            />\n          )}\n          {state === 'loading' && <Loading />}\n          {state === 'success' && files && <Success files={files} onRemove={handleRemove} />}\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  );\n};\n\nexport default FileInput;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "flip-scroll",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/flip-scroll.tsx",
          "target": "@components/block/flip-scroll.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React, { useRef } from 'react';\nimport Image from 'next/image';\nimport { motion, MotionValue, useScroll, useTransform } from 'motion/react';\n\nconst BackFace = () => (\n    <div\n        className=\"border border-white/15 h-[500px] w-full absolute rounded-3xl top-0 left-0 flex items-center justify-center bg-black text-white font-bold text-7xl\"\n        style={{ transform: 'rotateY(180deg) translateZ(-1px)', backfaceVisibility: 'hidden' }}\n    >\n        ?\n    </div>\n);\n\nconst FlipScrollItem = ({\n    image,\n    index,\n    isLeft,\n    scrollYProgress,\n    totalItems,\n    mode\n}: {\n    image: string;\n    index: number;\n    isLeft: boolean;\n    scrollYProgress: MotionValue<number>;\n    totalItems: number;\n    mode: 'alternate' | 'normal';\n}) => {\n    const direction = isLeft ? -1 : 1;\n    const position = index / totalItems;\n\n    const translateX = useTransform(\n        scrollYProgress,\n        [0, position, 1],\n        [-index * 400 * direction, 0, (totalItems - index) * 400 * direction]\n    );\n\n    const normalRotateY = useTransform(\n        scrollYProgress,\n        [0, position - 0.04, position - 0.015, position + 0.015, position + 0.04, 1],\n        [180, 180, 0, 0, -180, -180]\n    );\n    const alternateRotateY = useTransform(\n        scrollYProgress,\n        [0, position, 1],\n        [index * -180 * direction, 0, (totalItems - index) * 180 * direction]\n    );\n\n    return (\n        <motion.div\n            style={{ translateX, perspective: 1000, transformStyle: 'preserve-3d' }}\n            className=\"h-[500px] max-w-sm w-full absolute rounded-3xl overflow-visible\"\n        >\n            <motion.div\n                style={{ rotateY: mode === 'normal' ? normalRotateY : alternateRotateY, transformStyle: 'preserve-3d' }}\n                className=\"relative\"\n            >\n                <Image\n                    src={image}\n                    alt={image}\n                    width={1000}\n                    height={1000}\n                    style={{ backfaceVisibility: 'hidden', transform: 'translateZ(1px)' }}\n                    className=\"object-cover h-[500px] w-full rounded-3xl absolute top-0 left-0\"\n                />\n                <BackFace />\n            </motion.div>\n        </motion.div>\n    );\n};\n\ninterface FlipScrollProps {\n    items: { image: string }[];\n    mode?: 'alternate' | 'normal';\n}\n\nexport function FlipScroll({ items, mode = 'normal' }: FlipScrollProps) {\n    const ref = useRef<HTMLDivElement>(null);\n\n    const { scrollYProgress } = useScroll({\n        container: ref,\n        offset: ['start start', 'end end']\n    });\n\n    return (\n        <div ref={ref} className=\"h-full w-full overflow-y-auto relative\" style={{ minHeight: '400px' }}>\n            <div className=\"h-full w-full absolute top-0 left-0\">\n                <div className=\"w-full\" style={{ height: (items.length - 3) * 500 }} />\n            </div>\n            <div className=\"grid grid-cols-1 h-full w-full sticky top-0 left-0\">\n                <div className=\"flex flex-col h-full justify-center w-full items-center relative\">\n                    {items.map((image, index) => (\n                        <FlipScrollItem\n                            key={`flip-scroll-item-${index}`}\n                            image={image.image}\n                            index={index}\n                            isLeft={true}\n                            scrollYProgress={scrollYProgress}\n                            totalItems={items.length}\n                            mode={mode}\n                        />\n                    ))}\n                </div>\n            </div>\n        </div>\n    );\n}\n\nexport default FlipScroll;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "flip-text",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/flip-text.tsx",
          "target": "@components/block/flip-text.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface FlipTextProps {\n    /**\n     * Additional CSS classes for the wrapper\n     */\n    className?: string;\n\n    /**\n     * The text content to animate (will be split by spaces)\n     */\n    children: string;\n\n    /**\n     * Duration of the flip animation in seconds\n     * @default 2.2\n     */\n    duration?: number;\n\n    /**\n     * Initial delay before animation starts in seconds\n     * @default 0\n     */\n    delay?: number;\n\n    /**\n     * Whether the animation should loop infinitely\n     * @default true\n     */\n    loop?: boolean;\n\n    /**\n     * Custom separator for splitting text (default is space)\n     * @default \" \"\n     */\n    separator?: string;\n\n    /**\n     * Whether all characters should animate together (no stagger)\n     * @default false\n     */\n    together?: boolean;\n}\n\nexport function FlipText({\n    className,\n    children,\n    duration = 2.2,\n    delay = 0,\n    loop = true,\n    separator = \" \",\n    together = false,\n}: FlipTextProps) {\n    const words = useMemo(() => children.split(separator), [children, separator]);\n    const totalChars = children.length;\n\n    // Calculate character index for each position\n    const getCharIndex = (wordIndex: number, charIndex: number) => {\n        let index = 0;\n        for (let i = 0; i < wordIndex; i++) {\n            index += words[i].length + (separator === \" \" ? 1 : separator.length);\n        }\n        return index + charIndex;\n    };\n\n    return (\n        <div\n            className={cn(\n                \"flip-text-wrapper inline-block leading-none\",\n                className\n            )}\n            style={{ perspective: \"1000px\" }}\n        >\n            {words.map((word, wordIndex) => {\n                const chars = word.split(\"\");\n\n                return (\n                    <span\n                        key={wordIndex}\n                        className=\"word inline-block whitespace-nowrap\"\n                        style={{ transformStyle: \"preserve-3d\" }}\n                    >\n                        {chars.map((char, charIndex) => {\n                            const currentGlobalIndex = getCharIndex(wordIndex, charIndex);\n\n                            // Calculate delay - if together, use same delay for all\n                            let calculatedDelay = delay;\n                            if (!together) {\n                                const normalizedIndex = currentGlobalIndex / totalChars;\n                                const sineValue = Math.sin(normalizedIndex * (Math.PI / 2));\n                                calculatedDelay = sineValue * (duration * 0.25) + delay;\n                            }\n\n                            return (\n                                <span\n                                    key={charIndex}\n                                    className=\"flip-char inline-block relative\"\n                                    data-char={char}\n                                    style={\n                                        {\n                                            \"--flip-duration\": `${duration}s`,\n                                            \"--flip-delay\": `${calculatedDelay}s`,\n                                            \"--flip-iteration\": loop ? \"infinite\" : \"1\",\n                                            transformStyle: \"preserve-3d\",\n                                        } as React.CSSProperties\n                                    }\n                                >\n                                    {char}\n                                </span>\n                            );\n                        })}\n                        {separator === \" \" && wordIndex < words.length - 1 && (\n                            <span className=\"whitespace inline-block\">&nbsp;</span>\n                        )}\n                        {separator !== \" \" && wordIndex < words.length - 1 && (\n                            <span className=\"separator inline-block\">{separator}</span>\n                        )}\n                    </span>\n                );\n            })}\n        </div>\n    );\n}\n\nexport default FlipText;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "flow-scroll",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/flow-scroll.tsx",
          "target": "@components/block/flow-scroll.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { motion, MotionValue, useScroll, useTransform } from 'motion/react';\nimport Image from 'next/image';\nimport React, { useRef } from 'react';\n\ninterface FlowScrollCardProps {\n    image: string;\n    index: number;\n    scrollYProgress: MotionValue<number>;\n    totalItems: number;\n}\n\nconst FlowScrollCard = ({ image, index, scrollYProgress, totalItems }: FlowScrollCardProps) => {\n    const ITEMS_PER_ROW = 3;\n    const prev = Math.max(0, index - ITEMS_PER_ROW);\n    const next = Math.min(totalItems - 1, index + ITEMS_PER_ROW);\n\n    const previousRow = Math.floor(prev / ITEMS_PER_ROW);\n    const currentRow = Math.floor(index / ITEMS_PER_ROW);\n    const nextRow = Math.floor(next / ITEMS_PER_ROW);\n    const totalRows = Math.floor(totalItems / ITEMS_PER_ROW);\n    const scrollRangePerRow = 1 / totalRows;\n\n    const entryAnimation = previousRow / totalRows - scrollRangePerRow;\n    const currPosition = currentRow / totalRows;\n    const holdAnimationStart = currPosition;\n    const holdAnimationEnd = currPosition;\n    const exitAnimation = nextRow / totalRows + scrollRangePerRow * 2;\n\n    const offsetToAdd = (scrollRangePerRow / totalItems) * (currentRow + 2);\n    const range = [0, entryAnimation - offsetToAdd, holdAnimationStart - offsetToAdd, holdAnimationEnd - offsetToAdd, exitAnimation - offsetToAdd, 1];\n\n    const scale = useTransform(scrollYProgress, range, [0.5, 0.5, 1, 1, 0.5, 0.5]);\n    const isLeft = index % ITEMS_PER_ROW === 0;\n    const isRight = index % ITEMS_PER_ROW === 2;\n    const xTransform = useTransform(scrollYProgress, range, [\n        isLeft ? '100%' : isRight ? '-100%' : '0%',\n        isLeft ? '100%' : isRight ? '-100%' : '0%',\n        '0%', '0%', '0%', '0%'\n    ]);\n    const rotate = useTransform(scrollYProgress, range, [isLeft ? -20 : isRight ? 20 : 0, isLeft ? -20 : isRight ? 20 : 0, 0, 0, 0, 0]);\n    const shadowY = useTransform(scrollYProgress, range, [50, 50, 25, 25, -50, -50]);\n\n    return (\n        <motion.div\n            style={{\n                scale,\n                x: xTransform,\n                rotate,\n                zIndex: !isLeft && !isRight ? 1 : 0,\n                boxShadow: useTransform(shadowY, (value) => `0px ${value}px 40px 10px rgba(0, 0, 0, 0.1)`)\n            }}\n            className=\"w-full sm:max-w-48 md:max-w-60 h-32 sm:h-60 md:h-72 overflow-hidden rounded-2xl\"\n        >\n            <Image src={image} alt={image} width={1000} height={1000} className=\"h-full w-full object-cover\" />\n        </motion.div>\n    );\n};\n\ninterface FlowScrollProps {\n    images: string[];\n}\n\nexport function FlowScroll({ images }: FlowScrollProps) {\n    const ref = useRef<HTMLDivElement>(null);\n    const { scrollYProgress } = useScroll({ container: ref, offset: ['start start', 'end end'] });\n\n    return (\n        <div ref={ref} className=\"w-full h-full overflow-y-auto flex justify-center py-36 pb-96\">\n            <div className=\"grid grid-cols-3 gap-4 md:gap-6 lg:gap-12 h-max\">\n                {images.map((image, index) => (\n                    <FlowScrollCard key={`flow-scroll-card-${index}`} image={image} index={index} scrollYProgress={scrollYProgress} totalItems={images.length} />\n                ))}\n            </div>\n        </div>\n    );\n}\n\nexport default FlowScroll;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "folder-preview",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/folder-preview.tsx",
          "target": "@components/block/folder-preview.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\n// ============================================\n// SVG Icon Components\n// ============================================\n\nconst FolderBackIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 20 16\" className={cn(\"w-full h-full fill-current\", className)}>\n        <path d=\"M7.5,0C7.4,0,2,0,2,0C0.9,0,0,0.9,0,2l0,12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V4c0-1.1-0.9-2-2-2c0,0-7.5,0-8,0C9,2,9.9,0,7.5,0z\" />\n    </svg>\n);\n\nconst FolderCoverIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 20 16\" className={cn(\"w-full h-full fill-current\", className)}>\n        <path d=\"M2,2h16c1.1,0,2,0.9,2,2v10c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2V4C0,2.9,0.9,2,2,2z\" />\n    </svg>\n);\n\nconst UsersIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 24 24\" className={cn(\"w-full h-full fill-current\", className)}>\n        <path\n            opacity=\"0.3\"\n            d=\"M22.2,17.7l-4-2c-0.5-0.3-0.8-0.8-0.8-1.3v-1.6c0.1-0.1,0.2-0.3,0.4-0.5c0.5-0.8,0.9-1.6,1.2-2.5c0.5-0.2,0.9-0.6,0.9-1.2V7c0-0.4-0.2-0.7-0.4-0.9V3.7c0,0,0.5-3.7-4.6-3.7c-5,0-4.6,3.7-4.6,3.7v2.4C10.1,6.3,9.9,6.7,9.9,7v1.7c0,0.4,0.2,0.8,0.6,1c0.4,1.8,1.5,3.1,1.5,3.1v1.5c0,0.6-0.3,1.1-0.8,1.3l-3.7,2c-1.1,0.6-1.7,1.7-1.7,2.9v1.3H24v-1.3C24,19.4,23.3,18.3,22.2,17.7z\"\n        />\n        <path\n            opacity=\"0.5\"\n            d=\"M7.5,17.7l2.5-1.3c0,0,0,0,0,0l1.2-0.7c0.5-0.3,0.8-0.8,0.8-1.3v-1.5c0,0-0.4-0.5-0.9-1.4l0,0c0,0,0,0,0,0c-0.1-0.1-0.1-0.2-0.2-0.3c0,0,0,0,0-0.1c-0.1-0.1-0.1-0.3-0.2-0.4c0,0,0,0,0,0c0-0.1-0.1-0.2-0.1-0.4c0,0,0-0.1,0-0.1c0-0.1-0.1-0.3-0.1-0.4c-0.3-0.2-0.6-0.6-0.6-1V7c0-0.4,0.2-0.7,0.4-0.9V3.8C9.8,3.3,8.9,2.9,7.4,2.9c-4,0-4.1,3.3-4.1,3.3v2.1C3.1,8.5,2.9,8.8,2.9,9.1v1.4c0,0.4,0.2,0.7,0.5,0.9c0.4,1.6,1.6,2.7,1.6,2.7v1.3c0,0.5-0.3,0.9-0.7,1.1l-2.8,1.7C0.6,18.8,0,19.7,0,20.8v1.2h5.8v-1.3C5.8,19.4,6.5,18.3,7.5,17.7z\"\n        />\n    </svg>\n);\n\nconst GlobeIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 24 24\" className={cn(\"w-full h-full fill-current\", className)}>\n        <circle cx=\"12\" cy=\"12\" r=\"10\" opacity=\"0.3\" />\n        <path d=\"M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S16.4,20,12,20z\" />\n    </svg>\n);\n\nconst PadlockIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 24 33.6\" className={cn(\"w-full h-full fill-current\", className)}>\n        <path d=\"M23,13.5h-1.7V9.4C21.4,4.2,17.2,0,12,0C6.8,0,2.6,4.2,2.6,9.4v4.1H1c-0.5,0-1,0.4-1,1v18.2c0,0.5,0.4,1,1,1H23c0.5,0,1-0.4,1-1V14.4C24,13.9,23.6,13.5,23,13.5z M13.5,24.5v3.9c0,0.3-0.3,0.6-0.6,0.6h-1.8c-0.3,0-0.6-0.3-0.6-0.6v-3.9c-0.7-0.5-1.1-1.3-1.1-2.1c0-1.4,1.2-2.6,2.6-2.6c1.4,0,2.6,1.2,2.6,2.6C14.6,23.3,14.2,24.1,13.5,24.5z M16.9,13.5H7.1V9.4c0-2.7,2.2-4.9,4.9-4.9c2.7,0,4.9,2.2,4.9,4.9V13.5z\" />\n    </svg>\n);\n\nconst CloudIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 24 22.2\" className={cn(\"w-full h-full fill-current\", className)}>\n        <path d=\"M19.5,5.8c-0.3-1.5-1-2.9-2.2-4c-1.3-1.2-3-1.8-4.7-1.8C11.3,0,10,0.4,8.9,1.1C8,1.7,7.2,2.5,6.6,3.5c-0.2,0-0.5-0.1-0.7-0.1c-2.1,0-3.8,1.7-3.8,3.8c0,0.3,0,0.5,0.1,0.8C0.8,9,0,10.6,0,12.3C0,13.6,0.5,15,1.4,16c1,1.1,2.2,1.7,3.6,1.8c0,0,0,0,0,0h4.2c0.4,0,0.7-0.3,0.7-0.7s-0.3-0.7-0.7-0.7H5c-2-0.1-3.7-2-3.7-4.2c0-1.4,0.8-2.7,2-3.4c0.3-0.2,0.4-0.5,0.3-0.8C3.5,7.8,3.4,7.5,3.4,7.2c0-1.4,1.1-2.5,2.5-2.5c0.3,0,0.6,0,0.8,0.1c0.3,0.1,0.7,0,0.8-0.3c0.9-2,2.9-3.2,5.1-3.2c2.9,0,5.3,2.2,5.6,5.1c0,0.3,0.3,0.5,0.6,0.6c2.2,0.4,3.9,2.4,3.9,4.7c0,2.5-1.9,4.6-4.3,4.8h-3.6c-0.4,0-0.7,0.3-0.7,0.7s0.3,0.7,0.7,0.7h3.7c0,0,0,0,0,0c1.5-0.1,2.9-0.8,4-2c1-1.1,1.6-2.6,1.6-4.1C24,8.9,22.1,6.5,19.5,5.8z M16,12.9c0.3-0.3,0.3-0.7,0-0.9l-3.5-3.5c-0.1-0.1-0.3-0.2-0.5-0.2c-0.2,0-0.3,0.1-0.5,0.2L8,12c-0.3,0.3-0.3,0.7,0,0.9c0.1,0.1,0.3,0.2,0.5,0.2c0.2,0,0.3-0.1,0.5-0.2l2.4-2.4v11c0,0.4,0.3,0.7,0.7,0.7s0.7-0.3,0.7-0.7v-11l2.4,2.4C15.3,13.2,15.7,13.2,16,12.9z\" />\n    </svg>\n);\n\nconst FileIcon = ({ className }: { className?: string }) => (\n    <svg viewBox=\"0 0 20 26.8\" className={cn(\"w-full h-full fill-current\", className)}>\n        <path d=\"M2.3,0C1,0,0,1,0,2.3v22.2c0,1.2,1,2.3,2.3,2.3h15.4c1.2,0,2.3-1,2.3-2.3V6l-6-6H2.3z\" />\n        <path opacity=\"0.1\" d=\"M13.9,3.7V0l6,6h-3.7C14.9,6,13.9,5,13.9,3.7z\" />\n    </svg>\n);\n\n// ============================================\n// Types & Interfaces\n// ============================================\n\nexport type FolderVariant =\n    | \"devi\"\n    | \"rudras\"\n    | \"ardra\"\n    | \"shakti\"\n    | \"kubera\"\n    | \"hari\"\n    | \"ravi\"\n    | \"durga\"\n    | \"nandi\";\n\nexport interface FolderPreviewProps {\n    variant?: FolderVariant;\n    images?: string[];\n    files?: { name: string; type?: \"txt\" | \"gif\" | \"mp3\" | \"default\" }[];\n    label?: string;\n    size?: \"sm\" | \"md\" | \"lg\";\n    className?: string;\n    onClick?: () => void;\n}\n\n// ============================================\n// Color Schemes for Each Variant\n// ============================================\n\nconst variantColors: Record<\n    FolderVariant,\n    {\n        back: string;\n        cover: string;\n        deco: string;\n        caption: string;\n        bg: string;\n    }\n> = {\n    devi: {\n        back: \"text-gray-500\",\n        cover: \"text-gray-400\",\n        deco: \"text-gray-400 brightness-125\",\n        caption: \"text-gray-800 dark:text-gray-200\",\n        bg: \"bg-gray-100 dark:bg-gray-900\",\n    },\n    rudras: {\n        back: \"text-gray-700 dark:text-gray-600\",\n        cover: \"text-gray-600 dark:text-gray-500\",\n        deco: \"text-gray-400\",\n        caption: \"text-blue-600 dark:text-blue-400\",\n        bg: \"bg-slate-200 dark:bg-slate-800\",\n    },\n    ardra: {\n        back: \"text-blue-800 dark:text-blue-700\",\n        cover: \"text-blue-600 dark:text-blue-500\",\n        deco: \"text-blue-700 dark:text-blue-600\",\n        caption: \"text-blue-500 dark:text-blue-400\",\n        bg: \"bg-gray-800 dark:bg-gray-950\",\n    },\n    shakti: {\n        back: \"text-indigo-800\",\n        cover: \"text-indigo-700\",\n        deco: \"text-indigo-800\",\n        caption: \"text-green-400\",\n        bg: \"bg-blue-600 dark:bg-blue-800\",\n    },\n    kubera: {\n        back: \"text-gray-900\",\n        cover: \"text-gray-700\",\n        deco: \"text-gray-600\",\n        caption: \"text-gray-900 dark:text-gray-100\",\n        bg: \"bg-emerald-400 dark:bg-emerald-600\",\n    },\n    hari: {\n        back: \"text-blue-800\",\n        cover: \"text-blue-700\",\n        deco: \"text-blue-800\",\n        caption: \"text-yellow-400\",\n        bg: \"bg-sky-500 dark:bg-sky-700\",\n    },\n    ravi: {\n        back: \"text-gray-900\",\n        cover: \"text-gray-700\",\n        deco: \"text-black dark:text-white\",\n        caption: \"text-gray-900 dark:text-gray-100\",\n        bg: \"bg-gray-200 dark:bg-gray-800\",\n    },\n    durga: {\n        back: \"text-green-600\",\n        cover: \"text-green-500\",\n        deco: \"text-green-600\",\n        caption: \"text-green-400 font-mono\",\n        bg: \"bg-gray-900 dark:bg-black\",\n    },\n    nandi: {\n        back: \"text-amber-500\",\n        cover: \"text-amber-400\",\n        deco: \"text-amber-500\",\n        caption: \"text-gray-900 dark:text-gray-100\",\n        bg: \"bg-green-100 dark:bg-green-950\",\n    },\n};\n\n// ============================================\n// Size Configuration\n// ============================================\n\nconst sizeConfig = {\n    sm: {\n        folder: \"w-16\",\n        thumb: \"w-10 h-10\",\n        deco: \"w-4 h-4\",\n        caption: \"text-xs\",\n    },\n    md: {\n        folder: \"w-24\",\n        thumb: \"w-14 h-14\",\n        deco: \"w-6 h-6\",\n        caption: \"text-sm\",\n    },\n    lg: {\n        folder: \"w-32\",\n        thumb: \"w-20 h-20\",\n        deco: \"w-8 h-8\",\n        caption: \"text-base\",\n    },\n};\n\n// ============================================\n// Animation Variants\n// ============================================\n\nconst createCircularPositions = (count: number, radius: number = 120) => {\n    return Array.from({ length: count }, (_, i) => {\n        const startAngle = Math.PI / count;\n        const angle = startAngle / 2 + startAngle * i;\n        return {\n            x: Math.round(radius * Math.cos(angle)),\n            y: Math.round(-radius * Math.sin(angle)),\n        };\n    });\n};\n\n// ============================================\n// Individual Folder Components for each Variant\n// ============================================\n\nconst DeviFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.devi;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    const positions = createCircularPositions(images.length, 80);\n\n    return (\n        <div className=\"relative\">\n            {/* Previews - positioned from folder center */}\n            <div className=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n                {images.map((img, i) => (\n                    <motion.img\n                        key={i}\n                        src={img}\n                        alt=\"\"\n                        className=\"absolute w-12 h-12 object-cover rounded-full border-2 border-white shadow-md\"\n                        initial={{ opacity: 0, scale: 0.7, x: 0, y: 0 }}\n                        animate={\n                            isHovered\n                                ? {\n                                    opacity: 1,\n                                    scale: 1,\n                                    x: positions[i]?.x || 0,\n                                    y: positions[i]?.y || 0,\n                                }\n                                : { opacity: 0, scale: 0.7, x: 0, y: 0 }\n                        }\n                        transition={{\n                            duration: 0.6,\n                            delay: (images.length - i - 1) * 0.04,\n                            ease: [0.2, 1, 0.3, 1],\n                        }}\n                    />\n                ))}\n            </div>\n\n            {/* Folder */}\n            <div className=\"relative cursor-pointer aspect-[20/16]\" style={{ perspective: \"800px\" }}>\n                {/* Back */}\n                <div className={cn(\"absolute inset-0 transition-colors duration-150\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\n                        \"relative transition-colors duration-150\",\n                        isHovered ? \"text-gray-600\" : colors.cover\n                    )}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <UsersIcon />\n                    </div>\n                </motion.div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\n\nconst RudrasFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.rudras;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    const positions = createCircularPositions(images.length, 80);\n\n    return (\n        <div className=\"relative\">\n            {/* Previews - positioned from folder center */}\n            <div className=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n                {images.map((img, i) => (\n                    <motion.img\n                        key={i}\n                        src={img}\n                        alt=\"\"\n                        className=\"absolute w-12 h-12 object-cover rounded-full border-2 border-white shadow-md\"\n                        initial={{ opacity: 0, scale: 0, x: 0, y: 0 }}\n                        animate={\n                            isHovered\n                                ? {\n                                    opacity: 1,\n                                    scale: 1,\n                                    x: -positions[i]?.x || 0,\n                                    y: positions[i]?.y || 0,\n                                }\n                                : { opacity: 0, scale: 0, x: 0, y: 0 }\n                        }\n                        transition={{\n                            duration: 0.8,\n                            delay: (images.length - i - 1) * 0.08,\n                            type: \"spring\",\n                            stiffness: 200,\n                            damping: 15,\n                        }}\n                    />\n                ))}\n            </div>\n\n            {/* Folder */}\n            <div className=\"relative cursor-pointer aspect-[20/16]\" style={{ perspective: \"800px\" }}>\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Paper Sheet Deco */}\n                <div className=\"absolute bottom-0.5 left-0.5 right-0.5 h-3/4 bg-white dark:bg-gray-200 rounded-lg\" />\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\"relative\", colors.cover)}\n                    style={{ transformOrigin: \"50% 100%\", transformStyle: \"preserve-3d\" }}\n                    animate={isHovered ? { rotateX: -30 } : { rotateX: 0 }}\n                    transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <UsersIcon />\n                    </div>\n                </motion.div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\n\nconst ArdraFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.ardra;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    const [randomPositions] = React.useState(() =>\n        images.map((_, i) => {\n            const radius = 60 + Math.random() * 20;\n            const angle = (2 * (i + 1) * Math.PI) / images.length;\n            return {\n                x: Math.round(radius * Math.cos(angle)),\n                y: Math.round(radius * Math.sin(angle)),\n                rotate: Math.random() * 6 - 3,\n            };\n        })\n    );\n\n    return (\n        <div className=\"relative\">\n            {/* Feedback Circle */}\n            <motion.div\n                className=\"absolute inset-0 flex items-center justify-center pointer-events-none\"\n                initial={{ opacity: 0 }}\n                animate={isHovered ? { opacity: 1 } : { opacity: 0 }}\n            >\n                <motion.div\n                    className=\"w-10 h-10 rounded-full bg-gray-900/50\"\n                    initial={{ scale: 1 }}\n                    animate={\n                        isHovered ? { opacity: [1, 0], scale: [1, 6] } : { opacity: 0, scale: 1 }\n                    }\n                    transition={{ duration: 0.9, ease: [0.1, 1, 0.3, 1] }}\n                />\n            </motion.div>\n\n            {/* Previews - positioned from folder center */}\n            <div className=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n                {images.map((img, i) => (\n                    <motion.img\n                        key={i}\n                        src={img}\n                        alt=\"\"\n                        className=\"absolute w-10 h-10 object-cover rounded-full border-2 border-white shadow-lg\"\n                        initial={{ opacity: 0, scale: 0.4, x: 0, y: 0, rotate: 0 }}\n                        animate={\n                            isHovered\n                                ? {\n                                    opacity: 1,\n                                    scale: 1,\n                                    x: randomPositions[i]?.x || 0,\n                                    y: randomPositions[i]?.y || 0,\n                                    rotate: randomPositions[i]?.rotate || 0,\n                                }\n                                : { opacity: 0, scale: 0.4, x: 0, y: 0, rotate: 0 }\n                        }\n                        transition={{ duration: 0.5, ease: [0.1, 1, 0.3, 1] }}\n                    />\n                ))}\n            </div>\n\n            {/* Folder */}\n            <motion.div\n                className=\"relative cursor-pointer aspect-[20/16]\"\n                animate={isHovered ? { scale: 0.85 } : { scale: 1 }}\n                transition={{ duration: 0.5, ease: [0.1, 1, 0.3, 1] }}\n            >\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Cover */}\n                <div className={cn(\"relative\", colors.cover)}>\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <GlobeIcon />\n                    </div>\n                </div>\n            </motion.div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\nconst ShaktiFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.shakti;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    return (\n        <motion.div\n            className=\"relative\"\n            animate={isHovered ? { y: 15 } : { y: 0 }}\n            transition={{ duration: 0.4, ease: [0.2, 1, 0.3, 1] }}\n        >\n            <div className=\"relative cursor-pointer\">\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Previews - Fan animation */}\n                <div className={cn(\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\", sizes.thumb)}>\n                    {images.map((img, i) => (\n                        <motion.img\n                            key={i}\n                            src={img}\n                            alt=\"\"\n                            className=\"absolute w-12 h-16 object-cover rounded shadow-lg origin-[-600%_50%]\"\n                            initial={{ opacity: 0, rotate: 0 }}\n                            animate={\n                                isHovered\n                                    ? { opacity: 1, rotate: -10 * (images.length - i - 1) - 15 }\n                                    : { opacity: 0, rotate: 0 }\n                            }\n                            transition={{\n                                duration: 0.5,\n                                delay: i * 0.08,\n                                ease: [0.1, 1, 0.3, 1],\n                            }}\n                        />\n                    ))}\n                </div>\n\n                {/* Cover */}\n                <div className={cn(\"relative\", colors.cover)}>\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 mt-1\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <PadlockIcon />\n                    </div>\n                </div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </motion.div>\n    );\n};\n\nconst KuberaFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.kubera;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    return (\n        <div className=\"relative\">\n            <div className=\"relative cursor-pointer\" style={{ perspective: \"800px\" }}>\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Paper Sheet Deco */}\n                <div className=\"absolute bottom-0.5 left-0.5 right-0.5 h-3/4 bg-white dark:bg-gray-200 rounded-lg\" />\n\n                {/* Floating Previews */}\n                <div className={cn(\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\", sizes.thumb)}>\n                    {images.map((img, i) => (\n                        <motion.img\n                            key={i}\n                            src={img}\n                            alt=\"\"\n                            className=\"absolute w-full h-full object-cover rounded-lg shadow-lg\"\n                            initial={{ opacity: 0 }}\n                            animate={\n                                isHovered\n                                    ? {\n                                        opacity: [1, 0],\n                                        y: [0, -200 - (i * 17 % 50)],\n                                        x: (i * 23 % 50) - 25,\n                                        rotate: (i * 13 % 40) - 20,\n                                    }\n                                    : { opacity: 0 }\n                            }\n                            transition={{\n                                duration: 0.4,\n                                delay: i * 0.3,\n                                repeat: isHovered ? Infinity : 0,\n                                ease: \"linear\",\n                            }}\n                        />\n                    ))}\n                </div>\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\"relative\", colors.cover)}\n                    style={{ transformOrigin: \"50% 100%\", transformStyle: \"preserve-3d\" }}\n                    animate={isHovered ? { rotateX: -40 } : { rotateX: 0 }}\n                    transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 mt-1\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <CloudIcon />\n                    </div>\n                </motion.div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\nconst HariFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.hari;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    const positions = createCircularPositions(images.length, 120);\n\n    return (\n        <div className=\"relative\">\n            {/* Feedback Circle */}\n            <motion.div\n                className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-14 h-14 rounded-full bg-sky-500\"\n                initial={{ opacity: 0, scale: 1 }}\n                animate={\n                    isHovered ? { opacity: [1, 0], scale: [1, 15] } : { opacity: 0, scale: 1 }\n                }\n                transition={{ duration: 1.1, delay: 0.2, ease: [0.1, 1, 0.3, 1] }}\n            />\n\n            {/* Jumping Previews */}\n            <div className={cn(\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\", sizes.thumb)}>\n                {images.map((img, i) => (\n                    <motion.img\n                        key={i}\n                        src={img}\n                        alt=\"\"\n                        className=\"absolute w-12 h-16 object-cover rounded shadow-lg\"\n                        initial={{ opacity: 0, scale: 0.5, x: 0, y: 0 }}\n                        animate={\n                            isHovered\n                                ? {\n                                    opacity: 1,\n                                    scale: 1,\n                                    x: -positions[i]?.x || 0,\n                                    y: -positions[i]?.y || 0,\n                                }\n                                : { opacity: 0, scale: 0.5, x: 0, y: 0 }\n                        }\n                        transition={{\n                            duration: 0.8,\n                            delay: 0.2,\n                            type: \"spring\",\n                            stiffness: 200,\n                            damping: 15,\n                        }}\n                    />\n                ))}\n            </div>\n\n            <motion.div\n                className=\"relative cursor-pointer\"\n                style={{ perspective: \"800px\", transformOrigin: \"50% 100%\" }}\n                animate={\n                    isHovered\n                        ? { y: -20, scaleX: 0.9, scaleY: 0.9 }\n                        : { y: 0, scaleX: 1, scaleY: 1 }\n                }\n                transition={{\n                    duration: 0.8,\n                    type: \"spring\",\n                    stiffness: 200,\n                    damping: 15,\n                }}\n            >\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Paper Sheet Deco */}\n                <div className=\"absolute bottom-0.5 left-0.5 right-0.5 h-3/4 bg-white/80 rounded-lg\" />\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\"relative\", colors.cover)}\n                    style={{ transformOrigin: \"50% 100%\", transformStyle: \"preserve-3d\" }}\n                    animate={isHovered ? { rotateX: -25 } : { rotateX: 0 }}\n                    transition={{ duration: 0.4, delay: 0.2, ease: [0.16, 1, 0.3, 1] }}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 mt-1\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <GlobeIcon />\n                    </div>\n                </motion.div>\n            </motion.div>\n\n            <motion.h3\n                className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}\n                animate={isHovered ? { opacity: 0 } : { opacity: 1 }}\n                transition={{ delay: isHovered ? 0.3 : 0 }}\n            >\n                {label}\n            </motion.h3>\n        </div>\n    );\n};\n\nconst RaviFolder: React.FC<{\n    images: string[];\n    isHovered: boolean;\n    colors: typeof variantColors.ravi;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ images, isHovered, colors, sizes, label }) => {\n    // Reorder images for card-spread effect\n    const reorder = (arr: string[]) => {\n        const result: string[] = [];\n        let i = Math.ceil(arr.length / 2);\n        let j = i - 1;\n        while (j >= 0) {\n            result.push(arr[j--]);\n            if (i < arr.length) result.push(arr[i++]);\n        }\n        return result;\n    };\n\n    const orderedImages = React.useMemo(() => reorder(images), [images]);\n\n    return (\n        <div className=\"relative\">\n            {/* Feedback Circle */}\n            <motion.div\n                className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-14 h-14 rounded-full bg-white\"\n                initial={{ opacity: 0, scale: 1 }}\n                animate={\n                    !isHovered ? { opacity: [1, 0], scale: [1, 5] } : { opacity: 0, scale: 1 }\n                }\n                transition={{ duration: 0.8, delay: 0.35, ease: [0.1, 1, 0.3, 1] }}\n            />\n\n            <div className=\"relative cursor-pointer\" style={{ perspective: \"800px\" }}>\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Paper Sheet */}\n                <div className=\"absolute bottom-0.5 left-0.5 right-0.5 h-3/4 bg-white rounded-lg\" />\n\n                {/* Card Spread Previews */}\n                <div className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[75px] h-[65px]\">\n                    {orderedImages.map((img, i) => {\n                        const interval = 60;\n                        const c = orderedImages.length;\n                        const x =\n                            -interval * Math.floor(c / 2) + interval * i + (c % 2 === 0 ? interval / 2 : 0);\n                        const rotateInterval = 20;\n                        const rotate =\n                            -rotateInterval * Math.floor(c / 2) +\n                            rotateInterval * i +\n                            (c % 2 === 0 ? rotateInterval / 2 : 0);\n\n                        return (\n                            <motion.img\n                                key={i}\n                                src={img}\n                                alt=\"\"\n                                className=\"absolute w-full h-full object-cover rounded shadow-lg\"\n                                initial={{ opacity: 0, x: 0, y: 0, rotate: 0, scale: 1 }}\n                                animate={\n                                    isHovered\n                                        ? { opacity: 1, y: -70, x, rotate }\n                                        : { opacity: 0, x: 0, y: 0, rotate: 0, scale: 0.5 }\n                                }\n                                transition={{\n                                    duration: isHovered ? 0.4 : 0.3,\n                                    ease: isHovered ? [0.1, 1, 0.3, 1] : \"easeInOut\",\n                                }}\n                            />\n                        );\n                    })}\n                </div>\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\"relative\", colors.cover)}\n                    style={{ transformOrigin: \"50% 100%\", transformStyle: \"preserve-3d\" }}\n                    animate={isHovered ? { rotateX: -30 } : { rotateX: 0 }}\n                    transition={{\n                        duration: 0.4,\n                        delay: isHovered ? 0 : 0.3,\n                        ease: [0.16, 1, 0.3, 1],\n                    }}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 mt-1\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <PadlockIcon />\n                    </div>\n                </motion.div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\nconst DurgaFolder: React.FC<{\n    files: { name: string; type?: string }[];\n    isHovered: boolean;\n    colors: typeof variantColors.durga;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ files, isHovered, colors, sizes, label }) => {\n    return (\n        <div className=\"relative\">\n            {/* Text Preview - shows file list inside a tooltip bubble */}\n            <motion.div\n                className=\"absolute -right-2 top-0 bg-gray-800 dark:bg-gray-900 rounded-lg px-3 py-2 shadow-lg z-20 min-w-[100px]\"\n                initial={{ opacity: 0, x: -10, scale: 0.9 }}\n                animate={isHovered ? { opacity: 1, x: 0, scale: 1 } : { opacity: 0, x: -10, scale: 0.9 }}\n                transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}\n                style={{ transform: 'translateX(100%)' }}\n            >\n                {files.slice(0, 6).map((file, i) => (\n                    <motion.div\n                        key={i}\n                        className=\"text-gray-100 font-mono text-xs py-0.5 whitespace-nowrap\"\n                        initial={{ opacity: 0 }}\n                        animate={{ opacity: isHovered ? 1 : 0 }}\n                        transition={{ duration: 0.05, delay: i * 0.03 }}\n                    >\n                        {file.name}\n                    </motion.div>\n                ))}\n            </motion.div>\n\n            {/* Folder */}\n            <div className=\"relative cursor-pointer aspect-[20/16]\" style={{ perspective: \"800px\" }}>\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Paper Sheet */}\n                <div className=\"absolute bottom-0.5 left-0.5 right-0.5 h-3/4 bg-white dark:bg-gray-200 rounded-lg\" />\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\"relative\", colors.cover)}\n                    style={{ transformOrigin: \"50% 100%\", transformStyle: \"preserve-3d\" }}\n                    animate={isHovered ? { rotateX: -30 } : { rotateX: 0 }}\n                    transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <GlobeIcon />\n                    </div>\n                </motion.div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\nconst NandiFolder: React.FC<{\n    files: { name: string; type?: string }[];\n    isHovered: boolean;\n    colors: typeof variantColors.nandi;\n    sizes: typeof sizeConfig.md;\n    label?: string;\n}> = ({ files, isHovered, colors, sizes, label }) => {\n    const fileColorMap: Record<string, string> = {\n        txt: \"fill-blue-300\",\n        gif: \"fill-teal-400\",\n        mp3: \"fill-amber-400\",\n        default: \"fill-gray-400\",\n    };\n\n    return (\n        <div className=\"relative\">\n            {/* Magnifier Preview - compact bubble above folder */}\n            <motion.div\n                className=\"absolute left-1/2 -translate-x-1/2 bg-white dark:bg-gray-100 rounded-2xl shadow-xl z-20 p-3 grid grid-cols-3 gap-2\"\n                style={{ bottom: '100%', marginBottom: '8px', minWidth: '120px' }}\n                initial={{ opacity: 0, scale: 0.8, y: 10 }}\n                animate={\n                    isHovered\n                        ? { opacity: 1, scale: 1, y: 0 }\n                        : { opacity: 0, scale: 0.8, y: 10 }\n                }\n                transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}\n            >\n                {files.slice(0, 6).map((file, i) => (\n                    <div key={i} className=\"text-center\">\n                        <FileIcon\n                            className={cn(\"w-5 h-5 mx-auto\", fileColorMap[file.type || \"default\"])}\n                        />\n                        <span className=\"text-[8px] text-gray-600 block mt-0.5 truncate max-w-[35px]\">\n                            {file.name}\n                        </span>\n                    </div>\n                ))}\n            </motion.div>\n\n            {/* Folder */}\n            <div className=\"relative cursor-pointer aspect-[20/16]\" style={{ perspective: \"800px\" }}>\n                {/* Back */}\n                <div className={cn(\"absolute inset-0\", colors.back)}>\n                    <FolderBackIcon />\n                </div>\n\n                {/* Paper Sheet */}\n                <div className=\"absolute bottom-0.5 left-0.5 right-0.5 h-3/4 bg-white dark:bg-gray-200 rounded-lg\" />\n\n                {/* Cover */}\n                <motion.div\n                    className={cn(\"relative\", colors.cover)}\n                    style={{ transformOrigin: \"50% 100%\", transformStyle: \"preserve-3d\" }}\n                    animate={isHovered ? { rotateX: -30 } : { rotateX: 0 }}\n                    transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}\n                >\n                    <FolderCoverIcon />\n                    <div\n                        className={cn(\n                            \"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\",\n                            sizes.deco,\n                            colors.deco\n                        )}\n                    >\n                        <CloudIcon />\n                    </div>\n                </motion.div>\n            </div>\n\n            {label && (\n                <h3 className={cn(\"mt-3 font-medium text-center\", sizes.caption, colors.caption)}>\n                    {label}\n                </h3>\n            )}\n        </div>\n    );\n};\n\n// ============================================\n// Main FolderPreview Component\n// ============================================\n\nexport const FolderPreview = React.forwardRef<HTMLDivElement, FolderPreviewProps>(\n    (\n        {\n            variant = \"devi\",\n            images = [],\n            files = [],\n            label,\n            size = \"md\",\n            className,\n            onClick,\n        },\n        ref\n    ) => {\n        const [isHovered, setIsHovered] = React.useState(false);\n        const colors = variantColors[variant];\n        const sizes = sizeConfig[size];\n\n        const defaultImages = [\n            \"/folder-preview/user1.svg\",\n            \"/folder-preview/user2.svg\",\n            \"/folder-preview/user3.svg\",\n            \"/folder-preview/user4.svg\",\n            \"/folder-preview/user5.svg\",\n        ];\n\n        const defaultFiles = [\n            { name: \"docs\", type: \"default\" as const },\n            { name: \"template\", type: \"default\" as const },\n            { name: \"readme.md\", type: \"txt\" as const },\n            { name: \"app.js\", type: \"txt\" as const },\n            { name: \"test.sh\", type: \"txt\" as const },\n            { name: \"package.json\", type: \"txt\" as const },\n            { name: \"logo.svg\", type: \"default\" as const },\n            { name: \"...\", type: \"default\" as const },\n        ];\n\n        const imageList = images.length > 0 ? images : defaultImages;\n        const fileList = files.length > 0 ? files : defaultFiles;\n\n        const renderFolder = () => {\n            const props = { isHovered, colors, sizes, label };\n\n            switch (variant) {\n                case \"devi\":\n                    return <DeviFolder images={imageList} {...props} />;\n                case \"rudras\":\n                    return <RudrasFolder images={imageList} {...props} />;\n                case \"ardra\":\n                    return <ArdraFolder images={imageList} {...props} />;\n                case \"shakti\":\n                    return <ShaktiFolder images={imageList} {...props} />;\n                case \"kubera\":\n                    return <KuberaFolder images={imageList} {...props} />;\n                case \"hari\":\n                    return <HariFolder images={imageList} {...props} />;\n                case \"ravi\":\n                    return <RaviFolder images={imageList} {...props} />;\n                case \"durga\":\n                    return <DurgaFolder files={fileList} {...props} />;\n                case \"nandi\":\n                    return <NandiFolder files={fileList} {...props} />;\n                default:\n                    return <DeviFolder images={imageList} {...props} />;\n            }\n        };\n\n        return (\n            <div\n                ref={ref}\n                className={cn(\"inline-flex flex-col items-center overflow-visible\", sizes.folder, className)}\n                onMouseEnter={() => setIsHovered(true)}\n                onMouseLeave={() => setIsHovered(false)}\n                onClick={onClick}\n            >\n                {renderFolder()}\n            </div>\n        );\n    }\n);\n\nFolderPreview.displayName = \"FolderPreview\";\n\nexport default FolderPreview;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        },
        {
          "path": "public/folder-preview/user1.svg",
          "target": "public/folder-preview/user1.svg",
          "type": "registry:file",
          "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 174 174\" style=\"enable-background:new 0 0 174 174;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{clip-path:url(#SVGID_2_);fill:#EFAF7F;}\n\t.st1{clip-path:url(#SVGID_4_);}\n\t.st2{fill:#FFC785;}\n\t.st3{fill:#924A0B;}\n\t.st4{fill:#179E85;stroke:#179E85;stroke-width:1.084;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}\n\t.st5{fill:#F5BE92;}\n\t.st6{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}\n\t.st7{fill:#FFFFFF;}\n\t.st8{fill:url(#SVGID_5_);}\n\t.st9{fill:#2E4962;}\n\t.st10{fill:url(#SVGID_6_);}\n\t.st11{fill:#E48F67;}\n</style>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_1_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_2_\">\n\t\t<use xlink:href=\"#SVGID_1_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<ellipse class=\"st0\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n</g>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_3_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_4_\">\n\t\t<use xlink:href=\"#SVGID_3_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<g class=\"st1\">\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<ellipse class=\"st2\" cx=\"55.2\" cy=\"73.5\" rx=\"7.9\" ry=\"4.6\"/>\n\t\t\t\t<ellipse class=\"st2\" cx=\"120.6\" cy=\"73.5\" rx=\"7.9\" ry=\"4.6\"/>\n\t\t\t</g>\n\t\t\t<path class=\"st3\" d=\"M67.2,33.1c-6.7,1.1-10.5,5.7-12,12C51,61.3,59.7,96.7,89.8,97.1c15.5,0.2,26.7-3.7,30.7-16.3\n\t\t\t\tc4.1-12.6,5.7-32.8-3.1-44.1C106.3,22.5,82.9,22.3,67.2,33.1\"/>\n\t\t\t<path class=\"st4\" d=\"M75.2,116.2c-28.1,12.3-36.6,18.9-39.9,21.9c-5.1,4.6-8.2,22.1-11.2,36.5h63.7h63.7\n\t\t\t\tc-3-14.4-5.7-32-10.8-36.5c-3.3-3-11.5-9.4-39.6-21.7L75.2,116.2L75.2,116.2z\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st5\" d=\"M74.3,91.6v20.9V125c7.5,8.9,19.8,9.2,27.3,0v-12.5V91.6C101.6,74.8,74.3,74.8,74.3,91.6\"/>\n\t\t\t\t<path class=\"st5\" d=\"M88,35.6c-45.9,0-29.4,59.8-26.4,64.2c3.3,4.9,19,13.4,26.4,13.4s23.1-9.6,26.4-14.5\n\t\t\t\t\tC117.3,94.3,133.8,35.6,88,35.6\"/>\n\t\t\t</g>\n\t\t\t<path class=\"st3\" d=\"M65.6,50c8.1,4.9,14.3-2.4,21.6,0.7c7.3,3.1,28.2-5.4,32.4,27.8c6.9-24-4.7-44.1-31-45.3\n\t\t\t\tC60.6,32,49.3,53.9,57,77.8C56.5,64.9,59.7,56.4,65.6,50\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st6\" d=\"M73,115.9l-9.7,5.2l14.1,19.5L88,130.5L73,115.9L73,115.9z\"/>\n\t\t\t\t<path class=\"st6\" d=\"M103.1,115.9l9.7,5.2l-14.1,19.5L88,130.5L103.1,115.9z\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<path class=\"st3\" d=\"M64.9,47.9c0.6,4,6.5,4.6,9.7,4.9c6.2,0.6,13.8-1.8,18.9-5.3c0.4-0.3,0.2-0.8-0.2-0.9\n\t\t\tc-5.4-0.3-10.2,3.4-15.6,3.9c-3.9,0.3-9.2-0.2-12.2-2.9C65.2,47.4,64.9,47.7,64.9,47.9\"/>\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st7\" d=\"M69.3,77.6c0,0,1.5,2.5,6.1,2.5c4.6,0,5.4-2.2,5.4-2.9c0,0-1.8-3-5.4-3C71.8,74.3,69.3,77.6,69.3,77.6\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_5_\" cx=\"75.13\" cy=\"98.79\" r=\"2.615\" gradientTransform=\"matrix(1 0 0 -1 0 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#2E4962\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1.900000e-02\" style=\"stop-color:#314E68\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.106\" style=\"stop-color:#3C627D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.209\" style=\"stop-color:#44728E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.336\" style=\"stop-color:#4A7D9A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.514\" style=\"stop-color:#4D83A1\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#4E85A3\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st8\" cx=\"75.1\" cy=\"77.2\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st9\" d=\"M75.1,74.9c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4\n\t\t\t\t\t\t\tC72.8,75.9,73.8,74.9,75.1,74.9 M75.1,74.3c-1.6,0-2.9,1.3-2.9,2.9c0,1.6,1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9\n\t\t\t\t\t\t\tC78,75.6,76.7,74.3,75.1,74.3z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"75.1\" cy=\"77.2\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st7\" d=\"M106.6,77.6c0,0-1.5,2.5-6.1,2.5s-5.4-2.2-5.4-2.9c0,0,1.8-3,5.4-3C104,74.3,106.6,77.6,106.6,77.6\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_6_\" cx=\"432.71\" cy=\"98.79\" r=\"2.615\" gradientTransform=\"matrix(-1 0 0 -1 533.39 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#2E4962\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1.900000e-02\" style=\"stop-color:#314E68\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.106\" style=\"stop-color:#3C627D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.209\" style=\"stop-color:#44728E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.336\" style=\"stop-color:#4A7D9A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.514\" style=\"stop-color:#4D83A1\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#4E85A3\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st10\" cx=\"100.7\" cy=\"77.2\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st9\" d=\"M100.7,74.9c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4\n\t\t\t\t\t\t\tC98.3,75.9,99.4,74.9,100.7,74.9 M100.7,74.3c-1.6,0-2.9,1.3-2.9,2.9c0,1.6,1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9\n\t\t\t\t\t\t\tC103.6,75.6,102.3,74.3,100.7,74.3z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"100.7\" cy=\"77.2\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<path class=\"st11\" d=\"M82.9,89.7c-0.7,0.4-0.9,1.2-0.1,1.7c1.2,0.9,3,1,4.5,1c1.5,0,3.4-0.1,4.7-1c0.7-0.4,1.2-1.8,0.2-2.2\n\t\t\t\tc-0.2-0.1-0.5,0-0.4,0.3c0,0.3,0.3,0.6,0.3,0.9c-0.1,0.4-0.7,0.6-1,0.7c-1,0.4-2.1,0.4-3.2,0.5c-0.9,0-5.8,0.1-4.8-1.7\n\t\t\t\tC83,89.7,82.9,89.6,82.9,89.7\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st3\" d=\"M64.1,67.1c5.7-0.8,11.1-1.6,16.3,1.3c0.5,0.3,0.9-0.5,0.5-0.8c-4.4-2.7-12.4-3.8-16.8-0.7\n\t\t\t\t\tC64,67,64,67.1,64.1,67.1\"/>\n\t\t\t\t<path class=\"st3\" d=\"M111.5,66.9c-4.4-3.1-12.5-2-16.8,0.7c-0.5,0.3-0.1,1.1,0.5,0.8c5.3-3,10.7-2.1,16.3-1.3\n\t\t\t\t\tC111.6,67.1,111.6,67,111.5,66.9\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<path class=\"st7\" d=\"M99.9,96.7c-6.7,4-16.3,5.3-23.3,1L99.9,96.7\"/>\n\t\t<g>\n\t\t\t<path class=\"st3\" d=\"M107.6,44.2c-0.3,2.5-0.9,4.6-1.7,6.5c-0.5-0.8-1.6-1.2-2.4-0.5c-0.5-1.2-2.3-1.4-3.2-0.2\n\t\t\t\tc-0.1,0.2-0.2,0.4-0.3,0.5c-0.3-1.3-2.1-1.9-3.1-1c0,0,0-0.1,0-0.1c-0.3-1.6-2.5-1.6-3.3-0.4c-0.6,0.9-1.1,1.8-1.5,2.8\n\t\t\t\tc-0.1-0.2-0.2-0.4-0.4-0.6c0.4-0.6,0.7-1.2,1-1.8c1-2.2-1.3-3.9-3-2.9c-0.4-0.2-0.8-0.2-1.3,0C87.3,47,86.3,48,85.3,49\n\t\t\t\tc-0.3-0.5-0.8-0.8-1.4-0.6c-2.7,1-4.9,2.6-7.5,3.8c0-0.1,0-0.3-0.1-0.4c-0.2-0.8-1-1.2-1.8-1c-0.2,0.1-0.4,0.2-0.6,0.4\n\t\t\t\tc0,0,0,0,0,0c0,0,0,0.1-0.1,0.1c0,0,0,0,0,0c-0.1,0.1-0.1,0.1-0.2,0.2c0,0,0,0,0,0c0,0.1-0.1,0.1-0.1,0.2\n\t\t\t\tc-0.2-0.1-0.4-0.2-0.6-0.2c0.4-0.4,0.7-0.8,1-1.2c0.5-0.6-0.3-1.7-1.1-1.4c-0.2,0.1-0.4,0.2-0.7,0.3c0.4-0.7,0.8-1.4,1-2.1\n\t\t\t\tc0.1-0.5-0.2-1-0.6-1.3c-0.2-0.4-0.7-0.7-1.2-0.4c-3.6,2.2-6.5,5.4-9.9,7.9c-0.5,0.2-1,0.3-1.5,0.4c-0.6,0.1-0.7,0.9-0.4,1.3\n\t\t\t\tc0,0-0.1,0.1-0.1,0.1c-0.4,0.4-0.8,0.9-1.2,1.4c0.2-0.4,0.4-0.9,0.2-1.5c0-0.1,0-0.2-0.1-0.3c0-0.4-0.2-0.8-0.4-1.1\n\t\t\t\tc0.5-0.6,0.9-1.2,1.3-1.9c0.6-0.9-0.6-1.9-1.5-1.5c-0.5,0.3-1,0.6-1.5,0.9c1.6-3.1,3.8-5.8,6.4-8.1c4.1-3.7,8.3-3.7,13.4-2.7\n\t\t\t\tc1.4,0.3,2-1.4,0.9-2.2c-1.6-1.2-3.3-1.8-5-2c3-0.4,6.1-0.1,9,1c1,0.4,2-0.7,1.7-1.7c-1.3-4.4-7.2-3.8-10.8-3.3\n\t\t\t\tc-3.3,0.5-6.5,1.5-9.4,3.1c-0.3,0-0.6,0-1,0.2c-1.7,0.8-3.5,1.7-3.5,1.7c-14.6,9-15.4,40-9.3,46.4c0,0.1,1.9,2.2,2.5,0.9\n\t\t\t\tc1.7-3.3,2.2-6.9,4.3-10c2-3.1,5.5-4.6,8.9-5.6c6.7-1.8,13.9-1.7,20.7-2.8c11.1-1.8,27.2-8,24.1-22.2\n\t\t\t\tC109.2,42.8,107.7,43.2,107.6,44.2 M76.2,58.4C76.2,58.4,76.2,58.4,76.2,58.4C76.2,58.4,76.2,58.4,76.2,58.4\n\t\t\t\tC76.2,58.4,76.2,58.4,76.2,58.4 M93.5,58.7C93.5,58.6,93.5,58.6,93.5,58.7C93.5,58.6,93.5,58.6,93.5,58.7\n\t\t\t\tC93.5,58.6,93.5,58.7,93.5,58.7\"/>\n\t\t\t<path class=\"st3\" d=\"M127.5,47.1c-0.6-4-4-10.2-8.9-7.3c-0.6,0.4-0.5,1.4,0.2,1.6c0.6,0.2,1.2,0.4,1.7,0.6\n\t\t\t\tc-0.2,0.3-0.3,0.6-0.1,1c1.5,2.5,2,5.1,2.1,7.8c-0.1,0.2-0.2,0.4-0.2,0.7c-0.1,3.2-0.9,6.7-0.6,9.9c0.1,0.5,0.5,1,0.9,1.1\n\t\t\t\tc-0.9,3.5-1.7,6.7-1.6,8.9c0.1,1.1,1.5,1.3,1.9,0.3C125.9,64.2,128.7,55.2,127.5,47.1\"/>\n\t\t</g>\n\t</g>\n</g>\n</svg>\n"
        },
        {
          "path": "public/folder-preview/user2.svg",
          "target": "public/folder-preview/user2.svg",
          "type": "registry:file",
          "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 174 174\" style=\"enable-background:new 0 0 174 174;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{clip-path:url(#SVGID_2_);fill:#D888BA;}\n\t.st1{clip-path:url(#SVGID_4_);}\n\t.st2{fill:#F5BE92;}\n\t.st3{fill:#F9B54F;stroke:#F9B54F;stroke-width:1.084;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}\n\t.st4{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}\n\t.st5{fill:#FFFFFF;}\n\t.st6{fill:url(#SVGID_5_);}\n\t.st7{fill:#624A2E;}\n\t.st8{fill:url(#SVGID_6_);}\n\t.st9{fill:#E48F67;}\n\t.st10{fill:#474748;}\n</style>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_1_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_2_\">\n\t\t<use xlink:href=\"#SVGID_1_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<ellipse class=\"st0\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n</g>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_3_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_4_\">\n\t\t<use xlink:href=\"#SVGID_3_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<g class=\"st1\">\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<ellipse class=\"st2\" cx=\"54.8\" cy=\"72.7\" rx=\"7.9\" ry=\"4.6\"/>\n\t\t\t\t<ellipse class=\"st2\" cx=\"120.2\" cy=\"72.7\" rx=\"7.9\" ry=\"4.6\"/>\n\t\t\t</g>\n\t\t\t<path class=\"st3\" d=\"M74.8,115.4c-28.1,12.3-36.6,18.9-39.9,21.9c-5.1,4.6-8.2,22.1-11.2,36.5h63.7H151c-3-14.4-5.7-32-10.8-36.5\n\t\t\t\tc-3.3-3-11.5-9.4-39.6-21.7L74.8,115.4L74.8,115.4z\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st2\" d=\"M73.9,90.8v20.9v12.5c7.5,8.9,19.8,9.2,27.3,0v-12.5V90.8C101.2,74,73.9,74,73.9,90.8\"/>\n\t\t\t\t<path class=\"st2\" d=\"M87.5,34.8c-45.9,0-29.4,59.8-26.4,64.2c3.3,4.9,19,13.4,26.4,13.4s23.1-9.6,26.4-14.5\n\t\t\t\t\tC116.9,93.5,133.4,34.8,87.5,34.8\"/>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st4\" d=\"M72.6,115.1l-9.7,5.2l14.1,19.5l10.7-10.1L72.6,115.1L72.6,115.1z\"/>\n\t\t\t\t<path class=\"st4\" d=\"M102.6,115.1l9.7,5.2l-14.1,19.5l-10.7-10.1L102.6,115.1L102.6,115.1z\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st5\" d=\"M68.8,76.8c0,0,1.5,2.5,6.1,2.5s5.4-2.2,5.4-2.9c0,0-1.8-3-5.4-3C71.3,73.5,68.8,76.8,68.8,76.8\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_5_\" cx=\"74.69\" cy=\"99.59\" r=\"2.615\" gradientTransform=\"matrix(1 0 0 -1 0 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#624A2E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"3.600000e-02\" style=\"stop-color:#6D5633\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.122\" style=\"stop-color:#816B3D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.223\" style=\"stop-color:#907C45\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.348\" style=\"stop-color:#9B884A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.523\" style=\"stop-color:#A18F4D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#A3914E\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st6\" cx=\"74.7\" cy=\"76.4\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st7\" d=\"M74.7,74.1c1.3,0,2.4,1.1,2.4,2.4s-1.1,2.4-2.4,2.4s-2.4-1.1-2.4-2.4S73.4,74.1,74.7,74.1 M74.7,73.5\n\t\t\t\t\t\t\tc-1.6,0-2.9,1.3-2.9,2.9s1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9S76.3,73.5,74.7,73.5z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"74.7\" cy=\"76.4\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st5\" d=\"M106.1,76.8c0,0-1.5,2.5-6.1,2.5c-4.6,0-5.4-2.2-5.4-2.9c0,0,1.8-3,5.4-3C103.6,73.5,106.1,76.8,106.1,76.8\n\t\t\t\t\t\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_6_\" cx=\"-32.384\" cy=\"99.59\" r=\"2.615\" gradientTransform=\"matrix(-1 0 0 -1 67.85 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#624A2E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"3.600000e-02\" style=\"stop-color:#6D5633\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.122\" style=\"stop-color:#816B3D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.223\" style=\"stop-color:#907C45\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.348\" style=\"stop-color:#9B884A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.523\" style=\"stop-color:#A18F4D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#A3914E\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st8\" cx=\"100.2\" cy=\"76.4\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st7\" d=\"M100.2,74.1c1.3,0,2.4,1.1,2.4,2.4s-1.1,2.4-2.4,2.4s-2.4-1.1-2.4-2.4S98.9,74.1,100.2,74.1 M100.2,73.5\n\t\t\t\t\t\t\tc-1.6,0-2.9,1.3-2.9,2.9s1.3,2.9,2.9,2.9s2.9-1.3,2.9-2.9C103.1,74.8,101.8,73.5,100.2,73.5z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"100.2\" cy=\"76.4\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<path class=\"st9\" d=\"M82.4,88.9c-0.7,0.4-0.9,1.2-0.1,1.7c1.2,0.9,3,1,4.5,1c1.5,0,3.4-0.1,4.7-1c0.7-0.4,1.2-1.8,0.2-2.2\n\t\t\t\tc-0.2-0.1-0.5,0-0.4,0.3c0,0.3,0.3,0.6,0.3,0.9c-0.1,0.4-0.7,0.6-1,0.7c-1,0.4-2.1,0.4-3.2,0.5c-0.9,0-5.8,0.1-4.8-1.7\n\t\t\t\tC82.6,89,82.5,88.9,82.4,88.9\"/>\n\t\t\t<path class=\"st5\" d=\"M96.3,99.9c-4.9,3-12.1,4-17.3,0.7L96.3,99.9\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st10\" d=\"M63.7,66.3c5.7-0.8,11.1-1.6,16.3,1.3c0.5,0.3,0.9-0.5,0.5-0.8C76.1,64.1,68,63,63.6,66.2\n\t\t\t\t\tC63.5,66.2,63.6,66.3,63.7,66.3\"/>\n\t\t\t\t<path class=\"st10\" d=\"M111.1,66.2c-4.4-3.1-12.5-2-16.8,0.7c-0.5,0.3-0.1,1.1,0.5,0.8c5.3-3,10.7-2.1,16.3-1.3\n\t\t\t\t\tC111.1,66.3,111.2,66.2,111.1,66.2\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<g>\n\t\t\t<path class=\"st10\" d=\"M118.7,82.8c-0.1-0.1-0.2-0.2-0.4-0.3c0.1-0.7,0.3-1.5,0.4-2.2c-0.2,0-0.3,0-0.4,0c0.5-2,0.3-4.1,0.4-6.2\n\t\t\t\tc0.2-3,1.2-6.1,0.8-9.1c-0.1-0.6-0.8-0.8-1.1-0.3c-1.3,2-1.6,4.7-1.9,7.1c-0.3,2.6-1,5.9-0.4,8.6c-0.1,0-0.1,0.1-0.1,0.1\n\t\t\t\tc0,0,0,0,0,0c0,0.7-0.2,1.3-0.6,1.8c-0.2,0.3-0.3,0.5-0.5,0.8c-1.3,3.1-3.8,5.9-6.3,7.9c-0.1,0.1-0.3,0.2-0.5,0.4\n\t\t\t\tc-1.9,2-4.4,3.7-7.1,4.2c-0.7,0.1-1.3,0-1.8-0.3c-0.1,0-0.1,0-0.2,0c0,0-0.1,0-0.1,0.1c-6.2-3.1-16.6-3-22.6,0.2\n\t\t\t\tc-0.4,0.3-0.8,0.6-1.2,0.8c-0.4,0.5-0.9,0.8-1.7,0.9c-3.2,0.1-5.7-2.7-7.5-5c-1.7-2.3-3.2-4.8-4.3-7.5c0,0,0,0,0,0\n\t\t\t\tc-0.1,0-0.1-0.1-0.2-0.1c0-0.1,0-0.3,0.1-0.4c-0.1-0.4-0.1-0.8,0-1.2c-0.1-1-0.4-1.9-1.1-2.5c0,0,0,0,0,0c0.2-2.3-0.3-4.7-0.7-7\n\t\t\t\tc-0.6-2.9-1.3-6.2-3.2-8.5c-0.3-0.4-1.2-0.1-1.1,0.5c0.3,2.6,1.4,5,2,7.6c0.5,2.3,0.6,4.5,0.8,6.8c-0.2,0-0.3,0-0.5,0.1\n\t\t\t\tc-0.8,0.3-1.5,1.1-1.4,2c-0.2,0.1-0.4,0.2-0.5,0.4c-0.3,0.6-0.3,1.1-0.2,1.7c0.1,0.5,0.3,1.1,0.8,1.4c-0.1,0.5-0.1,1,0,1.4\n\t\t\t\tc0,0.2,0.1,0.4,0.3,0.5c0.1,0.4,0.3,0.8,0.5,1.1c0.1,0.1,0.2,0.2,0.3,0.2c-0.6,1-0.8,2.2-0.8,3.2c0,1.3,0.7,3.8,2.2,3.8\n\t\t\t\tc-0.4,2,1,4.4,2.8,5.4c-0.1,1.2,0.5,2.4,1.4,3.2c0.5,0.5,1.2,1,1.9,1.2c0.5,0.1,0.8,0.1,1.2,0c0.2,1.3,0.9,2.5,1.8,3.3\n\t\t\t\tc0.7,0.6,2.2,1.4,3.5,1.4c0,1.1,0.9,2.1,2,2.3c0.6,0.1,1.2,0.1,1.7,0.1c0.4,0,1-0.3,1.4-0.6l0.1,0.1c0.4,2.7,4,4.8,6.5,3.6\n\t\t\t\tc0.4,0.8,1.1,1.5,1.9,1.7c1.3,0.3,1.7-0.6,2.8-0.7c2-0.2,3.8,0.2,4.9-1.5c1.3,1.3,4.1,0.5,5.5-0.1c1.4-0.7,3.8-2.7,4.1-4.6\n\t\t\t\tc0.9-0.1,2-0.5,2.6-0.8c0.8-0.4,2.3-1.6,2.7-2.8c0.8,0.1,1.7-0.2,2.5-0.5c1.4-0.7,2.4-2.1,2.2-3.7c0-0.1,0-0.1-0.1-0.1\n\t\t\t\tc0.7-0.2,1.2-0.8,1.6-1.4c0.4-0.7,0.8-1.2,1.4-1.6c0.4-0.3,0.9-0.7,1.2-1.2c0.8-1.5,1.4-3.7,0.8-5.4c0.9-0.4,1.3-1.4,1.5-2.3\n\t\t\t\tc0.2-0.8,0.4-2.1,0-3C119.3,85.8,119.4,84,118.7,82.8 M98.3,99.8c-0.1,0.1-0.2,0.2-0.2,0.3c-0.3,0.1-0.6,0.4-0.8,0.7\n\t\t\t\tc-0.2,0.4-0.2,0.8-0.1,1.2c-0.2,0.1-0.5,0.2-0.7,0.4c-1.3,1-1.7,2.3-1.7,3.7c-0.3,0-0.6,0.1-0.9,0.2c-0.6,0-1.2,0.2-1.7,0.4\n\t\t\t\tc-0.4,0.2-0.8,0.5-1.1,0.9c0-0.1-0.1-0.2-0.1-0.3c-0.4-0.8-1.5-1.1-2.3-1.2c-0.9,0-1.7,0.5-2.4,1c-0.3,0.2-0.5,0.5-0.7,0.8\n\t\t\t\tc-0.2-0.2-0.3-0.3-0.5-0.4c-0.7-0.5-1.6-0.5-2.4-0.3c-0.1,0-0.3,0.1-0.4,0.1c-1.1-1.6-2.6-3-4.6-2.6c-0.3,0.1-0.5,0.2-0.7,0.3\n\t\t\t\tc-0.1-0.1-0.1-0.2-0.2-0.4c-0.1-0.1-0.1-0.1-0.2-0.2c0.1-0.2,0.1-0.5,0.2-0.7c0.6-1.5,0.8-3.4-0.4-4.4c0.1-0.1,0.1-0.1,0.2-0.1\n\t\t\t\tc3.5-0.9,6.8-1.8,10.5-1.9C91.3,97.1,94.6,98.6,98.3,99.8C98.3,99.7,98.3,99.8,98.3,99.8\"/>\n\t\t\t<path class=\"st10\" d=\"M87.8,47.6c-0.2-0.5-0.6-0.5-0.9-0.3c-0.2-0.5-1-0.7-1.3-0.1c-1.1,1.7-2.4,3.1-4.5,3.3\n\t\t\t\tc-1.2,0.1-2.7-0.6-3.9-0.3c-0.1-0.5-0.9-0.8-1.3-0.2c-3.1,4.1-7.6,6.1-12.3,3.3c0,0-0.1,0-0.1,0c-0.3-0.7-1.5-0.5-1.5,0.3\n\t\t\t\tc0,2.1,0.2,4.2,0,6.4c-0.4,3-2.9,6-6.1,4c-0.5-1.7-1.4-3.2-1.8-5c-0.2-1-0.2-2.1,0-3.1c0.3-1.3,1.5-1.4,2.5-2\n\t\t\t\tc0.7-0.4,0.5-1.4-0.2-1.6c-0.3-0.1-0.6-0.1-0.9-0.1c-3.5-4.3,1.2-11,6-12c0.2,0.2,0.5,0.3,0.8,0.2c0.5-0.1,1-0.2,1.6-0.3\n\t\t\t\tc0.7,0,1.3,0.2,1.9,0c0.7-0.1,0.7-1,0.4-1.4c-0.3-0.4-0.8-0.6-1.3-0.7c2.8-3.8,9.7-3.5,14.1-2.6c0.2,0.1,0.3,0.1,0.5,0.1\n\t\t\t\tc0.1,0,0.3,0.1,0.4,0.1c0.8,0.2,1.5-1,0.6-1.4c0,0,0,0,0,0c0.8-1.6,1.7-2.5,3.7-3c1.5-0.4,2.9-0.4,4.4-0.4\n\t\t\t\tc0.8,0.2,1.5,0.6,2.2,0.8c1,0.3,1.7,1,2.6,1.5c0.6,0.4,1.4-0.2,1.2-0.9c-0.2-0.6-0.6-1.1-1-1.5c4.1-0.1,8.7,2.7,7.5,7.3\n\t\t\t\tc-0.1,0.2,0,0.4,0.1,0.6c0.1,0.3,0.4,0.6,0.8,0.5c2.9-0.9,5.6-1.1,8.5-0.4c1.4,0.3,2.9,1.1,3.8,2.3c1.1,1.5,0.2,3-0.1,4.5\n\t\t\t\tc-0.1,0.2,0,0.5,0.1,0.7c-0.1,0.4,0.2,0.7,0.7,0.8c5.7,0.1,7.2,6.9,3.6,10.7c-0.4,0.4-0.3,1.3,0.4,1.4c2.2,0.5,2.8,2.2,2.8,4.2\n\t\t\t\tc0,2.4-1.4,3.3-3.6,3.2c-1.2-1-1.6-2.4-1.4-4.3c0-0.3-0.3-0.7-0.6-0.8c-5-0.8-8.9-4.3-9.3-9.5c-0.1-0.5-0.8-0.8-1.1-0.4\n\t\t\t\tc-0.1-0.5-0.3-0.9-0.6-1.2c-0.3-0.3-0.6-0.2-0.8,0.1c-0.1,0-0.2,0-0.2,0.1C99.4,53.8,90.2,54,87.8,47.6\"/>\n\t\t\t<path class=\"st10\" d=\"M119,58.8c-0.7-0.1-0.8-1-0.4-1.4c3.5-3.8,2.1-10.6-3.6-10.7c-0.5,0-0.7-0.4-0.7-0.8\n\t\t\t\tc-0.1-0.2-0.2-0.4-0.1-0.7c0.3-1.5,1.2-3,0.1-4.5c-0.9-1.2-2.4-1.9-3.8-2.3c-2.9-0.7-5.7-0.5-8.5,0.4c-0.4,0.1-0.7-0.1-0.8-0.5\n\t\t\t\tc-0.1-0.2-0.2-0.4-0.1-0.6c1.2-4.5-3.4-7.4-7.5-7.3c0.5,0.4,0.9,0.9,1,1.5c0.2,0.7-0.6,1.3-1.2,0.9c-0.9-0.5-1.6-1.2-2.6-1.5\n\t\t\t\tc-0.8-0.2-1.5-0.6-2.2-0.8c-1.5,0-2.9,0-4.4,0.4c-2.1,0.6-2.9,1.5-3.7,3c0,0,0,0,0,0c0.8,0.4,0.2,1.6-0.6,1.4\n\t\t\t\tc-0.1,0-0.3,0-0.4-0.1c-0.2,0-0.4,0-0.5-0.1c-4.4-0.8-11.3-1.2-14.1,2.6c0.5,0.1,1,0.3,1.3,0.7c0.4,0.4,0.3,1.3-0.4,1.4\n\t\t\t\tc-0.7,0.1-1.3-0.1-1.9,0c-0.5,0-1.1,0.2-1.6,0.3c-0.4,0.1-0.6,0-0.8-0.2c-4.8,1-9.5,7.7-6,12c0.3,0,0.6,0,0.9,0.1\n\t\t\t\tc0.7,0.2,0.9,1.2,0.2,1.6c-1,0.6-2.2,0.7-2.5,2c-0.2,1-0.2,2.1,0,3.1c0.4,1.8,1.3,3.3,1.8,5c3.2,2,5.7-1,6.1-4\n\t\t\t\tc0.3-2.1,0-4.2,0-6.4c0-0.8,1.2-1,1.5-0.3c0,0,0.1,0,0.1,0c4.8,2.7,9.2,0.8,12.3-3.3c0.4-0.6,1.2-0.3,1.3,0.2\n\t\t\t\tc1.2-0.2,2.7,0.4,3.9,0.3c2.1-0.2,3.4-1.6,4.5-3.3c0.4-0.6,1.1-0.4,1.3,0.1c0.3-0.2,0.8-0.1,0.9,0.3c2.3,6.4,11.6,6.2,16.2,2.7\n\t\t\t\tc0.1-0.1,0.2-0.1,0.2-0.1c0.2-0.2,0.5-0.4,0.8-0.1c0.4,0.4,0.6,0.7,0.6,1.2c0.3-0.4,1-0.1,1.1,0.4c0.5,5.2,4.3,8.7,9.3,9.5\n\t\t\t\tc0.3,0,0.6,0.5,0.6,0.8c-0.2,1.9,0.2,3.3,1.4,4.3c2.2,0.1,3.5-0.8,3.6-3.2C121.8,61,121.2,59.3,119,58.8 M120.7,57.6\n\t\t\t\tc5.8,2.8,1.5,13.9-3.2,10.3c0,0,0,0-0.1-0.1c-1.7-1-2.3-3-2.3-5.1c-5.5-0.6-10.3-5-9.6-10.8c-0.2,0.2-0.6,0.1-0.8-0.1\n\t\t\t\tc-4.7,5.3-16.2,3.8-18.1-3.4c-0.7,2.1-2.3,3.4-4.6,3.8c-1.4,0.3-4.3,0.2-5.4-1c-2.9,4.6-8.2,6.5-12.8,3.7\n\t\t\t\tc0.6,2.8,0.6,6.2-0.9,8.5c-1.9,2.8-5.5,3.8-8,1.3c0,0,0,0,0,0c-2.4-1.1-3.5-5.3-3.4-7.8c0.1-1.8,1-4,2.6-4.7\n\t\t\t\tc-4.1-5.2,1.6-13.8,7.8-13.8c0.4-0.2,0.9-0.4,1.4-0.5c2.3-4.9,9.7-6.1,14.9-4.7c0.2-1.9,2.2-3.6,4-4.3c2.5-1,6.4-1.5,8.8,0\n\t\t\t\tc0.2,0.1,0.4,0.4,0.4,0.6c0,0,0,0,0,0c5-1.5,11.6,1.8,11.3,7.3c2.9-1.5,7.3-1,9.9,0.2c3.2,1.4,5.5,5,3.6,8.2\n\t\t\t\tC122.3,45.4,124.1,53,120.7,57.6\"/>\n\t\t\t<path class=\"st10\" d=\"M57,66.6c0.3,0.6,0.6,1.2,0.9,1.7c0.9,2.3,1.8,6.5,0.7,6.6c-0.6,0-1,1-0.4,1.3c2.6,1.1,3.3-1.8,3.3-3.8\n\t\t\t\tc0-2.2-0.8-5.8-2.5-7.4C58,63.8,56.2,65.3,57,66.6\"/>\n\t\t\t<path class=\"st10\" d=\"M117.7,76.3c-2.2-3.1,0.4-5.2,0.2-8.4c-0.1-1.2-1.8-2-2.5-0.7c-1.7,3.7-2.8,7.5,0,10.8\n\t\t\t\tC116.5,79.4,118.7,77.7,117.7,76.3\"/>\n\t\t</g>\n\t</g>\n</g>\n</svg>\n"
        },
        {
          "path": "public/folder-preview/user3.svg",
          "target": "public/folder-preview/user3.svg",
          "type": "registry:file",
          "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 174 174\" style=\"enable-background:new 0 0 174 174;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{clip-path:url(#SVGID_2_);fill:#7CC5D8;}\n\t.st1{clip-path:url(#SVGID_4_);}\n\t.st2{fill:#F5BE92;}\n\t.st3{fill:#92653D;}\n\t.st4{fill:#4C9DD2;stroke:#4C9DD2;stroke-width:1.084;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}\n\t.st5{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;}\n\t.st6{fill:#FFFFFF;}\n\t.st7{fill:url(#SVGID_5_);}\n\t.st8{fill:#624A2E;}\n\t.st9{fill:url(#SVGID_6_);}\n\t.st10{fill:#E48F67;}\n\t.st11{fill:#0A6F90;}\n</style>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_1_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_2_\">\n\t\t<use xlink:href=\"#SVGID_1_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<ellipse class=\"st0\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n</g>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_3_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_4_\">\n\t\t<use xlink:href=\"#SVGID_3_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<g class=\"st1\">\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<ellipse class=\"st2\" cx=\"54.4\" cy=\"72.9\" rx=\"7.9\" ry=\"4.6\"/>\n\t\t\t\t<ellipse class=\"st2\" cx=\"119.8\" cy=\"72.9\" rx=\"7.9\" ry=\"4.6\"/>\n\t\t\t</g>\n\t\t\t<path class=\"st3\" d=\"M110,32.5c6.7,1.1,10.5,5.7,12.1,12c4.2,16.3-4.5,51.7-34.6,52.1c-15.5,0.2-26.7-3.7-30.7-16.3\n\t\t\t\tc-4.1-12.6-5.7-32.8,3.1-44.1C70.9,21.9,94.3,21.8,110,32.5\"/>\n\t\t\t<path class=\"st4\" d=\"M74.4,115.6c-28.1,12.3-36.6,18.9-39.9,21.9c-5.1,4.6-8.2,22.1-11.2,36.5H87h63.7c-3-14.4-5.7-32-10.8-36.5\n\t\t\t\tc-3.3-3-11.5-9.4-39.6-21.7L74.4,115.6L74.4,115.6z\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st2\" d=\"M73.5,91v20.9v12.5c7.5,8.9,19.8,9.2,27.3,0v-12.5V91C100.8,74.2,73.5,74.2,73.5,91\"/>\n\t\t\t\t<path class=\"st2\" d=\"M87.2,35c-45.9,0-29.4,59.8-26.4,64.2c3.3,4.9,19,13.4,26.4,13.4s23.1-9.6,26.4-14.5\n\t\t\t\t\tC116.5,93.7,133,35,87.2,35\"/>\n\t\t\t</g>\n\t\t\t<path class=\"st3\" d=\"M64.8,49.4c8.1,4.9,14.3-2.4,21.6,0.7c7.3,3.1,28.2-5.4,32.4,27.8c6.9-24-4.7-44.1-31-45.3\n\t\t\t\tc-28-1.2-39.4,20.7-31.6,44.6C55.7,64.3,58.9,55.8,64.8,49.4\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st5\" d=\"M72.2,115.3l-9.7,5.2L76.6,140l10.7-10.1L72.2,115.3L72.2,115.3z\"/>\n\t\t\t\t<path class=\"st5\" d=\"M102.3,115.3l9.7,5.2L97.9,140l-10.7-10.1C87.2,129.9,102.3,115.3,102.3,115.3z\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<path class=\"st3\" d=\"M64.1,47.4c0.6,4,6.5,4.6,9.7,4.9c6.2,0.6,13.8-1.8,18.9-5.3c0.4-0.3,0.2-0.8-0.2-0.9\n\t\t\tc-5.4-0.3-10.2,3.4-15.6,3.9c-3.9,0.3-9.2-0.2-12.2-2.9C64.4,46.9,64.1,47.1,64.1,47.4\"/>\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st6\" d=\"M68.5,77.1c0,0,1.5,2.5,6.1,2.5c4.6,0,5.4-2.2,5.4-2.9c0,0-1.8-3-5.4-3S68.5,77.1,68.5,77.1\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_5_\" cx=\"74.33\" cy=\"99.38\" r=\"2.615\" gradientTransform=\"matrix(1 0 0 -1 0 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#624A2E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"3.600000e-02\" style=\"stop-color:#6D5633\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.122\" style=\"stop-color:#816B3D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.223\" style=\"stop-color:#907C45\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.348\" style=\"stop-color:#9B884A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.523\" style=\"stop-color:#A18F4D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#A3914E\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st7\" cx=\"74.3\" cy=\"76.6\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st8\" d=\"M74.3,74.3c1.3,0,2.4,1.1,2.4,2.4S75.6,79,74.3,79C73,79,72,77.9,72,76.6C72,75.3,73,74.3,74.3,74.3\n\t\t\t\t\t\t\t M74.3,73.7c-1.6,0-2.9,1.3-2.9,2.9s1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9C77.2,75,75.9,73.7,74.3,73.7z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"74.3\" cy=\"76.6\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st6\" d=\"M105.8,77.1c0,0-1.5,2.5-6.1,2.5c-4.6,0-5.4-2.2-5.4-2.9c0,0,1.8-3,5.4-3C103.2,73.7,105.8,77.1,105.8,77.1\n\t\t\t\t\t\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_6_\" cx=\"-471.41\" cy=\"99.38\" r=\"2.615\" gradientTransform=\"matrix(1 0 0 -1 0 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#624A2E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"3.600000e-02\" style=\"stop-color:#6D5633\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.122\" style=\"stop-color:#816B3D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.223\" style=\"stop-color:#907C45\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.348\" style=\"stop-color:#9B884A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.523\" style=\"stop-color:#A18F4D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#A3914E\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st9\" cx=\"99.9\" cy=\"76.6\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st8\" d=\"M99.9,74.3c1.3,0,2.4,1.1,2.4,2.4s-1.1,2.4-2.4,2.4s-2.4-1.1-2.4-2.4S98.6,74.3,99.9,74.3 M99.9,73.7\n\t\t\t\t\t\t\tc-1.6,0-2.9,1.3-2.9,2.9s1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9C102.8,75,101.5,73.7,99.9,73.7z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"99.9\" cy=\"76.6\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st10\" d=\"M82.1,89.1c-0.7,0.4-0.9,1.2-0.1,1.7c1.2,0.9,3,1,4.5,1c1.5,0,3.4-0.1,4.7-1c0.7-0.4,1.2-1.8,0.2-2.2\n\t\t\t\t\tc-0.2-0.1-0.5,0-0.4,0.3c0,0.3,0.3,0.6,0.3,0.9c-0.1,0.4-0.7,0.6-1,0.7c-1,0.4-2.1,0.4-3.2,0.5c-0.9,0-5.8,0.1-4.8-1.7\n\t\t\t\t\tC82.2,89.2,82.1,89.1,82.1,89.1\"/>\n\t\t\t\t<path class=\"st10\" d=\"M78.6,101c5,3.7,12.7,2.7,17.5-0.8c0.1-0.1,0-0.3-0.1-0.2c-5.9,2.5-11.2,3.2-17.2,0.7\n\t\t\t\t\tC78.5,100.6,78.4,100.9,78.6,101\"/>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st3\" d=\"M63.3,66.5c5.7-0.8,11.1-1.6,16.3,1.3c0.5,0.3,0.9-0.5,0.5-0.8c-4.4-2.7-12.4-3.8-16.8-0.7\n\t\t\t\t\tC63.2,66.4,63.2,66.5,63.3,66.5\"/>\n\t\t\t\t<path class=\"st3\" d=\"M110.7,66.4c-4.4-3.1-12.5-2-16.8,0.7c-0.5,0.3-0.1,1.1,0.5,0.8c5.3-3,10.7-2.1,16.3-1.3\n\t\t\t\t\tC110.8,66.5,110.8,66.4,110.7,66.4\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<path class=\"st11\" d=\"M99.8,87.9c0.2,0,0.5,0,0.8,0c7.1-0.6,8.8-3.8,9.6-7.8c0.9-4.4,0.9-5.9,2.1-6.4c0.8-0.3,1-1.3,1-2.4\n\t\t\tc0-1.1-0.1-1.3-1.4-1.7c-1.1-0.4-7.1-0.9-11.7-0.6l0,1.2c3.6-0.2,7,0.2,8,0.9c2.3,1.5,1.6,8.6-0.3,12.1c-1.3,2.4-4.8,3.7-8,3.7\n\t\t\tL99.8,87.9 M86.6,71.2c-1.6,0-8-1.8-12.5-2.2c-0.3,0-0.7-0.1-1-0.1l0,1.2c3.7,0.2,7.5,1,8.8,3c2.2,3.2-1.7,11.2-5.2,12.9\n\t\t\tc-1.1,0.5-2.5,0.8-3.9,0.8l0,1.1c6.1,0,8.4-4.4,9.1-5.9c1.4-2.8,1.1-6.6,4.6-6.6c3.5,0,3,3.7,4.3,6.5c0.7,1.5,2.7,6.1,9,5.9l0-1.1\n\t\t\tc-1.6,0-3.1-0.2-4.1-0.8c-3.4-1.7-6.8-9.6-4.4-12.9c1.4-2,5.2-2.8,8.9-3l0-1.2c-0.3,0-0.6,0-0.8,0.1\n\t\t\tC95.2,69.3,90.2,71.1,86.6,71.2 M73.1,68.9c-4.5-0.2-10.4,0.3-11.5,0.7c-1.4,0.5-1.5,0.6-1.6,1.8c-0.1,1.1,0.1,2.1,0.9,2.4\n\t\t\tc1.1,0.4,1.1,1.9,1.7,6.3c0.5,4,2.1,7.2,9.1,7.7c0.4,0,0.7,0,1,0l0-1.1c-3.2,0-6.8-1.2-8.1-3.7c-1.7-3.5-1.9-10.6,0.4-12.1\n\t\t\tc1-0.6,4.4-1.1,7.9-0.9L73.1,68.9\"/>\n\t\t<g>\n\t\t\t<path class=\"st3\" d=\"M116.4,60.3c-1.3-0.2-2.4-1-3.3-1.9c-0.5-0.5-0.9-1.1-1.1-1.8c-0.2-0.7,0-1.2-0.6-1.8\n\t\t\t\tc-0.3-0.3-0.8-0.4-1.2-0.1c0,0,0,0,0,0c0-0.2,0.1-0.5,0.1-0.7c0.2-0.9-0.8-1.5-1.5-0.9c-0.4,0.3-0.7,0.7-0.8,1.2\n\t\t\t\tc0-0.8-0.6-1.5-1.6-1.3c-0.8,0.2-2.1,0.1-3.2-0.3c0-0.1,0-0.3,0-0.4c-0.4-1.3-1.3-2-1.9-3.2c-0.5-1.1-2.4-0.8-2.6,0.3\n\t\t\t\tc0,0.1,0,0.1,0,0.1c-0.2-0.4-0.4-0.8-0.7-1.2c-0.4-0.5-1.2-0.5-1.4,0.2c-0.2,0.5-0.2,1-0.1,1.5c-0.4,0.2-0.9,0.3-1.4,0.5\n\t\t\t\tc-0.1,0-0.1-0.1-0.2-0.1c-1.7-0.7-3.5-1.3-4.9-2.5c-0.5-0.4-1.2-0.4-1.6-0.1c-0.8-0.5-1.9-0.5-2.6,0.5c-2.5,3.7-4.4,0.3-6.3-0.7\n\t\t\t\tc-0.8-0.4-1.9,0.1-1.9,1.1c0,0.2,0,0.4,0.1,0.5c-0.2-0.2-0.3-0.4-0.4-0.6c-0.6-1-1.8-1-2.5-0.3c-1.7,1.4-3.8,2-6,2.3\n\t\t\t\tc-0.3,0-0.6,0-0.8,0c0.3-0.3,0.6-0.8,0.4-1.2c-0.1-0.2,0-0.1,0,0.1c0-0.5-0.3-1-0.8-1.1c-0.9-0.2-1.7-0.7-2.6-0.8\n\t\t\t\tc-0.6-0.1-1.2,0.6-0.7,1.1c0.1,0.1,0.2,0.2,0.2,0.2c0,0.1,0,0.2,0,0.3c-0.6-0.5-1.3-1.1-2-1.7c-0.4-0.3-1-0.2-1.1,0.3\n\t\t\t\tc-1.7,7.2,9.1,6,14.1,3.8c3.5,4.3,11.1,5.2,13.3-0.3c1.3,1.8,4.2,2.1,6.8,1.5c0.7-0.1,1.3-0.3,1.8-0.6c0.6,1,1.4,1.9,2.2,2.5\n\t\t\t\tc2.3,1.6,5.1,1.8,7.7,0.9c0.3-0.1,0.6-0.3,0.7-0.6c-0.3,1.5,0.4,3.3,1.3,4.5c1.6,2,4.2,3.5,6.8,3.3\n\t\t\t\tC117.3,62.6,117.8,60.5,116.4,60.3\"/>\n\t\t\t<path class=\"st3\" d=\"M61.4,38.9c0-0.1,0-0.1,0-0.2c0.4-0.1,0.6-0.4,0.7-0.8c0.7,0.2,1.5-0.2,1.5-1.2c-0.1-2,0.2-3.4,2.3-3.9\n\t\t\t\tc1.6-0.4,3.5-0.1,5.2,0c1.2,0.1,1.7-1.5,0.9-2.3c-0.6-0.6-1.4-1-2.2-1.3c0.4-0.3,0.8-0.6,1.1-0.7c0.3-0.1,0.5-0.1,0.8-0.2\n\t\t\t\tc-0.1,0.5-0.1,1.1,0,1.8c0.1,0.7,1.2,0.5,1.2-0.2c0-0.6,0.1-1.1,0.4-1.5c0.6,0.2,1.1,0.4,1.8,0.2c0.4-0.2,0.7-0.5,0.7-0.9\n\t\t\t\tc0.6,0.1,1.2,0.4,1.8,0.8c0.6,0.4,1,1.1,1.7,1.3c0.6,0.2,1.1-0.3,1.2-0.9c0.1-1-0.6-1.7-1.5-2.3c0.8-1.3,2.5-1.6,4-1.1\n\t\t\t\tc0,0.5,0.3,0.9,0.8,1.1c0.6,0.1,1.2,0.3,1.8,0.3c0.1,0,0.2,0,0.3,0c0,0.7,0.2,1.5,0.4,2.2c0.2,0.6,1.1,0.5,1.1-0.1\n\t\t\t\tc-0.1-2.8,2-3.4,4.3-2.8c1.1,0.3,2.2,0.9,3.1,1.6c0.8,0.7,1.3,1.6,2.1,2.1c0.4,0.3,1,0,1.2-0.5c0.8-3.4-4.6-5.6-7.1-5.9\n\t\t\t\tc-1.4-0.2-2.9,0.2-4,1.2c-0.1,0.1-0.2,0.2-0.3,0.3c0-0.1-0.1-0.2-0.2-0.2c-2.4-2.5-8.4-2.5-9.3,1.1c-0.8-0.2-1.6-0.3-2.2-0.3\n\t\t\t\tc-0.4,0-0.8,0.1-1.1,0.2c-1.3-0.3-3,0-3.9,0.4c-0.9,0.4-1.8,1.1-2.4,1.9c-0.1,0.2-0.2,0.4-0.3,0.6c-1.6-0.1-3.2,0.2-4.5,0.7\n\t\t\t\tc-3.1,1.1-3.4,3.9-3,6.7c-3.1,0.7-6,5.5-4.7,7.9c0.1,0.9,0.5,1.8,1.2,2.4c-1.4,0.3-2.4,1.1-3,2.2c-2,2-1.9,6,0.1,8.2\n\t\t\t\tc0.2,0.2,0.3,0.4,0.5,0.6c0.2,0.1,0.3,0.2,0.5,0.2c1.2,0.3,2.5-0.8,1.7-2.1c-0.7-1.2-1-2.5-0.8-3.9c0.1-0.5,0.2-0.8,0.4-1.1\n\t\t\t\tc0.1-0.1,0.3-0.1,0.4-0.1c0.8-0.1,1.2-0.6,1.3-1.2c0.2-0.1,0.4-0.1,0.5-0.2c1.3-0.3,1.2-1.9,0.3-2.5c0.4-0.5,0.6-1.4,0.1-2\n\t\t\t\tc-1.4-1.7,0.1-4.6,2-5.5C60.5,39.5,61.3,39.4,61.4,38.9\"/>\n\t\t</g>\n\t</g>\n</g>\n</svg>\n"
        },
        {
          "path": "public/folder-preview/user4.svg",
          "target": "public/folder-preview/user4.svg",
          "type": "registry:file",
          "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 174 174\" style=\"enable-background:new 0 0 174 174;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{clip-path:url(#SVGID_2_);fill:#CE8A9E;}\n\t.st1{clip-path:url(#SVGID_4_);}\n\t.st2{fill:#924A0B;}\n\t.st3{fill:#179E85;stroke:#179E85;stroke-width:1.122;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}\n\t.st4{fill:#FFC785;}\n\t.st5{fill:#FFFFFF;}\n\t.st6{fill:url(#SVGID_5_);}\n\t.st7{fill:#2E6232;}\n\t.st8{fill:url(#SVGID_6_);}\n\t.st9{fill:#E48F67;}\n\t.st10{fill:#F0463A;}\n</style>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_1_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_2_\">\n\t\t<use xlink:href=\"#SVGID_1_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<ellipse class=\"st0\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n</g>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_3_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_4_\">\n\t\t<use xlink:href=\"#SVGID_3_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<g class=\"st1\">\n\t\t<g>\n\t\t\t<path class=\"st2\" d=\"M122.3,143.2c6.9-78.8,0.6-107.8-34.4-106.8c-45.5,1.2-45.3,40.4-33.1,109.4\n\t\t\t\tC75.1,143.8,107.4,143,122.3,143.2\"/>\n\t\t\t<path class=\"st3\" d=\"M98.7,128.5c-0.3,0.4-26.2,0.8-26.5,1.2c-8.2,11.6-25.8,16.7-39.8,19.9c-14,3.1-14.5,20.8-14.5,32.6h136\n\t\t\t\tc0-11.8-0.3-29.5-14.5-32.6C125.1,146.4,106.5,140.7,98.7,128.5L98.7,128.5z\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st4\" d=\"M72.2,103.6V126v11c-4.7,8.9,8.6,37.3,13.7,37.2c4.8-0.1,17.9-26,13.5-37.2v-11v-22.4\n\t\t\t\t\tC99.5,85.6,72.2,85.6,72.2,103.6\"/>\n\t\t\t\t<path class=\"st4\" d=\"M121.9,88.4c-2.7-1.2-6.6,1.7-8.6,6.4c-2,4.7-1.5,9.5,1.3,10.7c2.7,1.2,6.6-1.7,8.6-6.4\n\t\t\t\t\tC125.3,94.4,124.7,89.6,121.9,88.4\"/>\n\t\t\t\t<path class=\"st4\" d=\"M49.7,88.4c2.7-1.2,6.6,1.7,8.6,6.4c2,4.7,1.5,9.5-1.3,10.7c-2.7,1.2-6.6-1.7-8.6-6.4\n\t\t\t\t\tC46.4,94.4,47,89.6,49.7,88.4\"/>\n\t\t\t</g>\n\t\t\t<path class=\"st2\" d=\"M51.5,92.4C44.6,54.6,67,42.5,85.9,42.5c21.3,0,43.3,16.3,33.1,51.1c-20.3-1-38.8-13-56.7-36.8\n\t\t\t\tC59.2,69,57.1,84.7,51.5,92.4\"/>\n\t\t\t<path class=\"st4\" d=\"M85.9,126.7c-8.9,0-27.6-14-33.4-33.9c-5.9-20.2,5.2-49.1,33.4-49.1c28.2,0,39.3,28.9,33.4,49.1\n\t\t\t\tC113.5,112.8,94.7,126.7,85.9,126.7\"/>\n\t\t</g>\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st5\" d=\"M66.7,87.2c0,0,1.5,2.5,6.1,2.5c4.6,0,5.4-2.2,5.4-2.9c0,0-1.8-3-5.4-3C69.3,83.8,66.7,87.2,66.7,87.2\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_5_\" cx=\"72.28\" cy=\"89.31\" r=\"2.441\" gradientTransform=\"matrix(1 0 0 -1 0 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#2E6232\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1.900000e-02\" style=\"stop-color:#326834\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.106\" style=\"stop-color:#407D3E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.209\" style=\"stop-color:#4B8E45\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.336\" style=\"stop-color:#529A4A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.514\" style=\"stop-color:#57A14D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#58A34E\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<circle class=\"st6\" cx=\"72.6\" cy=\"86.7\" r=\"2.6\"/>\n\t\t\t\t\t\t<path class=\"st7\" d=\"M72.6,84.4c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4\n\t\t\t\t\t\t\tC70.3,85.4,71.3,84.4,72.6,84.4 M72.6,83.9c-1.6,0-2.9,1.3-2.9,2.9c0,1.6,1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9\n\t\t\t\t\t\t\tC75.5,85.1,74.2,83.9,72.6,83.9z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"72.6\" cy=\"86.7\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st5\" d=\"M104,87.2c0,0-1.5,2.5-6.1,2.5c-4.6,0-5.4-2.2-5.4-2.9c0,0,1.8-3,5.4-3S104,87.2,104,87.2\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<radialGradient id=\"SVGID_6_\" cx=\"-30.309\" cy=\"89.265\" r=\"2.619\" gradientTransform=\"matrix(-1 0 0 -1 67.85 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#2E6232\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1.900000e-02\" style=\"stop-color:#326834\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.106\" style=\"stop-color:#407D3E\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.209\" style=\"stop-color:#4B8E45\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.336\" style=\"stop-color:#529A4A\"/>\n\t\t\t\t\t\t\t<stop  offset=\"0.514\" style=\"stop-color:#57A14D\"/>\n\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#58A34E\"/>\n\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t<path class=\"st8\" d=\"M98.2,89.3c-1.4,0-2.6-1.2-2.6-2.6s1.2-2.6,2.6-2.6c1.4,0,2.6,1.2,2.6,2.6S99.6,89.3,98.2,89.3\"/>\n\t\t\t\t\t\t<path class=\"st7\" d=\"M98.2,84.4c1.3,0,2.4,1.1,2.4,2.4s-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4\n\t\t\t\t\t\t\tC95.8,85.4,96.9,84.4,98.2,84.4 M98.2,83.9c-1.6,0-2.9,1.3-2.9,2.9s1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9\n\t\t\t\t\t\t\tC101,85.1,99.7,83.9,98.2,83.9z\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<circle cx=\"98.2\" cy=\"86.7\" r=\"1.2\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<path class=\"st9\" d=\"M80.3,97.2c-0.7,0.4-0.9,1.2-0.1,1.7c1.2,0.9,3,1,4.5,1c1.5,0,3.4-0.1,4.7-1c0.7-0.4,1.2-1.8,0.2-2.2\n\t\t\t\tc-0.2-0.1-0.5,0-0.4,0.3c0,0.3,0.3,0.6,0.3,0.9c-0.1,0.4-0.7,0.6-1,0.7c-1,0.4-2.1,0.4-3.2,0.5c-0.9,0-5.8,0.1-4.8-1.7\n\t\t\t\tC80.5,97.3,80.4,97.2,80.3,97.2\"/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st2\" d=\"M61.6,80.6c5.5-1.2,11.3-1.7,16.4,1.2c0.3,0.2,0.6-0.3,0.3-0.5C73.8,78.5,66.2,77.7,61.6,80.6\n\t\t\t\t\tC61.5,80.5,61.5,80.6,61.6,80.6\"/>\n\t\t\t\t<path class=\"st2\" d=\"M109,80.5c-4.7-2.8-12.3-2-16.8,0.8c-0.3,0.2,0,0.7,0.3,0.5C97.7,78.9,103.5,79.3,109,80.5\n\t\t\t\t\tC109,80.6,109.1,80.5,109,80.5\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<path class=\"st2\" d=\"M121.5,74.5c1,0,1-1.5,0-1.5c-0.4,0-0.6,0.2-0.7,0.5c-0.5-2.5-1.1-4.9-1.7-7.3c0.2-0.4,0.1-1-0.1-1.5\n\t\t\tc-1.6-2.8-3.4-5.5-5.4-8.1c-0.7-1.5-1.8-2.8-2.5-4.4c-0.4-0.9-1.2-1.1-1.9-0.9c-0.5-0.5-0.9-1-1.4-1.5c-0.6-0.6-1.2-0.7-1.8-0.5\n\t\t\tc-0.6-0.7-1.3-1.4-2-2c-0.4-0.4-0.8-0.5-1.2-0.6c-0.1-0.2-0.3-0.4-0.4-0.6c-0.9-1.3-2.3-1.1-3.1-0.3c-0.1-0.1-0.3-0.3-0.4-0.4\n\t\t\tc-0.5-0.5-1.3-0.6-2-0.4c-1.1-1.1-2.3-2.1-3.6-2.9c-1.1-0.6-2-0.2-2.5,0.6c-0.5-0.4-1.2-0.3-1.7,0c-0.1-0.2-0.2-0.3-0.3-0.5\n\t\t\tc-0.8-1.2-2.2-0.9-2.9,0c-0.2,0-0.4-0.1-0.7-0.1c-0.4-0.4-0.8-0.8-1.2-1.2c-0.9-0.8-2.3-0.4-2.7,0.6c-0.9-0.2-1.8-0.3-2.7-0.6\n\t\t\tc-1.4-0.4-2.3,0.9-2.2,2c0,0-0.1,0-0.1,0c-1.9,0-4,0.7-4.7,1.8c-0.4,0.4-0.7,0.9-0.5,1.5c0.1,0.2,0.2,0.5,0.3,0.7\n\t\t\tc-0.2-0.2-0.4-0.4-0.5-0.7c-0.9-1.1-2.2-0.2-2.4,0.9c-0.7-0.2-1.4,0.2-1.7,0.9c-0.2-0.2-0.3-0.3-0.5-0.5c-0.9-0.8-2.8,0-2.3,1.3\n\t\t\tc0.2,0.6,0.5,1.2,0.7,1.8c-0.2-0.3-0.4-0.5-0.6-0.8c-1-1.3-2.8-0.2-2.1,1.3c0.1,0.3,0.3,0.6,0.4,0.9c-0.1,0.2-0.2,0.4-0.2,0.7\n\t\t\tc0,0,0,0-0.1-0.1c-0.8-0.9-2-0.1-1.9,0.9c-0.1-0.1-0.2-0.2-0.3-0.4c-0.9-0.9-2.4,0.3-1.8,1.4c0.3,0.5,0.6,0.9,0.9,1.4\n\t\t\tc-0.2,0.1-0.3,0.2-0.4,0.3c0,0,0,0,0,0c-0.8-1-2.3,0.3-1.7,1.3c0.3,0.4,0.5,0.9,0.8,1.3c-0.4-0.4-0.8-0.7-1.1-1.1\n\t\t\tc-0.9-0.9-2.1,0.2-1.7,1.3c0.1,0.3,0.3,0.6,0.5,1c-0.3,0.2-0.5,0.6-0.3,1c0.1,0.2,0.2,0.5,0.3,0.7c-0.5,0-1,0.4-1,0.9\n\t\t\tc-0.6,0-1,0.6-0.7,1.2c0,0.2,0.1,0.4,0.3,0.6c-0.3-0.1-0.5,0-0.8,0.1c-0.7-0.6-1.4-1.2-2.1-1.9c-0.1-0.1-0.4,0-0.3,0.2\n\t\t\tc0.4,1.2,1,2.3,1.6,3.2c-1.8,6.1-2.6,12.5-1.8,18.7c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0.1,0,0.1c-0.1,4.6,0.6,9.1,3.4,12.9\n\t\t\tc0.4,0.5,1.2,0.8,1.7,0.2c2.6-2.9,1.8-7,1.2-10.6c-1.1-6.1-1.3-12.3-1.3-18.5c6.8,3.9,17.6-0.1,24.4-2.8c9-3.5,25.4-7.5,34.4-1\n\t\t\tc0,0,0.1,0.1,0.1,0.1c0.1,0.1,0.3,0.2,0.4,0.3c0.3,0.2,0.5,0.4,0.8,0.6c0.2,0.2,0.3,0.4,0.5,0.6c0.1,0.1,0.1,0.1,0.2,0.2\n\t\t\tc0.1,1.5,0.2,3,0.3,4.5c0,4.1-0.4,8.3-0.5,12.3c0,0.9-0.5,5.6-0.6,6.7c-0.1,1.7,0.3,3.8,1.5,5c0.8,0.8,2,0.4,2.5-0.4\n\t\t\tc0.1-0.2,1-1.1,1.1-1.5c2-6.3,1.6-13,0.4-19.5C121,74.4,121.2,74.5,121.5,74.5\"/>\n\t\t<g>\n\t\t\t<path class=\"st10\" d=\"M95.3,111.5c0,0-7.7-4.7-8.8-3.2c-0.1,0.1-0.1,0.2-0.2,0.3c-0.1-0.1-0.1-0.2-0.2-0.3\n\t\t\t\tc-1.1-1.5-8.8,3.2-8.8,3.2s4.3,4,9,3.9C91.3,115.6,95.3,111.5,95.3,111.5C95.3,111.5,95.3,111.5,95.3,111.5\n\t\t\t\tC95.3,111.5,95.3,111.5,95.3,111.5 M91.8,111.5c0,0-2.7,1.1-5.7,1.1c-1.8,0-3.6-0.6-5.2-1.3c0,0,2.5-1.2,5.5-1.2\n\t\t\t\tC88.2,110.1,90.2,111,91.8,111.5\"/>\n\t\t\t<path class=\"st5\" d=\"M86.3,110.1c-2.9,0-5.5,1.2-5.5,1.2c1.6,0.7,3.4,1.3,5.2,1.3c3,0,5.7-1.1,5.7-1.1\n\t\t\t\tC90.2,111,88.2,110.1,86.3,110.1\"/>\n\t\t</g>\n\t</g>\n</g>\n</svg>\n"
        },
        {
          "path": "public/folder-preview/user5.svg",
          "target": "public/folder-preview/user5.svg",
          "type": "registry:file",
          "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 174 174\" style=\"enable-background:new 0 0 174 174;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{clip-path:url(#SVGID_2_);fill:#8FC9C3;}\n\t.st1{clip-path:url(#SVGID_4_);}\n\t.st2{fill:#9368A0;}\n\t.st3{fill:#DEA146;}\n\t.st4{fill:#FFC785;}\n\t.st5{fill:#FFFFFF;}\n\t.st6{fill:url(#SVGID_5_);}\n\t.st7{fill:#025784;}\n\t.st8{fill:url(#SVGID_6_);}\n\t.st9{fill:#E48F67;}\n\t.st10{fill:#5F5659;}\n\t.st11{fill:#F0463A;}\n</style>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_1_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_2_\">\n\t\t<use xlink:href=\"#SVGID_1_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<ellipse class=\"st0\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n</g>\n<g>\n\t<defs>\n\t\t<ellipse id=\"SVGID_3_\" cx=\"87\" cy=\"87\" rx=\"87\" ry=\"87\"/>\n\t</defs>\n\t<clipPath id=\"SVGID_4_\">\n\t\t<use xlink:href=\"#SVGID_3_\"  style=\"overflow:visible;\"/>\n\t</clipPath>\n\t<g class=\"st1\">\n\t\t<g>\n\t\t\t<path class=\"st2\" d=\"M98.9,122.2c-0.3,0.4-26.1,0.8-26.4,1.2C64.4,135,46.9,140.1,33,143.2c-13.9,3.1-14.4,20.7-14.4,32.4h135.2\n\t\t\t\tc0-11.7-0.3-29.3-14.4-32.4C125.2,140,106.6,134.3,98.9,122.2\"/>\n\t\t\t<path class=\"st3\" d=\"M132.8,117.7c-0.5-19.1-5.2-81.2-46.9-82.2c-38.7,1.2-45.7,63.3-44.4,83.5C58.2,121,116.3,121,132.8,117.7\"\n\t\t\t\t/>\n\t\t\t<g>\n\t\t\t\t<path class=\"st4\" d=\"M72.6,97.5v22.2v11c6.8,13.9,19,14.2,27.1,0v-11V97.5C99.7,79.6,72.6,79.6,72.6,97.5\"/>\n\t\t\t\t<ellipse class=\"st4\" cx=\"119.8\" cy=\"90.9\" rx=\"9.7\" ry=\"5.6\"/>\n\t\t\t\t<ellipse class=\"st4\" cx=\"53.8\" cy=\"90.9\" rx=\"9.7\" ry=\"5.6\"/>\n\t\t\t\t<path class=\"st4\" d=\"M86.2,120.4c-8.8,0-27.4-13.9-33.2-33.7c-5.8-20.1,5.1-48.8,33.2-48.8c28.1,0,39,28.8,33.2,48.8\n\t\t\t\t\tC113.6,106.6,95,120.4,86.2,120.4\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<g>\n\t\t\t<g>\n\t\t\t\t<g>\n\t\t\t\t\t<path class=\"st5\" d=\"M67.6,82.7c0,0,1.5,2.5,6.1,2.5c4.6,0,5.4-2.2,5.4-2.9c0,0-1.8-3-5.4-3S67.6,82.6,67.6,82.7\"/>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t<g>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t<radialGradient id=\"SVGID_5_\" cx=\"73.05\" cy=\"126.198\" r=\"2.441\" gradientTransform=\"matrix(1 0 0 -1 0 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#2E624A\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"7.000001e-03\" style=\"stop-color:#2F634D\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"8.000000e-02\" style=\"stop-color:#396768\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.161\" style=\"stop-color:#416B7D\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.258\" style=\"stop-color:#476E8E\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.377\" style=\"stop-color:#4B709A\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.544\" style=\"stop-color:#4D71A1\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#4E71A3\"/>\n\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t<circle class=\"st6\" cx=\"73.4\" cy=\"82.2\" r=\"2.6\"/>\n\t\t\t\t\t\t\t<path class=\"st7\" d=\"M73.4,79.9c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4S72.1,79.9,73.4,79.9\n\t\t\t\t\t\t\t\t M73.4,79.3c-1.6,0-2.9,1.3-2.9,2.9c0,1.6,1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9C76.3,80.6,75,79.3,73.4,79.3z\"/>\n\t\t\t\t\t\t</g>\n\t\t\t\t\t\t<circle cx=\"73.4\" cy=\"82.2\" r=\"1.2\"/>\n\t\t\t\t\t</g>\n\t\t\t\t</g>\n\t\t\t\t<g>\n\t\t\t\t\t<path class=\"st5\" d=\"M104.9,82.7c0,0-1.5,2.5-6.1,2.5c-4.6,0-5.4-2.2-5.4-2.9c0,0,1.8-3,5.4-3\n\t\t\t\t\t\tC102.3,79.3,104.9,82.6,104.9,82.7\"/>\n\t\t\t\t\t<g>\n\t\t\t\t\t\t<g>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t<radialGradient id=\"SVGID_6_\" cx=\"-31.139\" cy=\"93.785\" r=\"2.615\" gradientTransform=\"matrix(-1 0 0 -1 67.85 176)\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t\t\t\t\t\t<stop  offset=\"0\" style=\"stop-color:#2E624A\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"7.000001e-03\" style=\"stop-color:#2F634D\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"8.000000e-02\" style=\"stop-color:#396768\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.161\" style=\"stop-color:#416B7D\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.258\" style=\"stop-color:#476E8E\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.377\" style=\"stop-color:#4B709A\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"0.544\" style=\"stop-color:#4D71A1\"/>\n\t\t\t\t\t\t\t\t<stop  offset=\"1\" style=\"stop-color:#4E71A3\"/>\n\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t<path class=\"st8\" d=\"M99,84.8c-1.4,0-2.6-1.2-2.6-2.6c0-1.4,1.2-2.6,2.6-2.6c1.4,0,2.6,1.2,2.6,2.6\n\t\t\t\t\t\t\t\tC101.6,83.7,100.4,84.8,99,84.8\"/>\n\t\t\t\t\t\t\t<path class=\"st7\" d=\"M99,79.9c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4S97.7,79.9,99,79.9\n\t\t\t\t\t\t\t\t M99,79.3c-1.6,0-2.9,1.3-2.9,2.9c0,1.6,1.3,2.9,2.9,2.9c1.6,0,2.9-1.3,2.9-2.9C101.9,80.6,100.6,79.3,99,79.3z\"/>\n\t\t\t\t\t\t</g>\n\t\t\t\t\t\t<circle cx=\"99\" cy=\"82.2\" r=\"1.2\"/>\n\t\t\t\t\t</g>\n\t\t\t\t</g>\n\t\t\t\t<path class=\"st9\" d=\"M81.2,94.7c-0.7,0.4-0.9,1.2-0.1,1.7c1.2,0.9,3,1,4.5,1c1.5,0,3.4-0.1,4.7-1c0.7-0.4,1.2-1.8,0.2-2.2\n\t\t\t\t\tc-0.2-0.1-0.5,0-0.4,0.3c0,0.3,0.3,0.6,0.3,0.9c-0.1,0.4-0.7,0.6-1,0.7c-1,0.4-2.1,0.4-3.2,0.5c-0.9,0-5.8,0.1-4.8-1.7\n\t\t\t\t\tC81.3,94.8,81.3,94.7,81.2,94.7\"/>\n\t\t\t\t<g>\n\t\t\t\t\t<path class=\"st10\" d=\"M62.4,74.1c5.5-1.2,11.3-1.7,16.4,1.2c0.3,0.2,0.6-0.3,0.3-0.5C74.6,72,67,71.2,62.4,74.1\n\t\t\t\t\t\tC62.3,74,62.4,74.1,62.4,74.1\"/>\n\t\t\t\t\t<path class=\"st10\" d=\"M109.8,74c-4.7-2.8-12.3-2-16.8,0.8c-0.3,0.2,0,0.7,0.3,0.5C98.5,72.4,104.3,72.8,109.8,74\n\t\t\t\t\t\tC109.9,74.1,109.9,74,109.8,74\"/>\n\t\t\t\t</g>\n\t\t\t</g>\n\t\t\t<g>\n\t\t\t\t<path class=\"st11\" d=\"M95.2,107L95.2,107c0.2-0.1,0.4-0.1,0.4-0.1c-0.2,0.1-0.5,0.1-0.8,0.1l-1.9-0.2c-2.6-0.6-5.4-2.2-5.4-2.2\n\t\t\t\t\tl-1,1.2l-1-1.2c0,0-5.1,3-8,2.3c3.8,3.5,8.7,3.3,8.7,3.3c4.6,0.1,9.2-3.3,9.2-3.3C95.5,106.9,95.4,107,95.2,107 M78.7,107.1\n\t\t\t\t\tC78.7,107.1,78.7,107.1,78.7,107.1c-0.5-0.1-0.8-0.2-0.9-0.2C77.9,106.9,78.3,107,78.7,107.1\"/>\n\t\t\t\t<path class=\"st5\" d=\"M86,108.8c-1.8,0-6-1-6-1.3c0-0.1,1.8-0.3,3.5-0.6c1.4-0.2,2.7-0.6,3.1-0.6c0.4,0,1.6,0.4,2.8,0.6\n\t\t\t\t\tc1.5,0.3,3,0.4,3.2,0.5C92.8,107.5,90,108.9,86,108.8\"/>\n\t\t\t</g>\n\t\t</g>\n\t\t<path class=\"st2\" d=\"M115.8,76.4c-4.4-1.3-9-2.2-13.6-1.9c-3.9,0.3-7.8,1-11,3.5c-0.1,0.1-0.3,0.2-0.5,0.2c-0.8,0-1.6-0.1-2.4-0.1\n\t\t\tc-2.6,0-5.1,0-7.7,0c-0.2,0-0.3-0.2-0.5-0.3c-1.8-0.9-3.6-2-5.5-2.5c-6.3-1.8-12.6-0.9-18.8,0.9c-1.7,0.5-1.8,1-0.6,2.3\n\t\t\tc1.9,2.1,3.9,4.1,5.8,6.3c3.7,4.3,8.3,6.4,14,5.7c3.4-0.4,6.4-1.5,7.6-5.1c0.5-1.5,0.6-3,1-4.8c1.3-0.4,3-0.4,4.6,0.1\n\t\t\tc-0.3,3.4,0.9,7.8,5.1,9.1c5.6,1.8,10.9,1.1,15.2-3c2.8-2.7,5.4-5.7,8.1-8.5C117.4,77.4,117.2,76.8,115.8,76.4 M78.1,88.8\n\t\t\tc-4.1,1.7-8.1,1.4-11.9-1c-2.6-1.7-4.5-4-5.3-7c-0.5-1.9,0-3.1,1.9-3.7c1.7-0.5,3.4-0.6,5.2-0.9c3,0.3,5.9,0.5,8.6,1.7\n\t\t\tc1.2,0.5,2.3,1.2,3.2,2.1C82.7,83,81.9,87.2,78.1,88.8 M110.4,80.7c-1.2,5.1-6.9,9.3-12.5,9.1c-1.6-0.1-3.2-0.4-4.7-1\n\t\t\tc-3.9-1.6-4.6-6-1.7-9c2.1-2.1,4.9-2.7,7.6-3.1c1.4-0.2,2.8-0.3,4.8-0.4c1.2,0.2,2.9,0.3,4.5,0.8\n\t\t\tC110.3,77.6,110.8,78.8,110.4,80.7\"/>\n\t\t<g>\n\t\t\t<path class=\"st3\" d=\"M83.8,53c3.9,17.7,22.5,27.7,38.6,24.3c-0.5-19.9-12.5-40.9-36.3-40.9c-25.5,0-37.5,24.2-36.2,45.2\n\t\t\t\tC66.3,83.8,70.8,74.5,83.8,53\"/>\n\t\t\t<path class=\"st3\" d=\"M117.8,90.2l-3.5,1.9c0,0,4-9.4,3.6-16.4l3.5-1.1C121.4,74.5,121.3,86.3,117.8,90.2\"/>\n\t\t</g>\n\t</g>\n</g>\n</svg>\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "footer",
      "type": "registry:block",
      "dependencies": [],
      "files": [
        {
          "path": "components/block/footer.tsx",
          "target": "@components/block/footer.tsx",
          "type": "registry:block",
          "content": "import React from 'react';\n\nconst Footer: React.FC = () => {\n  return (\n    <footer className=\"relative bg-black text-white w-full min-h-[600px] flex flex-col overflow-hidden pt-20 px-6 md:px-12 lg:px-24\">\n\n      <article id=\"lighting-wrap\">\n        <article id=\"lightings\">\n          <section id=\"light-one\" className=\"lighting-section\">\n            <section id=\"light-two\" className=\"lighting-section\">\n              <section id=\"light-three\" className=\"lighting-section\">\n                <section id=\"light-four\" className=\"lighting-section\">\n                  <section id=\"light-five\" className=\"lighting-section\" />\n                </section>\n              </section>\n            </section>\n          </section>\n        </article>\n      </article>\n\n      <div className=\"flex flex-col lg:flex-row justify-between w-full h-full pb-20 z-10 relative\">\n\n        <div className=\"flex flex-col mb-32 lg:mb-28 max-w-xl\">\n\n          <div className=\"mb-8\">\n            <span className=\"inline-block px-4 py-1.5 rounded-full bg-zinc-900 text-zinc-400 text-[10px] font-medium tracking-[0.2em] uppercase\">\n              Obsidian UI\n            </span>\n          </div>\n\n          <h2 className=\"text-3xl md:text-5xl lg:text-6xl font-serif-elegant leading-tight\">\n            Build landing pages<br />\n            that feel alive<br />\n            and effortless\n          </h2>\n\n          <p className=\"mt-6 text-zinc-400 text-sm leading-relaxed max-w-md\">\n            ObsidianUI helps you build stunning landing pages using beautifully\n            animated, copy-paste ready components — crafted to be subtle,\n            tasteful, and production-ready.\n          </p>\n        </div>\n\n        <div className=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-12 lg:gap-24\">\n\n          <div className=\"flex flex-col space-y-6\">\n            <h3 className=\"text-zinc-500 text-[11px] font-medium tracking-[0.2em] uppercase\">\n              Product\n            </h3>\n            <ul className=\"flex flex-col space-y-3\">\n              <li><FooterLink href=\"#\">Components</FooterLink></li>\n              <li><FooterLink href=\"#\">Animations</FooterLink></li>\n              <li><FooterLink href=\"#\">Templates</FooterLink></li>\n              <li><FooterLink href=\"#\">Showcase</FooterLink></li>\n            </ul>\n          </div>\n\n          <div className=\"flex flex-col space-y-6\">\n            <h3 className=\"text-zinc-500 text-[11px] font-medium tracking-[0.2em] uppercase\">\n              Resources\n            </h3>\n            <ul className=\"flex flex-col space-y-3\">\n              <li><FooterLink href=\"#\">Documentation</FooterLink></li>\n              <li><FooterLink href=\"#\">Getting Started</FooterLink></li>\n              <li><FooterLink href=\"#\">Changelog</FooterLink></li>\n              <li><FooterLink href=\"#\">Roadmap</FooterLink></li>\n            </ul>\n          </div>\n\n          <div className=\"flex flex-col space-y-6\">\n            <h3 className=\"text-zinc-500 text-[11px] font-medium tracking-[0.2em] uppercase\">\n              Community\n            </h3>\n            <ul className=\"flex flex-col space-y-3\">\n              <li><FooterLink href=\"#\">GitHub</FooterLink></li>\n              <li><FooterLink href=\"#\">Discord</FooterLink></li>\n              <li><FooterLink href=\"#\">Twitter / X</FooterLink></li>\n              <li><FooterLink href=\"#\">Instagram</FooterLink></li>\n            </ul>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"absolute bottom-0 left-0 w-full pointer-events-none select-none flex justify-center overflow-hidden\">\n        <h1 className=\"text-[18vw] font-serif-elegant leading-none translate-y-[0%] tracking-tighter whitespace-nowrap wordmark-gradient\">\n          Obsidian UI\n        </h1>\n      </div>\n\n      <div className=\"mt-auto pb-8 z-10 text-center w-full relative\">\n        <p className=\"text-[10px] tracking-[0.2em] text-zinc-700 font-medium uppercase\">\n          © 2025 Obsidian UI — Crafted for modern web builders\n        </p>\n      </div>\n    </footer>\n  );\n};\n\ninterface FooterLinkProps {\n  href: string;\n  children: React.ReactNode;\n}\n\nconst FooterLink: React.FC<FooterLinkProps> = ({ href, children }) => {\n  return (\n    <a\n      href={href}\n      className=\"text-zinc-200 hover:text-white text-sm tracking-wide transition-colors duration-200 ease-in-out block\"\n    >\n      {children}\n    </a>\n  );\n};\n\nexport default Footer;"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "form",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-label",
        "@radix-ui/react-slot",
        "clsx",
        "react-hook-form",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/form.tsx",
          "target": "@ui/form.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n  Controller,\n  FormProvider,\n  useFormContext,\n  useFormState,\n  type ControllerProps,\n  type FieldPath,\n  type FieldValues,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = {\n  name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n  {} as FormFieldContextValue\n)\n\nconst FormField = <\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n  ...props\n}: ControllerProps<TFieldValues, TName>) => {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  )\n}\n\nconst useFormField = () => {\n  const fieldContext = React.useContext(FormFieldContext)\n  const itemContext = React.useContext(FormItemContext)\n  const { getFieldState } = useFormContext()\n  const formState = useFormState({ name: fieldContext.name })\n  const fieldState = getFieldState(fieldContext.name, formState)\n\n  if (!fieldContext) {\n    throw new Error(\"useFormField should be used within <FormField>\")\n  }\n\n  const { id } = itemContext\n\n  return {\n    id,\n    name: fieldContext.name,\n    formItemId: `${id}-form-item`,\n    formDescriptionId: `${id}-form-item-description`,\n    formMessageId: `${id}-form-item-message`,\n    ...fieldState,\n  }\n}\n\ntype FormItemContextValue = {\n  id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n  {} as FormItemContextValue\n)\n\nfunction FormItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const id = React.useId()\n\n  return (\n    <FormItemContext.Provider value={{ id }}>\n      <div\n        data-slot=\"form-item\"\n        className={cn(\"grid gap-2\", className)}\n        {...props}\n      />\n    </FormItemContext.Provider>\n  )\n}\n\nfunction FormLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  const { error, formItemId } = useFormField()\n\n  return (\n    <Label\n      data-slot=\"form-label\"\n      data-error={!!error}\n      className={cn(\"data-[error=true]:text-destructive\", className)}\n      htmlFor={formItemId}\n      {...props}\n    />\n  )\n}\n\nfunction FormControl({ ...props }: React.ComponentProps<typeof Slot>) {\n  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n  return (\n    <Slot\n      data-slot=\"form-control\"\n      id={formItemId}\n      aria-describedby={\n        !error\n          ? `${formDescriptionId}`\n          : `${formDescriptionId} ${formMessageId}`\n      }\n      aria-invalid={!!error}\n      {...props}\n    />\n  )\n}\n\nfunction FormDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { formDescriptionId } = useFormField()\n\n  return (\n    <p\n      data-slot=\"form-description\"\n      id={formDescriptionId}\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction FormMessage({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { error, formMessageId } = useFormField()\n  const body = error ? String(error?.message ?? \"\") : props.children\n\n  if (!body) {\n    return null\n  }\n\n  return (\n    <p\n      data-slot=\"form-message\"\n      id={formMessageId}\n      className={cn(\"text-destructive text-sm\", className)}\n      {...props}\n    >\n      {body}\n    </p>\n  )\n}\n\nexport {\n  useFormField,\n  Form,\n  FormItem,\n  FormLabel,\n  FormControl,\n  FormDescription,\n  FormMessage,\n  FormField,\n}\n"
        },
        {
          "path": "components/ui/label.tsx",
          "target": "@ui/label.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Label({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  return (\n    <LabelPrimitive.Root\n      data-slot=\"label\"\n      className={cn(\n        \"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "fractal-glass",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge",
        "three"
      ],
      "files": [
        {
          "path": "components/block/fractal-glass.jsx",
          "target": "@components/block/fractal-glass.jsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { useEffect, useRef } from\"react\";\nimport * as THREE from\"three\";\nimport { WebGLSurface, useEffectReducedMotion } from \"@/lib/effects/shared/webgl-surface\";\n\nconst vertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n`;\n\nconst fragmentShader = `\n uniform sampler2D uTexture;\n uniform vec2 uResolution;\n uniform vec2 uTextureSize;\n uniform vec2 uMouse;\n uniform float uParallaxStrength;\n uniform float uDistortionMultiplier;\n uniform float uGlassStrength;\n uniform float uStripesFrequency;\n uniform float uGlassSmoothness;\n uniform float uEdgePadding;\n\n varying vec2 vUv;\n\n vec2 getCoverUV(vec2 uv, vec2 textureSize) {\n if (textureSize.x < 1.0 || textureSize.y < 1.0) return uv;\n\n vec2 s = uResolution / textureSize;\n float scale = max(s.x, s.y);\n\n vec2 scaledSize = textureSize * scale;\n vec2 offset = (uResolution - scaledSize) * 0.5;\n\n return (uv * uResolution - offset) / scaledSize;\n }\n\n float displacement(float x, float num_stripes, float strength) {\n float modulus = 1.0 / num_stripes;\n return mod(x, modulus) * strength;\n }\n\n float fractalGlass(float x) {\n float stripeWidth = 1.0 / uStripesFrequency;\n float sampleStep = uGlassSmoothness * stripeWidth;\n float d = 0.0;\n for (int i = -5; i <= 5; i++) {\n d += displacement(x + float(i) * sampleStep, uStripesFrequency, uGlassStrength);\n }\n d = d / 11.0;\n return x + d;\n }\n\n float smoothEdge(float x, float padding) {\n float edge = padding;\n if (x < edge) {\n return smoothstep(0.0, edge, x);\n } else if (x > 1.0 - edge) {\n return smoothstep(1.0, 1.0 - edge, x);\n }\n return 1.0;\n }\n\n void main() {\n vec2 uv = vUv;\n\n float originalX = uv.x;\n\n float edgeFactor = smoothEdge(originalX, uEdgePadding);\n\n float distortedX = fractalGlass(originalX);\n\n uv.x = mix(originalX, distortedX, edgeFactor);\n\n float distortionFactor = uv.x - originalX;\n\n float parallaxDirection = -sign(0.5 - uMouse.x);\n\n vec2 parallaxOffset = vec2(\n parallaxDirection * abs(uMouse.x - 0.5) * uParallaxStrength * (1.0 + abs(distortionFactor) * uDistortionMultiplier),\n 0.0\n );\n\n parallaxOffset *= edgeFactor;\n\n uv += parallaxOffset;\n\n vec2 coverUV = getCoverUV(uv, uTextureSize);\n\n if (coverUV.x < 0.0 || coverUV.x > 1.0 || coverUV.y < 0.0 || coverUV.y > 1.0) {\n coverUV = clamp(coverUV, 0.0, 1.0);\n }\n\n vec4 color = texture2D(uTexture, coverUV);\n\n gl_FragColor = color;\n }\n`;\n\nfunction GlassStripParallax({\n imageSrc =\"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/fractal-glass/fractal-glass-img01.jpg\", // ← new prop: path/URL to image file\n videoSrc = null, \n mediaType =\"image\", \n stripesFrequency = 8.0,\n glassStrength = 0.8,\n glassSmoothness = 0.5,\n parallaxStrength = 0.6,\n distortionMultiplier = 8.0,\n edgePadding = 0.12,\n}) {\n const mountRef = useRef(null);\n const reducedMotion = useEffectReducedMotion();\n const videoRef = useRef(null); // keeps reference to video element for cleanup\n\n useEffect(() => {\n const el = mountRef.current;\n if (!el) return;\n\n let disposed = false;\n const W = Math.max(1, el.clientWidth);\n const H = Math.max(1, el.clientHeight);\n\n const renderer = new THREE.WebGLRenderer({ antialias: true });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(W, H);\n el.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);\n camera.position.z = 1;\n\n const uniforms = {\n uTexture: { value: new THREE.Texture() },\n uResolution: { value: new THREE.Vector2(W, H) },\n uTextureSize: { value: new THREE.Vector2(1, 1) },\n uMouse: { value: new THREE.Vector2(0.5, 0.5) },\n uParallaxStrength: { value: parallaxStrength },\n uDistortionMultiplier:{ value: distortionMultiplier },\n uGlassStrength: { value: glassStrength },\n uStripesFrequency: { value: stripesFrequency },\n uGlassSmoothness: { value: glassSmoothness },\n uEdgePadding: { value: edgePadding },\n };\n\n let videoEl = null;\n let videoTexture = null;\n\n if (mediaType ===\"video\" && videoSrc) {\n // ── Video path ──────────────────────────────────────────────\n videoEl = document.createElement(\"video\");\n videoEl.src = videoSrc;\n videoEl.crossOrigin =\"anonymous\";\n videoEl.loop = true;\n videoEl.muted = true;\n videoEl.playsInline = true;\n videoEl.autoplay = !reducedMotion;\n videoRef.current = videoEl;\n\n videoEl.onloadedmetadata = () => {\n uniforms.uTextureSize.value.set(videoEl.videoWidth, videoEl.videoHeight);\n };\n\n if (!reducedMotion) videoEl.play().catch(() => {\n // Autoplay blocked — still renders first frame when available\n });\n\n videoTexture = new THREE.VideoTexture(videoEl);\n videoTexture.minFilter = THREE.LinearFilter;\n videoTexture.magFilter = THREE.LinearFilter;\n videoTexture.wrapS = THREE.ClampToEdgeWrapping;\n videoTexture.wrapT = THREE.ClampToEdgeWrapping;\n uniforms.uTexture.value.dispose();\n uniforms.uTexture.value = videoTexture;\n videoEl.onloadeddata = () => { if (!disposed) renderer.render(scene, camera); };\n\n } else {\n // ── Image path ──────────────────────────────────────────────\n const loader = new THREE.TextureLoader();\n loader.crossOrigin =\"anonymous\";\n loader.load(imageSrc, (tex) => {\n if (disposed) { tex.dispose(); return; }\n uniforms.uTexture.value.dispose();\n tex.minFilter = THREE.LinearFilter;\n tex.magFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n uniforms.uTexture.value = tex;\n uniforms.uTextureSize.value.set(\n tex.image.naturalWidth || tex.image.width || 1920,\n tex.image.naturalHeight || tex.image.height || 1080\n );\n renderer.render(scene, camera);\n });\n }\n\n const geo = new THREE.PlaneGeometry(2, 2);\n const mat = new THREE.ShaderMaterial({ vertexShader, fragmentShader, uniforms });\n scene.add(new THREE.Mesh(geo, mat));\n\n const target = { x: 0.5, y: 0.5 };\n const current = { x: 0.5, y: 0.5 };\n\n const setTarget = (x, y) => {\n if (reducedMotion) return;\n const rect = el.getBoundingClientRect();\n target.x = (x - rect.left) / Math.max(1, rect.width);\n target.y = 1 - (y - rect.top) / Math.max(1, rect.height);\n };\n const onMouse = (e) => setTarget(e.clientX, e.clientY);\n el.addEventListener(\"pointermove\", onMouse);\n\n const onResize = () => {\n const w = Math.max(1, el.clientWidth), h = Math.max(1, el.clientHeight);\n renderer.setSize(w, h);\n uniforms.uResolution.value.set(w, h);\n renderer.render(scene, camera);\n };\n const observer = new ResizeObserver(onResize);\n observer.observe(el);\n\n let raf;\n const tick = () => {\n if (!reducedMotion) raf = requestAnimationFrame(tick);\n current.x += (target.x - current.x) * 0.04;\n current.y += (target.y - current.y) * 0.04;\n uniforms.uMouse.value.set(current.x, current.y);\n // For video, mark texture as needing update every frame\n if (videoTexture) videoTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n tick();\n\n return () => {\n disposed = true;\n cancelAnimationFrame(raf);\n el.removeEventListener(\"pointermove\", onMouse);\n observer.disconnect();\n if (videoEl) {\n videoEl.onloadedmetadata = null;\n videoEl.onloadeddata = null;\n videoEl.pause();\n videoEl.removeAttribute(\"src\");\n videoEl.load();\n videoRef.current = null;\n }\n uniforms.uTexture.value.dispose();\n renderer.dispose();\n mat.dispose();\n geo.dispose();\n if (el.contains(renderer.domElement)) el.removeChild(renderer.domElement);\n };\n }, [imageSrc, videoSrc, mediaType, stripesFrequency, glassStrength, glassSmoothness,\n parallaxStrength, distortionMultiplier, edgePadding, reducedMotion]);\n\n return <div ref={mountRef} className=\"absolute inset-0 h-full w-full overflow-hidden\" />;\n}\n\n/** @param {{ imageSrc?: string, videoSrc?: string | null, mediaType?: string, stripesFrequency?: number, glassStrength?: number, glassSmoothness?: number, parallaxStrength?: number, distortionMultiplier?: number, edgePadding?: number, className?: string, style?: import(\"react\").CSSProperties }} props */\nexport function FractalGlass({ imageSrc = \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/fractal-glass/fractal-glass-img01.jpg\", className, style, ...props } = {}) {\n return <WebGLSurface className={className} style={style} imageSrc={imageSrc} label=\"ObsidianUI refracted glass image\">\n   <GlassStripParallax imageSrc={imageSrc} {...props} />\n </WebGLSurface>;\n}\n"
        },
        {
          "path": "lib/effects/shared/webgl-surface.jsx",
          "target": "@lib/effects/shared/webgl-surface.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\n\nimport { Component, useSyncExternalStore } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst subscribeMotion = (notify) => {\n  const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n  query.addEventListener(\"change\", notify);\n  return () => query.removeEventListener(\"change\", notify);\n};\n\nexport function useEffectReducedMotion() {\n  return useSyncExternalStore(subscribeMotion, () => window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches, () => true);\n}\n\nlet webglAvailable;\nfunction supportsWebGL() {\n  if (webglAvailable !== undefined) return webglAvailable;\n  try {\n    const canvas = document.createElement(\"canvas\");\n    const context = canvas.getContext(\"webgl2\");\n    webglAvailable = Boolean(context);\n    context?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n  } catch {\n    webglAvailable = false;\n  }\n  return webglAvailable;\n}\nconst subscribeAvailability = () => () => {};\n\nclass SurfaceBoundary extends Component {\n  state = { failed: false };\n  static getDerivedStateFromError() { return { failed: true }; }\n  render() { return this.state.failed ? this.props.fallback : this.props.children; }\n}\n\n/** @param {{ children?: import(\"react\").ReactNode, className?: string, style?: import(\"react\").CSSProperties, imageSrc?: string, label?: string }} props */\nexport function WebGLSurface({ children, className, style, imageSrc, label = \"ObsidianUI visual effect\" }) {\n  const supported = useSyncExternalStore(subscribeAvailability, supportsWebGL, () => false);\n  const fallback = <div role=\"img\" aria-label={label} className=\"absolute inset-0 bg-cover bg-center\" style={{ backgroundImage: imageSrc ? `url(${JSON.stringify(imageSrc)})` : undefined }} />;\n  return (\n    <div className={cn(\"relative isolate h-[28rem] w-full overflow-hidden bg-black\", className)} style={{ containerType: \"size\", ...style }}>\n      {fallback}\n      {supported && <SurfaceBoundary fallback={fallback}>{children}</SurfaceBoundary>}\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "glowing-scroll-indicator",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/glowing-scroll-indicator.tsx",
          "target": "@components/block/glowing-scroll-indicator.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { motion, MotionValue, useScroll, useTransform } from 'motion/react';\nimport React, { useSyncExternalStore } from 'react';\n\nconst subscribeToDom = (notify: () => void) => {\n    const observer = new MutationObserver(notify);\n    observer.observe(document.body, { childList: true, subtree: true });\n    return () => observer.disconnect();\n};\n\nconst BARS = 40;\n\nconst ScrollBar = ({\n    index,\n    scrollProgress\n}: {\n    index: number;\n    scrollProgress: MotionValue<number>;\n}) => {\n    const thisBarPosition = index / BARS;\n    const preStep = Math.max(0, (index - 3) / BARS);\n    const postStep = Math.min(1, (index + 3) / BARS);\n\n    const height = useTransform(\n        scrollProgress,\n        [0, preStep, thisBarPosition, postStep, 1],\n        [5, 15, 35, 15, 5]\n    );\n    const opacity = useTransform(\n        scrollProgress,\n        [0, preStep, thisBarPosition, postStep, 1],\n        [0.1, 0.4, 1, 0.4, 0.1]\n    );\n    const width = useTransform(scrollProgress, [0, thisBarPosition, 1], [1.5, 5, 1.5]);\n\n    return (\n        <motion.div\n            className=\"bg-white dark:bg-white\"\n            style={{\n                height: height,\n                opacity: useTransform(opacity, (value) => `${value}`),\n                width: useTransform(width, (value) => `${value}px`)\n            }}\n        />\n    );\n};\n\nconst ScrollIndicatorBars = ({\n    container,\n    direction\n}: {\n    container: HTMLElement;\n    direction: 'vertical' | 'horizontal';\n}) => {\n    const ref = React.useRef<HTMLElement>(container);\n\n    React.useEffect(() => {\n        ref.current = container;\n    }, [container]);\n\n    const { scrollXProgress, scrollYProgress } = useScroll({ container: ref });\n\n    const scrollProgress = direction === 'vertical' ? scrollYProgress : scrollXProgress;\n    const left = useTransform(scrollProgress, [0, 1], [0, 100]);\n\n    return (\n        <div className=\"flex items-end justify-center gap-1 md:gap-2 relative w-fit\">\n            {Array.from({ length: BARS }).map((_, index) => (\n                <ScrollBar\n                    key={`scroll-bar-${index}`}\n                    index={index}\n                    scrollProgress={scrollProgress}\n                />\n            ))}\n            <motion.div\n                className=\"h-20 bg-red-700 w-1 absolute bottom-0 left-1/2 -translate-x-1/2\"\n                style={{ left: useTransform(left, (value) => `${value}%`) }}\n            >\n                <div className=\"w-3.5 h-3.5 rounded-full shadow-sm bg-red-500 absolute top-0 left-1/2 -translate-x-1/2\" />\n            </motion.div>\n        </div>\n    );\n};\n\ninterface GlowingScrollIndicatorProps {\n    scrollContainerId?: string;\n    direction?: 'vertical' | 'horizontal';\n}\n\nexport function GlowingScrollIndicator({\n    scrollContainerId = 'scroll-target',\n    direction = 'vertical'\n}: GlowingScrollIndicatorProps) {\n    const container = useSyncExternalStore(\n        subscribeToDom,\n        () => document.getElementById(scrollContainerId),\n        () => null\n    );\n\n    if (!container) return null;\n\n    return <ScrollIndicatorBars container={container} direction={direction} />;\n}\n\nexport default GlowingScrollIndicator;\n\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "grid-lift",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/grid-lift.jsx",
          "target": "@components/block/grid-lift.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport React, { useEffect, useRef, useState } from\"react\";\nimport { cn } from \"@/lib/utils\";\nimport { useEffectReducedMotion } from \"@/lib/effects/shared/webgl-surface\";\n\nconst lerp = (start, end, amount) => start + (end - start) * amount;\n\n/** @param {{ text?: string, imageSrc?: string, showControls?: boolean, className?: string, style?: import(\"react\").CSSProperties }} props */\nexport function GridLift({ text = \"OBSIDIANUI\", imageSrc = \"/effects/grid-lift/obsidianui-wordmark.svg\", showControls = false, className, style } = {}) {\n const reducedMotion = useEffectReducedMotion();\n const canvasRef = useRef(null);\n const fileInputRef = useRef(null);\n const uploadRef = useRef(null);\n useEffect(() => () => {\n   const upload = uploadRef.current;\n   if (!upload) return;\n   if (upload.reader.readyState === 1) upload.reader.abort();\n   if (upload.image) { upload.image.onload = null; upload.image.onerror = null; }\n   if (upload.url) URL.revokeObjectURL(upload.url);\n }, []);\n\n const mouseRef = useRef({ x: -9999, y: -9999, active: false });\n const cellsRef = useRef([]);\n const svgImageRef = useRef(null);\n\n const [svgName, setSvgName] = useState(\"obsidianui-wordmark.svg\");\n const [svgVersion, setSvgVersion] = useState(0);\n const [maskSourceState, setMaskSourceState] = useState(\"SVG\");\n const [maskText, setMaskText] = useState(text);\n\n const handleSetMask = setMaskSourceState;\n const maskSource = maskSourceState;\n const fontSize = 350, fontWeight = 200, maskScale = 1.08;\n const gridSpacing = 13, strokeSize = 1.15, hoverRadius = 550, hoverFalloff = 1.55;\n const interactionRange = 120, liftHeight = 58, liftRotation = -88, liftSmoothness = 0.08;\n const baseOpacity = 1, hoverOpacity = 0.6;\n const backgroundColor = \"#000000\", gridColor = \"#272727\", hoverColor = \"#ffffff\";\n const safeText =\n typeof maskText ===\"string\" && maskText.trim().length > 0 ? maskText :\"OBSIDIANUI\";\n\n const handleSVGUpload = (event) => {\n const file = event.target.files?.[0];\n if (!file) return;\n if (file.type !==\"image/svg+xml\" && !file.name.endsWith(\".svg\")) {\n alert(\"Please upload an SVG file.\");\n return;\n }\n const reader = new FileReader();\n const previous = uploadRef.current;\n if (previous?.reader.readyState === 1) previous.reader.abort();\n if (previous?.url) URL.revokeObjectURL(previous.url);\n uploadRef.current = { reader, image: null, url: null };\n reader.onload = () => {\n let svgText = typeof reader.result ===\"string\" ? reader.result :\"\";\n // Some SVGs have no intrinsic size, which results in `naturalWidth/Height` being 0\n // and the canvas draw being invisible. Ensure a width/height based on viewBox.\n if (svgText) {\n const hasWidth = /\\bwidth\\s*=/.test(svgText);\n const hasHeight = /\\bheight\\s*=/.test(svgText);\n if (!hasWidth || !hasHeight) {\n const viewBoxMatch = svgText.match(/\\bviewBox\\s*=\\s*[\"']([^\"']+)[\"']/i);\n let vw = 512;\n let vh = 512;\n if (viewBoxMatch) {\n const parts = viewBoxMatch[1].trim().split(/[\\s,]+/).map(Number);\n if (parts.length === 4 && parts.every((n) => Number.isFinite(n))) {\n vw = Math.max(1, parts[2]);\n vh = Math.max(1, parts[3]);\n }\n }\n svgText = svgText.replace(\n /<svg\\b([^>]*)>/i,\n (_match, attrs) =>\n `<svg${attrs}${hasWidth ?\"\" : ` width=\"${vw}\"`}${hasHeight ?\"\" : ` height=\"${vh}\"`}>`,\n );\n }\n }\n\n const blob = new Blob([svgText || reader.result], { type:\"image/svg+xml\" });\n const url = URL.createObjectURL(blob);\n const image = new Image();\n uploadRef.current = { reader, image, url };\n image.onload = () => {\n svgImageRef.current = image;\n setSvgName(file.name);\n setSvgVersion((v) => v + 1);\n URL.revokeObjectURL(url);\n };\n image.onerror = () => URL.revokeObjectURL(url);\n image.src = url;\n };\n reader.readAsText(file);\n // allow re-uploading the same file (fires `change` again)\n event.target.value =\"\";\n };\n\n // Default SVG mask (ObsidianUI wordmark)\n useEffect(() => {\n const image = new Image();\n image.onload = () => {\n svgImageRef.current = image;\n setSvgVersion((v) => v + 1);\n };\n image.src = imageSrc;\n return () => { image.onload = null; image.onerror = null; };\n }, [imageSrc]);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext(\"2d\", { alpha: false });\n const maskCanvas = document.createElement(\"canvas\");\n const maskCtx = maskCanvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx || !maskCtx) return;\n const surface = canvas.parentElement;\n\n let width = 0, height = 0, dpr = 1, animationFrame;\n\n const createTextMask = () => {\n maskCtx.clearRect(0, 0, width, height);\n maskCtx.save();\n maskCtx.fillStyle =\"#ffffff\";\n maskCtx.textAlign =\"center\";\n maskCtx.textBaseline =\"middle\";\n\n const lines = safeText.split(\"\\n\").filter(Boolean);\n const safeLines = lines.length > 0 ? lines : [\"OBSIDIANUI\"];\n const maxTextWidth = width * 0.72 * maskScale;\n const maxTextHeight = height * 0.48 * maskScale;\n let fittedFontSize = fontSize || 230;\n\n for (let size = fittedFontSize; size > 10; size -= 2) {\n maskCtx.font = `${fontWeight || 900} ${size}px Anton, Impact, Haettenschweiler,\"Arial Black\", sans-serif`;\n const widestLine = Math.max(...safeLines.map((l) => maskCtx.measureText(l).width));\n const totalHeight = safeLines.length * size * 0.9;\n if (widestLine <= maxTextWidth && totalHeight <= maxTextHeight) { fittedFontSize = size; break; }\n }\n\n maskCtx.font = `${fontWeight || 900} ${fittedFontSize}px Anton, Impact, Haettenschweiler,\"Arial Black\", sans-serif`;\n const lineHeight = fittedFontSize * 0.9;\n const startY = height / 2 - ((safeLines.length - 1) * lineHeight) / 2;\n safeLines.forEach((line, i) => maskCtx.fillText(line, width / 2, startY + i * lineHeight));\n maskCtx.restore();\n };\n\n const createSVGMask = () => {\n maskCtx.clearRect(0, 0, width, height);\n const image = svgImageRef.current;\n if (!image) return;\n const iw = image.naturalWidth || image.width || 1;\n const ih = image.naturalHeight || image.height || 1;\n const imageRatio = iw / ih;\n const screenRatio = width / height;\n let drawWidth, drawHeight;\n if (imageRatio > screenRatio) { drawWidth = width * 0.62 * maskScale; drawHeight = drawWidth / imageRatio; }\n else { drawHeight = height * 0.5 * maskScale; drawWidth = drawHeight * imageRatio; }\n maskCtx.save();\n maskCtx.drawImage(image, width / 2 - drawWidth / 2, height / 2 - drawHeight / 2, drawWidth, drawHeight);\n maskCtx.restore();\n };\n\n const createMask = () => {\n maskCtx.clearRect(0, 0, width, height);\n if (maskSource === \"Text\") createTextMask();\n else createSVGMask();\n };\n\n const isInsideMask = (x, y) => {\n if (x < 0 || y < 0 || x >= width || y >= height) return false;\n const pixel = maskCtx.getImageData(Math.floor(x * dpr), Math.floor(y * dpr), 1, 1).data;\n return pixel[3] > 20 || pixel[0] > 20 || pixel[1] > 20 || pixel[2] > 20;\n };\n\n const buildCells = () => {\n const cells = [];\n for (let x = 0; x <= width; x += gridSpacing) {\n for (let y = 0; y <= height; y += gridSpacing) {\n const cx = x + gridSpacing / 2;\n const cy = y + gridSpacing / 2;\n if (!isInsideMask(cx, cy)) continue;\n cells.push({\n x, y, cx, cy, lift: 0, targetLift: 0,\n topInside: isInsideMask(cx, y),\n leftInside: isInsideMask(x, cy),\n rightInside: isInsideMask(x + gridSpacing, cy),\n bottomInside: isInsideMask(cx, y + gridSpacing),\n });\n }\n }\n cellsRef.current = cells;\n };\n\n const resize = () => {\n dpr = Math.min(window.devicePixelRatio || 1, 2);\n width = Math.max(1, surface.clientWidth);\n height = Math.max(1, surface.clientHeight);\n canvas.width = width * dpr; canvas.height = height * dpr;\n\n maskCanvas.width = width * dpr; maskCanvas.height = height * dpr;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n maskCtx.setTransform(dpr, 0, 0, dpr, 0, 0);\n createMask(); buildCells();\n };\n\n const getNearestMaskDistance = (mouseX, mouseY) => {\n let nearest = Infinity;\n for (const cell of cellsRef.current) {\n const dx = cell.cx - mouseX, dy = cell.cy - mouseY;\n const d = Math.sqrt(dx * dx + dy * dy);\n if (d < nearest) nearest = d;\n }\n return nearest;\n };\n\n const getHoverInfluence = (x, y) => {\n const mouse = mouseRef.current;\n if (!mouse.active) return 0;\n if (getNearestMaskDistance(mouse.x, mouse.y) > interactionRange) return 0;\n const dx = x - mouse.x, dy = y - mouse.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n if (distance > hoverRadius) return 0;\n return Math.pow(1 - distance / hoverRadius, hoverFalloff);\n };\n\n const updateCells = () => {\n for (const cell of cellsRef.current) {\n cell.targetLift = getHoverInfluence(cell.cx, cell.cy);\n cell.lift = reducedMotion ? cell.targetLift : lerp(cell.lift, cell.targetLift, liftSmoothness);\n if (cell.lift < 0.001) cell.lift = 0;\n }\n };\n\n const drawBaseGrid = () => {\n ctx.save();\n ctx.strokeStyle = gridColor; ctx.globalAlpha = baseOpacity; ctx.lineWidth = strokeSize;\n ctx.beginPath();\n for (let x = 0; x <= width; x += gridSpacing) { ctx.moveTo(x, 0); ctx.lineTo(x, height); }\n for (let y = 0; y <= height; y += gridSpacing) { ctx.moveTo(0, y); ctx.lineTo(width, y); }\n ctx.stroke(); ctx.restore();\n };\n\n const drawCellEdges = (cell, ox, oy, alpha, lw, color) => {\n const x1 = cell.x + ox, y1 = cell.y + oy;\n const x2 = cell.x + gridSpacing + ox, y2 = cell.y + gridSpacing + oy;\n ctx.globalAlpha = alpha; ctx.strokeStyle = color; ctx.lineWidth = lw;\n ctx.beginPath();\n if (cell.topInside) { ctx.moveTo(x1, y1); ctx.lineTo(x2, y1); }\n if (cell.leftInside) { ctx.moveTo(x1, y1); ctx.lineTo(x1, y2); }\n if (cell.rightInside) { ctx.moveTo(x2, y1); ctx.lineTo(x2, y2); }\n if (cell.bottomInside) { ctx.moveTo(x1, y2); ctx.lineTo(x2, y2); }\n ctx.stroke();\n };\n\n const drawRaisedMaskGrid = () => {\n const angle = (liftRotation * Math.PI) / 180;\n const liftX = Math.cos(angle) * liftHeight;\n const liftY = Math.sin(angle) * liftHeight;\n ctx.save(); ctx.lineCap =\"square\"; ctx.lineJoin =\"miter\";\n\n for (const cell of cellsRef.current) {\n const influence = cell.lift;\n if (influence <= 0.001) continue;\n const ox = liftX * influence, oy = liftY * influence;\n const x1 = cell.x, y1 = cell.y, x2 = cell.x + gridSpacing, y2 = cell.y + gridSpacing;\n const alpha = hoverOpacity * influence;\n\n for (let i = 0; i < 30; i++) {\n const t = i / 30;\n drawCellEdges(cell, ox * t, oy * t, alpha * (0.025 + t * 0.075), strokeSize * 0.8, hoverColor);\n }\n\n ctx.globalAlpha = alpha * 0.34; ctx.strokeStyle = hoverColor;\n ctx.lineWidth = Math.max(0.6, strokeSize * 0.7); ctx.beginPath();\n if (cell.topInside) { ctx.moveTo(x1,y1); ctx.lineTo(x1+ox,y1+oy); ctx.moveTo(x2,y1); ctx.lineTo(x2+ox,y1+oy); }\n if (cell.leftInside) { ctx.moveTo(x1,y1); ctx.lineTo(x1+ox,y1+oy); ctx.moveTo(x1,y2); ctx.lineTo(x1+ox,y2+oy); }\n if (cell.rightInside) { ctx.moveTo(x2,y1); ctx.lineTo(x2+ox,y1+oy); ctx.moveTo(x2,y2); ctx.lineTo(x2+ox,y2+oy); }\n if (cell.bottomInside) { ctx.moveTo(x1,y2); ctx.lineTo(x1+ox,y2+oy); ctx.moveTo(x2,y2); ctx.lineTo(x2+ox,y2+oy); }\n ctx.stroke();\n drawCellEdges(cell, ox, oy, alpha, strokeSize + influence * 0.8, hoverColor);\n }\n ctx.restore();\n };\n\n const render = () => {\n updateCells();\n ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, width, height);\n drawBaseGrid(); drawRaisedMaskGrid();\n if (!reducedMotion) animationFrame = requestAnimationFrame(render);\n };\n\n const onPointerMove = (e) => {\n const rect = canvas.getBoundingClientRect();\n mouseRef.current.x = e.clientX - rect.left;\n mouseRef.current.y = e.clientY - rect.top;\n mouseRef.current.active = true;\n if (reducedMotion) render();\n };\n const onPointerLeave = () => { mouseRef.current.active = false; if (reducedMotion) render(); };\n\n resize(); render();\n const observer = new ResizeObserver(() => { resize(); if (reducedMotion) render(); });\n observer.observe(surface);\n canvas.addEventListener(\"pointermove\", onPointerMove);\n canvas.addEventListener(\"pointerleave\", onPointerLeave);\n\n return () => {\n cancelAnimationFrame(animationFrame);\n observer.disconnect();\n canvas.removeEventListener(\"pointermove\", onPointerMove);\n canvas.removeEventListener(\"pointerleave\", onPointerLeave);\n };\n }, [\n maskSource, safeText, fontSize, fontWeight, maskScale,\n gridSpacing, strokeSize, hoverRadius, hoverFalloff, interactionRange,\n liftHeight, liftRotation, liftSmoothness, baseOpacity, hoverOpacity,\n backgroundColor, gridColor, hoverColor, svgVersion, reducedMotion,\n ]);\n\n return (\n <>\n <div className={cn(\"relative h-[28rem] w-full overflow-hidden bg-black\", className)} style={style} data-mask-source={maskSourceState}>\n {showControls && <div className=\"absolute left-4 top-4 z-50 pointer-events-auto max-sm:bottom-3 max-sm:left-3 max-sm:right-3 max-sm:top-auto\">\n <div className=\"flex flex-col items-stretch gap-2 rounded-[12px] border border-[#232323] bg-[#0f0f0f] p-[10px] max-sm:w-full max-sm:rounded-[16px]\">\n <div className=\"flex items-center justify-between gap-[10px]\">\n <div className=\"text-[12px] font-black tracking-[0.18em] text-white\">OBSIDIANUI</div>\n <div className=\"inline-flex gap-[6px] rounded-[10px] border border-[#2a2a2a] bg-[#141414] p-1\">\n <button\n type=\"button\"\n className={`h-[26px] cursor-pointer rounded-[8px] border px-[10px] text-[12px] font-semibold ${\n maskSourceState ===\"Text\"\n ? \"border-[#ff5f00] bg-[#ff5f00] text-black\"\n : \"border-transparent bg-transparent text-[#bdbdbd] hover:bg-[#1f1f1f] hover:text-white\"\n }`}\n onClick={() => handleSetMask(\"Text\")}\n >\n Text\n </button>\n <button\n type=\"button\"\n className={`h-6.5 cursor-pointer rounded-md border px-2.5 text-[12px] font-semibold ${\n maskSourceState ===\"SVG\"\n ? \"border-[#ff5f00] bg-[#ff5f00] text-black\"\n : \"border-transparent bg-transparent text-[#bdbdbd] hover:bg-[#1f1f1f] hover:text-white\"\n }`}\n onClick={() => handleSetMask(\"SVG\")}\n >\n SVG\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2.5 max-sm:w-full\">\n {maskSourceState ===\"Text\" && (\n <input\n className=\"h-7.5 w-full rounded-md border border-[#2a2a2a] bg-[#141414] px-2.5 text-[12px] font-bold tracking-[0.04em] text-white outline-none focus:border-[#ff5f00] max-sm:min-w-0 max-sm:flex-1\"\n value={maskText}\n onChange={(e) => setMaskText(e.target.value)}\n aria-label=\"Grid mask text\"\n placeholder=\"Text…\"\n />\n )}\n {maskSourceState ===\"SVG\" && (\n <button\n type=\"button\"\n className=\"h-7.5 cursor-pointer rounded-md border border-[#2a2a2a] bg-[#141414] px-2.5 text-[12px] font-bold text-white hover:border-[#ff5f00] hover:bg-[#1f1f1f]\"\n onClick={() => fileInputRef.current?.click()}\n title={svgName}\n >\n Upload\n </button>\n )}\n </div>\n <div className=\"mt-0.5 hidden text-[3.5vw] font-semibold tracking-[0.02em] text-white/70 pointer-events-none max-sm:block\">\n Best on desktop: hover and drift through the grid - mobile shows a preview.\n </div>\n </div>\n </div>}\n <canvas\n ref={canvasRef}\n aria-label=\"Interactive raised ObsidianUI grid\"\n role=\"img\"\n className=\"absolute inset-0 block h-full w-full cursor-crosshair\"\n style={{ background: backgroundColor }}\n />\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".svg,image/svg+xml\"\n onChange={handleSVGUpload}\n style={{ display:\"none\" }}\n />\n </div>\n </>\n );\n}\n"
        },
        {
          "path": "lib/effects/shared/webgl-surface.jsx",
          "target": "@lib/effects/shared/webgl-surface.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\n\nimport { Component, useSyncExternalStore } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst subscribeMotion = (notify) => {\n  const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n  query.addEventListener(\"change\", notify);\n  return () => query.removeEventListener(\"change\", notify);\n};\n\nexport function useEffectReducedMotion() {\n  return useSyncExternalStore(subscribeMotion, () => window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches, () => true);\n}\n\nlet webglAvailable;\nfunction supportsWebGL() {\n  if (webglAvailable !== undefined) return webglAvailable;\n  try {\n    const canvas = document.createElement(\"canvas\");\n    const context = canvas.getContext(\"webgl2\");\n    webglAvailable = Boolean(context);\n    context?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n  } catch {\n    webglAvailable = false;\n  }\n  return webglAvailable;\n}\nconst subscribeAvailability = () => () => {};\n\nclass SurfaceBoundary extends Component {\n  state = { failed: false };\n  static getDerivedStateFromError() { return { failed: true }; }\n  render() { return this.state.failed ? this.props.fallback : this.props.children; }\n}\n\n/** @param {{ children?: import(\"react\").ReactNode, className?: string, style?: import(\"react\").CSSProperties, imageSrc?: string, label?: string }} props */\nexport function WebGLSurface({ children, className, style, imageSrc, label = \"ObsidianUI visual effect\" }) {\n  const supported = useSyncExternalStore(subscribeAvailability, supportsWebGL, () => false);\n  const fallback = <div role=\"img\" aria-label={label} className=\"absolute inset-0 bg-cover bg-center\" style={{ backgroundImage: imageSrc ? `url(${JSON.stringify(imageSrc)})` : undefined }} />;\n  return (\n    <div className={cn(\"relative isolate h-[28rem] w-full overflow-hidden bg-black\", className)} style={{ containerType: \"size\", ...style }}>\n      {fallback}\n      {supported && <SurfaceBoundary fallback={fallback}>{children}</SurfaceBoundary>}\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        },
        {
          "path": "public/effects/grid-lift/obsidianui-wordmark.svg",
          "target": "public/effects/grid-lift/obsidianui-wordmark.svg",
          "type": "registry:file",
          "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1400\" height=\"260\" viewBox=\"0 0 1400 260\"><text x=\"700\" y=\"200\" text-anchor=\"middle\" fill=\"white\" font-family=\"Impact,Arial Black,sans-serif\" font-weight=\"900\" font-size=\"215\" textLength=\"1370\" lengthAdjust=\"spacingAndGlyphs\">OBSIDIANUI</text></svg>"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "horizontal-scroll",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/horizontal-scroll.tsx",
          "target": "@components/block/horizontal-scroll.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React, { useRef } from 'react';\nimport Image from 'next/image';\nimport { motion, MotionValue, useScroll, useTransform } from 'motion/react';\n\nconst HorizontalScrollItem = ({\n    image,\n    index,\n    isLeft,\n    scrollYProgress,\n    totalItems\n}: {\n    image: string;\n    index: number;\n    isLeft: boolean;\n    scrollYProgress: MotionValue<number>;\n    totalItems: number;\n}) => {\n    const direction = isLeft ? -1 : 1;\n    const position = index / totalItems;\n\n    const translateX = useTransform(\n        scrollYProgress,\n        [0, position, 1],\n        [-index * 500 * direction, 0, (totalItems - index) * 500 * direction]\n    );\n    const rotateY = useTransform(\n        scrollYProgress,\n        [0, position, 1],\n        [index * 35 * direction, 0, (totalItems - index) * -35 * direction]\n    );\n    const blur = useTransform(scrollYProgress, [0, position, 1], [index * 2, 0, (totalItems - index) * 2]);\n    const contrast = useTransform(scrollYProgress, [0, position, 1], [index * 0.5, 1, (totalItems - index) * 0.5]);\n\n    const translateY = useTransform(\n        scrollYProgress,\n        [0, position, 1],\n        [(totalItems - index) * 20, totalItems * 20, index * 20]\n    );\n\n    const filter = useTransform([blur, contrast], ([blur, contrast]) => `blur(${blur}px) contrast(${contrast})`);\n\n    return (\n        <motion.div\n            style={{\n                translateX,\n                filter,\n                translateY: useTransform(translateY, (value) => `${value - 400}px`),\n                perspective: 1000,\n                transformStyle: 'preserve-3d'\n            }}\n            className=\"h-[300px] max-w-sm w-full absolute rounded-xl overflow-visible\"\n        >\n            <motion.div style={{ rotateY }}>\n                <Image src={image} alt={image} width={1000} height={1000} className=\"object-cover h-[300px] w-full rounded-xl\" />\n            </motion.div>\n        </motion.div>\n    );\n};\n\ninterface HorizontalScrollProps {\n    items: { image: string }[];\n}\n\nexport function HorizontalScroll({ items }: HorizontalScrollProps) {\n    const ref = useRef<HTMLDivElement>(null);\n\n    const { scrollYProgress } = useScroll({\n        container: ref,\n        offset: ['start start', 'end end']\n    });\n\n    return (\n        <div ref={ref} className=\"h-full w-full overflow-y-auto relative\" style={{ minHeight: '400px' }}>\n            <div className=\"h-full w-full absolute top-0 left-0\">\n                <div className=\"w-full\" style={{ height: (items.length - 3) * 300 }} />\n            </div>\n            <div className=\"grid grid-cols-1 gap-12 h-full w-full sticky top-0 left-0\">\n                <div className=\"flex flex-col h-full justify-center w-full items-center relative\">\n                    {items.map((image, index) => (\n                        <HorizontalScrollItem\n                            key={`horizontal-scroll-item-${index}`}\n                            image={image.image}\n                            index={index}\n                            isLeft={true}\n                            scrollYProgress={scrollYProgress}\n                            totalItems={items.length}\n                        />\n                    ))}\n                </div>\n            </div>\n        </div>\n    );\n}\n\nexport default HorizontalScroll;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "hover-card",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-hover-card",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/hover-card.tsx",
          "target": "@ui/hover-card.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as HoverCardPrimitive from \"@radix-ui/react-hover-card\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction HoverCard({\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {\n  return <HoverCardPrimitive.Root data-slot=\"hover-card\" {...props} />\n}\n\nfunction HoverCardTrigger({\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {\n  return (\n    <HoverCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />\n  )\n}\n\nfunction HoverCardContent({\n  className,\n  align = \"center\",\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {\n  return (\n    <HoverCardPrimitive.Portal data-slot=\"hover-card-portal\">\n      <HoverCardPrimitive.Content\n        data-slot=\"hover-card-content\"\n        align={align}\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden\",\n          className\n        )}\n        {...props}\n      />\n    </HoverCardPrimitive.Portal>\n  )\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "hover-img",
      "type": "registry:block",
      "dependencies": [
        "gsap"
      ],
      "files": [
        {
          "path": "components/block/hover-img.css",
          "target": "@components/block/hover-img.css",
          "type": "registry:file",
          "content": ".hover-img-container {\n  --hi-bg: #f2f2f2;\n  --hi-text: #000000;\n  --hi-text-muted: #666666;\n  --hi-border: rgba(0, 0, 0, 0.15);\n\n  font-family: \"Raleway\", \"Inter\", system-ui, sans-serif;\n  min-height: 100vh;\n  width: 100%;\n  background: var(--hi-bg);\n  color: var(--hi-text);\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  position: relative;\n  overflow: hidden;\n}\n\n/* Dark mode support */\n.dark .hover-img-container,\n:root[class~=\"dark\"] .hover-img-container {\n  --hi-bg: #0a0a0a;\n  --hi-text: #ffffff;\n  --hi-text-muted: #999999;\n  --hi-border: rgba(255, 255, 255, 0.2);\n}\n\n.hover-img-projects {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: 1200px;\n}\n\n.hover-img-project {\n  width: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 2rem 4rem;\n  border-top: 1px solid var(--hi-border);\n  cursor: pointer;\n  transition: opacity 0.5s ease;\n}\n\n.hover-img-project:last-child {\n  border-bottom: 1px solid var(--hi-border);\n}\n\n.hover-img-project h2 {\n  font-size: 2.5rem;\n  font-weight: 500;\n  letter-spacing: -0.02em;\n  transition: transform 0.5s ease;\n  margin: 0;\n}\n\n.hover-img-project p {\n  font-size: 1rem;\n  font-weight: 400;\n  color: var(--hi-text-muted);\n  transition: transform 0.5s ease;\n  margin: 0;\n}\n\n.hover-img-project:hover {\n  opacity: 0.5;\n}\n\n.hover-img-project:hover h2 {\n  transform: translateX(-15px);\n}\n\n.hover-img-project:hover p {\n  transform: translateX(15px);\n}\n\n.hover-img-thumbnail-wrapper {\n  position: fixed;\n  width: 400px;\n  height: 250px;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  pointer-events: none;\n  top: 0;\n  left: 0;\n  transform-origin: center center;\n  z-index: 100;\n  border-radius: 12px;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3);\n}\n\n.hover-img-thumbnail {\n  width: 100%;\n  height: 100%;\n  flex-shrink: 0;\n}\n\n.hover-img-thumbnail img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n}\n\n/* Responsive */\n@media (max-width: 1024px) {\n  .hover-img-project {\n    padding: 2rem 4rem;\n  }\n\n  .hover-img-project h2 {\n    font-size: 2.5rem;\n  }\n}\n\n@media (max-width: 768px) {\n  .hover-img-project {\n    padding: 1.5rem 2rem;\n  }\n\n  .hover-img-project h2 {\n    font-size: 1.75rem;\n  }\n\n  .hover-img-project p {\n    font-size: 0.875rem;\n  }\n\n  .hover-img-thumbnail-wrapper {\n    display: none;\n  }\n}\n.hover-img-compact {\n  min-height: auto;\n  padding: 0;\n}\n\n.hover-img-compact .hover-img-project {\n  padding: 0.75rem 1rem;\n}\n\n.hover-img-compact .hover-img-project h2 {\n  font-size: 1.125rem;\n}\n\n.hover-img-compact .hover-img-project p {\n  font-size: 0.7rem;\n}\n\n.hover-img-compact .hover-img-thumbnail-wrapper {\n  width: 160px;\n  height: 100px;\n  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2);\n}\n"
        },
        {
          "path": "components/block/hover-img.tsx",
          "target": "@components/block/hover-img.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport React, { useRef, useEffect } from \"react\";\nimport gsap from \"gsap\";\nimport \"@/components/block/hover-img.css\";\n\ninterface ProjectItem {\n    title: string;\n    label: string;\n    imageSrc: string;\n}\n\nconst defaultProjects: ProjectItem[] = [\n    {\n        title: \"Shree Krishna\",\n        label: \"The Supreme Personality of Godhead\",\n        imageSrc: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/hover-img/hover-img-img01-alt.jpg\",\n    },\n    {\n        title: \"Radha Krishna\",\n        label: \"The Divine Couple\",\n        imageSrc: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/hover-img/hover-img-img02.jpg\",\n    },\n    {\n        title: \"Divine Love\",\n        label: \"Eternal Bond\",\n        imageSrc: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/hover-img/hover-img-img03.jpg\",\n    },\n];\n\ninterface HoverImgProps {\n    projects?: ProjectItem[];\n    className?: string;\n    isContained?: boolean; // New prop for grid previews\n    compact?: boolean; // New prop for compact layout\n}\n\nexport function HoverImg({ projects = defaultProjects, className, isContained = false, compact = false }: HoverImgProps) {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const thumbnailRef = useRef<HTMLDivElement>(null);\n    const xToRef = useRef<gsap.QuickToFunc | null>(null);\n    const yToRef = useRef<gsap.QuickToFunc | null>(null);\n\n    useEffect(() => {\n        const projectThumbnail = thumbnailRef.current;\n        const projectsContainer = containerRef.current?.querySelector(\n            \".hover-img-projects\"\n        ) as HTMLElement | null;\n\n        if (!projectThumbnail || !projectsContainer) return;\n\n        const projectElements = gsap.utils.toArray(\n            \".hover-img-project\",\n            projectsContainer\n        ) as HTMLElement[];\n        const thumbnails = gsap.utils.toArray(\n            \".hover-img-thumbnail\",\n            projectThumbnail\n        ) as HTMLElement[];\n\n        gsap.set(projectThumbnail, { scale: 0, xPercent: -50, yPercent: -50 });\n\n        xToRef.current = gsap.quickTo(projectThumbnail, \"x\", {\n            duration: 0.4,\n            ease: \"power3.out\",\n        });\n        yToRef.current = gsap.quickTo(projectThumbnail, \"y\", {\n            duration: 0.4,\n            ease: \"power3.out\",\n        });\n\n        const handleMouseMove = (e: MouseEvent) => {\n            let x = e.clientX;\n            let y = e.clientY;\n\n            if (isContained && containerRef.current) {\n                const rect = containerRef.current.getBoundingClientRect();\n                x = e.clientX - rect.left;\n                y = e.clientY - rect.top;\n            }\n\n            xToRef.current?.(x);\n            yToRef.current?.(y);\n        };\n\n        const handleMouseLeave = () => {\n            gsap.to(projectThumbnail, {\n                scale: 0,\n                duration: 0.3,\n                ease: \"power2.out\",\n                overwrite: \"auto\",\n            });\n        };\n\n        projectsContainer.addEventListener(\"mousemove\", handleMouseMove);\n        projectsContainer.addEventListener(\"mouseleave\", handleMouseLeave);\n\n        const projectListeners: Array<() => void> = [];\n\n        projectElements.forEach((project, index) => {\n            const handleMouseEnter = () => {\n                gsap.to(projectThumbnail, {\n                    scale: 1,\n                    duration: 0.4,\n                    ease: \"power2.out\",\n                    overwrite: \"auto\",\n                });\n\n                gsap.to(thumbnails, {\n                    yPercent: -100 * index,\n                    duration: 0.4,\n                    ease: \"power2.out\",\n                    overwrite: \"auto\",\n                });\n            };\n\n            project.addEventListener(\"mouseenter\", handleMouseEnter);\n            projectListeners.push(() =>\n                project.removeEventListener(\"mouseenter\", handleMouseEnter)\n            );\n        });\n\n        return () => {\n            projectsContainer.removeEventListener(\"mousemove\", handleMouseMove);\n            projectsContainer.removeEventListener(\"mouseleave\", handleMouseLeave);\n            projectListeners.forEach((cleanup) => cleanup());\n        };\n    }, [projects, isContained]);\n\n    return (\n        <div className={`hover-img-container ${compact ? \"hover-img-compact\" : \"\"} ${className || \"\"}`} ref={containerRef}>\n            <div className=\"hover-img-projects\">\n                {projects.map((project, index) => (\n                    <div className=\"hover-img-project\" key={index}>\n                        <h2>{project.title}</h2>\n                        <p>{project.label}</p>\n                    </div>\n                ))}\n            </div>\n\n            <div\n                className=\"hover-img-thumbnail-wrapper\"\n                ref={thumbnailRef}\n                style={isContained ? { position: \"absolute\" } : undefined}\n            >\n                {projects.map((project, index) => (\n                    <div className=\"hover-img-thumbnail\" key={index}>\n                        {/* eslint-disable-next-line @next/next/no-img-element */}\n                        <img src={project.imageSrc} alt={project.title} />\n                    </div>\n                ))}\n            </div>\n        </div>\n    );\n}\n\nexport default HoverImg;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "input",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/input.tsx",
          "target": "@ui/input.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        \"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n        \"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "input-group",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "target": "@ui/input-group.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Textarea } from \"@/components/ui/textarea\"\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"input-group\"\n      role=\"group\"\n      className={cn(\n        \"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none\",\n        \"h-9 min-w-0 has-[>textarea]:h-auto\",\n\n        // Variants based on alignment.\n        \"has-[>[data-align=inline-start]]:[&>input]:pl-2\",\n        \"has-[>[data-align=inline-end]]:[&>input]:pr-2\",\n        \"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3\",\n        \"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3\",\n\n        // Focus state.\n        \"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]\",\n\n        // Error state.\n        \"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40\",\n\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupAddonVariants = cva(\n  \"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50\",\n  {\n    variants: {\n      align: {\n        \"inline-start\":\n          \"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]\",\n        \"inline-end\":\n          \"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]\",\n        \"block-start\":\n          \"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5\",\n        \"block-end\":\n          \"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5\",\n      },\n    },\n    defaultVariants: {\n      align: \"inline-start\",\n    },\n  }\n)\n\nfunction InputGroupAddon({\n  className,\n  align = \"inline-start\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"input-group-addon\"\n      data-align={align}\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest(\"button\")) {\n          return\n        }\n        e.currentTarget.parentElement?.querySelector(\"input\")?.focus()\n      }}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupButtonVariants = cva(\n  \"text-sm shadow-none flex gap-2 items-center\",\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2\",\n        sm: \"h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5\",\n        \"icon-xs\":\n          \"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0\",\n        \"icon-sm\": \"size-8 p-0 has-[>svg]:p-0\",\n      },\n    },\n    defaultVariants: {\n      size: \"xs\",\n    },\n  }\n)\n\nfunction InputGroupButton({\n  className,\n  type = \"button\",\n  variant = \"ghost\",\n  size = \"xs\",\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, \"size\"> &\n  VariantProps<typeof inputGroupButtonVariants>) {\n  return (\n    <Button\n      type={type}\n      data-size={size}\n      variant={variant}\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\n        \"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<\"input\">) {\n  return (\n    <Input\n      data-slot=\"input-group-control\"\n      className={cn(\n        \"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<\"textarea\">) {\n  return (\n    <Textarea\n      data-slot=\"input-group-control\"\n      className={cn(\n        \"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n}\n"
        },
        {
          "path": "components/ui/input.tsx",
          "target": "@ui/input.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        \"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n        \"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "target": "@ui/textarea.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n  return (\n    <textarea\n      data-slot=\"textarea\"\n      className={cn(\n        \"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Textarea }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "input-otp",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "input-otp",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/input-otp.tsx",
          "target": "@ui/input-otp.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { OTPInput, OTPInputContext } from \"input-otp\"\nimport { MinusIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction InputOTP({\n  className,\n  containerClassName,\n  ...props\n}: React.ComponentProps<typeof OTPInput> & {\n  containerClassName?: string\n}) {\n  return (\n    <OTPInput\n      data-slot=\"input-otp\"\n      containerClassName={cn(\n        \"flex items-center gap-2 has-disabled:opacity-50\",\n        containerClassName\n      )}\n      className={cn(\"disabled:cursor-not-allowed\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputOTPGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"input-otp-group\"\n      className={cn(\"flex items-center\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputOTPSlot({\n  index,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  index: number\n}) {\n  const inputOTPContext = React.useContext(OTPInputContext)\n  const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}\n\n  return (\n    <div\n      data-slot=\"input-otp-slot\"\n      data-active={isActive}\n      className={cn(\n        \"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]\",\n        className\n      )}\n      {...props}\n    >\n      {char}\n      {hasFakeCaret && (\n        <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n          <div className=\"animate-caret-blink bg-foreground h-4 w-px duration-1000\" />\n        </div>\n      )}\n    </div>\n  )\n}\n\nfunction InputOTPSeparator({ ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div data-slot=\"input-otp-separator\" role=\"separator\" {...props}>\n      <MinusIcon />\n    </div>\n  )\n}\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "interactive-arrows",
      "type": "registry:block",
      "dependencies": [
        "@radix-ui/react-select",
        "clsx",
        "lucide-react",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/interactive-arrows.jsx",
          "target": "@components/block/interactive-arrows.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport Arrows from \"@/lib/effects/interactive-arrows/arrows\";\nimport ArrowsOpacity from \"@/lib/effects/interactive-arrows/arrows-opacity\";\nimport ArrowsLimit from \"@/lib/effects/interactive-arrows/arrows-limit\";\nimport ArrowsPlay from \"@/lib/effects/interactive-arrows/arrows-play\";\nimport Lines from \"@/lib/effects/interactive-arrows/lines\";\nimport Points from \"@/lib/effects/interactive-arrows/points\";\n\nconst engines = { arrows: Arrows, opacity: ArrowsOpacity, smooth: ArrowsLimit, playful: ArrowsPlay, lines: Lines, points: Points };\nconst behaviors = [\n  { value: \"arrows\", label: \"Responsive arrows\" },\n  { value: \"opacity\", label: \"Opacity\" },\n  { value: \"smooth\", label: \"Smooth arrows\" },\n  { value: \"playful\", label: \"Playful arrows\" },\n  { value: \"lines\", label: \"Lines\" },\n  { value: \"points\", label: \"Points\" },\n];\n\n/** @param {{ variant?: \"arrows\" | \"opacity\" | \"smooth\" | \"playful\" | \"lines\" | \"points\", showControls?: boolean, className?: string, height?: import(\"react\").CSSProperties[\"height\"], style?: import(\"react\").CSSProperties }} props */\nexport function InteractiveArrows({ variant = \"arrows\", showControls = false, className, height = 400, style } = {}) {\n  const [selected, setSelected] = useState(variant);\n  const active = showControls ? selected : variant;\n  const Engine = engines[active] || Arrows;\n  const dark = active === \"smooth\" || active === \"playful\";\n\n  return <div className={cn(\"relative isolate flex w-full flex-col bg-background\", className)} style={{ height, containerType: \"inline-size\", ...style }}>\n    <div data-arrow-stage=\"\" className=\"relative min-h-0 flex-1 overflow-hidden rounded-xl\" style={{ background: dark ? \"#101010\" : \"#f5f4ef\", color: dark ? \"#fff\" : \"#111\" }}>\n      <Engine />\n    </div>\n    {showControls && <div data-arrow-controls=\"\" className=\"flex shrink-0 items-center justify-between gap-3 bg-background px-3 py-2.5 text-foreground\">\n      <span className=\"text-xs text-muted-foreground\">Arrow behavior</span>\n      <Select value={selected} onValueChange={setSelected}>\n        <SelectTrigger aria-label=\"Arrow behavior\" className=\"h-9 min-w-[170px] rounded-lg border-border/70 bg-background text-xs shadow-none transition-[background-color,border-color] duration-150 hover:bg-muted/60 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/20 dark:bg-background dark:hover:bg-muted/60 [&_svg]:transition-transform [&_svg]:duration-200 data-[state=open]:[&_svg]:rotate-180 motion-reduce:transition-none motion-reduce:[&_svg]:transition-none\">\n          <SelectValue />\n        </SelectTrigger>\n        <SelectContent side=\"bottom\" align=\"end\" sideOffset={6} position=\"popper\" className=\"rounded-xl border-border/70 p-1 shadow-[0_8px_28px_-12px_rgb(0_0_0/0.22)] data-[state=open]:duration-250 data-[state=closed]:duration-150 data-[state=open]:zoom-in-97 data-[state=closed]:zoom-out-99 motion-reduce:animate-none\">\n          {behaviors.map(behavior => <SelectItem key={behavior.value} value={behavior.value} className=\"min-h-9 rounded-lg px-2.5 text-xs transition-colors duration-150 motion-reduce:transition-none\">{behavior.label}</SelectItem>)}\n        </SelectContent>\n      </Select>\n    </div>}\n  </div>;\n}\n"
        },
        {
          "path": "components/ui/select.tsx",
          "target": "@ui/select.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Select({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n  return <SelectPrimitive.Root data-slot=\"select\" {...props} />\n}\n\nfunction SelectGroup({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />\n}\n\nfunction SelectValue({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Value>) {\n  return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />\n}\n\nfunction SelectTrigger({\n  className,\n  size = \"default\",\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n  size?: \"sm\" | \"default\"\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"size-4 opacity-50\" />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  )\n}\n\nfunction SelectContent({\n  className,\n  children,\n  position = \"popper\",\n  align = \"center\",\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Content\n        data-slot=\"select-content\"\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md\",\n          position === \"popper\" &&\n            \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n          className\n        )}\n        position={position}\n        align={align}\n        {...props}\n      >\n        <SelectScrollUpButton />\n        <SelectPrimitive.Viewport\n          className={cn(\n            \"p-1\",\n            position === \"popper\" &&\n              \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1\"\n          )}\n        >\n          {children}\n        </SelectPrimitive.Viewport>\n        <SelectScrollDownButton />\n      </SelectPrimitive.Content>\n    </SelectPrimitive.Portal>\n  )\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Label>) {\n  return (\n    <SelectPrimitive.Label\n      data-slot=\"select-label\"\n      className={cn(\"text-muted-foreground px-2 py-1.5 text-xs\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  )\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n  return (\n    <SelectPrimitive.ScrollUpButton\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronUpIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollUpButton>\n  )\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n  return (\n    <SelectPrimitive.ScrollDownButton\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollDownButton>\n  )\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n}\n"
        },
        {
          "path": "lib/effects/interactive-arrows/arrows-limit.jsx",
          "target": "@lib/effects/interactive-arrows/arrows-limit.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\nimport { useEffect, useRef } from 'react';\nimport { useReducedMotion } from 'motion/react';\n\nconst ArrowsLimit = ({ rows = 3, columns = 6 }) => {\n  const canvasRef = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const mouseRef = useRef({ x: 0, y: 0 });\n  const arrowsRef = useRef([]);\n  const animationFrameRef = useRef(null);\n\n  useEffect(() => {\n    class Point {\n      constructor(x, y) {\n        this.x = x || 0;\n        this.y = y || 0;\n      }\n    }\n\n    class Arrow {\n      constructor(position) {\n        this.pos = position;\n        this.dx = 0;\n        this.dy = 0;\n        this.angle = 0;\n        this.ease = 0.1;\n      }\n\n      update(mouseX, mouseY) {\n        const targetDx = mouseX - this.pos.x;\n        const targetDy = mouseY - this.pos.y;\n        this.dx += (targetDx - this.dx) * this.ease * 0.35;\n        this.dy += (targetDy - this.dy) * this.ease * 0.35;\n        this.angle = Math.atan2(this.dy, this.dx);\n      }\n\n      draw(ctx) {\n        ctx.save();\n        ctx.translate(this.pos.x, this.pos.y);\n        ctx.rotate(this.angle);\n        const arrowScale = Math.min(1, canvasRef.current.width / 800);\n        ctx.scale(arrowScale, arrowScale);\n        ctx.beginPath();\n\n        ctx.moveTo(50, 0);\n        ctx.lineTo(-50, 0);\n        ctx.moveTo(50, 0);\n        ctx.lineTo(10, -40);\n        ctx.moveTo(50, 0);\n        ctx.lineTo(10, 40);\n        ctx.lineWidth = 2;\n        ctx.strokeStyle = 'white';\n        ctx.stroke();\n\n        ctx.restore();\n      }\n    }\n\n    const initializeArrows = (canvas) => {\n      const arrows = [];\n      const spacingX = canvas.width / (columns + 1);\n      const spacingY = canvas.height / (rows + 1);\n\n      for (let y = 1;y <= rows;y++) {\n        for (let x = 1;x <= columns;x++) {\n          arrows.push(\n            new Arrow(\n              new Point(\n                x * spacingX,\n                y * spacingY\n              )\n            )\n          );\n        }\n      }\n      return arrows;\n    };\n\n    const handleResize = () => {\n      const canvas = canvasRef.current;\n      if (canvas) {\n        canvas.width = canvas.parentElement.clientWidth || 1;\n        canvas.height = canvas.parentElement.clientHeight || 1;\n        arrowsRef.current = initializeArrows(canvas);\n      }\n    };\n\n    const handleMouseMove = (e) => {\n      if (!motionEnabled) return;\n      const canvas = canvasRef.current;\n      const rect = canvas.getBoundingClientRect();\n      if (\n        e.clientX >= rect.left &&\n        e.clientX <= rect.right &&\n        e.clientY >= rect.top &&\n        e.clientY <= rect.bottom\n      ) {\n        mouseRef.current = {\n          x: e.clientX - rect.left,\n          y: e.clientY - rect.top,\n        };\n      }\n    };\n\n    const main = () => {\n      const canvas = canvasRef.current;\n      const ctx = canvas?.getContext('2d');\n      if (!ctx) return;\n      const arrows = arrowsRef.current;\n      const mouse = mouseRef.current;\n\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n      arrows.forEach(arrow => {\n        arrow.update(mouse.x, mouse.y);\n        arrow.draw(ctx);\n      });\n\n      if (motionEnabled) animationFrameRef.current = requestAnimationFrame(main);\n    };\n\n    const canvas = canvasRef.current;\n    if (!canvas || !canvas.getContext(\"2d\")) return;\n    canvas.width = canvas.parentElement.clientWidth || 1;\n    canvas.height = canvas.parentElement.clientHeight || 1;\n\n    arrowsRef.current = initializeArrows(canvas);\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(canvas.parentElement);\n    canvas.addEventListener('mousemove', handleMouseMove);\n\n    main();\n\n    return () => {\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [rows, columns, motionEnabled]);\n\n  return (\n    <div className=\"w-full h-full\">\n      <canvas\n        ref={canvasRef} aria-hidden=\"true\"\n        className=\"w-full h-full \"\n      />\n    </div>\n  );\n};\n\nexport default ArrowsLimit;"
        },
        {
          "path": "lib/effects/interactive-arrows/arrows-opacity.jsx",
          "target": "@lib/effects/interactive-arrows/arrows-opacity.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\nimport { useEffect, useRef } from 'react';\nimport { useReducedMotion } from 'motion/react';\n\nconst ArrowsOpacity = () => {\n  const canvasRef = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const requestIdRef = useRef(null);\n  const arrowArrRef = useRef([]);\n  const mouseRef = useRef({ x: 0, y: 0 });\n\n  useEffect(() => {\n    class Point {\n      constructor(x, y) {\n        this.x = x || 0;\n        this.y = y || 0;\n      }\n    }\n\n    class Arrow {\n      constructor(position) {\n        this.pos = position;\n        this.dx = 0;\n        this.dy = 0;\n        this.angle = 0;\n        this.dist = 0;\n      }\n\n      update(mx, my) {\n        this.dx = mx - this.pos.x;\n        this.dy = my - this.pos.y;\n        this.dist = Math.sqrt(this.dx * this.dx + this.dy * this.dy);\n        this.angle = Math.atan2(this.dy, this.dx);\n      }\n\n      draw(ctx) {\n        ctx.save();\n        ctx.translate(this.pos.x, this.pos.y);\n        ctx.rotate(this.angle);\n        ctx.beginPath();\n        ctx.moveTo(30, 0);\n        ctx.lineTo(-30, 0);\n        ctx.moveTo(30, 0);\n        ctx.lineTo(5, -30);\n        ctx.moveTo(30, 0);\n        ctx.lineTo(5, 30);\n        ctx.lineWidth = 5;\n        const alpha = 1 - (this.dist / 300);\n        ctx.strokeStyle = `rgba(0, 0, 0, ${Math.max(0, alpha)})`;\n        ctx.stroke();\n        ctx.restore();\n      }\n    }\n\n    const initializeArrows = (canvas) => {\n      arrowArrRef.current = [];\n      for (let y = 0;y < canvas.height / 20;y++) {\n        for (let x = 0;x < canvas.width / 50;x++) {\n          const arr = new Arrow(new Point(x * 75, y * 75));\n          arrowArrRef.current.push(arr);\n        }\n      }\n    };\n\n    const handleResize = () => {\n      const canvas = canvasRef.current;\n      canvas.width = canvas.parentElement.clientWidth || 1;\n      canvas.height = canvas.parentElement.clientHeight || 1;\n      initializeArrows(canvas);\n    };\n\n    const handleMouseMove = (e) => {\n      if (!motionEnabled) return;\n      const canvas = canvasRef.current;\n      const rect = canvas.getBoundingClientRect();\n      const scaleX = canvas.width / rect.width;\n      const scaleY = canvas.height / rect.height;\n      const mouseX = (e.clientX - rect.left) * scaleX;\n      const mouseY = (e.clientY - rect.top) * scaleY;\n      if (mouseX >= 0 && mouseX <= canvas.width && mouseY >= 0 && mouseY <= canvas.height) {\n        mouseRef.current = { x: mouseX, y: mouseY };\n      }\n    };\n\n    const animate = () => {\n      const canvas = canvasRef.current;\n      const ctx = canvas?.getContext('2d');\n      if (!ctx) return;\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      const { x: mx, y: my } = mouseRef.current;\n      for (let y = 0;y < canvas.height / 20;y++) {\n        for (let x = 0;x < canvas.width / 50;x++) {\n          const arrow = arrowArrRef.current[y * Math.ceil(canvas.width / 50) + x];\n          if (arrow) {\n            arrow.update(mx, my);\n            arrow.draw(ctx);\n          }\n        }\n      }\n      if (motionEnabled) requestIdRef.current = requestAnimationFrame(animate);\n    };\n\n    const canvas = canvasRef.current;\n    if (!canvas || !canvas.getContext(\"2d\")) return;\n    canvas.width = canvas.parentElement.clientWidth || 1;\n    canvas.height = canvas.parentElement.clientHeight || 1;\n    initializeArrows(canvas);\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(canvas.parentElement);\n    canvas.addEventListener('mousemove', handleMouseMove);\n    animate();\n    return () => {\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      if (requestIdRef.current) cancelAnimationFrame(requestIdRef.current);\n    };\n  }, [motionEnabled]);\n\n  return (\n    <canvas ref={canvasRef} aria-hidden=\"true\" style={{ width: '100%', height: '100%', pointerEvents: 'auto' }} />\n  );\n};\n\nexport default ArrowsOpacity;\n"
        },
        {
          "path": "lib/effects/interactive-arrows/arrows-play.jsx",
          "target": "@lib/effects/interactive-arrows/arrows-play.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\nimport { useEffect, useRef } from 'react';\nimport { useReducedMotion } from 'motion/react';\n\nconst ArrowsPlay = () => {\n  const canvasRef = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const mouseRef = useRef({ x: 0, y: 0 });\n  const arrowsRef = useRef([]);\n  const animationFrameRef = useRef(null);\n  const divRef = useRef(null); // For the center div with text\n\n  useEffect(() => {\n    class Point {\n      constructor(x, y) {\n        this.x = x || 0;\n        this.y = y || 0;\n      }\n    }\n\n    class Arrow {\n      constructor(position) {\n        this.pos = position;\n        this.dx = 0;\n        this.dy = 0;\n        this.angle = 0;\n        this.rotationEase = 0.12;\n      }\n\n      update(mouseX, mouseY) {\n        this.dx = mouseX - this.pos.x;\n        this.dy = mouseY - this.pos.y;\n        const targetAngle = Math.atan2(this.dy, this.dx);\n\n        // Ease rotation across the shortest arc so large direction changes do not snap.\n        let delta = targetAngle - this.angle;\n        while (delta > Math.PI) delta -= Math.PI * 2;\n        while (delta < -Math.PI) delta += Math.PI * 2;\n\n        this.angle += delta * this.rotationEase;\n      }\n\n      draw(ctx) {\n        ctx.save();\n        ctx.translate(this.pos.x, this.pos.y);\n        ctx.rotate(this.angle);\n        ctx.beginPath();\n        ctx.moveTo(30, 0);\n        ctx.lineTo(-30, 0);\n        ctx.moveTo(30, 0);\n        ctx.lineTo(10, -20);\n        ctx.moveTo(30, 0);\n        ctx.lineTo(10, 20);\n        ctx.lineWidth = 2.5;\n        ctx.strokeStyle = 'white';\n        ctx.stroke();\n        ctx.restore();\n      }\n    }\n\n    const initializeArrows = (canvas) => {\n      const arrows = [];\n      const spacing = 120;\n\n      const cols = Math.floor(canvas.width / spacing);\n      const rows = Math.floor(canvas.height / spacing);\n\n      const xPadding = (canvas.width - (cols * spacing)) / 2;\n      const yPadding = (canvas.height - (rows * spacing)) / 2;\n\n      // Get div position relative to canvas\n      const divRect = divRef.current.getBoundingClientRect();\n      const canvasRect = canvas.getBoundingClientRect();\n\n      const divLeft = divRect.left - canvasRect.left;\n      const divRight = divRect.right - canvasRect.left;\n      const divTop = divRect.top - canvasRect.top;\n      const divBottom = divRect.bottom - canvasRect.top;\n\n      for (let y = 0;y <= rows;y++) {\n        for (let x = 0;x <= cols;x++) {\n          const arrowPos = new Point(\n            x * spacing + xPadding,\n            y * spacing + yPadding\n          );\n\n\n          if (\n            arrowPos.x >= divLeft &&\n            arrowPos.x <= divRight &&\n            arrowPos.y >= divTop &&\n            arrowPos.y <= divBottom\n          ) {\n            continue;\n          }\n\n          arrows.push(new Arrow(arrowPos));\n        }\n      }\n      return arrows;\n    };\n\n\n    const handleResize = () => {\n      const canvas = canvasRef.current;\n      if (canvas) {\n        canvas.width = canvas.parentElement.clientWidth || 1;\n        canvas.height = canvas.parentElement.clientHeight || 1;\n        arrowsRef.current = initializeArrows(canvas);\n      }\n    };\n\n    const handleMouseMove = (e) => {\n      if (!motionEnabled) return;\n      const canvas = canvasRef.current;\n      const rect = canvas.getBoundingClientRect();\n\n      if (\n        e.clientX >= rect.left &&\n        e.clientX <= rect.right &&\n        e.clientY >= rect.top &&\n        e.clientY <= rect.bottom\n      ) {\n        mouseRef.current = {\n          x: e.clientX - rect.left,\n          y: e.clientY - rect.top,\n        };\n      }\n    };\n\n    const main = () => {\n      const canvas = canvasRef.current;\n      const ctx = canvas?.getContext('2d');\n      if (!ctx) return;\n      const arrows = arrowsRef.current;\n      const mouse = mouseRef.current;\n\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n      arrows.forEach(arrow => {\n        arrow.update(mouse.x, mouse.y);\n        arrow.draw(ctx);\n      });\n\n      if (motionEnabled) animationFrameRef.current = requestAnimationFrame(main);\n    };\n\n    const canvas = canvasRef.current;\n    if (!canvas || !canvas.getContext(\"2d\")) return;\n    canvas.width = canvas.parentElement.clientWidth || 1;\n    canvas.height = canvas.parentElement.clientHeight || 1;\n    mouseRef.current = {\n      x: canvas.width / 2,\n      y: canvas.height / 2,\n    };\n\n    arrowsRef.current = initializeArrows(canvas);\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(canvas.parentElement);\n    canvas.addEventListener('mousemove', handleMouseMove);\n\n    main();\n\n    return () => {\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [motionEnabled]);\n\n  return (\n    <div className=\"relative w-full h-full \">\n      <canvas\n        ref={canvasRef} aria-hidden=\"true\"\n        className=\"w-full h-full\"\n        style={{ cursor: 'pointer' }}\n      />\n      <div\n        ref={divRef}\n        className=\"pointer-events-none absolute left-1/2 top-1/2 z-10 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center\"\n        style={{\n          cursor: \"pointer\",\n          height: \"25cqw\",\n          width: \"32cqw\",\n          backgroundColor: \"transparent\",\n        }}\n      >\n        <p className='text-center text-[15cqw] leading-none text-white transition-all duration-500 ease'>\n          Play\n        </p>\n      </div>\n    </div>\n  );\n};\n\nexport default ArrowsPlay;\n"
        },
        {
          "path": "lib/effects/interactive-arrows/arrows.jsx",
          "target": "@lib/effects/interactive-arrows/arrows.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\nimport { useEffect, useRef } from 'react';\nimport { useReducedMotion } from 'motion/react';\n\nconst Arrows = () => {\n  const canvasRef = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const mouseRef = useRef({ x: 0, y: 0 });\n  const arrowsRef = useRef([]);\n  const animationFrameRef = useRef(null);\n\n  useEffect(() => {\n    class Point {\n      constructor(x, y) {\n        this.x = x || 0;\n        this.y = y || 0;\n      }\n    }\n\n    class Arrow {\n      constructor(position) {\n        this.pos = position;\n        this.dx = 0;\n        this.dy = 0;\n        this.angle = 0;\n        this.isHovered = false;\n        this.originalPos = { ...position };\n        this.targetPos = { ...position };\n      }\n\n      update(mouseX, mouseY) {\n        this.dx = mouseX - this.pos.x;\n        this.dy = mouseY - this.pos.y;\n\n        const targetAngle = Math.atan2(this.dy, this.dx) * 0.95;\n\n        // Lerp angle via shortest arc to avoid wrap-around jumps\n        let delta = targetAngle - this.angle;\n        while (delta > Math.PI) delta -= 2 * Math.PI;\n        while (delta < -Math.PI) delta += 2 * Math.PI;\n        this.angle += delta * 0.15;\n\n        // Check if mouse is near this arrow\n        const distance = Math.sqrt(\n          Math.pow(mouseX - this.pos.x, 2) +\n          Math.pow(mouseY - this.pos.y, 2)\n        );\n\n        const wasHovered = this.isHovered;\n        this.isHovered = distance < 30;\n\n        // Handle spacing animation\n        if (this.isHovered !== wasHovered) {\n          if (this.isHovered) {\n            // Push surrounding arrows away\n            arrowsRef.current.forEach(otherArrow => {\n              if (otherArrow !== this) {\n                const dx = otherArrow.pos.x - this.pos.x;\n                const dy = otherArrow.pos.y - this.pos.y;\n                const dist = Math.sqrt(dx * dx + dy * dy);\n                if (dist < 70) {\n                  const pushForce = (70 - dist) / 70;\n                  otherArrow.targetPos = {\n                    x: otherArrow.originalPos.x + (dx / dist) * 20 * pushForce,\n                    y: otherArrow.originalPos.y + (dy / dist) * 20 * pushForce\n                  };\n                }\n              }\n            });\n          } else {\n            // Reset surrounding arrows\n            arrowsRef.current.forEach(arrow => {\n              arrow.targetPos = { ...arrow.originalPos };\n            });\n          }\n        }\n\n        // Smooth position transition\n        this.pos.x += (this.targetPos.x - this.pos.x) * 0.1;\n        this.pos.y += (this.targetPos.y - this.pos.y) * 0.1;\n      }\n\n      draw(ctx) {\n        ctx.save();\n        ctx.translate(this.pos.x, this.pos.y);\n        ctx.rotate(this.angle);\n        ctx.beginPath();\n\n        ctx.moveTo(20, 0);\n        ctx.lineTo(-20, 0);\n        ctx.moveTo(20, 0);\n        ctx.lineTo(5, -15);\n        ctx.moveTo(20, 0);\n        ctx.lineTo(5, 15);\n        ctx.lineWidth = this.isHovered ? 3 : 2;\n        ctx.strokeStyle = 'black';\n        ctx.stroke();\n        ctx.restore();\n      }\n    }\n\n    const initializeArrows = (canvas) => {\n      const arrows = [];\n      const spacing = 50;\n      const cols = Math.floor(canvas.width / spacing);\n      const rows = Math.floor(canvas.height / spacing);\n      const xPadding = (canvas.width - (cols * spacing)) / 2;\n      const yPadding = (canvas.height - (rows * spacing)) / 2;\n      for (let y = 0;y <= rows;y++) {\n        for (let x = 0;x <= cols;x++) {\n          arrows.push(new Arrow(new Point(x * spacing + xPadding, y * spacing + yPadding)));\n        }\n      }\n      return arrows;\n    };\n\n    const handleResize = () => {\n      const canvas = canvasRef.current;\n      if (canvas) {\n        canvas.width = canvas.parentElement.clientWidth || 1;\n        canvas.height = canvas.parentElement.clientHeight || 1;\n        arrowsRef.current = initializeArrows(canvas);\n      }\n    };\n\n    const handleMouseMove = (e) => {\n      if (!motionEnabled) return;\n      const canvas = canvasRef.current;\n      if (canvas) {\n        const rect = canvas.getBoundingClientRect();\n        const scaleX = canvas.width / rect.width;\n        const scaleY = canvas.height / rect.height;\n        mouseRef.current = {\n          x: (e.clientX - rect.left) * scaleX,\n          y: (e.clientY - rect.top) * scaleY,\n        };\n      }\n    };\n\n    const main = () => {\n      const canvas = canvasRef.current;\n      const ctx = canvas?.getContext('2d');\n      if (!ctx) return;\n      const arrows = arrowsRef.current;\n      const mouse = mouseRef.current;\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      arrows.forEach(arrow => {\n        arrow.update(mouse.x, mouse.y);\n        arrow.draw(ctx);\n      });\n      if (motionEnabled) animationFrameRef.current = requestAnimationFrame(main);\n    };\n\n    const canvas = canvasRef.current;\n    if (!canvas || !canvas.getContext(\"2d\")) return;\n    canvas.width = canvas.parentElement.clientWidth || 1;\n    canvas.height = canvas.parentElement.clientHeight || 1;\n    arrowsRef.current = initializeArrows(canvas);\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(canvas.parentElement);\n    canvas.addEventListener('mousemove', handleMouseMove);\n    main();\n    return () => {\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [motionEnabled]);\n\n  return (\n    // <div className=\"w-full h-full bg-white\">\n    <canvas ref={canvasRef} aria-hidden=\"true\" className=\"w-full h-full\" />\n    // </div>\n  );\n};\n\nexport default Arrows;"
        },
        {
          "path": "lib/effects/interactive-arrows/lines.jsx",
          "target": "@lib/effects/interactive-arrows/lines.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\nimport { useEffect, useRef } from 'react';\nimport { useReducedMotion } from 'motion/react';\n\nconst Lines = () => {\n  const canvasRef = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const mouseRef = useRef({ x: 0, y: 0 });\n  const pointsRef = useRef([]);\n  const animationFrameRef = useRef(null);\n\n  const lineLength = 30;\n\n  useEffect(() => {\n    class Point {\n      constructor(x, y) {\n        this.x = x || 0;\n        this.y = y || 0;\n      }\n\n      draw(ctx, mouseX, mouseY) {\n        const dx = mouseX - this.x;\n        const dy = mouseY - this.y;\n        const distance = Math.sqrt(dx * dx + dy * dy) || 1;\n        const unitX = dx / distance;\n        const unitY = dy / distance;\n        const lineEndX = this.x + unitX * lineLength;\n        const lineEndY = this.y + unitY * lineLength;\n\n        ctx.beginPath();\n        ctx.moveTo(this.x, this.y);\n        ctx.lineTo(lineEndX, lineEndY);\n        ctx.strokeStyle = 'black';\n        ctx.lineWidth = 1.5;\n        ctx.stroke();\n      }\n    }\n\n    const initializePoints = (canvas) => {\n      const points = [];\n      const spacing = 60;\n      const cols = Math.floor(canvas.width / spacing);\n      const rows = Math.floor(canvas.height / spacing);\n\n      for (let y = 0;y <= rows;y++) {\n        for (let x = 0;x <= cols;x++) {\n          points.push(new Point(x * spacing, y * spacing));\n        }\n      }\n      return points;\n    };\n\n    const handleResize = () => {\n      const canvas = canvasRef.current;\n      if (canvas) {\n        canvas.width = canvas.parentElement.clientWidth || 1;\n        canvas.height = canvas.parentElement.clientHeight || 1;\n        pointsRef.current = initializePoints(canvas);\n      }\n    };\n\n    const handleMouseMove = (e) => {\n      if (!motionEnabled) return;\n      const canvas = canvasRef.current;\n      const rect = canvas.getBoundingClientRect();\n      const scaleX = canvas.width / rect.width;\n      const scaleY = canvas.height / rect.height;\n      mouseRef.current = {\n        x: (e.clientX - rect.left) * scaleX,\n        y: (e.clientY - rect.top) * scaleY,\n      };\n    };\n\n    const main = () => {\n      const canvas = canvasRef.current;\n      const ctx = canvas?.getContext('2d');\n      if (!ctx) return;\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      pointsRef.current.forEach(point => point.draw(ctx, mouseRef.current.x, mouseRef.current.y));\n      if (motionEnabled) animationFrameRef.current = requestAnimationFrame(main);\n    };\n\n    const canvas = canvasRef.current;\n    if (!canvas || !canvas.getContext(\"2d\")) return;\n    canvas.width = canvas.parentElement.clientWidth || 1;\n    canvas.height = canvas.parentElement.clientHeight || 1;\n    pointsRef.current = initializePoints(canvas);\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(canvas.parentElement);\n    canvas.addEventListener('mousemove', handleMouseMove);\n    main();\n    return () => {\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      cancelAnimationFrame(animationFrameRef.current);\n    };\n  }, [motionEnabled]);\n\n  return <canvas ref={canvasRef} aria-hidden=\"true\" className=\"w-full h-full \" style={{ cursor: 'pointer' }} />;\n};\n\nexport default Lines;\n"
        },
        {
          "path": "lib/effects/interactive-arrows/points.jsx",
          "target": "@lib/effects/interactive-arrows/points.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\nimport { useEffect, useRef } from 'react';\nimport { useReducedMotion } from 'motion/react';\n\nconst Points = () => {\n  const canvasRef = useRef(null);\n  const motionEnabled = !useReducedMotion();\n  const mouseRef = useRef({ x: 0, y: 0 });\n  const pointsRef = useRef([]);\n  const animationFrameRef = useRef(null);\n  const mouseEnteredRef = useRef(false);\n  const lineLengthRef = useRef(0);\n  const maxLineLength = 25;\n  const easingSpeed = 0.05;\n\n  useEffect(() => {\n    class Point {\n      constructor(x, y) {\n        this.x = x || 0;\n        this.y = y || 0;\n      }\n\n      draw(ctx, mouseX, mouseY) {\n        if (mouseEnteredRef.current) {\n          const dx = mouseX - this.x;\n          const dy = mouseY - this.y;\n\n\n          const distance = Math.sqrt(dx * dx + dy * dy) || 1;\n          const unitX = dx / distance;\n          const unitY = dy / distance;\n\n\n          const lineEndX = this.x + unitX * lineLengthRef.current;\n          const lineEndY = this.y + unitY * lineLengthRef.current;\n\n\n          ctx.beginPath();\n          ctx.moveTo(this.x, this.y);\n          ctx.lineTo(lineEndX, lineEndY);\n          ctx.strokeStyle = 'black';\n          ctx.lineWidth = 1.5;\n          ctx.stroke();\n        } else {\n          ctx.beginPath();\n          ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);\n          ctx.fillStyle = 'black';\n          ctx.fill();\n        }\n      }\n    }\n\n    const initializePoints = (canvas) => {\n      const points = [];\n      const spacing = 60;\n      const cols = Math.floor(canvas.width / spacing);\n      const rows = Math.floor(canvas.height / spacing);\n\n      const xPadding = (canvas.width - (cols * spacing)) / 2;\n      const yPadding = (canvas.height - (rows * spacing)) / 2;\n\n      for (let y = 0;y <= rows;y++) {\n        for (let x = 0;x <= cols;x++) {\n          points.push(new Point(\n            x * spacing + xPadding,\n            y * spacing + yPadding\n          ));\n        }\n      }\n      return points;\n    };\n\n    const handleResize = () => {\n      const canvas = canvasRef.current;\n      if (canvas) {\n        canvas.width = canvas.parentElement.clientWidth || 1;\n        canvas.height = canvas.parentElement.clientHeight || 1;\n        pointsRef.current = initializePoints(canvas);\n      }\n    };\n\n    const handleMouseMove = (e) => {\n      if (!motionEnabled) return;\n      const canvas = canvasRef.current;\n      const rect = canvas.getBoundingClientRect();\n      const scaleX = canvas.width / rect.width;\n      const scaleY = canvas.height / rect.height;\n      mouseRef.current = {\n        x: (e.clientX - rect.left) * scaleX,\n        y: (e.clientY - rect.top) * scaleY,\n      };\n    };\n\n    const handleMouseEnter = () => {\n      mouseEnteredRef.current = true;\n    }\n\n    const handleMouseLeave = () => {\n      mouseEnteredRef.current = false;\n    };\n\n    const updateLineLength = () => {\n      if (mouseEnteredRef.current && lineLengthRef.current < maxLineLength) {\n        lineLengthRef.current = Math.min(lineLengthRef.current + easingSpeed, maxLineLength);\n      } else if (!mouseEnteredRef.current && lineLengthRef.current > 0) {\n        lineLengthRef.current = Math.max(lineLengthRef.current - easingSpeed, 0);\n      }\n    };\n\n    const main = () => {\n      const canvas = canvasRef.current;\n      const ctx = canvas?.getContext('2d');\n      if (!ctx) return;\n      const points = pointsRef.current;\n      const mouse = mouseRef.current;\n\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      updateLineLength();\n\n      points.forEach(point => {\n        point.draw(ctx, mouse.x, mouse.y);\n      });\n\n      if (motionEnabled) animationFrameRef.current = requestAnimationFrame(main);\n    };\n\n    const canvas = canvasRef.current;\n    if (!canvas || !canvas.getContext(\"2d\")) return;\n    canvas.width = canvas.parentElement.clientWidth || 1;\n    canvas.height = canvas.parentElement.clientHeight || 1;\n\n    pointsRef.current = initializePoints(canvas);\n    const observer = new ResizeObserver(handleResize);\n    observer.observe(canvas.parentElement);\n    canvas.addEventListener('mousemove', handleMouseMove);\n    canvas.addEventListener('mouseenter', handleMouseEnter);\n    canvas.addEventListener('mouseleave', handleMouseLeave);\n\n    main();\n\n    return () => {\n      observer.disconnect();\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      canvas.removeEventListener('mouseenter', handleMouseEnter);\n      canvas.removeEventListener('mouseleave', handleMouseLeave);\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [motionEnabled]);\n\n  return (\n    <div className=\"w-full h-full \">\n      <canvas\n        ref={canvasRef} aria-hidden=\"true\"\n        className=\"w-full h-full\"\n        style={{ cursor: 'pointer' }}\n      />\n    </div>\n  );\n};\n\nexport default Points;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "interactive-blur-reveal",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/interactive-blur-reveal.jsx",
          "target": "@components/block/interactive-blur-reveal.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst MAX_TRAIL_POINTS = 240;\nconst FULLSCREEN_TRIANGLE_VERTICES = new Float32Array([\n  -1, -1,\n  1, -1,\n  -1, 1,\n  -1, 1,\n  1, -1,\n  1, 1,\n]);\nconst TEXTURE_UNIT_BASE = 0;\nconst TEXTURE_UNIT_NOISE = 1;\nconst DEFAULT_POINTER_POSITION = 0.5;\nconst DEFAULT_FRAME_TIME_MS = 16.67;\nconst MAX_FRAME_DELTA_MS = 64;\nconst POINTER_LERP_FACTOR = 0.001;\nconst POINTER_LEAVE_DURATION_MS = 180;\nconst TRAIL_LIFETIME_MS = 650;\nconst MIN_POINTER_DISTANCE_INSIDE = 0.0022;\nconst MIN_POINTER_DISTANCE_LEAVING = 0.0013;\n\nconst BLUR_REVEAL_VERT = /* glsl */ `#version 300 es\nin vec2 position;\n\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst BLUR_REVEAL_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\n\n#define MAX_TRAIL_POINTS 240\n\nuniform vec2      iResolution;\nuniform float     iTime;\nuniform vec2      iTrail[MAX_TRAIL_POINTS];\nuniform float     iTrailAlpha[MAX_TRAIL_POINTS];\nuniform int       iTrailCount;\nuniform sampler2D iChannel0;\nuniform sampler2D iChannel1;\n\nout vec4 fragColor;\n\nvec2 distortUv(vec2 uv) {\n  vec2 noiseUv = uv * 2.2;\n  vec2 noiseOffset = texture(iChannel1, noiseUv).xy - 0.5;\n\n  return uv + noiseOffset * 0.012;\n}\n\nvec4 blur21(sampler2D tex, vec2 uv, float radiusPx) {\n  vec2 px = radiusPx / iResolution;\n  vec4 color = vec4(0.0);\n\n  color += texture(tex, uv) * 0.12;\n\n  color += texture(tex, uv + px * vec2(1.0, 0.0)) * 0.08;\n  color += texture(tex, uv + px * vec2(-1.0, 0.0)) * 0.08;\n  color += texture(tex, uv + px * vec2(0.0, 1.0)) * 0.08;\n  color += texture(tex, uv + px * vec2(0.0, -1.0)) * 0.08;\n\n  color += texture(tex, uv + px * vec2(1.0, 1.0)) * 0.065;\n  color += texture(tex, uv + px * vec2(-1.0, 1.0)) * 0.065;\n  color += texture(tex, uv + px * vec2(1.0, -1.0)) * 0.065;\n  color += texture(tex, uv + px * vec2(-1.0, -1.0)) * 0.065;\n\n  color += texture(tex, uv + px * vec2(2.0, 0.0)) * 0.045;\n  color += texture(tex, uv + px * vec2(-2.0, 0.0)) * 0.045;\n  color += texture(tex, uv + px * vec2(0.0, 2.0)) * 0.045;\n  color += texture(tex, uv + px * vec2(0.0, -2.0)) * 0.045;\n\n  color += texture(tex, uv + px * vec2(3.0, 1.0)) * 0.025;\n  color += texture(tex, uv + px * vec2(-3.0, 1.0)) * 0.025;\n  color += texture(tex, uv + px * vec2(3.0, -1.0)) * 0.025;\n  color += texture(tex, uv + px * vec2(-3.0, -1.0)) * 0.025;\n\n  return color;\n}\n\nfloat sdSegment(vec2 point, vec2 start, vec2 end) {\n  vec2 pointDelta = point - start;\n  vec2 segmentDelta = end - start;\n  float segmentProjection = clamp(\n    dot(pointDelta, segmentDelta) / max(dot(segmentDelta, segmentDelta), 0.00001),\n    0.0,\n    1.0\n  );\n\n  return length(pointDelta - segmentDelta * segmentProjection);\n}\n\nfloat fluidTrailRevealMask(vec2 uv) {\n  float aspect = iResolution.x / iResolution.y;\n  vec2 point = vec2(uv.x * aspect, uv.y);\n  float mask = 0.0;\n\n  for (int i = 0; i < MAX_TRAIL_POINTS - 1; i++) {\n    if (i >= iTrailCount - 1) {\n      break;\n    }\n\n    vec2 start = vec2(iTrail[i].x * aspect, iTrail[i].y);\n    vec2 end = vec2(iTrail[i + 1].x * aspect, iTrail[i + 1].y);\n    float alpha = min(iTrailAlpha[i], iTrailAlpha[i + 1]);\n    float distance = sdSegment(point, start, end);\n\n    float noiseA = texture(iChannel1, uv * 4.0 + float(i) * 0.018).r;\n    float noiseB = texture(iChannel1, uv * 10.0 + vec2(noiseA * 0.4, float(i) * 0.01)).r;\n    float noiseC = texture(iChannel1, uv * 24.0 - float(i) * 0.006).r;\n    float fluidNoise = noiseA * 0.45 + noiseB * 0.35 + noiseC * 0.20;\n\n    float radius = 0.095 + (fluidNoise - 0.5) * 0.055;\n    float softness = 0.09;\n    float localMask = 1.0 - smoothstep(radius, radius + softness, distance);\n\n    mask = max(mask, localMask * alpha);\n  }\n\n  float cloudNoise = texture(iChannel1, uv * 7.0).r;\n  float fineNoise = texture(iChannel1, uv * 22.0).r;\n\n  mask *= smoothstep(0.12, 0.95, mask + cloudNoise * 0.25 + fineNoise * 0.12);\n\n  return clamp(mask, 0.0, 1.0);\n}\n\nvec3 filmGrain(vec2 uv) {\n  // Layered grain keeps the frosted area from feeling digitally flat.\n  vec2 coarseUv = uv * (iResolution.xy / 260.0) + vec2(iTime * 0.035, -iTime * 0.028);\n  vec2 fineUv = uv * (iResolution.xy / 120.0) + vec2(-iTime * 0.055, iTime * 0.041);\n  vec3 coarse = texture(iChannel1, coarseUv).rgb - 0.5;\n  float fine = texture(iChannel1, fineUv).r - 0.5;\n  vec3 chroma = vec3(coarse.r, coarse.g * 0.9, coarse.b * 1.1);\n\n  return chroma * 0.95 + fine * 0.65;\n}\n\nvoid main() {\n  vec2 screenUv = gl_FragCoord.xy / iResolution.xy;\n  screenUv.y = 1.0 - screenUv.y;\n\n  vec2 imageUv = screenUv;\n  vec4 frostedImage = blur21(iChannel0, distortUv(imageUv), 42.0);\n  vec4 clearImage = texture(iChannel0, imageUv);\n  float revealMask = fluidTrailRevealMask(screenUv);\n  float grain = texture(iChannel1, screenUv * iResolution.xy / 180.0).r;\n\n  frostedImage.rgb = mix(frostedImage.rgb, vec3(0.70, 0.76, 0.78), 0.18);\n  frostedImage.rgb += (grain - 0.5) * 0.045;\n  frostedImage.rgb *= 0.96;\n\n  vec4 mixed = mix(frostedImage, clearImage, revealMask);\n\n  // Grain stays stronger in the frosted region so the reveal feels tactile.\n  float grainAmount = mix(0.24, 0.12, revealMask);\n  mixed.rgb += filmGrain(screenUv) * grainAmount;\n\n  // A soft vignette keeps the edges from competing with the reveal path.\n  vec2 vignetteDelta = screenUv - 0.5;\n  float vignette = smoothstep(0.85, 0.25, dot(vignetteDelta, vignetteDelta) * 1.35);\n  mixed.rgb *= mix(0.96, 1.0, vignette);\n\n  fragColor = vec4(clamp(mixed.rgb, 0.0, 1.0), 1.0);\n}\n`;\n\nfunction createShader(gl, type, source) {\n  const shader = gl.createShader(type);\n  if (!shader) throw new Error(\"Shader allocation failed.\");\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    gl.deleteShader(shader);\n    throw new Error(\"Shader compilation failed.\");\n  }\n  return shader;\n}\n\nfunction createProgram(gl, vertexSource, fragmentSource) {\n  let vertexShader;\n  let fragmentShader;\n  let program;\n  try {\n    vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n    fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n    program = gl.createProgram();\n    if (!program) throw new Error(\"Program allocation failed.\");\n    gl.attachShader(program, vertexShader);\n    gl.attachShader(program, fragmentShader);\n    gl.linkProgram(program);\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n      throw new Error(\"Program linking failed.\");\n    }\n    return program;\n  } catch (error) {\n    if (program) gl.deleteProgram(program);\n    throw error;\n  } finally {\n    if (vertexShader) gl.deleteShader(vertexShader);\n    if (fragmentShader) gl.deleteShader(fragmentShader);\n  }\n}\n\nfunction loadImage(source, signal) {\n  return new Promise((resolve, reject) => {\n    const image = source instanceof HTMLImageElement ? source : new Image();\n    const removeListeners = () => {\n      image.removeEventListener(\"load\", onLoad);\n      image.removeEventListener(\"error\", onError);\n      signal.removeEventListener(\"abort\", onAbort);\n    };\n    const onLoad = () => { removeListeners(); resolve(image); };\n    const onError = () => { removeListeners(); reject(new Error(\"Image unavailable.\")); };\n    const onAbort = () => { removeListeners(); reject(new Error(\"Image loading cancelled.\")); };\n    if (signal.aborted) { onAbort(); return; }\n    image.addEventListener(\"load\", onLoad);\n    image.addEventListener(\"error\", onError);\n    signal.addEventListener(\"abort\", onAbort, { once: true });\n    if (!(source instanceof HTMLImageElement)) {\n      image.crossOrigin = \"anonymous\";\n      image.src = source;\n    }\n    if (image.complete && image.naturalWidth > 0) onLoad();\n  });\n}\n\nfunction createTexture(gl, image, unit, shouldRepeat = false) {\n  const texture = gl.createTexture();\n  if (!texture) throw new Error(\"Texture allocation failed.\");\n  try {\n    gl.activeTexture(gl.TEXTURE0 + unit);\n    gl.bindTexture(gl.TEXTURE_2D, texture);\n    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, shouldRepeat ? gl.REPEAT : gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, shouldRepeat ? gl.REPEAT : gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    return texture;\n  } catch (error) {\n    gl.deleteTexture(texture);\n    throw error;\n  }\n}\n\n/**\n * @param {{ imageSrc?: string, noiseSrc?: string, iChannel0?: string | HTMLImageElement, iChannel1?: string | HTMLImageElement, className?: string, style?: import(\"react\").CSSProperties, paused?: boolean, alt?: string }} props\n */\nexport function InteractiveBlurReveal({\n  imageSrc = \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-blur-reveal/interactive-blur-reveal-img01.webp\",\n  noiseSrc = \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-blur-reveal/interactive-blur-reveal-noise.png\",\n  iChannel0 = imageSrc,\n  iChannel1 = noiseSrc,\n  className,\n  style,\n  paused = false,\n  alt = \"ObsidianUI frosted image with a fluid cursor reveal\",\n} = {}) {\n  const canvasRef = useRef(null);\n  const trailRef = useRef([]);\n  const pointerRef = useRef({\n    isInside: false,\n    targetX: DEFAULT_POINTER_POSITION,\n    targetY: DEFAULT_POINTER_POSITION,\n    x: DEFAULT_POINTER_POSITION,\n    y: DEFAULT_POINTER_POSITION,\n    lastTime: 0,\n    isLeaving: false,\n    leaveAt: 0,\n  });\n\n  useEffect(() => {\n    let isDisposed = false;\n    let animationFrameId = 0;\n    const controller = new AbortController();\n    const disposeResources = [];\n    const motion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const isStatic = () => paused || motion.matches;\n    trailRef.current = [];\n    const cleanup = () => {\n      if (isDisposed) return;\n      isDisposed = true;\n      controller.abort();\n      cancelAnimationFrame(animationFrameId);\n      disposeResources.reverse().forEach((dispose) => dispose());\n    };\n\n    // ─── WebGL Setup ───────────────────────────────────────────────────────\n    async function init() {\n      const canvas = canvasRef.current;\n\n      if (!canvas) return;\n\n      const gl = canvas.getContext(\"webgl2\");\n\n      if (!gl) {\n        return;\n      }\n\n      const program = createProgram(gl, BLUR_REVEAL_VERT, BLUR_REVEAL_FRAG);\n      disposeResources.push(() => gl.deleteProgram(program));\n      const positionBuffer = gl.createBuffer();\n      if (!positionBuffer) throw new Error(\"Buffer allocation failed.\");\n      disposeResources.push(() => gl.deleteBuffer(positionBuffer));\n      const onContextLost = (event) => {\n        event.preventDefault();\n        canvas.style.opacity = \"0\";\n        cleanup();\n      };\n      canvas.addEventListener(\"webglcontextlost\", onContextLost);\n      disposeResources.push(() => canvas.removeEventListener(\"webglcontextlost\", onContextLost));\n      const positionLocation = gl.getAttribLocation(program, \"position\");\n      const resolutionLocation = gl.getUniformLocation(program, \"iResolution\");\n      const timeLocation = gl.getUniformLocation(program, \"iTime\");\n      const trailLocation = gl.getUniformLocation(program, \"iTrail[0]\");\n      const trailAlphaLocation = gl.getUniformLocation(program, \"iTrailAlpha[0]\");\n      const trailCountLocation = gl.getUniformLocation(program, \"iTrailCount\");\n      const channel0Location = gl.getUniformLocation(program, \"iChannel0\");\n      const channel1Location = gl.getUniformLocation(program, \"iChannel1\");\n\n      gl.useProgram(program);\n      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n      gl.bufferData(gl.ARRAY_BUFFER, FULLSCREEN_TRIANGLE_VERTICES, gl.STATIC_DRAW);\n      gl.enableVertexAttribArray(positionLocation);\n      gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\n\n      const [baseImage, noiseImage] = await Promise.all([\n        loadImage(iChannel0, controller.signal),\n        loadImage(iChannel1, controller.signal),\n      ]);\n\n      if (isDisposed) return;\n\n      const baseTexture = createTexture(gl, baseImage, TEXTURE_UNIT_BASE);\n      disposeResources.push(() => gl.deleteTexture(baseTexture));\n      const noiseTexture = createTexture(gl, noiseImage, TEXTURE_UNIT_NOISE, true);\n      disposeResources.push(() => gl.deleteTexture(noiseTexture));\n\n      gl.uniform1i(channel0Location, TEXTURE_UNIT_BASE);\n      gl.uniform1i(channel1Location, TEXTURE_UNIT_NOISE);\n\n      // ─── Canvas Resize\n      function onResize() {\n        const devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n        const rect = canvas.getBoundingClientRect();\n        const nextWidth = Math.max(1, Math.floor(rect.width * devicePixelRatio));\n        const nextHeight = Math.max(1, Math.floor(rect.height * devicePixelRatio));\n\n        if (canvas.width === nextWidth && canvas.height === nextHeight) return;\n\n        canvas.width = nextWidth;\n        canvas.height = nextHeight;\n\n        gl.viewport(0, 0, canvas.width, canvas.height);\n      }\n\n      // ─── Animation Loop\n      const trailData = new Float32Array(MAX_TRAIL_POINTS * 2);\n      const trailAlphaData = new Float32Array(MAX_TRAIL_POINTS);\n      function render() {\n        if (isDisposed) return;\n\n        const now = performance.now();\n\n        gl.uniform1f(timeLocation, isStatic() ? 0 : now / 1000);\n\n        const pointer = pointerRef.current;\n        const frameDelta = pointer.lastTime\n          ? Math.min(MAX_FRAME_DELTA_MS, now - pointer.lastTime)\n          : DEFAULT_FRAME_TIME_MS;\n\n        pointer.lastTime = now;\n\n        // Exponential smoothing keeps the cursor feel consistent across frame rates.\n        const smoothing = 1.0 - Math.pow(POINTER_LERP_FACTOR, frameDelta / 1000);\n\n        pointer.x += (pointer.targetX - pointer.x) * smoothing;\n        pointer.y += (pointer.targetY - pointer.y) * smoothing;\n\n        const leavingAge = pointer.isLeaving ? now - pointer.leaveAt : 0;\n        const isLeavingActive =\n          pointer.isLeaving && leavingAge < POINTER_LEAVE_DURATION_MS;\n        const pushStrength = isStatic() ? 0 : pointer.isInside\n          ? 1\n          : isLeavingActive\n            ? 1 - leavingAge / POINTER_LEAVE_DURATION_MS\n            : 0;\n\n        if (pushStrength > 0) {\n          const trail = trailRef.current;\n          const lastPoint = trail[trail.length - 1];\n          const deltaX = lastPoint ? pointer.x - lastPoint.x : 1;\n          const deltaY = lastPoint ? pointer.y - lastPoint.y : 1;\n          const distance = Math.hypot(deltaX, deltaY);\n          const minPointerDistance = pointer.isInside\n            ? MIN_POINTER_DISTANCE_INSIDE\n            : MIN_POINTER_DISTANCE_LEAVING;\n\n          if (!lastPoint || distance > minPointerDistance) {\n            trail.push({\n              x: pointer.x,\n              y: pointer.y,\n              time: now,\n              strength: pushStrength,\n            });\n          }\n        }\n\n        let trail = trailRef.current.filter(\n          (point) => now - point.time < TRAIL_LIFETIME_MS\n        );\n\n        if (trail.length > MAX_TRAIL_POINTS) {\n          trail = trail.slice(trail.length - MAX_TRAIL_POINTS);\n        }\n\n        trailRef.current = trail;\n\n        trailData.fill(0);\n        trailAlphaData.fill(0);\n\n        trail.forEach((point, index) => {\n          const age = now - point.time;\n          const life = Math.max(0, 1 - age / TRAIL_LIFETIME_MS);\n\n          // Smooth alpha easing avoids a visible cutoff at the end of the trail.\n          const baseAlpha = life * life * (3 - 2 * life);\n\n          trailData[index * 2] = point.x;\n          trailData[index * 2 + 1] = point.y;\n          trailAlphaData[index] = baseAlpha * (point.strength ?? 1);\n        });\n\n        gl.clear(gl.COLOR_BUFFER_BIT);\n        gl.uniform2f(resolutionLocation, canvas.width, canvas.height);\n        gl.uniform2fv(trailLocation, trailData);\n        gl.uniform1fv(trailAlphaLocation, trailAlphaData);\n        gl.uniform1i(trailCountLocation, trail.length);\n        gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n        canvas.style.opacity = \"1\";\n        if (!isStatic()) animationFrameId = requestAnimationFrame(render);\n      }\n\n      const observer = new ResizeObserver(() => { onResize(); if (isStatic()) render(); });\n      observer.observe(canvas.parentElement);\n      disposeResources.push(() => observer.disconnect());\n      const onMotionChange = () => {\n        cancelAnimationFrame(animationFrameId);\n        trailRef.current = [];\n        render();\n      };\n      motion.addEventListener(\"change\", onMotionChange);\n      disposeResources.push(() => motion.removeEventListener(\"change\", onMotionChange));\n      onResize();\n      render();\n\n    }\n\n    init().catch(() => {\n      if (isDisposed) return;\n      if (canvasRef.current) canvasRef.current.style.opacity = \"0\";\n      cleanup();\n    });\n    return cleanup;\n  }, [iChannel0, iChannel1, paused]);\n\n  function updatePointerFromEvent(event) {\n    const canvas = canvasRef.current;\n\n    if (!canvas) return;\n\n    const rect = canvas.getBoundingClientRect();\n    const pointer = pointerRef.current;\n\n    pointer.targetX = Math.max(0, Math.min(1, (event.clientX - rect.left) / Math.max(1, rect.width)));\n    pointer.targetY = Math.max(0, Math.min(1, (event.clientY - rect.top) / Math.max(1, rect.height)));\n  }\n\n  function onPointerEnter(event) {\n    const pointer = pointerRef.current;\n\n    pointer.isInside = true;\n    pointer.isLeaving = false;\n    updatePointerFromEvent(event);\n  }\n\n  function onPointerMove(event) {\n    updatePointerFromEvent(event);\n  }\n\n  function onPointerLeave() {\n    const pointer = pointerRef.current;\n\n    pointer.isInside = false;\n    pointer.isLeaving = true;\n    pointer.leaveAt = performance.now();\n  }\n\n  return (\n    <div\n      role=\"img\"\n      aria-label={alt}\n      className={cn(\"relative h-[28rem] w-full overflow-hidden bg-black\", className)}\n      style={style}\n    >\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 scale-110 bg-cover bg-center blur-xl\"\n        style={{ backgroundImage: typeof iChannel0 === \"string\" ? `url(${JSON.stringify(iChannel0)})` : undefined }}\n      />\n      <canvas\n        ref={canvasRef}\n        aria-hidden=\"true\"\n        onPointerEnter={onPointerEnter}\n        onPointerMove={onPointerMove}\n        onPointerLeave={onPointerLeave}\n        onPointerCancel={onPointerLeave}\n        className=\"absolute inset-0 block h-full w-full opacity-0\"\n      />\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "interactive-hover-button",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/interactive-hover-button.tsx",
          "target": "@components/block/interactive-hover-button.tsx",
          "type": "registry:block",
          "content": "import { ArrowRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport function InteractiveHoverButton({\n  children,\n  className,\n  ...props\n}: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n  return (\n    <button\n      className={cn(\n        \"group bg-background relative w-auto cursor-pointer overflow-hidden rounded-full border p-2 px-6 text-center font-semibold\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"flex items-center gap-2\">\n        <div className=\"bg-primary h-2 w-2 rounded-full transition-all duration-300 group-hover:scale-[100.8]\"></div>\n        <span className=\"inline-block transition-all duration-300 group-hover:translate-x-12 group-hover:opacity-0\">\n          {children}\n        </span>\n      </div>\n      <div className=\"text-primary-foreground absolute top-0 z-10 flex h-full w-full translate-x-12 items-center justify-center gap-2 opacity-0 transition-all duration-300 group-hover:-translate-x-5 group-hover:opacity-100\">\n        <span>{children}</span>\n        <ArrowRight />\n      </div>\n    </button>\n  )\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "interactive-hover-slider",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "tailwind-merge",
        "three"
      ],
      "files": [
        {
          "path": "components/block/interactive-hover-slider.jsx",
          "target": "@components/block/interactive-hover-slider.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef, useState, useCallback } from\"react\";\nimport * as THREE from\"three\";\nimport gsap from\"gsap\";\nimport { WebGLSurface, useEffectReducedMotion } from \"@/lib/effects/shared/webgl-surface\";\n\nconst vertexShader = /* glsl */`\nuniform vec2 uVelocity;\nuniform vec2 uViewport;\nuniform float uCurvature;\n\nvarying vec2 vUv;\n\nfloat circularArc(float d) {\n float maxAngle = 1.15;\n float theta = clamp(d, 0.0, 1.0) * maxAngle;\n return (1.0 - cos(theta)) / (1.0 - cos(maxAngle));\n}\n\nvoid main() {\n vUv = uv;\n\n vec4 worldPos = modelMatrix * vec4(position, 1.0);\n\n float nx = worldPos.x / uViewport.x;\n float ny = worldPos.y / uViewport.y;\n\n float cx = clamp(nx, -1.0, 1.0);\n float cy = clamp(ny, -1.0, 1.0);\n\n // distance from center (0 center → 1 edges)\n float distY = abs(cy);\n float distX = abs(cx);\n\n // circular-arc falloff: keeps the same edge max, but reads as a real curve\n float curveY = circularArc(distY);\n float curveX = circularArc(distX);\n\n // GSAP controls uCurvature, so the curve can ease smoothly back to plane.\n float edgeLift = curveY * uCurvature + curveX * (uCurvature * 0.1);\n\n // final Z (ONLY edges move forward, center stays stable)\n float finalZOffset = edgeLift;\n\n // perspective\n float focalLength = max(uViewport.y * 2.2, 900.0);\n float perspective = focalLength / (focalLength - finalZOffset);\n\n vec3 finalPos = worldPos.xyz;\n finalPos.xy *= perspective;\n finalPos.z += finalZOffset;\n\n gl_Position = projectionMatrix * viewMatrix * vec4(finalPos, 1.0);\n}\n`;\n\nconst fragmentShader = /* glsl */`\n uniform sampler2D uTexture;\n uniform vec2 uPlaneSize;\n uniform vec2 uImageSize;\n uniform float uAlpha;\n uniform float uZoom;\n varying vec2 vUv;\n\n vec2 coverUv(vec2 uv, vec2 planeSize, vec2 imageSize) {\n float planeRatio = planeSize.x / planeSize.y;\n float imageRatio = imageSize.x / imageSize.y;\n vec2 scale = vec2(1.0);\n if (planeRatio > imageRatio) {\n scale.y = imageRatio / planeRatio;\n } else {\n scale.x = planeRatio / imageRatio;\n }\n uv = (uv - 0.5) * scale + 0.5;\n return (uv - 0.5) / uZoom + 0.5;\n }\n\n void main() {\n vec2 uv = coverUv(vUv, uPlaneSize, uImageSize);\n if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) discard;\n vec4 tex = texture2D(uTexture, uv);\n gl_FragColor = vec4(tex.rgb, tex.a * uAlpha);\n }\n`;\n\nconst clamp = (v, mn, mx) => Math.min(Math.max(v, mn), mx);\nconst lerp = (a, b, t) => a + (b - a) * t;\n\nfunction HoverSliderScene({ items, compact }) {\n const reducedMotion = useEffectReducedMotion();\n const mountRef = useRef(null);\n const glRef = useRef(null);\n const isDesktopRef = useRef(false);\n const stateRef = useRef({ activeIndex: 0, hovering: false, hasClicked: false });\n\n const [highlightedIndex, setHighlightedIndex] = useState(null);\n\n useEffect(() => {\n if (!items.length) return;\n const mount = mountRef.current;\n if (!mount) return;\n\n let disposed = false;\n let repaint = () => {};\n let W = Math.max(1, mount.clientWidth);\n let H = Math.max(1, mount.clientHeight);\n\n const CARD_ASPECT = 1.7;\n const GAP = 14;\n const VISIBLE = 7;\n const HALF = 3;\n\n const getCardH = () => Math.round(H * (compact ? 0.39 : W < 768 ? 0.34 : 0.46));\n const getCardW = () => {\n const cardW = getCardH() * CARD_ASPECT;\n return Math.round(W < 768 ? Math.min(cardW, W * 0.88) : cardW);\n };\n\n const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });\n renderer.setPixelRatio(Math.min(devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n Object.assign(renderer.domElement.style, {\n position: \"absolute\",\n top:\"0\",\n left:\"0\",\n right:\"0\",\n bottom: W < 768 ?\"auto\" :\"0\",\n width:\"100%\",\n height: \"100%\",\n zIndex:\"15\", pointerEvents:\"none\",\n });\n mount.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n const updateCamera = () => {\n camera.left = -W / 2; camera.right = W / 2;\n camera.top = H / 2; camera.bottom = -H / 2;\n camera.near = -2000; camera.far = 2000;\n camera.updateProjectionMatrix();\n };\n camera.position.z = 1000;\n updateCamera();\n renderer.setSize(W, H, false);\n\n const loader = new THREE.TextureLoader();\n const texCache = {};\n const getTexture = (src) => {\n if (texCache[src]) return texCache[src];\n const tex = loader.load(src, (t) => {\n if (disposed) { t.dispose(); return; }\n t.colorSpace = THREE.SRGBColorSpace;\n t.minFilter = THREE.LinearFilter;\n t.magFilter = THREE.LinearFilter;\n t.userData.iw = t.image?.width || 1;\n t.userData.ih = t.image?.height || 1;\n repaint();\n });\n tex.userData.iw = 1;\n tex.userData.ih = 1;\n texCache[src] = tex;\n return tex;\n };\n items.forEach(item => getTexture(item.img));\n\n const syncImageSize = (mesh) => {\n const t = mesh.material.uniforms.uTexture.value;\n if (!t?.image) return;\n mesh.material.uniforms.uImageSize.value.set(\n t.image.width || t.userData.iw || 1,\n t.image.height || t.userData.ih || 1,\n );\n };\n\n const geo = new THREE.PlaneGeometry(1, 1, 80, 80);\n let CW = getCardW(), CH = getCardH();\n\n const makeMat = (tex) => new THREE.ShaderMaterial({\n uniforms: {\n uTexture: { value: tex },\n uPlaneSize: { value: new THREE.Vector2(CW, CH) },\n uImageSize: { value: new THREE.Vector2(tex.userData.iw, tex.userData.ih) },\n uVelocity: { value: new THREE.Vector2(0, 0) },\n uAlpha: { value: 0 },\n uZoom: { value: 1.06 },\n uViewport: { value: new THREE.Vector2(W / 2, H / 2) },\n uCurvature: { value: 0 },\n },\n vertexShader,\n fragmentShader,\n transparent: true,\n depthWrite: false,\n side: THREE.DoubleSide,\n });\n\n const firstTex = getTexture(items[0].img);\n const meshes = Array.from({ length: VISIBLE }, (_, i) => {\n const mesh = new THREE.Mesh(geo, makeMat(firstTex));\n mesh.renderOrder = i;\n scene.add(mesh);\n return mesh;\n });\n\n // ── Curve / idle state ────────────────────────────────────────────────────\n const curveAnim = { value: 0, zoom: 1.06 };\n const anim = { alpha: 0 };\n const ACTIVE_CURVE = 400;\n const SOFT_CURVE = 80;\n let floatIdx = 0;\n let prevFloat = 0;\n const vel = new THREE.Vector2(0, 0);\n let raf = 0;\n\n const getCurveForTravel = (targetIdx) => {\n const travel = Math.abs(targetIdx - floatIdx);\n const p = clamp((travel - 0.35) / 3.5, 0, 1);\n const eased = p * p * (3 - 2 * p);\n return lerp(SOFT_CURVE, ACTIVE_CURVE, eased);\n };\n\n const releaseCurve = (targetIdx = stateRef.current.activeIndex) => {\n if (reducedMotion) { curveAnim.value = 0; return; }\n const peakCurve = getCurveForTravel(targetIdx);\n gsap.killTweensOf(curveAnim);\n curveAnim.zoom = 1.06;\n gsap\n .timeline()\n .to(curveAnim, {\n value: peakCurve,\n zoom: 1.06,\n duration: 0.12,\n ease:\"power2.out\",\n })\n .to(curveAnim, {\n value: 0,\n zoom: 1.06,\n duration: 1.25,\n ease:\"power2.inOut\",\n });\n };\n\n const show = (targetIdx) => {\n if (reducedMotion) { anim.alpha = 1; curveAnim.value = 0; return; }\n gsap.killTweensOf(anim);\n gsap.to(anim, { alpha: 1, duration: reducedMotion ? 0 : 0.45, ease:\"power3.out\" });\n releaseCurve(targetIdx);\n };\n\n const hide = () => {\n if (reducedMotion) { anim.alpha = 0; curveAnim.value = 0; return; }\n gsap.killTweensOf(anim);\n gsap.killTweensOf(curveAnim);\n gsap.to(anim, { alpha: 0, duration: reducedMotion ? 0 : 0.35, ease:\"power2.out\" });\n gsap.to(curveAnim, { value: 0, zoom: 1.06, duration: reducedMotion ? 0 : 0.55, ease:\"power2.inOut\" });\n };\n\n const onRowChange = (targetIdx) => {\n releaseCurve(targetIdx);\n };\n\n const onResize = () => {\n const wasDesktop = isDesktopRef.current;\n W = Math.max(1, mount.clientWidth);\n H = Math.max(1, mount.clientHeight);\n isDesktopRef.current = W >= 768;\n Object.assign(renderer.domElement.style, {\n position: \"absolute\",\n bottom: W < 768 ?\"auto\" :\"0\",\n height: \"100%\",\n });\n renderer.setSize(W, H, false);\n updateCamera();\n CW = getCardW();\n CH = getCardH();\n meshes.forEach(m => {\n // width is always CW — never scaled\n m.material.uniforms.uPlaneSize.value.set(CW, CH);\n m.material.uniforms.uViewport.value.set(W / 2, H / 2);\n });\n\n if (!wasDesktop && isDesktopRef.current) {\n stateRef.current.hasClicked = true;\n stateRef.current.hovering = true;\n stateRef.current.activeIndex = 0;\n setHighlightedIndex(0);\n show(0);\n }\n };\n const observer = new ResizeObserver(() => { onResize(); repaint(); });\n observer.observe(mount);\n onResize();\n\n {\n stateRef.current.hasClicked = true;\n stateRef.current.hovering = true;\n setHighlightedIndex(0);\n show(0);\n }\n\n const tick = () => {\n if (!reducedMotion) raf = requestAnimationFrame(tick);\n\n const targetIdx = stateRef.current.activeIndex;\n\n\n// distance between current and target\nconst diff = targetIdx - floatIdx;\nconst dist = Math.abs(diff);\n\n// 🎯 inverse lerp (more distance = slower catch-up)\nconst t = clamp(0.18 - dist * 0.06, 0.05, 0.18);\n\n// smooth scrolling\nfloatIdx = reducedMotion ? targetIdx : floatIdx + diff * t;\n\nconst delta = floatIdx - prevFloat;\n\n// stronger + faster velocity response\nvel.y = lerp(vel.y, delta * 60, 0.16);\nvel.x = lerp(vel.x, 0, 0.14);\n\nprevFloat = floatIdx;\n\n const centreInt = Math.round(floatIdx);\n const drift = floatIdx - centreInt;\n\n for (let i = 0; i < VISIBLE; i++) {\n const offset = i - HALF;\n const itemIdx = ((centreInt + offset) % items.length + items.length) % items.length;\n const posY = (-offset + drift) * (CH + GAP);\n const dist = Math.abs(offset - drift);\n\n // ── Width is ALWAYS CW — no scaling on X ─────────────────────────────\n // Height still scales so non-centre cards recede slightly.\n const scaleH = Math.max(0.76, 1.0 - dist * 0.06);\n const sw = CW; // fixed width for every plane\n const sh = CH * scaleH; // height varies with distance\n\n const baseOpacity = 0.88; // 👈 controls center opacity (tweak this)\nconst opacity = Math.max(0, baseOpacity - dist * 0.22) * anim.alpha;\n\n const wantTex = getTexture(items[itemIdx].img);\n if (meshes[i].material.uniforms.uTexture.value !== wantTex) {\n meshes[i].material.uniforms.uTexture.value = wantTex;\n }\n syncImageSize(meshes[i]);\n\n meshes[i].position.set(compact ? W * 0.24 : 0, posY, i);\n meshes[i].scale.set(sw, sh, 1);\n meshes[i].rotation.z = 0;\n meshes[i].material.uniforms.uVelocity.value.set(vel.x, vel.y * 0.28);\n meshes[i].material.uniforms.uAlpha.value = opacity;\n meshes[i].material.uniforms.uZoom.value = curveAnim.zoom - clamp(1.0 - dist, 0, 1) * 0.04;\n // uPlaneSize reflects actual rendered size (sw × sh) for correct cover-UV math\n meshes[i].material.uniforms.uPlaneSize.value.set(sw, sh);\n meshes[i].material.uniforms.uCurvature.value = curveAnim.value;\n meshes[i].material.uniforms.uViewport.value.set(W / 2, H / 2);\n }\n\n renderer.render(scene, camera);\n };\n\n repaint = () => { if (reducedMotion && !disposed) tick(); };\n tick();\n\n glRef.current = {\n setActive: (i) => { stateRef.current.activeIndex = i; repaint(); },\n show,\n hide,\n onRowChange,\n };\n\n return () => {\n disposed = true;\n cancelAnimationFrame(raf);\n observer.disconnect();\n gsap.killTweensOf(anim);\n gsap.killTweensOf(curveAnim);\n geo.dispose();\n meshes.forEach(m => m.material.dispose());\n Object.values(texCache).forEach(t => t.dispose());\n renderer.dispose();\n renderer.domElement.remove();\n glRef.current = null;\n };\n }, [items, reducedMotion, compact]);\n\n const onEnter = useCallback((index) => {\n if (isDesktopRef.current) {\n const wasHovering = stateRef.current.hovering;\n stateRef.current.hasClicked = true;\n stateRef.current.hovering = true;\n stateRef.current.activeIndex = index;\n setHighlightedIndex(index);\n glRef.current?.setActive(index);\n if (!wasHovering) {\n glRef.current?.show(index);\n } else {\n glRef.current?.onRowChange(index);\n }\n return;\n }\n if (!stateRef.current.hasClicked) {\n setHighlightedIndex(index);\n return;\n }\n const wasHovering = stateRef.current.hovering;\n stateRef.current.hovering = true;\n stateRef.current.activeIndex = index;\n setHighlightedIndex(index);\n glRef.current?.setActive(index);\n if (!wasHovering) {\n glRef.current?.show(index);\n } else {\n glRef.current?.onRowChange(index);\n }\n }, []);\n\n const onLeave = useCallback((event) => {\n if (event.pointerType ===\"touch\") return;\n if (stateRef.current.hasClicked) return;\n stateRef.current.hovering = false;\n setHighlightedIndex(null);\n glRef.current?.hide();\n }, []);\n\n const activateRow = useCallback((index) => {\n const wasHovering = stateRef.current.hovering;\n stateRef.current.hasClicked = true;\n stateRef.current.hovering = true;\n stateRef.current.activeIndex = index;\n setHighlightedIndex(index);\n glRef.current?.setActive(index);\n if (!wasHovering) {\n glRef.current?.show(index);\n } else {\n glRef.current?.onRowChange(index);\n }\n }, []);\n\n return (\n <section\n ref={mountRef}\n onPointerLeave={onLeave}\n className={`relative isolate h-full w-full overflow-hidden bg-[#f0ede6] text-[#1e1c18] ${compact ? \"px-3 py-3\" : \"px-4 py-5\"}`}\n style={{ cursor:\"crosshair\" }}\n >\n <header\n className=\"relative z-20 flex items-start max-sm:px-2 justify-between text-[11px]\"\n style={{ color:\"rgba(30,28,24,0.6)\" }}\n >\n <div>ObsidianUI</div>\n {!compact && <nav className=\"absolute left-1/2 top-0 flex -translate-x-1/2 gap-3 max-sm:gap-5\">\n <span>Featured Works,</span>\n <span>Archive</span>\n <span>About</span>\n </nav>}\n {!compact && <div className=\"flex items-center gap-2 max-sm:hidden\">\n <span className=\"inline-block h-2 w-2 bg-[#e63000]\" />\n <span>Selected work</span>\n </div>}\n </header>\n\n <div className={`relative z-20 overflow-x-auto pb-4 ${compact ? \"mt-4 w-1/2\" : \"mt-10 w-full\"}`}>\n <div\n className={`grid gap-2 text-[10px] uppercase tracking-widest ${compact ? \"pb-2\" : \"pb-4\"}`}\n style={{\n gridTemplateColumns:compact ? \"20px minmax(0,1fr)\" : \"30px minmax(68px,1fr) 38px\",\n color:\"rgba(30,28,24,0.6)\",\n borderBottom:\"1px solid rgba(30,28,24,0.06)\",\n }}\n >\n <div>ID</div>\n <div>Title</div>\n {!compact && <div>Year</div>}\n </div>\n\n {items.map((item, index) => {\n const active = highlightedIndex === index;\n return (\n <button\n key={item.id}\n onPointerEnter={() => onEnter(index)}\n onPointerDown={() => activateRow(index)}\n onClick={() => activateRow(index)}\n type=\"button\"\n aria-label={`Project ${item.id}: ${item.title}, ${item.year}`}\n aria-pressed={active}\n onFocus={() => activateRow(index)}\n className=\"w-full border-0 bg-transparent text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#e63000]\"\n style={{\n display:\"grid\",\n gridTemplateColumns:compact ? \"20px minmax(0,1fr)\" : \"30px minmax(68px,1fr) 38px\",\n gap:\"8px\",\n padding:compact ? \"5px 0\" : \"7px 0\",\n borderBottom:\"1px solid rgba(30,28,24,0.06)\",\n cursor:\"crosshair\",\n transition:\"color 0.15s\",\n color: active ?\"#e63000\" :\"rgba(30,28,24,0.6)\",\n }}\n >\n <span style={{ fontSize: 11, letterSpacing:\"0.03em\" }}>{item.id}</span>\n <span style={{ fontSize: compact ? 10 : 12, fontWeight: 500 }}>{item.title}</span>\n {!compact && <span style={{ fontSize: 11 }}>{item.year}</span>}\n </button>\n );\n })}\n </div>\n\n </section>\n );\n}\n\nconst defaultItems = [\n { id: \"01\", title: \"Soft Forms\", focus: \"Visual study\", year: \"2026\", img: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-hover-slider/interactive-hover-slider-img01.webp\" },\n { id: \"02\", title: \"Botanical\", focus: \"Art direction\", year: \"2026\", img: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-hover-slider/interactive-hover-slider-img02.webp\" },\n { id: \"03\", title: \"Afterlight\", focus: \"Identity\", year: \"2026\", img: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-hover-slider/interactive-hover-slider-img03.webp\" },\n { id: \"04\", title: \"Glasswork\", focus: \"Materials\", year: \"2026\", img: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-hover-slider/interactive-hover-slider-img04.png\" },\n { id: \"05\", title: \"Motion Study\", focus: \"Experiment\", year: \"2026\", img: \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/interactive-hover-slider/interactive-hover-slider-img05.png\" },\n];\n\n/** @param {{ items?: { id: string, title: string, focus: string, year: string, img: string }[], compact?: boolean, className?: string, style?: import(\"react\").CSSProperties }} props */\nexport function InteractiveHoverSlider({ items = defaultItems, compact = false, className, style } = {}) {\n return <WebGLSurface className={className} style={style} imageSrc={items[0]?.img} label=\"ObsidianUI project image gallery\">\n  {items.length > 0 && <HoverSliderScene items={items} compact={compact} />}\n </WebGLSurface>;\n}\n"
        },
        {
          "path": "lib/effects/shared/webgl-surface.jsx",
          "target": "@lib/effects/shared/webgl-surface.jsx",
          "type": "registry:lib",
          "content": "\"use client\";\n\nimport { Component, useSyncExternalStore } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst subscribeMotion = (notify) => {\n  const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n  query.addEventListener(\"change\", notify);\n  return () => query.removeEventListener(\"change\", notify);\n};\n\nexport function useEffectReducedMotion() {\n  return useSyncExternalStore(subscribeMotion, () => window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches, () => true);\n}\n\nlet webglAvailable;\nfunction supportsWebGL() {\n  if (webglAvailable !== undefined) return webglAvailable;\n  try {\n    const canvas = document.createElement(\"canvas\");\n    const context = canvas.getContext(\"webgl2\");\n    webglAvailable = Boolean(context);\n    context?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n  } catch {\n    webglAvailable = false;\n  }\n  return webglAvailable;\n}\nconst subscribeAvailability = () => () => {};\n\nclass SurfaceBoundary extends Component {\n  state = { failed: false };\n  static getDerivedStateFromError() { return { failed: true }; }\n  render() { return this.state.failed ? this.props.fallback : this.props.children; }\n}\n\n/** @param {{ children?: import(\"react\").ReactNode, className?: string, style?: import(\"react\").CSSProperties, imageSrc?: string, label?: string }} props */\nexport function WebGLSurface({ children, className, style, imageSrc, label = \"ObsidianUI visual effect\" }) {\n  const supported = useSyncExternalStore(subscribeAvailability, supportsWebGL, () => false);\n  const fallback = <div role=\"img\" aria-label={label} className=\"absolute inset-0 bg-cover bg-center\" style={{ backgroundImage: imageSrc ? `url(${JSON.stringify(imageSrc)})` : undefined }} />;\n  return (\n    <div className={cn(\"relative isolate h-[28rem] w-full overflow-hidden bg-black\", className)} style={{ containerType: \"size\", ...style }}>\n      {fallback}\n      {supported && <SurfaceBoundary fallback={fallback}>{children}</SurfaceBoundary>}\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "item",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-separator",
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/item.tsx",
          "target": "@ui/item.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Separator } from \"@/components/ui/separator\"\n\nfunction ItemGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      role=\"list\"\n      data-slot=\"item-group\"\n      className={cn(\"group/item-group flex flex-col\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ItemSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"item-separator\"\n      orientation=\"horizontal\"\n      className={cn(\"my-0\", className)}\n      {...props}\n    />\n  )\n}\n\nconst itemVariants = cva(\n  \"group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline: \"border-border\",\n        muted: \"bg-muted/50\",\n      },\n      size: {\n        default: \"p-4 gap-4 \",\n        sm: \"py-3 px-4 gap-2.5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Item({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof itemVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : \"div\"\n  return (\n    <Comp\n      data-slot=\"item\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(itemVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nconst itemMediaVariants = cva(\n  \"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        icon: \"size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4\",\n        image:\n          \"size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction ItemMedia({\n  className,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof itemMediaVariants>) {\n  return (\n    <div\n      data-slot=\"item-media\"\n      data-variant={variant}\n      className={cn(itemMediaVariants({ variant, className }))}\n      {...props}\n    />\n  )\n}\n\nfunction ItemContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-content\"\n      className={cn(\n        \"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-title\"\n      className={cn(\n        \"flex w-fit items-center gap-2 text-sm leading-snug font-medium\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"item-description\"\n      className={cn(\n        \"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance\",\n        \"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-actions\"\n      className={cn(\"flex items-center gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ItemHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-header\"\n      className={cn(\n        \"flex basis-full items-center justify-between gap-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-footer\"\n      className={cn(\n        \"flex basis-full items-center justify-between gap-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Item,\n  ItemMedia,\n  ItemContent,\n  ItemActions,\n  ItemGroup,\n  ItemSeparator,\n  ItemTitle,\n  ItemDescription,\n  ItemHeader,\n  ItemFooter,\n}\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "target": "@ui/separator.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "jelly-loader",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/jelly-loader.tsx",
          "target": "@components/block/jelly-loader.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React from 'react';\nimport { motion, Transition } from 'motion/react';\n\ntype JellyLoaderProps = {\n    numberOfCubes?: number;\n    colors?: string[];\n};\n\nexport function JellyLoader({\n    numberOfCubes = 8,\n    colors = ['#FFE4E1', '#FFB6C1', '#FF8A95', '#FF6B8A', '#E91E63', '#C2185B', '#AD1457', '#880E4F']\n}: JellyLoaderProps) {\n    const transition: Transition = {\n        duration: 1.5,\n        repeat: Infinity,\n        repeatDelay: 0.5,\n        ease: 'easeOut'\n    };\n\n    return (\n        <div className=\"-translate-x-1/5 flex items-center justify-center\">\n            {Array.from({ length: numberOfCubes }).map((_, index) => {\n                const x = index * 10;\n                const y = -index * 10;\n\n                return (\n                    <motion.span\n                        key={index}\n                        className=\"h-[70px] w-[100px] absolute rounded-full\"\n                        style={{\n                            x,\n                            y,\n                            zIndex: numberOfCubes - index,\n                            backgroundColor: colors[index % colors.length],\n                            opacity: 1 - index * 0.05\n                        }}\n                        initial={{ scale: 1 }}\n                        animate={{ scale: [1, 0.75, 1], rotate: [0, 360] }}\n                        transition={{\n                            ...transition,\n                            delay: index * 0.05\n                        }}\n                    />\n                );\n            })}\n        </div>\n    );\n}\n\nexport default JellyLoader;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "kbd",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/kbd.tsx",
          "target": "@ui/kbd.tsx",
          "type": "registry:ui",
          "content": "import { cn } from \"@/lib/utils\"\n\nfunction Kbd({ className, ...props }: React.ComponentProps<\"kbd\">) {\n  return (\n    <kbd\n      data-slot=\"kbd\"\n      className={cn(\n        \"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none\",\n        \"[&_svg:not([class*='size-'])]:size-3\",\n        \"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <kbd\n      data-slot=\"kbd-group\"\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Kbd, KbdGroup }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "label",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-label",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/label.tsx",
          "target": "@ui/label.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Label({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  return (\n    <LabelPrimitive.Root\n      data-slot=\"label\"\n      className={cn(\n        \"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "liquid-metal",
      "type": "registry:block",
      "dependencies": [
        "@paper-design/shaders-react",
        "clsx",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/liquid-metal.tsx",
          "target": "@components/block/liquid-metal.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport React, { memo, forwardRef } from \"react\";\nimport { LiquidMetal as LiquidMetalShader } from \"@paper-design/shaders-react\";\nimport { cn } from \"@/lib/utils\";\nimport { useReducedMotion } from \"motion/react\";\n\n// ============================================================================\n// LiquidMetal - Base shader wrapper component\n// ============================================================================\n\nexport interface LiquidMetalProps {\n    /** Base background color of the liquid metal */\n    colorBack?: string;\n    /** Tint/highlight color for the chrome effect */\n    colorTint?: string;\n    /** Animation speed (0.1 - 2.0 recommended) */\n    speed?: number;\n    /** Pattern complexity/repetition (1 - 10) */\n    repetition?: number;\n    /** Wave distortion amount (0 - 1) */\n    distortion?: number;\n    /** Texture scale */\n    scale?: number;\n    /** Additional CSS classes */\n    className?: string;\n    /** Inline styles */\n    style?: React.CSSProperties;\n}\n\nexport const LiquidMetal = memo(function LiquidMetal({\n    colorBack = \"#aaaaac\",\n    colorTint = \"#ffffff\",\n    speed = 0.5,\n    repetition = 4,\n    distortion = 0.1,\n    scale = 1,\n    className,\n    style,\n}: LiquidMetalProps) {\n    const reduceMotion = useReducedMotion();\n    return (\n        <div\n            className={cn(\"absolute inset-0 z-0 overflow-hidden\", className)}\n            style={style}\n        >\n            <LiquidMetalShader\n                colorBack={colorBack}\n                colorTint={colorTint}\n                speed={reduceMotion ? 0 : speed}\n                repetition={repetition}\n                distortion={distortion}\n                softness={0}\n                shiftRed={0.3}\n                shiftBlue={-0.3}\n                angle={45}\n                shape=\"none\"\n                scale={scale}\n                fit=\"cover\"\n                style={{ width: \"100%\", height: \"100%\" }}\n            />\n        </div>\n    );\n});\n\nLiquidMetal.displayName = \"LiquidMetal\";\n\n// ============================================================================\n// LiquidMetalButton - Premium button with liquid metal border effect\n// ============================================================================\n\ninterface LiquidMetalControlProps {\n    /** Button content */\n    children: React.ReactNode;\n    /** Optional icon displayed on the left */\n    icon?: React.ReactNode;\n    /** Border width in pixels */\n    borderWidth?: number;\n    /** Configuration for the LiquidMetal shader */\n    metalConfig?: Omit<LiquidMetalProps, \"className\" | \"style\">;\n    /** Size variant */\n    size?: \"xs\" | \"sm\" | \"md\" | \"lg\";\n}\n\nexport type LiquidMetalButtonProps = LiquidMetalControlProps & (\n    | (React.ButtonHTMLAttributes<HTMLButtonElement> & { href?: never })\n    | (React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string; disabled?: boolean })\n);\n\nexport const LiquidMetalButton = forwardRef<\n    HTMLButtonElement | HTMLAnchorElement,\n    LiquidMetalButtonProps\n>(\n    (\n        {\n            children,\n            icon,\n            borderWidth = 4,\n            metalConfig,\n            size = \"md\",\n            className,\n            disabled,\n            href,\n            ...props\n        },\n        ref\n    ) => {\n        const sizeStyles = {\n            xs: \"py-1.5 pl-1.5 pr-4 gap-2 text-xs\",\n            sm: \"py-2 pl-2 pr-6 gap-3 text-sm\",\n            md: \"py-3 pl-3 pr-8 gap-4 text-base\",\n            lg: \"py-4 pl-4 pr-10 gap-6 text-lg\",\n        };\n\n        const iconSizes = {\n            xs: \"w-6 h-6\",\n            sm: \"w-8 h-8\",\n            md: \"w-10 h-10\",\n            lg: \"w-12 h-12\",\n        };\n\n        const controlClassName = cn(\n            \"relative group inline-block rounded-full cursor-pointer border-none bg-transparent p-0 outline-none transition-transform active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:pointer-events-none\",\n            className\n        );\n        const content = (\n                <div\n                    className=\"relative rounded-full overflow-hidden shadow-[0_20px_50px_-12px_rgba(0,0,0,0.25)]\"\n                    style={{ padding: borderWidth }}\n                >\n                    {/* Liquid Metal Border Layer */}\n                    <LiquidMetal\n                        colorBack={metalConfig?.colorBack ?? \"#888888\"}\n                        colorTint={metalConfig?.colorTint ?? \"#ffffff\"}\n                        speed={metalConfig?.speed ?? 0.4}\n                        repetition={metalConfig?.repetition ?? 4}\n                        distortion={metalConfig?.distortion ?? 0.15}\n                        scale={metalConfig?.scale ?? 1}\n                        className=\"absolute inset-0 z-0 rounded-full\"\n                    />\n\n                    {/* Inner Button Body */}\n                    <div\n                        className={cn(\n                            \"relative z-10 rounded-full flex items-center\",\n                            \"bg-background\",\n                            \"transition-colors duration-200\",\n                            \"group-hover:bg-muted\",\n                            sizeStyles[size]\n                        )}\n                    >\n                        {icon && (\n                            <div\n                                className={cn(\n                                    \"rounded-full flex items-center justify-center\",\n                                    \"bg-muted\",\n                                    \"shadow-[inset_0_2px_4px_rgba(0,0,0,0.06)]\",\n                                    iconSizes[size]\n                                )}\n                            >\n                                <span className=\"text-muted-foreground\" aria-hidden=\"true\">\n                                    {icon}\n                                </span>\n                            </div>\n                        )}\n                        <span className=\"font-medium tracking-tight text-foreground\">\n                            {children}\n                        </span>\n                    </div>\n                </div>\n        );\n\n        if (href !== undefined) {\n            const anchorProps = props as React.AnchorHTMLAttributes<HTMLAnchorElement>;\n            return (\n                <a\n                    {...anchorProps}\n                    ref={ref as React.Ref<HTMLAnchorElement>}\n                    href={disabled ? undefined : href}\n                    aria-disabled={disabled || undefined}\n                    tabIndex={disabled ? -1 : anchorProps.tabIndex}\n                    className={controlClassName}\n                    onClick={(event) => {\n                        if (disabled) {\n                            event.preventDefault();\n                            return;\n                        }\n                        anchorProps.onClick?.(event);\n                    }}\n                >\n                    {content}\n                </a>\n            );\n        }\n\n        return (\n            <button\n                type=\"button\"\n                {...props as React.ButtonHTMLAttributes<HTMLButtonElement>}\n                ref={ref as React.Ref<HTMLButtonElement>}\n                disabled={disabled}\n                className={controlClassName}\n            >\n                {content}\n            </button>\n        );\n    }\n);\n\nLiquidMetalButton.displayName = \"LiquidMetalButton\";\n\nexport default LiquidMetalButton;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "magnet-tabs",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/magnet-tabs.tsx",
          "target": "@components/block/magnet-tabs.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React from 'react';\nimport { motion } from 'motion/react';\n\ninterface MagnetTabsProps {\n    slug: string;\n    options: string[];\n    onSelect: (option: string) => void;\n    activeTab: string;\n}\n\nexport function MagnetTabs({ slug, options, onSelect, activeTab }: MagnetTabsProps) {\n    const [hovered, setHovered] = React.useState<string | undefined>(undefined);\n\n    return (\n        <div className=\"flex items-start justify-start\">\n            <ul className=\"flex border-[1px] border-black/10 dark:border-white/10 border-b-0\">\n                {options.map((option) => {\n                    const isActive = activeTab === option;\n                    return (\n                        <li\n                            onMouseEnter={() => setHovered(option)}\n                            onMouseLeave={() => setHovered(undefined)}\n                            key={slug + option}\n                            onClick={() => onSelect(option)}\n                            className=\"relative cursor-pointer shrink-0\"\n                        >\n                            <p\n                                className={`z-10 relative px-3 py-2 transition-all text-sm ${isActive ? 'opacity-100' : 'opacity-50 hover:opacity-100'\n                                    }`}\n                            >\n                                {option}\n                            </p>\n\n                            {isActive && (\n                                <motion.div\n                                    layout\n                                    layoutId={slug + 'magnet'}\n                                    transition={{ duration: 0.2, type: 'spring', bounce: 0.2 }}\n                                    className=\"w-full h-1 absolute bottom-full left-0 bg-blue-500 rounded-sm\"\n                                />\n                            )}\n\n                            {(hovered === option || (hovered === undefined && isActive)) && (\n                                <motion.div\n                                    layout\n                                    layoutId={slug + 'tab-bar-highlight'}\n                                    transition={{ duration: 0.2, type: 'spring', bounce: 0 }}\n                                    className=\"w-full h-full absolute bottom-0 left-0 bg-black/5 dark:bg-white/10 rounded-sm\"\n                                />\n                            )}\n                        </li>\n                    );\n                })}\n            </ul>\n        </div>\n    );\n}\n\nexport default MagnetTabs;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "magnetic-image-trail",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/magnetic-image-trail.jsx",
          "target": "@components/block/magnetic-image-trail.jsx",
          "type": "registry:block",
          "content": "\"use client\";\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst imageUrls = [\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-distortion.jpg\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img01.webp\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img02.webp\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img03.webp\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img04.png\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img05.png\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img06.png\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img07.png\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img08.jpg\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img09.jpg\",\n  \"https://pub-830233752de349e29c6104a501b309d4.r2.dev/effects/magnetic-image-trail/magnetic-image-trail-img10.jpg\",\n];\n\nconst SLOTS = [\n  { y: 0, size: 1.19, w: 206, h: 167 }, // mid - just a bit bigger\n  { y: -30, size: 1.10, w: 214, h: 167 }, // 2nd closest - little bigger, spread more\n  { y: 30, size: 1.20, w: 214, h: 167 },\n  { y: -60, size: 1.06, w: 186, h: 144 }, // spread more\n  { y: 60, size: 1.06, w: 186, h: 144 },\n\n  { y: -94, size: 0.91, w: 157, h: 122 }, // spread more\n  { y: 94, size: 0.91, w: 157, h: 122 },\n  { y: -122, size: 0.80, w: 139, h: 109 },\n  { y: 122, size: 0.80, w: 139, h: 109 },\n\n  { y: -18, size: 1.04, w: 166, h: 129 }, // very little more than before\n  { y: 18, size: 1.02, w: 161, h: 126 },\n  { y: -52, size: 0.97, w: 153, h: 118 },\n  { y: 52, size: 0.97, w: 153, h: 118 },\n\n  { y: -78, size: 0.87, w: 137, h: 108 },\n  { y: 78, size: 0.87, w: 137, h: 108 },\n  { y: -134, size: 0.70, w: 111, h: 83 }, // spread a bit more at edges\n  { y: 134, size: 0.70, w: 111, h: 83 },\n  { y: 10, size: 0.91, w: 140, h: 108 }, // very little\n];\n\n// Faster + tighter motion.\nconst SPEED = 0.0003;\nconst MAX_SPEED = 0.0015; // Added max speed limit for generation\nconst FOLLOW = 0.22;\nconst SPREAD_X = 250; // Slightly more spread since sizes increased\n\n// Interaction Config\nconst MOUSE_SPEED_BOOST = 0.003;\nconst FORWARD_PUSH_AMOUNT = 40;\nconst UPWARD_PUSH_AMOUNT = 30;\nconst SCALE_OUT_MIN = 0.68;\nconst SCALE_OUT_MAX = 1.48;\n\nfunction clamp(v, min, max) {\n  return Math.max(min, Math.min(max, v));\n}\n\nfunction lerp(a, b, t) {\n  return a + (b - a) * t;\n}\n\nfunction drawRoundedImage(ctx, img, x, y, w, h, r = 0) {\n  if (!img?.complete || img.naturalWidth <= 0) return;\n\n  const imgAspect = img.naturalWidth / img.naturalHeight;\n  const boxAspect = w / h;\n\n  let sx, sy, sw, sh;\n\n  if (imgAspect > boxAspect) {\n    sh = img.naturalHeight;\n    sw = sh * boxAspect;\n    sx = (img.naturalWidth - sw) / 2;\n    sy = 0;\n  } else {\n    sw = img.naturalWidth;\n    sh = sw / boxAspect;\n    sx = 0;\n    sy = (img.naturalHeight - sh) / 2;\n  }\n\n  if (r > 0) {\n    ctx.save();\n    ctx.beginPath();\n    ctx.roundRect(x, y, w, h, r);\n    ctx.clip();\n    ctx.drawImage(img, sx, sy, sw, sh, x, y, w, h);\n    ctx.restore();\n    return;\n  }\n\n  // No rounded corners.\n  ctx.drawImage(img, sx, sy, sw, sh, x, y, w, h);\n}\n\n/**\n * @param {{ images?: string[], children?: import(\"react\").ReactNode, className?: string,\n * style?: import(\"react\").CSSProperties, height?: import(\"react\").CSSProperties[\"height\"],\n * background?: string, textColor?: string, compositionScale?: number }} props\n */\nfunction ImageTrail({\n  images = imageUrls,\n  children = <>ObsidianUI.<br />Interfaces people remember.</>,\n  className,\n  style,\n  height = \"100vh\",\n  background = \"#EDEBE6\",\n  textColor = \"#111\",\n  compositionScale = 0.78,\n} = {}) {\n  const wrapRef = useRef(null);\n  const canvasRef = useRef(null);\n  const rafRef = useRef(0);\n\n  const pointer = useRef({ x: 0, y: 0 });\n  const lerpedPointer = useRef({ x: 0, y: 0 }); // lerped pointer position\n  const smooth = useRef({ x: 0, y: 0 });\n  const dirRef = useRef({ x: 1, y: 0 });\n  const lastMoveAt = useRef(0);\n  const phase = useRef(0);\n  const lastTime = useRef(0);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const wrap = wrapRef.current;\n    const ctx = canvas?.getContext(\"2d\");\n    if (!wrap || !canvas || !ctx) return;\n\n    const reducedMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const sources = images.length ? images : imageUrls;\n    const imgs = SLOTS.map((_, i) => {\n      const img = new Image();\n      img.crossOrigin = \"anonymous\";\n      img.src = sources[i % sources.length];\n      return img;\n    });\n\n    function resize() {\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      const w = wrap.clientWidth;\n      const h = wrap.clientHeight;\n\n      canvas.width = Math.round(w * dpr);\n      canvas.height = Math.round(h * dpr);\n      canvas.style.width = `${w}px`;\n      canvas.style.height = `${h}px`;\n\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n      pointer.current = { x: w / 2, y: h / 2 };\n      lerpedPointer.current = { x: w / 2, y: h / 2 };\n      smooth.current = { x: w / 2, y: h / 2 };\n      if (reducedMotion.matches) frame(0);\n    }\n\n    function frame(now) {\n      const W = wrap.clientWidth;\n      const H = wrap.clientHeight;\n      // Fit the original orbit and cards together inside embedded previews.\n      const fitScale = Math.min(1, W / 720, H / 520) * Math.max(0, compositionScale);\n      ctx.clearRect(0, 0, W, H);\n\n      const dt = Math.min(32, now - (lastTime.current || now));\n      lastTime.current = now;\n\n      // --- lerp pointer on every frame ---\n      // The lerped pointer smoothly follows the actual pointer.\n      // A lerp amount of 0.18 gives a nice responsive but eased motion.\n      const LERP_AMOUNT = 0.18;\n      lerpedPointer.current.x = lerp(lerpedPointer.current.x, pointer.current.x, LERP_AMOUNT);\n      lerpedPointer.current.y = lerp(lerpedPointer.current.y, pointer.current.y, LERP_AMOUNT);\n\n      // Phase increment will be calculated after mouse speed is determined\n\n      const prevSX = smooth.current.x;\n      const prevSY = smooth.current.y;\n\n      // use lerpedPointer instead of pointer directly for the trailing smooth position\n      smooth.current.x += (lerpedPointer.current.x - smooth.current.x) * FOLLOW;\n      smooth.current.y += (lerpedPointer.current.y - smooth.current.y) * FOLLOW;\n\n      const cx = smooth.current.x;\n      const cy = smooth.current.y;\n\n      const vx = cx - prevSX;\n      const vy = cy - prevSY;\n      const vmag = Math.hypot(vx, vy);\n      if (vmag > 0.35) {\n        const tx = vx / vmag;\n        const ty = vy / vmag;\n        dirRef.current.x = dirRef.current.x + (tx - dirRef.current.x) * 0.2;\n        dirRef.current.y = dirRef.current.y + (ty - dirRef.current.y) * 0.2;\n        const m = Math.hypot(dirRef.current.x, dirRef.current.y) || 1;\n        dirRef.current.x /= m;\n        dirRef.current.y /= m;\n        lastMoveAt.current = now;\n      } else if (!lastMoveAt.current) {\n        lastMoveAt.current = now;\n      }\n\n      const speed01 = clamp(vmag / 18, 0, 1);\n      // INCREASE GENERATING SPEED BASED ON MOUSE MOVEMENT, CLAMPED TO MAX_SPEED\n      const currentSpeed = clamp(SPEED + speed01 * MOUSE_SPEED_BOOST, SPEED, MAX_SPEED);\n      phase.current += dt * currentSpeed;\n      const dir = dirRef.current;\n\n      const cards = SLOTS.map((slot, i) => {\n        const n = SLOTS.length;\n\n        // Animation progress per image.\n        const t = (phase.current + i / n) % 1;\n\n        // -1 → 0 → 1\n        // This controls the movement along the diagonal path.\n        const pathNorm = t * 2 - 1;\n\n        // Diagonal movement direction.\n        // Start: bottom-right\n        // Center: middle\n        // End: top-left\n        // Bottom-left → top-right, but less steep.\n        const moveX = pathNorm;\n        const moveY = -pathNorm;\n\n        // Keep the circular cluster shape.\n        const yOffset = clamp(slot.y, -SPREAD_X * 0.82, SPREAD_X * 0.82);\n\n        const circleWidthAtY =\n          Math.sqrt(Math.max(0, SPREAD_X * SPREAD_X - yOffset * yOffset)) * 0.74;\n\n        // Lower number = flatter/slanting movement.\n        // 0.38 was too steep.\n        const diagonalPush = SPREAD_X * 0.15;\n\n        // Move the whole diagonal slightly upward,\n        // so it starts a little above bottom-left\n        // and ends a little below top-right.\n        const verticalLift = -SPREAD_X * 0.06;\n\n        const x = cx + moveX * circleWidthAtY;\n        const y = cy + yOffset + moveY * diagonalPush + verticalLift;\n\n        // Scale follows the same diagonal movement:\n        // small → big at center → small\n        const rawCenterScale = Math.max(0, 1 - Math.abs(pathNorm));\n        const easedCenterScale =\n          rawCenterScale * rawCenterScale * (3 - 2 * rawCenterScale);\n        const centerScale = lerp(0.06, 0.9, easedCenterScale);\n\n        // Scale images up in the direction of mouse movement.\n        // Images ahead of the mouse direction get larger.\n        // Images behind the mouse direction get smaller.\n        const dx = x - cx;\n        const dy = y - cy;\n\n        const directionalProjection = clamp(\n          (dx * dir.x + dy * dir.y) / Math.max(1, SPREAD_X),\n          -1,\n          1\n        );\n\n        // Scaling out works on OPPOSITE direction.\n        // Images behind the mouse direction get larger (scale out).\n        const oppositeProjection = -directionalProjection;\n        const backwardAmount = (oppositeProjection + 1) * 0.5;\n\n        const movementBoost = lerp(1, 1.18, speed01);\n        const directionalScale = lerp(SCALE_OUT_MIN, SCALE_OUT_MAX, backwardAmount) * movementBoost;\n        const scale = centerScale * directionalScale * 1.14;\n\n        // Image translate effect on mouse move direction.\n        // Images ahead of the mouse are pushed forward.\n        const forwardPush = Math.max(0, directionalProjection) * speed01 * FORWARD_PUSH_AMOUNT;\n\n        // Images scaling out (behind) translate a little up\n        const upwardPush = Math.max(0, oppositeProjection) * speed01 * UPWARD_PUSH_AMOUNT;\n\n        return {\n          i,\n          img: imgs[i % imgs.length],\n\n          x: x + dir.x * forwardPush,\n          y: y + dir.y * forwardPush - upwardPush,\n\n          w: slot.w * slot.size * scale,\n          h: slot.h * slot.size * scale,\n\n          rot: 0,\n          alpha: 1,\n          order: i,\n        };\n      });\n\n      // Stable stacking: never sort by `depth` (which changes during animation).\n      cards.sort((a, b) => a.i - b.i);\n\n      for (const card of cards) {\n        if (card.w < 2 || card.h < 2) continue;\n\n        ctx.save();\n        ctx.translate(cx + (card.x - cx) * fitScale, cy + (card.y - cy) * fitScale);\n        ctx.globalAlpha = 1;\n        ctx.shadowColor = \"rgba(0,0,0,0.14)\";\n        ctx.shadowBlur = 12 * fitScale;\n        ctx.shadowOffsetY = 5 * fitScale;\n\n        const width = card.w * fitScale;\n        const height = card.h * fitScale;\n        drawRoundedImage(ctx, card.img, -width / 2, -height / 2, width, height, 0);\n\n        ctx.restore();\n      }\n\n      if (!reducedMotion.matches) rafRef.current = requestAnimationFrame(frame);\n    }\n\n    function syncMotion() {\n      cancelAnimationFrame(rafRef.current);\n      lastTime.current = 0;\n      if (reducedMotion.matches) frame(0);\n      else rafRef.current = requestAnimationFrame(frame);\n    }\n\n    // Observe the preview itself; gallery layout changes need not resize the window.\n    const observer = new ResizeObserver(resize);\n    observer.observe(wrap);\n    imgs.forEach((img) => {\n      img.onload = () => { if (reducedMotion.matches) frame(0); };\n    });\n    resize();\n    syncMotion();\n    reducedMotion.addEventListener(\"change\", syncMotion);\n\n    return () => {\n      cancelAnimationFrame(rafRef.current);\n      observer.disconnect();\n      reducedMotion.removeEventListener(\"change\", syncMotion);\n      imgs.forEach((img) => { img.onload = null; });\n    };\n  }, [images, compositionScale]);\n\n  const updatePointer = useCallback((e) => {\n    const rect = wrapRef.current?.getBoundingClientRect();\n    if (!rect) return;\n    // On mouse move, we set pointer to the actual event location (no lerp here).\n    pointer.current = {\n      x: e.clientX - rect.left,\n      y: e.clientY - rect.top,\n    };\n  }, []);\n\n  return (\n    <section\n      ref={wrapRef}\n      className={cn(\"isolate\", className)}\n      onPointerMove={updatePointer}\n      onPointerEnter={updatePointer}\n      style={{\n        position: \"relative\",\n        width: \"100%\",\n        height,\n        background,\n        overflow: \"hidden\",\n        containerType: \"inline-size\",\n        ...style,\n      }}\n    >\n      <div\n        className=\"w-[70%] mx-auto\"\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          pointerEvents: \"none\",\n          userSelect: \"none\",\n          zIndex: 1,\n          color: textColor,\n          fontSize: \"clamp(18px, 3.5cqw, 44px)\",\n\n          letterSpacing: \"-0.03em\",\n          lineHeight: 1.05,\n          textAlign: \"center\",\n          padding: \"0 20px\",\n        }}\n      >\n        {children}\n      </div>\n      <canvas\n        ref={canvasRef}\n        aria-hidden=\"true\"\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          width: \"100%\",\n          height: \"100%\",\n          pointerEvents: \"none\",\n          zIndex: 2,\n        }}\n      />\n    </section>\n  );\n}\n\nexport { ImageTrail as MagneticImageTrail };\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "mask-cursor-effect",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/mask-cursor-effect.tsx",
          "target": "@components/block/mask-cursor-effect.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React, { useEffect, useRef, useState } from 'react';\nimport { motion, useMotionValue, useSpring } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\nconst getMaskDataUrl = () => {\n    const svgString = `<svg width=\"526\" height=\"526\" viewBox=\"0 0 526 526\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <circle cx=\"263\" cy=\"263\" r=\"263\" fill=\"black\" />\n  </svg>`;\n    return `data:image/svg+xml;base64,${btoa(svgString)}`;\n};\n\ninterface MaskCursorEffectProps {\n    children: React.ReactNode;\n    hiddenComponent?: React.ReactNode;\n    className?: string;\n    compressedMaskSize?: number;\n    expandedMaskSize?: number;\n    backgroundColor?: string;\n}\n\nexport function MaskCursorEffect({\n    children,\n    hiddenComponent,\n    className,\n    compressedMaskSize = 40,\n    expandedMaskSize = 350,\n    backgroundColor = '#EA5A47'\n}: MaskCursorEffectProps) {\n    const [mousePosition, setMousePosition] = useState({ x: 20, y: 20 });\n    const [isHovered, setIsHovered] = useState(false);\n    const wrapperRef = useRef<HTMLDivElement>(null);\n\n    const MASK_SIZE = isHovered ? expandedMaskSize : compressedMaskSize;\n\n    const maskX = useSpring(useMotionValue(mousePosition.x - MASK_SIZE / 2), { stiffness: 500, damping: 50 });\n    const maskY = useSpring(useMotionValue(mousePosition.y - MASK_SIZE / 2), { stiffness: 500, damping: 50 });\n    const maskSizeSpring = useSpring(useMotionValue(MASK_SIZE), { stiffness: 500, damping: 50 });\n\n    useEffect(() => {\n        maskX.set(mousePosition.x - MASK_SIZE / 2);\n        maskY.set(mousePosition.y - MASK_SIZE / 2);\n        maskSizeSpring.set(MASK_SIZE);\n    }, [mousePosition, MASK_SIZE, maskX, maskY, maskSizeSpring]);\n\n    useEffect(() => {\n        const wrapper = wrapperRef.current;\n        if (!wrapper) return;\n        const handleMouseMove = (e: MouseEvent) => {\n            const { left, top } = wrapper.getBoundingClientRect();\n            setMousePosition({ x: e.clientX - left, y: e.clientY - top });\n        };\n        wrapper.addEventListener('mousemove', handleMouseMove);\n        return () => wrapper.removeEventListener('mousemove', handleMouseMove);\n    }, []);\n\n    return (\n        <div ref={wrapperRef} className=\"h-full w-full relative flex flex-col\">\n            <motion.div\n                style={{\n                    maskImage: `url(\"${getMaskDataUrl()}\")`,\n                    WebkitMaskImage: `url(\"${getMaskDataUrl()}\")`,\n                    maskRepeat: 'no-repeat',\n                    WebkitMaskRepeat: 'no-repeat',\n                    WebkitMaskPosition: `${mousePosition.x - MASK_SIZE / 2}px ${mousePosition.y - MASK_SIZE / 2}px`,\n                    maskPosition: `${mousePosition.x - MASK_SIZE / 2}px ${mousePosition.y - MASK_SIZE / 2}px`,\n                    WebkitMaskSize: `${MASK_SIZE}px ${MASK_SIZE}px`,\n                    maskSize: `${MASK_SIZE}px ${MASK_SIZE}px`,\n                    backgroundColor,\n                    color: 'black',\n                    transition: 'mask-size 0.3s ease, -webkit-mask-size 0.3s ease'\n                }}\n                className={cn('h-[800px] w-full flex items-center justify-center absolute z-10', className)}\n            >\n                <div onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>\n                    {hiddenComponent}\n                </div>\n            </motion.div>\n            <div className={cn('h-[800px] w-full flex items-center justify-center text-white/70', className)}>\n                {children}\n            </div>\n        </div>\n    );\n}\n\nexport default MaskCursorEffect;\n\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "masonry-grid",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/masonry-grid.tsx",
          "target": "@components/block/masonry-grid.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React, { useState } from 'react';\nimport { motion } from 'motion/react';\nimport Image from 'next/image';\n\ninterface MasonryGridProps {\n    items: { image: string; title: string; description: string }[];\n    columns?: number;\n}\n\nexport function MasonryGrid({ items, columns }: MasonryGridProps) {\n    const [imagesLoaded, setImagesLoaded] = useState<{ [key: string]: boolean }>({});\n\n    if (!items || items.length === 0) {\n        return <div className=\"text-center p-4\">No items to display</div>;\n    }\n\n    const getColumnCount = () => {\n        if (typeof window === 'undefined') return 1;\n        const width = window.innerWidth;\n        if (width >= 1024) return 4;\n        if (width >= 768) return 3;\n        if (width >= 640) return 2;\n        return 1;\n    };\n\n    return (\n        <div\n            style={{ columns: columns }}\n            className={`${!columns && 'columns-1 sm:columns-2 md:columns-3 lg:columns-4'} gap-2 overflow-y-auto p-3 w-full max-w-4xl`}\n        >\n            {items.map((item, index) => {\n                const columnCount = columns || getColumnCount();\n                const rowIndex = Math.floor(index / columnCount);\n\n                return (\n                    <motion.div\n                        key={index}\n                        className=\"break-inside-avoid mb-4 relative group rounded-xl overflow-hidden p-1 border border-transparent hover:border-neutral-300 dark:hover:border-neutral-700\"\n                        initial={{ opacity: 0, y: 20 }}\n                        animate={{ opacity: 1, y: 0, transition: { duration: 0.5, delay: rowIndex * 0.1, ease: 'easeOut' } }}\n                        whileHover={{ scale: 1.05 }}\n                    >\n                        <div className=\"relative w-full flex gap-1 flex-col items-start justify-start\">\n                            {!imagesLoaded[item.image] && (\n                                <div className=\"absolute inset-0 w-full h-[300px] bg-neutral-500/50 animate-pulse rounded-lg\" />\n                            )}\n                            <Image\n                                src={item.image}\n                                alt={item.title}\n                                width={400}\n                                height={300}\n                                className={`w-full h-auto transition-transform duration-300 rounded-lg ${!imagesLoaded[item.image] ? 'opacity-0' : 'opacity-100'}`}\n                                sizes=\"(max-width: 640px) 100vw, (max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw\"\n                                onLoad={() => setImagesLoaded((prev) => ({ ...prev, [item.image]: true }))}\n                                onError={() => setImagesLoaded((prev) => ({ ...prev, [item.image]: true }))}\n                            />\n                            {imagesLoaded[item.image] && (\n                                <div className=\"w-full\">\n                                    <h3 className=\"text-sm font-medium\">{item.title}</h3>\n                                    <p className=\"mt-0 text-xs text-neutral-500 line-clamp-2 overflow-hidden\">{item.description}</p>\n                                </div>\n                            )}\n                        </div>\n                    </motion.div>\n                );\n            })}\n        </div>\n    );\n}\n\nexport default MasonryGrid;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "menubar",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-menubar",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/menubar.tsx",
          "target": "@ui/menubar.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Menubar({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Root>) {\n  return (\n    <MenubarPrimitive.Root\n      data-slot=\"menubar\"\n      className={cn(\n        \"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarMenu({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {\n  return <MenubarPrimitive.Menu data-slot=\"menubar-menu\" {...props} />\n}\n\nfunction MenubarGroup({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Group>) {\n  return <MenubarPrimitive.Group data-slot=\"menubar-group\" {...props} />\n}\n\nfunction MenubarPortal({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {\n  return <MenubarPrimitive.Portal data-slot=\"menubar-portal\" {...props} />\n}\n\nfunction MenubarRadioGroup({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {\n  return (\n    <MenubarPrimitive.RadioGroup data-slot=\"menubar-radio-group\" {...props} />\n  )\n}\n\nfunction MenubarTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {\n  return (\n    <MenubarPrimitive.Trigger\n      data-slot=\"menubar-trigger\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarContent({\n  className,\n  align = \"start\",\n  alignOffset = -4,\n  sideOffset = 8,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Content>) {\n  return (\n    <MenubarPortal>\n      <MenubarPrimitive.Content\n        data-slot=\"menubar-content\"\n        align={align}\n        alignOffset={alignOffset}\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md\",\n          className\n        )}\n        {...props}\n      />\n    </MenubarPortal>\n  )\n}\n\nfunction MenubarItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <MenubarPrimitive.Item\n      data-slot=\"menubar-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {\n  return (\n    <MenubarPrimitive.CheckboxItem\n      data-slot=\"menubar-checkbox-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <MenubarPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </MenubarPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </MenubarPrimitive.CheckboxItem>\n  )\n}\n\nfunction MenubarRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {\n  return (\n    <MenubarPrimitive.RadioItem\n      data-slot=\"menubar-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <MenubarPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </MenubarPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </MenubarPrimitive.RadioItem>\n  )\n}\n\nfunction MenubarLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <MenubarPrimitive.Label\n      data-slot=\"menubar-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {\n  return (\n    <MenubarPrimitive.Separator\n      data-slot=\"menubar-separator\"\n      className={cn(\"bg-border -mx-1 my-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"menubar-shortcut\"\n      className={cn(\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarSub({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {\n  return <MenubarPrimitive.Sub data-slot=\"menubar-sub\" {...props} />\n}\n\nfunction MenubarSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <MenubarPrimitive.SubTrigger\n      data-slot=\"menubar-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto h-4 w-4\" />\n    </MenubarPrimitive.SubTrigger>\n  )\n}\n\nfunction MenubarSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {\n  return (\n    <MenubarPrimitive.SubContent\n      data-slot=\"menubar-sub-content\"\n      className={cn(\n        \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Menubar,\n  MenubarPortal,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarGroup,\n  MenubarSeparator,\n  MenubarLabel,\n  MenubarItem,\n  MenubarShortcut,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarSub,\n  MenubarSubTrigger,\n  MenubarSubContent,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "navigation-menu",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-navigation-menu",
        "class-variance-authority",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/navigation-menu.tsx",
          "target": "@ui/navigation-menu.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction NavigationMenu({\n  className,\n  children,\n  viewport = true,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {\n  viewport?: boolean\n}) {\n  return (\n    <NavigationMenuPrimitive.Root\n      data-slot=\"navigation-menu\"\n      data-viewport={viewport}\n      className={cn(\n        \"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {viewport && <NavigationMenuViewport />}\n    </NavigationMenuPrimitive.Root>\n  )\n}\n\nfunction NavigationMenuList({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {\n  return (\n    <NavigationMenuPrimitive.List\n      data-slot=\"navigation-menu-list\"\n      className={cn(\n        \"group flex flex-1 list-none items-center justify-center gap-1\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {\n  return (\n    <NavigationMenuPrimitive.Item\n      data-slot=\"navigation-menu-item\"\n      className={cn(\"relative\", className)}\n      {...props}\n    />\n  )\n}\n\nconst navigationMenuTriggerStyle = cva(\n  \"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1\"\n)\n\nfunction NavigationMenuTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {\n  return (\n    <NavigationMenuPrimitive.Trigger\n      data-slot=\"navigation-menu-trigger\"\n      className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n      {...props}\n    >\n      {children}{\" \"}\n      <ChevronDownIcon\n        className=\"relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n        aria-hidden=\"true\"\n      />\n    </NavigationMenuPrimitive.Trigger>\n  )\n}\n\nfunction NavigationMenuContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {\n  return (\n    <NavigationMenuPrimitive.Content\n      data-slot=\"navigation-menu-content\"\n      className={cn(\n        \"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto\",\n        \"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuViewport({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {\n  return (\n    <div\n      className={cn(\n        \"absolute top-full left-0 isolate z-50 flex justify-center\"\n      )}\n    >\n      <NavigationMenuPrimitive.Viewport\n        data-slot=\"navigation-menu-viewport\"\n        className={cn(\n          \"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction NavigationMenuLink({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {\n  return (\n    <NavigationMenuPrimitive.Link\n      data-slot=\"navigation-menu-link\"\n      className={cn(\n        \"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuIndicator({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {\n  return (\n    <NavigationMenuPrimitive.Indicator\n      data-slot=\"navigation-menu-indicator\"\n      className={cn(\n        \"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md\" />\n    </NavigationMenuPrimitive.Indicator>\n  )\n}\n\nexport {\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n  navigationMenuTriggerStyle,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "otp-input",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/otp-input.tsx",
          "target": "@components/block/otp-input.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport React, { useId, useRef, useState } from 'react';\nimport { motion, useReducedMotion } from 'motion/react';\nimport { Check } from 'lucide-react';\nimport { cn } from '@/lib/utils';\n\ninterface OTPInputProps {\n    correctOTP?: string;\n    onSuccess?: () => void;\n    onError?: () => void;\n}\n\nexport function OTPInput({ correctOTP = '424242', onSuccess, onError }: OTPInputProps) {\n    const id = useId();\n    const length = Math.max(1, correctOTP.length);\n    const [digits, setDigits] = useState<string[]>([]);\n    const [state, setState] = useState<'idle' | 'error' | 'success'>('idle');\n    const inputs = useRef<Array<HTMLInputElement | null>>([]);\n    const reduceMotion = useReducedMotion();\n\n    const updateDigits = (next: string[]) => {\n        setDigits(next);\n        if (next.filter(Boolean).length !== length) {\n            setState('idle');\n            return;\n        }\n        if (next.join('') === correctOTP) {\n            setState('success');\n            inputs.current.forEach((input) => input?.blur());\n            onSuccess?.();\n        } else {\n            setState('error');\n            onError?.();\n        }\n    };\n\n    const enterDigits = (value: string, index: number) => {\n        const entered = value.replace(/\\D/g, '').slice(0, length - index);\n        const next = Array.from({ length }, (_, i) => digits[i] || '');\n        if (!entered) next[index] = '';\n        else entered.split('').forEach((digit, offset) => { next[index + offset] = digit; });\n        updateDigits(next);\n        if (entered && next.join('') !== correctOTP) {\n            inputs.current[Math.min(index + entered.length, length - 1)]?.focus();\n        }\n    };\n\n    return (\n        <div className=\"flex flex-col items-center justify-center gap-3\" role=\"group\" aria-labelledby={`${id}-label`}>\n            <p id={`${id}-label`} className=\"font-medium text-lg\">OTP Verification</p>\n            <motion.div\n                className=\"flex items-center justify-center gap-2\"\n                animate={{ x: state === 'error' && !reduceMotion ? [0, 3, -3, 3, -3, 0] : 0 }}\n                transition={{ duration: reduceMotion ? 0 : 0.22 }}\n            >\n                {Array.from({ length }, (_, index) => (\n                    <motion.div\n                        key={index}\n                        initial={reduceMotion ? false : { opacity: 0, y: 10 }}\n                        animate={{ opacity: 1, y: 0 }}\n                        transition={{ duration: reduceMotion ? 0 : 0.22, delay: reduceMotion ? 0 : index * 0.04 }}\n                        className={cn(\n                            'w-9 h-10 bg-muted rounded-lg ring-2 ring-transparent focus-within:ring-ring overflow-hidden',\n                            state === 'error' && 'ring-destructive',\n                            state === 'success' && 'ring-green-500',\n                        )}\n                    >\n                        <input\n                            ref={(input) => { inputs.current[index] = input; }}\n                            id={`${id}-digit-${index}`}\n                            aria-label={`Digit ${index + 1} of ${length}`}\n                            aria-invalid={state === 'error'}\n                            aria-describedby={state === 'error' ? `${id}-status` : undefined}\n                            inputMode=\"numeric\"\n                            pattern=\"[0-9]*\"\n                            autoComplete={index === 0 ? 'one-time-code' : 'off'}\n                            value={digits[index] || ''}\n                            onChange={(event) => enterDigits(event.target.value, index)}\n                            onPaste={(event) => {\n                                event.preventDefault();\n                                enterDigits(event.clipboardData.getData('text'), index);\n                            }}\n                            onKeyDown={(event) => {\n                                if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {\n                                    event.preventDefault();\n                                    inputs.current[Math.max(0, Math.min(length - 1, index + (event.key === 'ArrowLeft' ? -1 : 1)))]?.focus();\n                                } else if (event.key === 'Backspace') {\n                                    event.preventDefault();\n                                    const target = digits[index] ? index : Math.max(0, index - 1);\n                                    const next = Array.from({ length }, (_, i) => digits[i] || '');\n                                    next[target] = '';\n                                    updateDigits(next);\n                                    inputs.current[target]?.focus();\n                                }\n                            }}\n                            onFocus={(event) => event.target.select()}\n                            className=\"border-none outline-none w-9 h-10 text-center bg-transparent text-foreground\"\n                            disabled={state === 'success'}\n                        />\n                    </motion.div>\n                ))}\n            </motion.div>\n            <p id={`${id}-status`} role=\"status\" className={cn('min-h-5 text-sm', state === 'error' ? 'text-destructive' : 'text-green-600 dark:text-green-400')}>\n                {state === 'success' && <><Check className=\"inline size-4 mr-1\" aria-hidden=\"true\" />OTP Verified Successfully!</>}\n                {state === 'error' && 'Invalid OTP'}\n            </p>\n        </div>\n    );\n}\n\nexport default OTPInput;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "pagination",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/pagination.tsx",
          "target": "@ui/pagination.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\n\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n  return (\n    <nav\n      role=\"navigation\"\n      aria-label=\"pagination\"\n      data-slot=\"pagination\"\n      className={cn(\"mx-auto flex w-full justify-center\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationContent({\n  className,\n  ...props\n}: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"pagination-content\"\n      className={cn(\"flex flex-row items-center gap-1\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n  return <li data-slot=\"pagination-item\" {...props} />\n}\n\ntype PaginationLinkProps = {\n  isActive?: boolean\n} & Pick<React.ComponentProps<typeof Button>, \"size\"> &\n  React.ComponentProps<\"a\">\n\nfunction PaginationLink({\n  className,\n  isActive,\n  size = \"icon\",\n  ...props\n}: PaginationLinkProps) {\n  return (\n    <a\n      aria-current={isActive ? \"page\" : undefined}\n      data-slot=\"pagination-link\"\n      data-active={isActive}\n      className={cn(\n        buttonVariants({\n          variant: isActive ? \"outline\" : \"ghost\",\n          size,\n        }),\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationPrevious({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to previous page\"\n      size=\"default\"\n      className={cn(\"gap-1 px-2.5 sm:pl-2.5\", className)}\n      {...props}\n    >\n      <ChevronLeftIcon />\n      <span className=\"hidden sm:block\">Previous</span>\n    </PaginationLink>\n  )\n}\n\nfunction PaginationNext({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to next page\"\n      size=\"default\"\n      className={cn(\"gap-1 px-2.5 sm:pr-2.5\", className)}\n      {...props}\n    >\n      <span className=\"hidden sm:block\">Next</span>\n      <ChevronRightIcon />\n    </PaginationLink>\n  )\n}\n\nfunction PaginationEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"pagination-ellipsis\"\n      className={cn(\"flex size-9 items-center justify-center\", className)}\n      {...props}\n    >\n      <MoreHorizontalIcon className=\"size-4\" />\n      <span className=\"sr-only\">More pages</span>\n    </span>\n  )\n}\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationLink,\n  PaginationItem,\n  PaginationPrevious,\n  PaginationNext,\n  PaginationEllipsis,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "parallax-gallery",
      "type": "registry:block",
      "dependencies": [
        "gsap",
        "motion"
      ],
      "files": [
        {
          "path": "components/block/parallax-gallery.jsx",
          "target": "@components/block/parallax-gallery.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport gsap from \"gsap\";\nimport { ScrollTrigger } from \"gsap/ScrollTrigger\";\nimport { useReducedMotion } from \"motion/react\";\n\nif (typeof window !== \"undefined\") gsap.registerPlugin(ScrollTrigger);\n\nconst SCALE_OFFSCREEN = 1.2;\nconst ROTATION_DEG = 6;\nconst SCALE_CENTER = 1.0;\n/** @param {{images?: string[], bgColor?: string, scroller?: HTMLElement | import('react').RefObject<HTMLElement | null>, viewportHeight?: string,\n * frameWidth?: number, frameHeight?: number, thumbnailWidth?: number, thumbnailHeight?: number, className?: string}} props */\nexport function ParallaxGallery({ images = [], bgColor = \"#111111\", scroller, viewportHeight = \"100vh\", frameWidth = 500, frameHeight = 600, thumbnailWidth = 190, thumbnailHeight = 260, className = \"\" }) {\n  const slides = images.map((src, index) => ({ id: `slide-${index}`, src }));\n  const total = slides.length;\n\n  const [activeIndex, setActiveIndex] = useState(0);\n  const sectionRef = useRef(null);\n  const galleryRef = useRef(null);\n  const itemInnerRefs = useRef([]);\n  const thumbLeftRef = useRef(null);\n  const thumbRightRef = useRef(null);\n\n  const reduceMotion = useReducedMotion();\n  const frameW = frameWidth;\n  const frameH = frameHeight;\n  const slideW = frameW;\n  const thumbW = thumbnailWidth;\n  const thumbH = thumbnailHeight;\n  const thumbGap = 12;\n  const thumbItem = thumbW + thumbGap;\n\n  const applyPosition = useCallback(\n    (pos) => {\n      if (!galleryRef.current) return;\n\n      galleryRef.current.style.transform = `translate3d(${-pos * slideW}px, 0px, 0px)`;\n\n      itemInnerRefs.current.forEach((el, index) => {\n        if (!el) return;\n        const dist = index - pos;\n        const absDist = Math.abs(dist);\n        const rot = dist > 0 ? Math.min(absDist, 1) * ROTATION_DEG : -Math.min(absDist, 1) * ROTATION_DEG;\n        const scale = SCALE_CENTER + Math.min(absDist, 1) * (SCALE_OFFSCREEN - SCALE_CENTER);\n        el.style.transform = `rotate(${rot}deg) scale(${scale})`;\n      });\n\n      if (thumbLeftRef.current) thumbLeftRef.current.style.transform = `translate3d(${-pos * thumbItem}px, -50%, 0px)`;\n      if (thumbRightRef.current) thumbRightRef.current.style.transform = `translate3d(${-(pos + 1) * thumbItem}px, -50%, 0px)`;\n      setActiveIndex(Math.max(0, Math.min(total - 1, Math.round(pos))));\n    },\n    [slideW, thumbItem, total]\n  );\n\n  useEffect(() => {\n    if (total <= 1 || reduceMotion) return;\n\n    const section = sectionRef.current;\n    if (!section) return;\n\n    const snapValues = Array.from({ length: total }, (_, index) => index / (total - 1));\n    const st = ScrollTrigger.create({\n      trigger: section,\n      scroller: scroller?.current !== undefined ? scroller.current : scroller,\n      start: \"top top\",\n      end: \"bottom bottom\",\n      snap: {\n        snapTo: snapValues,\n        duration: { min: 0.2, max: 0.5 },\n        ease: \"power2.inOut\",\n        delay: 0,\n        inertia: false,\n      },\n      onUpdate: (self) => applyPosition(self.progress * (total - 1)),\n    });\n\n    applyPosition(0);\n    return () => st.kill();\n  }, [applyPosition, total, reduceMotion, scroller]);\n\n  if (reduceMotion) return <div className={`grid gap-4 p-4 ${className}`} style={{ background: bgColor }}>{images.map((src, index) => <img key={`${src}-${index}`} src={src} alt={`Gallery photograph ${index + 1}`} className=\"mx-auto h-auto w-full max-w-lg\" />)}</div>;\n\n  return (\n    <section ref={sectionRef} className={`relative font-body text-white ${className}`} style={{ height: `calc(${viewportHeight} * ${Math.max(total, 1)})`, background: bgColor }}>\n      <div className=\"sticky top-0 w-full select-none overflow-hidden\" style={{ background: bgColor, height: viewportHeight }}>\n        <div className=\"absolute\" style={{ top: 36, right: 40, fontSize: 11, letterSpacing: \"0.22em\", textTransform: \"uppercase\" }}>\n          {String(activeIndex + 1).padStart(2, \"0\")} / {String(total).padStart(2, \"0\")}\n        </div>\n\n        <ThumbStrip refProp={thumbLeftRef} slides={slides} side=\"left\" frameW={frameW} thumbW={thumbW} thumbH={thumbH} thumbGap={thumbGap} bgColor={bgColor} />\n        <ThumbStrip refProp={thumbRightRef} slides={slides} side=\"right\" frameW={frameW} thumbW={thumbW} thumbH={thumbH} thumbGap={thumbGap} bgColor={bgColor} />\n\n        <div className=\"absolute top-1/2 left-1/2\" style={{ transform: \"translate(-50%, -50%)\", zIndex: 20 }}>\n          <div className=\"pointer-events-none absolute top-1/2 left-1/2 z-0 -translate-x-1/2 -translate-y-1/2 border border-dashed border-white/50\" style={{ height: frameH + 24, width: frameW + 24 }} />\n          <div className=\"absolute top-1/2 left-1/2 m-auto overflow-hidden\" style={{ width: frameW - 10, height: frameH - 10, transform: \"translate(-50%, -50%)\" }}>\n            <div ref={galleryRef} className=\"absolute top-0 left-0 bottom-0 flex will-change-transform\" style={{ width: slideW * total }}>\n              {slides.map((slide, index) => (\n                <div key={slide.id} className=\"relative shrink-0 overflow-hidden\" style={{ width: slideW, height: frameH }}>\n                  <div\n                    ref={(el) => { itemInnerRefs.current[index] = el; }}\n                    className=\"absolute inset-0 will-change-transform\"\n                    style={{\n                      transform: `rotate(${index === 0 ? 0 : ROTATION_DEG}deg) scale(${index === 0 ? 1 : SCALE_OFFSCREEN})`,\n                      transformOrigin: \"center center\",\n                    }}\n                  >\n                    <img src={slide.src} alt={slide.id} draggable=\"false\" className=\"h-full w-full object-cover\" />\n                  </div>\n                </div>\n              ))}\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n\nfunction ThumbStrip({ refProp, slides, side, frameW, thumbW, thumbH, thumbGap, bgColor }) {\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        top: \"50%\",\n        zIndex: 10,\n        overflow: \"hidden\",\n        background: bgColor,\n        ...(side === \"left\" ? { right: `calc(50% + ${frameW / 2}px)`, left: 0 } : { left: `calc(50% + ${frameW / 2}px)`, right: 0 }),\n        height: thumbH,\n        transform: \"translateY(-50%)\",\n      }}\n    >\n      <div\n        ref={refProp}\n        className=\"absolute top-1/2 flex will-change-transform\"\n        style={{\n          gap: thumbGap,\n          transform: \"translate3d(0px, -50%, 0px)\",\n          left: side === \"left\" ? \"100%\" : 0,\n          right: \"auto\",\n        }}\n      >\n        {slides.map((slide, index) => (\n          <div key={index} className=\"relative shrink-0 overflow-hidden\" style={{ width: thumbW, height: thumbH }}>\n            <img src={slide.src} alt={slide.id} draggable=\"false\" className=\"h-full w-full object-cover\" />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "pixelated-carousel",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/pixelated-carousel.tsx",
          "target": "@components/block/pixelated-carousel.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport Image from 'next/image';\nimport React, { useEffect, useRef, useState, useMemo } from 'react';\nimport { motion, useReducedMotion } from 'motion/react';\n\ninterface PixelatedCarouselProps {\n    pixelSize?: number;\n    animationDelayStep?: number;\n    images: string[];\n    pixelTransitionDuration?: number;\n}\n\nexport function PixelatedCarousel({\n    pixelSize = 100,\n    animationDelayStep = 0.02,\n    images,\n    pixelTransitionDuration = 0.1\n}: PixelatedCarouselProps) {\n    const [activeImageIndex, setActiveImageIndex] = useState(0);\n    const reduceMotion = useReducedMotion();\n    const ref = useRef<HTMLDivElement>(null);\n    const [grid, setGrid] = useState({ rows: 0, cols: 0, height: 0, width: 0 });\n    const isInitialized = grid.rows > 0 && grid.cols > 0;\n    const [isFirstCycle, setIsFirstCycle] = useState(true);\n\n    const sizeOfBox = useMemo(() => {\n        if (grid.rows === 0 || grid.cols === 0) return { h: 0, w: 0 };\n        return { h: grid.height / grid.rows, w: grid.width / grid.cols };\n    }, [grid]);\n\n    const shuffledDelays = useMemo(() => {\n        const totalBoxes = grid.rows * grid.cols;\n        if (totalBoxes === 0) return [];\n\n        const delays = Array.from({ length: totalBoxes }, (_, i) => i * pixelTransitionDuration);\n        for (let i = delays.length - 1; i > 0; i--) {\n            const j = (i * 31 + 17) % (i + 1);\n            [delays[i], delays[j]] = [delays[j], delays[i]];\n        }\n        return delays;\n    }, [grid.rows, grid.cols, pixelTransitionDuration]);\n\n    const boxState = useMemo(() => {\n        if (shuffledDelays.length === 0) return [];\n        return shuffledDelays.map((delay, index) => {\n            const baseShade = 20 - (index * 13 % 20);\n            return { delay, color: `rgb(${baseShade}, ${baseShade}, ${baseShade})` };\n        });\n    }, [shuffledDelays]);\n\n    useEffect(() => {\n        const container = ref.current;\n        if (!container) return;\n        const observer = new ResizeObserver(([entry]) => {\n            const { width, height } = entry.contentRect;\n            const size = Math.max(1, pixelSize);\n            const next = { rows: Math.max(1, Math.floor(height / size)), cols: Math.max(1, Math.floor(width / size)), width, height };\n            setGrid((previous) => previous.width === width && previous.height === height && previous.rows === next.rows && previous.cols === next.cols ? previous : next);\n        });\n        observer.observe(container);\n        return () => observer.disconnect();\n    }, [pixelSize]);\n\n    useEffect(() => {\n        if (!isInitialized || grid.rows === 0 || grid.cols === 0 || reduceMotion || images.length < 2) return;\n\n        const allBoxesBlackTime = animationDelayStep * (grid.rows * grid.cols - 1) + pixelTransitionDuration;\n\n        if (isFirstCycle) {\n            const firstTimeout = setTimeout(() => {\n                setActiveImageIndex((prev) => (prev + 1) % images.length);\n                setIsFirstCycle(false);\n            }, allBoxesBlackTime * 1000);\n            return () => clearTimeout(firstTimeout);\n        } else {\n            const id = setInterval(() => {\n                setActiveImageIndex((prev) => (prev + 1) % images.length);\n            }, allBoxesBlackTime * 1000 * 2);\n            return () => clearInterval(id);\n        }\n    }, [images.length, grid.rows, grid.cols, isInitialized, isFirstCycle, animationDelayStep, pixelTransitionDuration, reduceMotion]);\n\n    const gridElements = useMemo(() => {\n        if (!isInitialized || grid.rows === 0 || grid.cols === 0 || reduceMotion) return null;\n\n        return Array.from({ length: grid.rows }).map((_, row) => (\n            <span key={row} className=\"flex\">\n                {Array.from({ length: grid.cols }).map((_, col) => {\n                    const boxIndex = row * grid.cols + col;\n                    const box = boxState[boxIndex];\n                    if (!box) return null;\n\n                    return (\n                        <motion.span\n                            key={col}\n                            style={{ height: sizeOfBox.h, width: sizeOfBox.w, backgroundColor: box.color }}\n                            initial={{ opacity: 0 }}\n                            animate={{ opacity: 1 }}\n                            transition={{\n                                duration: pixelTransitionDuration,\n                                repeat: Infinity,\n                                repeatType: 'reverse',\n                                delay: box.delay,\n                                repeatDelay: grid.rows * grid.cols * animationDelayStep\n                            }}\n                        />\n                    );\n                })}\n            </span>\n        ));\n    }, [isInitialized, grid.rows, grid.cols, boxState, sizeOfBox, pixelTransitionDuration, animationDelayStep, reduceMotion]);\n\n    return (\n        <div ref={ref} className=\"h-full w-full relative\">\n            {isInitialized && <span className=\"h-full w-full absolute top-0 left-0 z-10\">{gridElements}</span>}\n            <div className=\"h-full w-full z-0 relative\">\n                {images.map((image, index) => (\n                    <Image\n                        src={image}\n                        key={image + index}\n                        alt=\"pixelated carousel\"\n                        fill\n                        className=\"object-cover absolute top-0 left-0 h-full w-full\"\n                        style={{ zIndex: activeImageIndex % images.length === index ? 10 : 0 }}\n                    />\n                ))}\n            </div>\n        </div>\n    );\n}\n\nexport default PixelatedCarousel;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "playground-button",
      "type": "registry:block",
      "dependencies": [
        "@radix-ui/react-slot",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/playground-button.tsx",
          "target": "@components/block/playground-button.tsx",
          "type": "registry:block",
          "content": "import { Button } from \"@/components/ui/button\"\n\nexport default function Button1() {\n  return (\n    <div className=\"flex gap-2\">\n      <Button>Primary</Button>\n      <Button variant=\"secondary\">Secondary</Button>\n      <Button variant=\"outline\">Outline</Button>\n      <Button variant=\"ghost\">Ghost</Button>\n    </div>\n  )\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "playground-navbar",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/playground-navbar.tsx",
          "target": "@components/block/playground-navbar.tsx",
          "type": "registry:block",
          "content": "\"use client\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { motion, useScroll, useMotionValueEvent } from \"motion/react\";\n\nconst navItems = [\n  { name: \"Home\", href: \"/\" },\n  { name: \"About\", href: \"/about\" },\n  { name: \"Contact\", href: \"/contact\" },\n];\n\nexport const Navbar1 = ({ items = navItems }: { items?: typeof navItems }) => {\n  const { scrollY } = useScroll();\n  const [isDown, setisDown] = useState(false);\n\n  useMotionValueEvent(scrollY, \"change\", (latest) =>\n    latest > 20 ? setisDown(true) : setisDown(false)\n  );\n  return (\n    <motion.div\n      animate={\n        isDown\n          ? {\n              scaleX: 0.99,\n              boxShadow: `0 4px 15px rgba(0, 0, 0, 0.05)`,\n              y: 10,\n              border: \"1px solid rgba(255, 255, 255, 0.02)\",\n              borderRadius: \"16px\",\n            }\n          : { scaleX: 1 }\n      }\n      transition={{ duration: 0.2, ease: \"easeIn\" }}\n      className=\"fixed top-4 inset-x-[2vw] sm:inset-x-[10vw] h-15 flex items-center justify-between px-4 sm:px-8 bg-background backdrop-blur-lg\"\n    >\n      <Link href={\"/\"} aria-label=\"ObsidianUI home\">\n        <Image\n          src=\"https://pub-830233752de349e29c6104a501b309d4.r2.dev/logo/bg-less.png\"\n          alt=\"\"\n          width={32}\n          height={32}\n          className=\"w-8 h-auto dark:hidden block \"\n        />\n        <Image\n          src=\"https://pub-830233752de349e29c6104a501b309d4.r2.dev/logo/final-dark.png\"\n          alt=\"\"\n          width={32}\n          height={32}\n          className=\"w-8 h-auto hidden dark:block \"\n        />\n      </Link>\n\n      <div className=\"flex gap-4 sm:gap-8\">\n        {items.map((item) => (\n          <Link key={item.href} href={item.href}>\n            <motion.div\n              whileHover={{\n                \"--w\": \"100%\",\n              }}\n              transition={{ duration: 0.2 }}\n              style={{\n                width: \"var(--w, 0%)\",\n              }}\n              className=\"  text-xs font-medium border-b-2 \"\n            >\n              {item.name}\n            </motion.div>\n          </Link>\n        ))}\n      </div>\n    </motion.div>\n  );\n};\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "popover",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-popover",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/popover.tsx",
          "target": "@ui/popover.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Popover({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />\n}\n\nfunction PopoverTrigger({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\n}\n\nfunction PopoverContent({\n  className,\n  align = \"center\",\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Content\n        data-slot=\"popover-content\"\n        align={align}\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden\",\n          className\n        )}\n        {...props}\n      />\n    </PopoverPrimitive.Portal>\n  )\n}\n\nfunction PopoverAnchor({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n  return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />\n}\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "progress",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-progress",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/progress.tsx",
          "target": "@ui/progress.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Progress({\n  className,\n  value,\n  ...props\n}: React.ComponentProps<typeof ProgressPrimitive.Root>) {\n  return (\n    <ProgressPrimitive.Root\n      data-slot=\"progress\"\n      className={cn(\n        \"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full\",\n        className\n      )}\n      {...props}\n    >\n      <ProgressPrimitive.Indicator\n        data-slot=\"progress-indicator\"\n        className=\"bg-primary h-full w-full flex-1 transition-all\"\n        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n      />\n    </ProgressPrimitive.Root>\n  )\n}\n\nexport { Progress }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "radio-group",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-radio-group",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/radio-group.tsx",
          "target": "@ui/radio-group.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\"\nimport { CircleIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction RadioGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {\n  return (\n    <RadioGroupPrimitive.Root\n      data-slot=\"radio-group\"\n      className={cn(\"grid gap-3\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction RadioGroupItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n  return (\n    <RadioGroupPrimitive.Item\n      data-slot=\"radio-group-item\"\n      className={cn(\n        \"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator\n        data-slot=\"radio-group-indicator\"\n        className=\"relative flex items-center justify-center\"\n      >\n        <CircleIcon className=\"fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n}\n\nexport { RadioGroup, RadioGroupItem }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "rectangular-text-reveal",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/rectangular-text-reveal.jsx",
          "target": "@components/block/rectangular-text-reveal.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\nimport { CustomEase } from \"gsap/CustomEase\";\nimport { ScrollTrigger } from \"gsap/ScrollTrigger\";\nimport { SplitText } from \"gsap/SplitText\";\nimport { cn } from \"@/lib/utils\";\n\nif (typeof window !== \"undefined\") {\n  gsap.registerPlugin(SplitText, CustomEase, ScrollTrigger);\n}\n\nconst DEFAULT_TAG = \"div\";\nconst DEFAULT_BASE_COLOR = \"#ea580c\";\nconst DEFAULT_OVERLAY_COLOR = \"var(--background)\";\nconst DEFAULT_STAGGER = 0.2;\nconst DEFAULT_COVER_DURATION = 0.34;\nconst DEFAULT_REVEAL_DURATION = 0.42;\nconst DEFAULT_OVERLAY_ENTER_DURATION = 0.24;\nconst DEFAULT_OVERLAY_EXIT_DURATION = 0.28;\nconst DEFAULT_INSET_X = \"0.08em\";\nconst DEFAULT_INSET_Y = \"0.08em\";\nconst DEFAULT_TRIGGER_START = \"top 85%\";\nconst DEFAULT_DIRECTION = \"left\";\nconst DEFAULT_DELAY = 0;\nconst REVEAL_EASE = \"obsidianRectangularReveal\";\nconst SCALE_X_ZERO = \"scaleX(0)\";\nconst SCALE_Y_ZERO = \"scaleY(0)\";\nconst LINE_CLASS_NAME = \"tb-line\";\n\nfunction getOrigins(direction) {\n  switch (direction) {\n    case \"right\":\n      return {\n        enterOrigin: \"100% 50%\",\n        exitOrigin: \"0% 50%\",\n        axis: \"scaleX\",\n      };\n    case \"top\":\n      return {\n        enterOrigin: \"50% 0%\",\n        exitOrigin: \"50% 100%\",\n        axis: \"scaleY\",\n      };\n    case \"bottom\":\n      return {\n        enterOrigin: \"50% 100%\",\n        exitOrigin: \"50% 0%\",\n        axis: \"scaleY\",\n      };\n    case \"left\":\n    default:\n      return {\n        enterOrigin: \"0% 50%\",\n        exitOrigin: \"100% 50%\",\n        axis: \"scaleX\",\n      };\n  }\n}\n\nfunction createRevealRect({\n  insetX,\n  insetY,\n  background,\n  transformOrigin,\n  axis,\n  zIndex,\n  dataAttribute,\n}) {\n  const rect = document.createElement(\"div\");\n  rect.setAttribute(dataAttribute, \"true\");\n  Object.assign(rect.style, {\n    position: \"absolute\",\n    left: `-${insetX}`,\n    right: `-${insetX}`,\n    top: `-${insetY}`,\n    bottom: `-${insetY}`,\n    background,\n    transformOrigin,\n    transform: axis === \"scaleX\" ? SCALE_X_ZERO : SCALE_Y_ZERO,\n    zIndex: String(zIndex),\n    pointerEvents: \"none\",\n    willChange: \"transform\",\n  });\n\n  return rect;\n}\n\n/**\n * @param {import('react').HTMLAttributes<HTMLElement> & {\n *   as?: import('react').ElementType, baseColor?: string, overlayColor?: string,\n *   useOverlay?: boolean, stagger?: number, coverDuration?: number, revealDuration?: number,\n *   overlayEnterDuration?: number, overlayExitDuration?: number,\n *   insetX?: string, insetY?: string, triggerStart?: string, once?: boolean,\n *   direction?: 'left' | 'right' | 'top' | 'bottom', delay?: number,\n *   scroller?: HTMLElement | Window | import('react').RefObject<HTMLElement | null>,\n *   trigger?: HTMLElement | import('react').RefObject<HTMLElement | null>\n * }} props\n */\nexport function RectangularTextReveal({\n  children,\n  as: Tag = DEFAULT_TAG,\n  className = \"\",\n  baseColor = DEFAULT_BASE_COLOR,\n  overlayColor = DEFAULT_OVERLAY_COLOR,\n  useOverlay = true,\n  stagger = DEFAULT_STAGGER,\n  coverDuration = DEFAULT_COVER_DURATION,\n  revealDuration = DEFAULT_REVEAL_DURATION,\n  overlayEnterDuration = DEFAULT_OVERLAY_ENTER_DURATION,\n  overlayExitDuration = DEFAULT_OVERLAY_EXIT_DURATION,\n  insetX = DEFAULT_INSET_X,\n  insetY = DEFAULT_INSET_Y,\n  triggerStart = DEFAULT_TRIGGER_START,\n  once = true,\n  direction = DEFAULT_DIRECTION,\n  delay = DEFAULT_DELAY,\n  scroller,\n  trigger,\n  style,\n  ...props\n}) {\n  const elementRef = useRef(null);\n\n  useEffect(() => {\n    if (!elementRef.current) {\n      return;\n    }\n\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      const { enterOrigin, exitOrigin, axis } = getOrigins(direction);\n\n      CustomEase.create(REVEAL_EASE, \"0.4,0,0.2,1\");\n\n      const split = new SplitText(elementRef.current, {\n        type: \"lines\",\n        linesClass: LINE_CLASS_NAME,\n      });\n\n      const wrappers = [];\n      const baseRects = [];\n      const overlayRects = [];\n      const lines = split.lines;\n\n      // Line setup\n      gsap.set(elementRef.current, { opacity: 1 });\n\n      lines.forEach((line) => {\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"relative\";\n        wrapper.style.display = \"block\";\n        wrapper.style.overflow = \"hidden\";\n        wrapper.style.width = \"fit-content\";\n        wrapper.style.maxWidth = \"100%\";\n\n        line.parentNode.insertBefore(wrapper, line);\n        wrapper.appendChild(line);\n\n        line.style.position = \"relative\";\n        line.style.display = \"block\";\n        line.style.width = \"fit-content\";\n        line.style.maxWidth = \"100%\";\n        line.style.zIndex = \"1\";\n        line.style.opacity = \"0\";\n        line.style.willChange = \"opacity\";\n\n        const baseRect = createRevealRect({\n          insetX,\n          insetY,\n          background: baseColor,\n          transformOrigin: enterOrigin,\n          axis,\n          zIndex: 2,\n          dataAttribute: \"data-reveal-base\",\n        });\n        wrapper.appendChild(baseRect);\n\n        let overlayRect = null;\n\n        if (useOverlay) {\n          overlayRect = createRevealRect({\n            insetX,\n            insetY,\n            background: overlayColor,\n            transformOrigin: enterOrigin,\n            axis,\n            zIndex: 3,\n            dataAttribute: \"data-reveal-overlay\",\n          });\n          wrapper.appendChild(overlayRect);\n        }\n\n        wrappers.push(wrapper);\n        baseRects.push(baseRect);\n        overlayRects.push(overlayRect);\n      });\n\n      // Timeline\n      const timeline = gsap.timeline({ paused: true });\n\n      lines.forEach((line, index) => {\n        const baseRect = baseRects[index];\n        const overlayRect = overlayRects[index];\n        const startAt = index * stagger;\n\n        if (useOverlay && overlayRect) {\n          timeline.to(\n            overlayRect,\n            {\n              [axis]: 1,\n              duration: overlayEnterDuration,\n              ease: REVEAL_EASE,\n              transformOrigin: enterOrigin,\n            },\n            startAt + 0.1\n          );\n        }\n\n        timeline\n          .to(\n            baseRect,\n            {\n              [axis]: 1,\n              duration: coverDuration,\n              ease: REVEAL_EASE,\n              transformOrigin: enterOrigin,\n            },\n            startAt\n          )\n          .set(\n            line,\n            {\n              opacity: 1,\n            },\n            startAt + coverDuration\n          );\n\n        if (useOverlay && overlayRect) {\n          timeline.to(\n            overlayRect,\n            {\n              [axis]: 0,\n              delay: 0.15,\n              duration: overlayExitDuration,\n              ease: REVEAL_EASE,\n              transformOrigin: exitOrigin,\n            },\n            startAt + coverDuration + 0.1\n          );\n        }\n\n        timeline.to(\n          baseRect,\n          {\n            [axis]: 0,\n            delay: useOverlay ? 0.2 : 0.12,\n            duration: revealDuration,\n            ease: REVEAL_EASE,\n            transformOrigin: exitOrigin,\n          },\n          startAt + coverDuration + 0.1\n        );\n      });\n\n      // Scroll trigger\n      let delayedPlay;\n      const scrollTrigger = ScrollTrigger.create({\n        trigger: trigger?.current !== undefined ? trigger.current : trigger ?? elementRef.current,\n        scroller: scroller?.current !== undefined ? scroller.current : scroller,\n        start: triggerStart,\n        once,\n        onEnter: () => {\n          delayedPlay?.kill();\n          delayedPlay = gsap.delayedCall(delay, () => timeline.play());\n        },\n        ...(once\n          ? {}\n          : {\n              onLeaveBack: () => {\n                delayedPlay?.kill();\n                timeline.pause(0);\n\n                lines.forEach((line) => {\n                  line.style.opacity = \"0\";\n                });\n\n                baseRects.forEach((rect) => {\n                  gsap.set(rect, {\n                    [axis]: 0,\n                    transformOrigin: enterOrigin,\n                  });\n                });\n\n                overlayRects.forEach((rect) => {\n                  if (!rect) {\n                    return;\n                  }\n\n                  gsap.set(rect, {\n                    [axis]: 0,\n                    transformOrigin: enterOrigin,\n                  });\n                });\n              },\n            }),\n      });\n\n      // Cleanup\n      return () => {\n        delayedPlay?.kill();\n        timeline.kill();\n        scrollTrigger.kill();\n        split.revert();\n\n        wrappers.forEach((wrapper) => {\n          if (!wrapper.parentNode) {\n            return;\n          }\n\n          while (wrapper.firstChild) {\n            wrapper.parentNode.insertBefore(wrapper.firstChild, wrapper);\n          }\n\n          wrapper.remove();\n        });\n      };\n    }, elementRef);\n\n    return () => media.revert();\n  }, [\n    children,\n    baseColor,\n    coverDuration,\n    delay,\n    direction,\n    insetX,\n    insetY,\n    once,\n    overlayColor,\n    overlayEnterDuration,\n    overlayExitDuration,\n    revealDuration,\n    stagger,\n    triggerStart,\n    useOverlay,\n    scroller,\n    trigger,\n  ]);\n\n  return (\n    <Tag {...props} ref={elementRef} className={cn(\"font-heading text-foreground\", className)} style={style}>\n      {children}\n    </Tag>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "resizable",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "lucide-react",
        "react-resizable-panels",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/resizable.tsx",
          "target": "@ui/resizable.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { GripVerticalIcon } from \"lucide-react\"\nimport * as ResizablePrimitive from \"react-resizable-panels\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ResizablePanelGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {\n  return (\n    <ResizablePrimitive.PanelGroup\n      data-slot=\"resizable-panel-group\"\n      className={cn(\n        \"flex h-full w-full data-[panel-group-direction=vertical]:flex-col\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ResizablePanel({\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {\n  return <ResizablePrimitive.Panel data-slot=\"resizable-panel\" {...props} />\n}\n\nfunction ResizableHandle({\n  withHandle,\n  className,\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n  withHandle?: boolean\n}) {\n  return (\n    <ResizablePrimitive.PanelResizeHandle\n      data-slot=\"resizable-handle\"\n      className={cn(\n        \"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n        className\n      )}\n      {...props}\n    >\n      {withHandle && (\n        <div className=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border\">\n          <GripVerticalIcon className=\"size-2.5\" />\n        </div>\n      )}\n    </ResizablePrimitive.PanelResizeHandle>\n  )\n}\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "rope-cursor",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/rope-cursor.jsx",
          "target": "@components/block/rope-cursor.jsx",
          "type": "registry:block",
          "content": "\"use client\"\n\nimport React, { useEffect, useRef, useState } from 'react'\nimport gsap from 'gsap'\nimport { useReducedMotion } from 'motion/react'\nimport { cn } from '@/lib/utils'\n\n/** @param {{ ropeColor?: string, ropeWidth?: number, ropeOpacity?: number, segmentLength?: number, segmentCount?: number, children?: import(\"react\").ReactNode, className?: string, height?: import(\"react\").CSSProperties[\"height\"], style?: import(\"react\").CSSProperties }} props */\nexport function RopeCursor({\n  children, className, height = 400, style,\n  ropeColor = '#bda985',\n  ropeWidth = 2,\n  ropeOpacity = 0.6,\n  segmentLength = 0,\n  segmentCount = 8,\n} = {}) {\n  const svgRef = useRef(null)\n  const pathRef = useRef(null)\n  const ropeSegments = useRef([])\n  const mousePosition = useRef({ x: null, y: null })\n  const [isVisible, setIsVisible] = useState(false)\n  const motionEnabled = !useReducedMotion()\n  const containerRef = useRef(null)\n\n\n  useEffect(() => {\n    if (!motionEnabled) return\n\n    const container = containerRef.current\n    if (!container) return\n    let isInitialized = false\n    let animationFrameId = null\n\n    const initializeRopeSegments = (startX, startY) => {\n      ropeSegments.current = Array.from({ length: segmentCount }, () => ({\n        x: startX,\n        y: startY\n      }))\n      isInitialized = true\n      setIsVisible(true)\n    }\n\n    const handleMouseMove = (event) => {\n      const rect = container.getBoundingClientRect()\n      mousePosition.current.x = event.clientX - rect.left\n      mousePosition.current.y = event.clientY - rect.top\n\n      if (!isInitialized && mousePosition.current.x !== null) {\n        initializeRopeSegments(mousePosition.current.x, mousePosition.current.y)\n      }\n    }\n\n    const updateLeadingSegment = (segments, targetX, targetY) => {\n      gsap.to(segments[0], {\n        x: targetX,\n        y: targetY,\n        duration: 0.05,\n        ease: 'power2.out', overwrite: true\n      })\n    }\n\n    const updateFollowingSegments = (segments) => {\n      for (let i = 1;i < segmentCount;i++) {\n        const previousSegment = segments[i - 1]\n        const currentSegment = segments[i]\n\n        const deltaX = previousSegment.x - currentSegment.x\n        const deltaY = previousSegment.y - currentSegment.y\n        const distanceBetweenSegments = Math.sqrt(deltaX * deltaX + deltaY * deltaY)\n\n        if (distanceBetweenSegments > segmentLength) {\n          const angleToTarget = Math.atan2(deltaY, deltaX)\n          const constrainedX = previousSegment.x - Math.cos(angleToTarget) * segmentLength\n          const constrainedY = previousSegment.y - Math.sin(angleToTarget) * segmentLength\n\n          gsap.to(currentSegment, {\n            x: constrainedX,\n            y: constrainedY,\n            duration: 0.15 + i * 0.01,\n            ease: 'power2.out', overwrite: true\n          })\n        }\n      }\n    }\n\n    const generateSmoothPath = (segments) => {\n      let pathData = `M ${segments[0].x} ${segments[0].y}`\n\n      for (let i = 1;i < segmentCount - 1;i++) {\n        const controlPointX = (segments[i].x + segments[i + 1].x) / 2\n        const controlPointY = (segments[i].y + segments[i + 1].y) / 2\n        pathData += ` Q ${segments[i].x} ${segments[i].y} ${controlPointX} ${controlPointY}`\n      }\n\n      const lastSegment = segments[segmentCount - 1]\n      pathData += ` L ${lastSegment.x} ${lastSegment.y}`\n\n      return pathData\n    }\n\n    const animate = () => {\n      const segments = ropeSegments.current\n      const mouse = mousePosition.current\n\n      if (!isInitialized || mouse.x === null) {\n        animationFrameId = requestAnimationFrame(animate)\n        return\n      }\n\n      updateLeadingSegment(segments, mouse.x, mouse.y)\n      updateFollowingSegments(segments)\n\n      if (pathRef.current) {\n        pathRef.current.setAttribute('d', generateSmoothPath(segments))\n      }\n\n      animationFrameId = requestAnimationFrame(animate)\n    }\n\n    container.addEventListener('mousemove', handleMouseMove)\n    animationFrameId = requestAnimationFrame(animate)\n\n    return () => {\n      container.removeEventListener('mousemove', handleMouseMove)\n      cancelAnimationFrame(animationFrameId)\n      gsap.killTweensOf(ropeSegments.current)\n    }\n  }, [segmentCount, segmentLength, motionEnabled])\n\n  return (\n    <div ref={containerRef} className={cn(\"relative isolate w-full overflow-hidden\", className)} style={{ height, containerType: \"inline-size\", ...style }}>\n      {children}\n      <svg\n        ref={svgRef}\n        className=\"w-full h-full absolute inset-0\"\n        aria-hidden=\"true\"\n        style={{ opacity: isVisible && motionEnabled ? 1 : 0, pointerEvents: \"none\" }}\n      >\n        <path\n          ref={pathRef}\n          fill=\"none\"\n          stroke={ropeColor}\n          strokeWidth={ropeWidth}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          opacity={ropeOpacity}\n        />\n      </svg>\n    </div>\n  )\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "scroll-area",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-scroll-area",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/scroll-area.tsx",
          "target": "@ui/scroll-area.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ScrollArea({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {\n  return (\n    <ScrollAreaPrimitive.Root\n      data-slot=\"scroll-area\"\n      className={cn(\"relative\", className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        data-slot=\"scroll-area-viewport\"\n        className=\"focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      <ScrollBar />\n      <ScrollAreaPrimitive.Corner />\n    </ScrollAreaPrimitive.Root>\n  )\n}\n\nfunction ScrollBar({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {\n  return (\n    <ScrollAreaPrimitive.ScrollAreaScrollbar\n      data-slot=\"scroll-area-scrollbar\"\n      orientation={orientation}\n      className={cn(\n        \"flex touch-none p-px transition-colors select-none\",\n        orientation === \"vertical\" &&\n          \"h-full w-2.5 border-l border-l-transparent\",\n        orientation === \"horizontal\" &&\n          \"h-2.5 flex-col border-t border-t-transparent\",\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.ScrollAreaThumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"bg-border relative flex-1 rounded-full\"\n      />\n    </ScrollAreaPrimitive.ScrollAreaScrollbar>\n  )\n}\n\nexport { ScrollArea, ScrollBar }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "scroll-effect",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/scroll-effect.css",
          "target": "@components/block/scroll-effect.css",
          "type": "registry:file",
          "content": "@property --ace-scroll-progress {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n.ace-scroll-root {\n  --g: 1rem;\n  --n: 2;\n  position: sticky;\n  top: 0;\n  height: 110dvh;\n  padding: var(--g);\n  display: grid;\n  grid-template: repeat(3, 1fr) / repeat(3, 1fr);\n  grid-auto-rows: 1fr;\n  gap: var(--g);\n  overflow: hidden;\n  container-type: inline-size;\n}\n\n.ace-scroll-image {\n  --_j: var(--j, 0);\n  --p: calc(2 * var(--_j) + 1);\n  --s: calc(2 * var(--_j) - 1);\n  grid-area: 2 / var(--p);\n  justify-self: end;\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n  border-radius: var(--g);\n  contain: size;\n}\n.ace-scroll-image:nth-child(n + 3) { --j: 1; }\n.ace-scroll-image.feat {\n  grid-area: 1 / 1 / -1 / -1;\n  height: 200%;\n  border-radius: 2px;\n  clip-path: inset(0 calc(min(1, 2 * var(--ace-scroll-progress)) * (100% + var(--g)) / 3) round var(--g));\n  z-index: 1;\n}\n.ace-scroll-image:not(.feat) {\n  --d: calc(clamp(0, var(--i) + (1 - 2 * var(--ace-scroll-progress)) * (var(--n) - 1), 1) * 33cqw);\n  transform: translate(calc(var(--s) * var(--d)), calc(0.5 * var(--d)));\n  z-index: 0;\n}\n.ace-scroll-image.mid, .ace-scroll-image.end {\n  --j: 1;\n  grid-column: var(--p);\n  grid-row: calc(var(--i) + 1);\n}\n.ace-scroll-image.mid:nth-child(5) { width: 80%; height: 150px; }\n.ace-scroll-image.mid:nth-child(4) { margin-top: -200px; width: 40%; height: 200px; }\n.ace-scroll-image.end:nth-child(1) { width: 80%; height: 200px; }\n.ace-scroll-image.end:nth-child(2) { width: 40%; height: 200px; }\n\n@media (prefers-reduced-motion: reduce) {\n  .ace-scroll-section { height: auto; }\n  .ace-scroll-root { position: relative; height: auto; grid-template: none / repeat(2, minmax(0, 1fr)); }\n  .ace-scroll-root .ace-scroll-image {\n    grid-area: auto;\n    width: 100%;\n    height: 240px;\n    margin: 0;\n    clip-path: none;\n    transform: none;\n  }\n}\n"
        },
        {
          "path": "components/block/scroll-effect.tsx",
          "target": "@components/block/scroll-effect.tsx",
          "type": "registry:block",
          "content": "\"use client\";\nimport React, { useRef } from 'react'\nimport { motion, useScroll, useTransform, type MotionStyle } from 'motion/react'\nimport { cn } from '@/lib/utils'\nimport '@/components/block/scroll-effect.css'\n\n\ntype ImageProps = {\n\n        start: string[];\n        middle: string[];\n        featured: string\n}\n\ninterface ScrollEffectProps{\n    images: ImageProps;\n    className?: string\n}\n\n export function ScrollEffect  ({ images , className }: ScrollEffectProps)  {\n\n\n\n    const sectionRef = useRef<HTMLDivElement>(null);\n\n\n    const { scrollYProgress } = useScroll({\n        target: sectionRef,\n        offset: ['start start', 'end start']\n    })\n\n\n\n    const k = useTransform(scrollYProgress, [0, 1], [0, 1])\n\n    const {start , middle , featured}= images\n\n\n\n\n    return (\n\n        <section ref={sectionRef}\n            className={cn('ace-scroll-section relative h-[400dvh]' , className)}\n        >\n\n            <motion.div className='ace-scroll-root' style={{\n                '--ace-scroll-progress': k\n            } as MotionStyle}>\n\n                {start.map((src, i) => (\n\n                    <img key={`start-${i}`}\n                        src={src}\n                        className={cn('ace-scroll-image end')}\n                        style={{\n                            '--i': i,\n                            '--j': 0\n                        } as React.CSSProperties}\n                        alt={`start-image${i}`}\n                    />\n                ))\n                }\n\n              \n\n\n                {/* featured image */}\n                <img src={featured}\n                    className='ace-scroll-image feat'\n                    alt='featured-image'\n                />\n\n                {/* middle images */}\n\n\n                {\n                    middle.map((src, i) =>\n\n                    (\n                        <img key={`mid-${i}`}\n                            src={src}\n                            className={cn('ace-scroll-image mid' , \n                               \n                            )}\n                            style={{\n                                '--i': i + start.length,\n                                '--j': 1\n                            } as React.CSSProperties}\n                            alt={`middle-image${i}`}\n\n                        />\n                    ))\n                }\n            </motion.div>\n\n        </section>\n\n    );\n};\n\n\n\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "scroll-stack",
      "type": "registry:block",
      "dependencies": [
        "gsap"
      ],
      "files": [
        {
          "path": "components/block/scroll-stack.jsx",
          "target": "@components/block/scroll-stack.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\nimport ScrollTrigger from \"gsap/ScrollTrigger\";\nimport \"@/lib/effects/scroll-stack/styles.css\";\n\nif (typeof window !== \"undefined\") gsap.registerPlugin(ScrollTrigger);\n\n/** @param {{bgColor?: string, cards?: Array<{id: string | number, title: string, description: string, bgColor: string, textColor: string}>,\n * scroller?: HTMLElement | import('react').RefObject<HTMLElement | null>, viewportHeight?: string, contained?: boolean, className?: string}} props */\nexport function ScrollStack({ bgColor = \"bg-white\", cards = [], scroller, viewportHeight = \"100vh\", contained = false, className = \"\" }) {\n  const sectionRef = useRef(null);\n  const rowRefs = useRef([]);\n  const cardRefs = useRef([]);\n\n  useEffect(() => {\n    rowRefs.current = rowRefs.current.slice(0, cards.length);\n    cardRefs.current = cardRefs.current.slice(0, cards.length);\n\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      const currentCards = cardRefs.current.filter(Boolean);\n      const currentRows = rowRefs.current.filter(Boolean);\n\n      currentCards.forEach((card, index) => {\n        gsap.set(card, {\n          autoAlpha: 1,\n          scale: index === 0 ? 1 : 1.1,\n          transformOrigin: \"center center\",\n        });\n      });\n\n      currentCards.slice(0, -1).forEach((card, index) => {\n        const nextRow = currentRows[index + 1];\n        const nextCard = currentCards[index + 1];\n        if (!nextRow || !nextCard) return;\n\n        const handoff = gsap.timeline({\n          scrollTrigger: {\n            trigger: nextRow,\n            scroller: scroller?.current !== undefined ? scroller.current : scroller,\n            start: \"top bottom+=20%\",\n            end: \"top top-=28%\",\n            scrub: true,\n            invalidateOnRefresh: true,\n          },\n        });\n\n        handoff.to(nextCard, { scale: 1, ease: \"none\" }, 0);\n\n        gsap.to(card, {\n          autoAlpha: 0,\n          ease: \"none\",\n          scrollTrigger: {\n            trigger: nextRow,\n            scroller: scroller?.current !== undefined ? scroller.current : scroller,\n            start: \"top top+=14%\",\n            end: \"top top+=2%\",\n            scrub: true,\n            invalidateOnRefresh: true,\n          },\n        });\n      });\n\n    }, sectionRef);\n\n    return () => media.revert();\n  }, [cards, scroller]);\n\n  return (\n    <section\n      ref={sectionRef}\n      data-contained={contained || undefined}\n      className={`obsidian-scroll-stack py-[7%] max-sm:py-[15%] font-body ${bgColor} ${className}`}\n      style={{ '--scroll-stack-viewport': viewportHeight, containerType: 'inline-size' }}\n    >\n      <div className=\"flex w-full flex-col items-center px-[5%] py-[10cqw]\">\n        {cards.map((item, index) => (\n          <div\n            key={item.id}\n            ref={(element) => {\n              rowRefs.current[index] = element;\n            }}\n            className={`scroll-stack-row relative w-full min-h-[calc(var(--scroll-stack-viewport)*1.8)] max-sm:min-h-[calc(var(--scroll-stack-viewport)*1.3)] ${\n              index === 0 ? \"\" : \"-mt-[calc(var(--scroll-stack-viewport)*0.7)] max-sm:-mt-[calc(var(--scroll-stack-viewport)*0.5)]\"\n            }`}\n          >\n            <div className=\"scroll-stack-sticky sticky top-[calc(var(--scroll-stack-viewport)*0.15)] max-sm:top-[calc(var(--scroll-stack-viewport)*0.1)]\" style={{ zIndex: index + 1 }}>\n              <div\n                ref={(element) => {\n                  cardRefs.current[index] = element;\n                }}\n                className=\"scroll-stack-card mx-auto flex h-[32cqw] w-[80%] items-center justify-between gap-[4cqw] rounded-[45px] px-[4cqw] py-[3cqw] max-sm:h-auto max-sm:min-h-[50cqw] max-sm:w-full max-sm:flex-col max-sm:rounded-[9cqw] max-sm:px-[8cqw] max-sm:py-[15cqw]\"\n                style={{ backgroundColor: item.bgColor }}\n              >\n                <div className=\"w-[50%] max-sm:w-full\">\n                  <h2\n                    className=\"scroll-stack-title w-full font-heading text-[5.5cqw] font-medium leading-[1.1] max-sm:text-[10cqw]\"\n                    style={{ color: item.textColor }}\n                  >\n                    {item.title}\n                  </h2>\n                </div>\n                <div className=\"flex w-[50%] flex-col justify-center gap-[2cqw] max-sm:w-full max-sm:gap-[7cqw]\">\n                  <p\n                    className=\"scroll-stack-description w-full text-justify text-[1.3cqw] leading-[1.5] max-sm:text-center max-sm:text-[4.5cqw]\"\n                    style={{ color: item.textColor }}\n                  >\n                    {item.description}\n                  </p>\n                </div>\n              </div>\n            </div>\n          </div>\n        ))}\n      </div>\n    </section>\n  );\n}\n\n"
        },
        {
          "path": "lib/effects/scroll-stack/styles.css",
          "target": "@lib/effects/scroll-stack/styles.css",
          "type": "registry:file",
          "content": ".obsidian-scroll-stack[data-contained] .scroll-stack-card {\n  width: 94%;\n  height: calc(var(--scroll-stack-viewport) * 0.64);\n  min-height: 150px;\n  border-radius: 24px;\n  padding: 6%;\n  flex-direction: row;\n}\n.obsidian-scroll-stack[data-contained] .scroll-stack-title { font-size: clamp(22px, 5.5cqw, 44px); }\n.obsidian-scroll-stack[data-contained] .scroll-stack-description { font-size: clamp(12px, 2cqw, 17px); text-align: left; }\n@media (prefers-reduced-motion: reduce) {\n  .obsidian-scroll-stack .scroll-stack-row { min-height: 0; margin-top: 0; padding-bottom: 24px; }\n  .obsidian-scroll-stack .scroll-stack-sticky { position: relative; top: 0; }\n}\n.obsidian-scroll-stack[data-contained] { padding: 0; }\n.obsidian-scroll-stack[data-contained] > div { padding: 8% 4%; }\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "select",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-select",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/select.tsx",
          "target": "@ui/select.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Select({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n  return <SelectPrimitive.Root data-slot=\"select\" {...props} />\n}\n\nfunction SelectGroup({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />\n}\n\nfunction SelectValue({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Value>) {\n  return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />\n}\n\nfunction SelectTrigger({\n  className,\n  size = \"default\",\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n  size?: \"sm\" | \"default\"\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"size-4 opacity-50\" />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  )\n}\n\nfunction SelectContent({\n  className,\n  children,\n  position = \"popper\",\n  align = \"center\",\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Content\n        data-slot=\"select-content\"\n        className={cn(\n          \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md\",\n          position === \"popper\" &&\n            \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n          className\n        )}\n        position={position}\n        align={align}\n        {...props}\n      >\n        <SelectScrollUpButton />\n        <SelectPrimitive.Viewport\n          className={cn(\n            \"p-1\",\n            position === \"popper\" &&\n              \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1\"\n          )}\n        >\n          {children}\n        </SelectPrimitive.Viewport>\n        <SelectScrollDownButton />\n      </SelectPrimitive.Content>\n    </SelectPrimitive.Portal>\n  )\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Label>) {\n  return (\n    <SelectPrimitive.Label\n      data-slot=\"select-label\"\n      className={cn(\"text-muted-foreground px-2 py-1.5 text-xs\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  )\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n  return (\n    <SelectPrimitive.ScrollUpButton\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronUpIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollUpButton>\n  )\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n  return (\n    <SelectPrimitive.ScrollDownButton\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollDownButton>\n  )\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "separator",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-separator",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/separator.tsx",
          "target": "@ui/separator.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "sheet",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-dialog",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/sheet.tsx",
          "target": "@ui/sheet.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\"\nimport { XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />\n}\n\nfunction SheetTrigger({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />\n}\n\nfunction SheetClose({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Close>) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />\n}\n\nfunction SheetPortal({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Portal>) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />\n}\n\nfunction SheetOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {\n  return (\n    <SheetPrimitive.Overlay\n      data-slot=\"sheet-overlay\"\n      className={cn(\n        \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = \"right\",\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\"\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Content\n        data-slot=\"sheet-content\"\n        className={cn(\n          \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n          side === \"right\" &&\n            \"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm\",\n          side === \"left\" &&\n            \"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm\",\n          side === \"top\" &&\n            \"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b\",\n          side === \"bottom\" &&\n            \"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <SheetPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\">\n          <XIcon className=\"size-4\" />\n          <span className=\"sr-only\">Close</span>\n        </SheetPrimitive.Close>\n      </SheetPrimitive.Content>\n    </SheetPortal>\n  )\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn(\"flex flex-col gap-1.5 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Title>) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn(\"text-foreground font-semibold\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Description>) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "sidebar",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-dialog",
        "@radix-ui/react-separator",
        "@radix-ui/react-slot",
        "@radix-ui/react-tooltip",
        "class-variance-authority",
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/button.tsx",
          "target": "@ui/button.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-xl px-6 has-[>svg]:px-4\",\n        icon: \"size-9 rounded-full\",\n        \"icon-sm\": \"size-8 rounded-full\",\n        \"icon-lg\": \"size-10 rounded-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "target": "@ui/input.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        \"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n        \"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "target": "@ui/separator.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "target": "@ui/sheet.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\"\nimport { XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />\n}\n\nfunction SheetTrigger({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />\n}\n\nfunction SheetClose({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Close>) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />\n}\n\nfunction SheetPortal({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Portal>) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />\n}\n\nfunction SheetOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {\n  return (\n    <SheetPrimitive.Overlay\n      data-slot=\"sheet-overlay\"\n      className={cn(\n        \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = \"right\",\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\"\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Content\n        data-slot=\"sheet-content\"\n        className={cn(\n          \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n          side === \"right\" &&\n            \"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm\",\n          side === \"left\" &&\n            \"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm\",\n          side === \"top\" &&\n            \"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b\",\n          side === \"bottom\" &&\n            \"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <SheetPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\">\n          <XIcon className=\"size-4\" />\n          <span className=\"sr-only\">Close</span>\n        </SheetPrimitive.Close>\n      </SheetPrimitive.Content>\n    </SheetPortal>\n  )\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn(\"flex flex-col gap-1.5 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Title>) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn(\"text-foreground font-semibold\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Description>) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n}\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "target": "@ui/sidebar.tsx",
          "type": "registry:ui",
          "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeftIcon } from \"lucide-react\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\ntype SidebarContextProps = {\n  state: \"expanded\" | \"collapsed\";\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === \"function\" ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen, setOpenMobile]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? \"expanded\" : \"collapsed\";\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <TooltipProvider delayDuration={0}>\n        <div\n          data-slot=\"sidebar-wrapper\"\n          style={\n            {\n              \"--sidebar-width\": SIDEBAR_WIDTH,\n              \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n              ...style,\n            } as React.CSSProperties\n          }\n          className={cn(\n            \"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </TooltipProvider>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = \"left\",\n  variant = \"sidebar\",\n  collapsible = \"offcanvas\",\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  side?: \"left\" | \"right\";\n  variant?: \"sidebar\" | \"floating\" | \"inset\";\n  collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === \"none\") {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          \"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n        <SheetContent\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          data-mobile=\"true\"\n          className=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n          style={\n            {\n              \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n          side={side}\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer text-sidebar-foreground hidden md:block\"\n      data-state={state}\n      data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n      data-variant={variant}\n      data-side={side}\n      data-slot=\"sidebar\"\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        data-slot=\"sidebar-gap\"\n        className={cn(\n          \"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n          \"group-data-[collapsible=offcanvas]:w-0\",\n          \"group-data-[side=right]:rotate-180\",\n          variant === \"floating\" || variant === \"inset\"\n            ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n            : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\"\n        )}\n      />\n      <div\n        data-slot=\"sidebar-container\"\n        className={cn(\n          \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex\",\n          side === \"left\"\n            ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n            : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n          // Adjust the padding for floating and inset variants.\n          variant === \"floating\" || variant === \"inset\"\n            ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n            : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-none group-data-[side=right]:border-l\",\n          className\n        )}\n        {...props}\n      >\n        <div\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n          className=\"bg-background/10  group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\"size-7\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle Sidebar\"\n      tabIndex={-1}\n      onClick={toggleSidebar}\n      title=\"Toggle Sidebar\"\n      className={cn(\n        \"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex\",\n        \"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize\",\n        \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n        \"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full\",\n        \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n        \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        \"bg-background relative flex w-full flex-1 flex-col\",\n        \"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn(\"bg-background h-8 w-full shadow-none\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn(\"flex flex-col gap-2 p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn(\"flex flex-col gap-2 p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn(\"bg-sidebar-border mx-2 w-auto\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      data-sidebar=\"group\"\n      className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : \"div\";\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-label\"\n      data-sidebar=\"group-label\"\n      className={cn(\n        \"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupAction({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-action\"\n      data-sidebar=\"group-action\"\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        // Increases the hit area of the button on mobile.\n        \"after:absolute after:-inset-2 md:after:hidden\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn(\"w-full text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn(\"group/menu-item relative\", className)}\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n        outline:\n          \"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]\",\n      },\n      size: {\n        default: \"h-8 text-sm\",\n        sm: \"h-7 text-xs\",\n        lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  asChild = false,\n  isActive = false,\n  variant = \"default\",\n  size = \"default\",\n  tooltip,\n  className,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  asChild?: boolean;\n  isActive?: boolean;\n  tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const Comp = asChild ? Slot : \"button\";\n  const { isMobile, state } = useSidebar();\n\n  const button = (\n    <Comp\n      data-slot=\"sidebar-menu-button\"\n      data-sidebar=\"menu-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n      {...props}\n    />\n  );\n\n  if (!tooltip) {\n    return button;\n  }\n\n  if (typeof tooltip === \"string\") {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent\n        side=\"right\"\n        align=\"center\"\n        hidden={state !== \"collapsed\" || isMobile}\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  asChild = false,\n  showOnHover = false,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  asChild?: boolean;\n  showOnHover?: boolean;\n}) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-action\"\n      data-sidebar=\"menu-action\"\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        // Increases the hit area of the button on mobile.\n        \"after:absolute after:-inset-2 md:after:hidden\",\n        \"peer-data-[size=sm]/menu-button:top-1\",\n        \"peer-data-[size=default]/menu-button:top-1.5\",\n        \"peer-data-[size=lg]/menu-button:top-2.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        showOnHover &&\n        \"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        \"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none\",\n        \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n        \"peer-data-[size=sm]/menu-button:top-1\",\n        \"peer-data-[size=default]/menu-button:top-1.5\",\n        \"peer-data-[size=lg]/menu-button:top-2.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  showIcon?: boolean;\n}) {\n  // Vary rows without a random server/client mismatch or an extra render.\n  const id = React.useId();\n  const width = `${50 + Array.from(id).reduce((sum, char) => sum + char.charCodeAt(0), 0) % 41}%`;\n\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            \"--skeleton-width\": width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        \"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      data-sidebar=\"menu-sub-item\"\n      className={cn(\"group/menu-sub-item relative\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  asChild = false,\n  size = \"md\",\n  isActive = false,\n  className,\n  ...props\n}: React.ComponentProps<\"a\"> & {\n  asChild?: boolean;\n  size?: \"sm\" | \"md\";\n  isActive?: boolean;\n}) {\n  const Comp = asChild ? Slot : \"a\";\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-sub-button\"\n      data-sidebar=\"menu-sub-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n        \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n        size === \"sm\" && \"text-xs\",\n        size === \"md\" && \"text-sm\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "target": "@ui/skeleton.tsx",
          "type": "registry:ui",
          "content": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"bg-accent animate-pulse rounded-md\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "target": "@ui/tooltip.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  )\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  )\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "target": "@hooks/use-mobile.ts",
          "type": "registry:hook",
          "content": "\"use client\";\n\nimport * as React from \"react\"\n\nconst MOBILE_BREAKPOINT = 768\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)\n    const onChange = () => {\n      setIsMobile(mql.matches)\n    }\n    mql.addEventListener(\"change\", onChange)\n    onChange()\n    return () => mql.removeEventListener(\"change\", onChange)\n  }, [])\n\n  return !!isMobile\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "sidebar-stackbits",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "motion",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/sidebar-stackbits.tsx",
          "target": "@components/block/sidebar-stackbits.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { Cloud, Folder, Home, Menu, PanelLeftClose, PanelsTopLeft, Star } from 'lucide-react';\nimport React, { useState } from 'react';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\nconst HomeChildren = () => {\n  return (\n    <div className=\"w-full h-full bg-zinc-900 p-6\">\n      <div className=\"mb-8\">\n        <div className=\"h-8 bg-zinc-700 rounded w-24 animate-pulse\"></div>\n      </div>\n\n      <div className=\"space-y-3 mb-8\">\n        <div className=\"w-full bg-gray-600 px-4 py-3 rounded-lg flex items-center gap-3\">\n          <div className=\"h-4 bg-gray-500 rounded w-4 animate-pulse\"></div>\n          <div className=\"h-4 bg-gray-500 rounded w-36 animate-pulse\"></div>\n        </div>\n      </div>\n\n      <div className=\"mb-6\">\n        <div className=\"flex items-center justify-between mb-3\">\n          <div className=\"h-4 bg-zinc-700 rounded w-12 animate-pulse\"></div>\n          <div className=\"h-4 bg-gray-500 rounded w-4 animate-pulse\"></div>\n        </div>\n        <div className=\"space-y-2\">\n          <div className=\"w-full bg-purple-800 px-4 py-3 rounded-lg flex items-center gap-3\">\n            <div className=\"h-4 bg-purple-600 rounded w-4 animate-pulse\"></div>\n            <div className=\"h-4 bg-purple-600 rounded w-20 animate-pulse\"></div>\n          </div>\n        </div>\n      </div>\n\n      <div>\n        <div className=\"flex items-center justify-between mb-3\">\n          <div className=\"h-4 bg-zinc-700 rounded w-16 animate-pulse\"></div>\n        </div>\n        <div className=\"space-y-2\">\n          <div className=\"w-full hover:bg-zinc-800 py-3 rounded-lg flex items-center gap-3\">\n            <div className=\"h-4 bg-zinc-600 rounded w-4 animate-pulse\"></div>\n            <div className=\"h-4 bg-zinc-600 rounded w-40 animate-pulse\"></div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst ProjectsChildren = () => {\n  return (\n    <div className=\"w-full h-full bg-zinc-800 p-6\">\n      <div className=\"mb-8\">\n        <div className=\"h-6 bg-blue-600 rounded w-32 animate-pulse mb-2\"></div>\n        <div className=\"h-4 bg-zinc-700 rounded w-48 animate-pulse\"></div>\n      </div>\n\n      <div className=\"grid grid-cols-2 gap-4 mb-6\">\n        <div className=\"bg-zinc-900 rounded-lg p-4 border-zinc-700\">\n          <div className=\"h-4 bg-zinc-600 rounded w-20 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-zinc-700 rounded w-16 animate-pulse\"></div>\n        </div>\n        <div className=\"bg-zinc-900 rounded-lg p-4 border-zinc-700\">\n          <div className=\"h-4 bg-zinc-600 rounded w-24 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-zinc-700 rounded w-20 animate-pulse\"></div>\n        </div>\n        <div className=\"bg-zinc-900 rounded-lg p-4 border-zinc-700\">\n          <div className=\"h-4 bg-zinc-600 rounded w-18 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-zinc-700 rounded w-14 animate-pulse\"></div>\n        </div>\n        <div className=\"bg-zinc-900 rounded-lg p-4 border-zinc-700\">\n          <div className=\"h-4 bg-zinc-600 rounded w-22 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-zinc-700 rounded w-18 animate-pulse\"></div>\n        </div>\n      </div>\n\n      <div>\n        <div className=\"h-4 bg-zinc-700 rounded w-24 animate-pulse mb-3\"></div>\n        <div className=\"space-y-2\">\n          <div className=\"flex items-center gap-3 p-2 hover:bg-zinc-800 rounded\">\n            <div className=\"h-3 bg-zinc-600 rounded-full w-3 animate-pulse\"></div>\n            <div className=\"h-3 bg-zinc-600 rounded w-32 animate-pulse\"></div>\n          </div>\n          <div className=\"flex items-center gap-3 p-2 hover:bg-zinc-800 rounded\">\n            <div className=\"h-3 bg-zinc-600 rounded-full w-3 animate-pulse\"></div>\n            <div className=\"h-3 bg-zinc-600 rounded w-28 animate-pulse\"></div>\n          </div>\n          <div className=\"flex items-center gap-3 p-2 hover:bg-zinc-800 rounded\">\n            <div className=\"h-3 bg-zinc-600 rounded-full w-3 animate-pulse\"></div>\n            <div className=\"h-3 bg-zinc-600 rounded w-36 animate-pulse\"></div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst TemplatesChildren = () => {\n  return (\n    <div className=\"w-full h-full bg-zinc-900 p-6\">\n      <div className=\"mb-8\">\n        <div className=\"h-6 bg-green-600 rounded w-28 animate-pulse mb-2\"></div>\n        <div className=\"h-4 bg-zinc-700 rounded w-40 animate-pulse\"></div>\n      </div>\n\n      <div className=\"space-y-4 mb-6\">\n        <div className=\"flex items-center gap-3 p-3 bg-zinc-800 rounded-lg\">\n          <div className=\"h-4 bg-zinc-600 rounded-full w-4 animate-pulse\"></div>\n          <div className=\"h-4 bg-zinc-600 rounded w-24 animate-pulse\"></div>\n        </div>\n        <div className=\"flex items-center gap-3 p-3 bg-zinc-800 rounded-lg\">\n          <div className=\"h-4 bg-zinc-600 rounded-full w-4 animate-pulse\"></div>\n          <div className=\"h-4 bg-zinc-600 rounded w-20 animate-pulse\"></div>\n        </div>\n      </div>\n\n      <div>\n        <div className=\"h-4 bg-zinc-700 rounded w-32 animate-pulse mb-3\"></div>\n        <div className=\"space-y-2\">\n          <div className=\"h-16 bg-gradient-to-r from-pink-500/20 to-purple-500/20 rounded-lg animate-pulse\"></div>\n          <div className=\"h-16 bg-gradient-to-r from-blue-500/20 to-cyan-500/20 rounded-lg animate-pulse\"></div>\n          <div className=\"h-16 bg-gradient-to-r from-green-500/20 to-emerald-500/20 rounded-lg animate-pulse\"></div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst AIChildren = () => {\n  return (\n    <div className=\"w-full h-full bg-zinc-800 p-6\">\n      <div className=\"mb-8\">\n        <div className=\"h-6 bg-purple-600 rounded w-20 animate-pulse mb-2\"></div>\n        <div className=\"h-4 bg-zinc-700 rounded w-36 animate-pulse\"></div>\n      </div>\n\n      <div className=\"grid grid-cols-2 gap-3 mb-6\">\n        <div className=\"bg-gradient-to-br from-purple-600/20 to-pink-600/20 rounded-lg p-4\">\n          <div className=\"h-5 bg-purple-500/50 rounded w-5 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-purple-500/50 rounded w-16 animate-pulse\"></div>\n        </div>\n        <div className=\"bg-gradient-to-br from-blue-600/20 to-cyan-600/20 rounded-lg p-4\">\n          <div className=\"h-5 bg-blue-500/50 rounded w-5 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-blue-500/50 rounded w-20 animate-pulse\"></div>\n        </div>\n        <div className=\"bg-gradient-to-br from-green-600/20 to-emerald-600/20 rounded-lg p-4 \">\n          <div className=\"h-5 bg-green-500/50 rounded w-5 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-green-500/50 rounded w-18 animate-pulse\"></div>\n        </div>\n        <div className=\"bg-gradient-to-br from-orange-600/20 to-red-600/20 rounded-lg p-4\">\n          <div className=\"h-5 bg-orange-500/50 rounded w-5 animate-pulse mb-2\"></div>\n          <div className=\"h-3 bg-orange-500/50 rounded w-22 animate-pulse\"></div>\n        </div>\n      </div>\n\n      <div>\n        <div className=\"h-4 bg-zinc-700 rounded w-28 animate-pulse mb-3\"></div>\n        <div className=\"space-y-2\">\n          <div className=\"flex items-center gap-3 p-2 bg-zinc-900 rounded\">\n            <div className=\"h-3 bg-zinc-600 rounded w-40 animate-pulse\"></div>\n          </div>\n          <div className=\"flex items-center gap-3 p-2 bg-zinc-900 rounded\">\n            <div className=\"h-3 bg-zinc-600 rounded w-36 animate-pulse\"></div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst CloudChildren = () => {\n  return (\n    <div className=\"w-full h-full bg-zinc-900 p-6\">\n      <div className=\"mb-8\">\n        <div className=\"h-6 bg-cyan-600 rounded w-24 animate-pulse mb-2\"></div>\n        <div className=\"h-4 bg-zinc-700 rounded w-32 animate-pulse\"></div>\n      </div>\n\n      <div className=\"bg-zinc-800 rounded-lg p-4 mb-6\">\n        <div className=\"flex items-center justify-between mb-3\">\n          <div className=\"h-4 bg-zinc-600 rounded w-20 animate-pulse\"></div>\n          <div className=\"h-4 bg-zinc-600 rounded w-16 animate-pulse\"></div>\n        </div>\n        <div className=\"w-full bg-zinc-700 rounded-full h-2 mb-2\">\n          <div className=\"bg-cyan-500 h-2 rounded-full w-3/4 animate-pulse\"></div>\n        </div>\n        <div className=\"h-3 bg-zinc-600 rounded w-24 animate-pulse\"></div>\n      </div>\n\n      <div className=\"space-y-3 mb-6\">\n        <div className=\"flex items-center gap-3 p-3 bg-zinc-800 rounded-lg hover:bg-zinc-700 transition-colors\">\n          <div className=\"h-4 bg-green-500/50 rounded w-4 animate-pulse\"></div>\n          <div className=\"h-4 bg-zinc-600 rounded w-20 animate-pulse\"></div>\n        </div>\n        <div className=\"flex items-center gap-3 p-3 bg-zinc-800 rounded-lg hover:bg-zinc-700 transition-colors\">\n          <div className=\"h-4 bg-blue-500/50 rounded w-4 animate-pulse\"></div>\n          <div className=\"h-4 bg-zinc-600 rounded w-24 animate-pulse\"></div>\n        </div>\n        <div className=\"flex items-center gap-3 p-3 bg-zinc-800 rounded-lg hover:bg-zinc-700 transition-colors\">\n          <div className=\"h-4 bg-purple-500/50 rounded w-4 animate-pulse\"></div>\n          <div className=\"h-4 bg-zinc-600 rounded w-28 animate-pulse\"></div>\n        </div>\n      </div>\n\n      <div>\n        <div className=\"h-4 bg-zinc-700 rounded w-24 animate-pulse mb-3\"></div>\n        <div className=\"space-y-2\">\n          <div className=\"flex items-center gap-3 p-2 hover:bg-zinc-800 rounded\">\n            <div className=\"h-4 bg-cyan-500/50 rounded w-4 animate-pulse\"></div>\n            <div className=\"h-3 bg-zinc-600 rounded w-32 animate-pulse\"></div>\n          </div>\n          <div className=\"flex items-center gap-3 p-2 hover:bg-zinc-800 rounded\">\n            <div className=\"h-4 bg-gray-500/50 rounded w-4 animate-pulse\"></div>\n            <div className=\"h-3 bg-zinc-600 rounded w-28 animate-pulse\"></div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst SidebarToggle = ({\n  isOpen,\n  setIsOpen\n}: {\n  isOpen: string | false;\n  setIsOpen: (isOpen: string | false) => void;\n}) => {\n  const renderIcon = () => {\n    switch (isOpen !== false) {\n      case true:\n        return <PanelLeftClose size={24} />;\n      case false:\n        return <Menu size={24} />;\n    }\n  };\n  return (\n    <li\n      onClick={() => {\n        if (isOpen) {\n          setIsOpen(false);\n        } else {\n          setIsOpen('Home');\n        }\n      }}\n      className=\"flex flex-col items-center justify-center gap-1 mb-2 cursor-pointer hover:bg-zinc-800 my-3 py-2.5 mx-4 rounded-md m-1.5\"\n    >\n      <AnimatePresence mode=\"popLayout\" initial={false}>\n        <motion.span\n          initial={{ opacity: 0.3, scale: 0.5, filter: 'blur(4px)' }}\n          animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}\n          exit={{ opacity: 0.3, scale: 0.5, filter: 'blur(4px)' }}\n          key={`sidebar-toggle-${isOpen ? true : false}`}\n        >\n          {renderIcon()}\n        </motion.span>\n      </AnimatePresence>\n    </li>\n  );\n};\n\nconst Sidebar = () => {\n  const [isOpen, setIsOpen] = useState<string | false>(false);\n  const [hovering, setHovering] = useState<string | null>(null);\n\n  const options = [\n    {\n      name: 'Home',\n      icon: <Home size={24} />,\n      children: <HomeChildren />\n    },\n    {\n      name: 'Projects',\n      icon: <Folder size={24} />,\n      children: <ProjectsChildren />\n    },\n    {\n      name: 'Templates',\n      icon: <PanelsTopLeft size={24} />,\n      children: <TemplatesChildren />\n    },\n    {\n      name: 'AI',\n      icon: <Star size={24} />,\n      children: <AIChildren />\n    },\n    {\n      name: 'Cloud',\n      icon: <Cloud size={24} />,\n      children: <CloudChildren />\n    }\n  ];\n\n  return (\n    <div className=\"h-full flex items-start\">\n      <ul className=\"h-full w-20 bg-zinc-900 flex flex-col gap-1 border-r border-zinc-800\">\n        <SidebarToggle isOpen={isOpen} setIsOpen={setIsOpen} />\n        {options.map((option) => {\n          const isActive = isOpen === option.name;\n\n          return (\n            <li\n              key={option.name}\n              onMouseEnter={() => {\n                if (!isOpen) setHovering(option.name);\n              }}\n              onMouseLeave={() => {\n                if (!isOpen) {\n                  setHovering(null);\n                }\n              }}\n              onClick={() => {\n                setIsOpen(option.name);\n              }}\n              className=\"group px-1.5 gap-1 py-1 flex flex-col items-center justify-center cursor-pointer select-none\"\n            >\n              <div\n                className={cn(\n                  'flex flex-col items-center justify-center gap-1 group-hover:bg-zinc-800 p-2.5 rounded-md text-white/90',\n                  isActive && 'bg-zinc-800 text-blue-400'\n                )}\n              >\n                {option.icon}\n              </div>\n              <p className=\"text-xs\">{option.name}</p>\n            </li>\n          );\n        })}\n      </ul>\n      <AnimatePresence mode=\"popLayout\" initial={false}>\n        {(hovering || isOpen) && (\n          <motion.section\n            id=\"sidebar-children\"\n            initial={{ clipPath: 'inset(100% 0 0 0)' }}\n            animate={{ clipPath: 'inset(0 0 0 0)' }}\n            exit={{ clipPath: 'inset(0 0 100% 0)', transition: { delay: 0.06 } }}\n            transition={{\n              duration: 0.2,\n              type: 'spring',\n              bounce: 0\n            }}\n            onMouseEnter={() => {\n              if (!isOpen) setHovering(isOpen || hovering);\n            }}\n            onMouseLeave={() => {\n              if (!isOpen) setHovering(null);\n            }}\n            key={isOpen ? isOpen : hovering}\n            className=\"h-full bg-zinc-900\"\n          >\n            <div className=\"w-[300px] h-full\">\n              {(isOpen || hovering) &&\n                options.find((opt) => opt.name === (isOpen || hovering))?.children}\n            </div>\n          </motion.section>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n\nexport default Sidebar;\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "skeleton",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/skeleton.tsx",
          "target": "@ui/skeleton.tsx",
          "type": "registry:ui",
          "content": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"bg-accent animate-pulse rounded-md\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "skeumorphic-music-card",
      "type": "registry:block",
      "dependencies": [
        "lucide-react"
      ],
      "files": [
        {
          "path": "components/block/skeumorphic-music-card.tsx",
          "target": "@components/block/skeumorphic-music-card.tsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { Play, Pause, SkipForward, SkipBack } from 'lucide-react';\nimport { useState } from 'react';\nimport Image from 'next/image';\ninterface SkeumorphicMusicCardProps {\n  title: string;\n  artist: string;\n  cover: string;\n  className?: string;\n}\n\nconst ControlButton = ({\n  isPressed,\n  onMouseDown,\n  onMouseUp,\n  onMouseLeave,\n  onTouchStart,\n  onTouchEnd,\n  onClick,\n  children,\n  size = 'small'\n}: {\n  isPressed: boolean;\n  onMouseDown: () => void;\n  onMouseUp: () => void;\n  onMouseLeave: () => void;\n  onTouchStart: () => void;\n  onTouchEnd: () => void;\n  onClick?: () => void;\n  children: React.ReactNode;\n  size?: 'small' | 'large';\n}) => (\n  <div\n    className=\"rounded-full p-[3px] border-[1px] border-black/5\"\n    style={{\n      background: 'linear-gradient(135deg, #c2d9f0 0%, #e6f0fa 100%)',\n      boxShadow: 'inset 1px 1px 2px rgba(255,255,255,0.6), inset -1px -1px 2px rgba(0,0,0,0.1)'\n    }}\n  >\n    <button\n      onClick={onClick}\n      onMouseDown={onMouseDown}\n      onMouseUp={onMouseUp}\n      onMouseLeave={onMouseLeave}\n      onTouchStart={onTouchStart}\n      onTouchEnd={onTouchEnd}\n      style={{\n        boxShadow: isPressed\n          ? 'inset 3px 3px 7px rgba(0,0,0,0.2), inset -1px -1px 3px rgba(255,255,255,0.5)'\n          : '-3px -3px 7px rgba(255,255,255,0.7), 3px 3px 7px rgba(0,0,0,0.2)',\n        background: isPressed\n          ? 'linear-gradient(135deg, #c2d9f0 0%, #d9e6f2 100%)'\n          : 'linear-gradient(135deg, #e6f0fa 0%, #c2d9f0 100%)',\n        transform: isPressed ? 'scale(0.95)' : 'scale(1)'\n      }}\n      className={`rounded-full transition-all duration-75 text-gray-700 hover:text-gray-900 ${\n        size === 'large' ? 'p-4' : 'p-2'\n      }`}\n    >\n      {children}\n    </button>\n  </div>\n);\n\nconst ProgressBar = () => (\n  <div\n    style={{\n      boxShadow: 'inset 2px 2px 3px rgba(0,0,0,0.1), inset -1px -1px 3px rgba(255,255,255,0.7)',\n      background: 'linear-gradient(135deg, #c2d9f0 0%, #d9e6f2 100%)'\n    }}\n    className=\"h-2 rounded-full overflow-hidden\"\n  >\n    <div\n      style={{\n        width: '35%',\n        background: 'linear-gradient(135deg, #6fa8dc 0%, #3d85c6 100%)',\n        boxShadow: '1px 1px 2px rgba(255,255,255,0.3)'\n      }}\n      className=\"h-full rounded-full\"\n    />\n  </div>\n);\n\nconst SkeumorphicMusicCard = ({ title, artist, cover, className }: SkeumorphicMusicCardProps) => {\n  const [isPlaying, setIsPlaying] = useState(false);\n  const [isPlayButtonPressed, setIsPlayButtonPressed] = useState(false);\n  const [isBackButtonPressed, setIsBackButtonPressed] = useState(false);\n  const [isForwardButtonPressed, setIsForwardButtonPressed] = useState(false);\n\n  const togglePlayPause = () => setIsPlaying(!isPlaying);\n\n  return (\n    <div\n      style={{\n        boxShadow: 'inset -8px -8px 15px rgba(255,255,255,0.8), inset 8px 8px 15px rgba(0,0,0,0.2)',\n        background: 'linear-gradient(135deg, #e6ecf0 0%, #cfd8e2 100%)'\n      }}\n      className={`p-5 rounded-[30px] border-[1px] border-black/5 ${className}`}\n    >\n      <div\n        style={{\n          boxShadow:\n            'inset 2px 2px 5px rgba(255,255,255,0.7), inset -2px -2px 5px rgba(0,0,0,0.1), 5px 5px 15px rgba(0,0,0,0.1)',\n          background: 'linear-gradient(135deg, #e6f0fa 0%, #c2d9f0 100%)'\n        }}\n        className=\"relative w-80 p-6 rounded-[20px] flex flex-col items-center border-[1px] border-black/5\"\n      >\n        {/* Album Cover */}\n        <div\n          style={{\n            boxShadow: '-2px -2px 5px rgba(255,255,255,0.5), 5px 5px 15px rgba(0,0,0,0.3)'\n          }}\n          className=\"w-40 h-40 rounded-xl overflow-hidden\"\n        >\n          <Image\n            src={cover}\n            alt={title}\n            height={1080}\n            width={1080}\n            className=\"w-full h-full object-cover\"\n          />\n        </div>\n\n        {/* Song Info */}\n        <div className=\"mt-6 text-center\">\n          <h3 className=\"text-lg font-semibold text-gray-800\">{title}</h3>\n          <p className=\"text-sm text-gray-600\">{artist}</p>\n        </div>\n\n        <div className=\"w-full mt-6 px-2\">\n          <ProgressBar />\n        </div>\n\n        {/* Controls */}\n        <div className=\"flex items-center justify-center gap-5 w-full px-2 mt-3\">\n          <ControlButton\n            isPressed={isBackButtonPressed}\n            onMouseDown={() => setIsBackButtonPressed(true)}\n            onMouseUp={() => setIsBackButtonPressed(false)}\n            onMouseLeave={() => setIsBackButtonPressed(false)}\n            onTouchStart={() => setIsBackButtonPressed(true)}\n            onTouchEnd={() => setIsBackButtonPressed(false)}\n          >\n            <SkipBack size={22} />\n          </ControlButton>\n\n          <ControlButton\n            isPressed={isPlayButtonPressed}\n            onMouseDown={() => setIsPlayButtonPressed(true)}\n            onMouseUp={() => setIsPlayButtonPressed(false)}\n            onMouseLeave={() => setIsPlayButtonPressed(false)}\n            onTouchStart={() => setIsPlayButtonPressed(true)}\n            onTouchEnd={() => setIsPlayButtonPressed(false)}\n            onClick={togglePlayPause}\n            size=\"large\"\n          >\n            {isPlaying ? <Pause size={24} /> : <Play size={24} />}\n          </ControlButton>\n\n          <ControlButton\n            isPressed={isForwardButtonPressed}\n            onMouseDown={() => setIsForwardButtonPressed(true)}\n            onMouseUp={() => setIsForwardButtonPressed(false)}\n            onMouseLeave={() => setIsForwardButtonPressed(false)}\n            onTouchStart={() => setIsForwardButtonPressed(true)}\n            onTouchEnd={() => setIsForwardButtonPressed(false)}\n          >\n            <SkipForward size={22} />\n          </ControlButton>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default SkeumorphicMusicCard;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "slider",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-slider",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/slider.tsx",
          "target": "@ui/slider.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Slider({\n  className,\n  defaultValue,\n  value,\n  min = 0,\n  max = 100,\n  ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n  const _values = React.useMemo(\n    () =>\n      Array.isArray(value)\n        ? value\n        : Array.isArray(defaultValue)\n          ? defaultValue\n          : [min, max],\n    [value, defaultValue, min, max]\n  )\n\n  return (\n    <SliderPrimitive.Root\n      data-slot=\"slider\"\n      defaultValue={defaultValue}\n      value={value}\n      min={min}\n      max={max}\n      className={cn(\n        \"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n        className\n      )}\n      {...props}\n    >\n      <SliderPrimitive.Track\n        data-slot=\"slider-track\"\n        className={cn(\n          \"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\"\n        )}\n      >\n        <SliderPrimitive.Range\n          data-slot=\"slider-range\"\n          className={cn(\n            \"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n          )}\n        />\n      </SliderPrimitive.Track>\n      {Array.from({ length: _values.length }, (_, index) => (\n        <SliderPrimitive.Thumb\n          data-slot=\"slider-thumb\"\n          key={index}\n          className=\"border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n        />\n      ))}\n    </SliderPrimitive.Root>\n  )\n}\n\nexport { Slider }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "smooth-scroll",
      "type": "registry:block",
      "dependencies": [
        "lenis"
      ],
      "files": [
        {
          "path": "components/block/smooth-scroll.tsx",
          "target": "@components/block/smooth-scroll.tsx",
          "type": "registry:block",
          "content": "'use client'\nimport { type ReactNode, useEffect } from 'react'\nimport type Lenis from 'lenis'\n\nexport function SmoothScroll({ children }: { children: ReactNode }) {\n    useEffect(() => {\n        const preference = window.matchMedia('(prefers-reduced-motion: reduce)')\n        let disposed = false\n        let generation = 0\n        let instance: Lenis | null = null\n        let frame: number | null = null\n\n        const stop = () => {\n            generation++\n            if (frame !== null) cancelAnimationFrame(frame)\n            frame = null\n            instance?.destroy()\n            instance = null\n        }\n\n        const update = async () => {\n            stop()\n            if (disposed || preference.matches) return\n            const currentGeneration = generation\n            try {\n                const LenisClass = (await import('lenis')).default\n                if (disposed || preference.matches || currentGeneration !== generation) return\n                instance = new LenisClass({\n                    duration: 1.5,\n                    easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),\n                    orientation: 'vertical',\n                    gestureOrientation: 'vertical',\n                    smoothWheel: true,\n                    wheelMultiplier: 1,\n                    touchMultiplier: 2,\n                    infinite: false,\n                })\n\n                function raf(time: number) {\n                    if (disposed || !instance || currentGeneration !== generation) return\n                    instance.raf(time)\n                    frame = requestAnimationFrame(raf)\n                }\n                frame = requestAnimationFrame(raf)\n            } catch (e) {\n                console.warn('Lenis not available:', e)\n            }\n        }\n\n        void update()\n        preference.addEventListener('change', update)\n\n        return () => {\n            disposed = true\n            preference.removeEventListener('change', update)\n            stop()\n        }\n    }, [])\n\n    return <>{children}</>\n}\n\nexport default SmoothScroll\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "sonner",
      "type": "registry:ui",
      "dependencies": [
        "lucide-react",
        "next-themes",
        "sonner"
      ],
      "files": [
        {
          "path": "components/ui/sonner.tsx",
          "target": "@ui/sonner.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport {\n  CircleCheckIcon,\n  InfoIcon,\n  Loader2Icon,\n  OctagonXIcon,\n  TriangleAlertIcon,\n} from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      icons={{\n        success: <CircleCheckIcon className=\"size-4\" />,\n        info: <InfoIcon className=\"size-4\" />,\n        warning: <TriangleAlertIcon className=\"size-4\" />,\n        error: <OctagonXIcon className=\"size-4\" />,\n        loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n      }}\n      style={\n        {\n          \"--normal-bg\": \"var(--popover)\",\n          \"--normal-text\": \"var(--popover-foreground)\",\n          \"--normal-border\": \"var(--border)\",\n          \"--border-radius\": \"var(--radius)\",\n        } as React.CSSProperties\n      }\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "spinner",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/spinner.tsx",
          "target": "@ui/spinner.tsx",
          "type": "registry:ui",
          "content": "import { Loader2Icon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n  return (\n    <Loader2Icon\n      role=\"status\"\n      aria-label=\"Loading\"\n      className={cn(\"size-4 animate-spin\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Spinner }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "svg-path-marquee",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/svg-path-marquee.jsx",
          "target": "@components/block/svg-path-marquee.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport React, { useCallback, useEffect, useId, useRef } from \"react\";\nimport {\n  motion,\n  useAnimationFrame,\n  useMotionValue,\n  useScroll,\n  useSpring,\n  useTransform,\n  useVelocity,\n  useReducedMotion,\n} from \"motion/react\";\n\n// Custom wrap function\nconst wrap = (min, max, value) => {\n  const range = max - min\n  return ((((value - min) % range) + range) % range) + min\n}\n\nfunction MarqueePathItem({\n  child,\n  repeatIndex,\n  itemIndex,\n  itemKey,\n  baseOffset,\n  itemCount,\n  easing,\n  calculateZIndex,\n  cssVariableInterpolation,\n  draggable,\n  grabCursor,\n  path,\n  enableRollingZIndex,\n  itemRefs,\n  isHoveredRef,\n}) {\n  const itemOffset = useTransform(baseOffset, (value) => {\n    const position = (itemIndex * 100) / itemCount\n    const wrappedValue = wrap(0, 100, value + position)\n    return `${easing ? easing(wrappedValue / 100) * 100 : wrappedValue}%`\n  })\n\n  const currentOffsetDistance = useMotionValue(0)\n  const zIndex = useTransform(currentOffsetDistance, (value) =>\n    calculateZIndex(value))\n  const itemRef = useRef(null)\n\n  useEffect(() => {\n    const unsubscribe = itemOffset.on(\"change\", (value) => {\n      const match = value.match(/^([\\d.]+)%$/)\n      if (match && match[1]) {\n        const numericValue = parseFloat(match[1])\n        currentOffsetDistance.set(numericValue)\n\n        if (itemRef.current) {\n          cssVariableInterpolation.forEach(({ property, from, to }) => {\n            const nextValue = from + (to - from) * (numericValue / 100)\n            itemRef.current.style.setProperty(property, String(nextValue))\n          })\n        }\n      }\n    })\n    return unsubscribe\n  }, [cssVariableInterpolation, currentOffsetDistance, itemOffset])\n\n  return (\n    <motion.div\n      key={itemKey}\n      ref={(el) => {\n        itemRef.current = el\n        if (el) itemRefs.current.set(itemKey, el)\n        else itemRefs.current.delete(itemKey)\n      }}\n      className={`absolute top-0 left-0 ${draggable && grabCursor ? 'cursor-grab' : ''}`}\n      style={{\n        offsetPath: `path('${path}')`,\n        offsetDistance: itemOffset,\n        zIndex: enableRollingZIndex ? zIndex : undefined,\n        willChange: \"offset-distance\",\n        backfaceVisibility: \"hidden\",\n      }}\n      aria-hidden={repeatIndex > 0}\n      onMouseEnter={() => (isHoveredRef.current = true)}\n      onMouseLeave={() => (isHoveredRef.current = false)}>\n      {child}\n    </motion.div>\n  )\n}\n\n/** @param {{children?: import('react').ReactNode, className?: string, path?: string, pathId?: string,\n * preserveAspectRatio?: string, showPath?: boolean, width?: string | number, height?: string | number, viewBox?: string,\n * baseVelocity?: number, direction?: string, easing?: (value: number) => number, slowdownOnHover?: boolean,\n * slowDownFactor?: number, slowDownSpringConfig?: {damping: number, stiffness: number}, useScrollVelocity?: boolean,\n * scrollAwareDirection?: boolean, scrollSpringConfig?: {damping: number, stiffness: number},\n * scrollContainer?: import('react').RefObject<HTMLElement | null>, repeat?: number, draggable?: boolean,\n * dragSensitivity?: number, dragVelocityDecay?: number, dragAwareDirection?: boolean, grabCursor?: boolean,\n * enableRollingZIndex?: boolean, zIndexBase?: number, zIndexRange?: number,\n * cssVariableInterpolation?: Array<{property: string, from: number, to: number}>, responsive?: boolean, label?: string}} props */\nexport const SvgPathMarquee = ({\n  children,\n  className,\n\n  // Path defaults\n  path,\n\n  pathId,\n  preserveAspectRatio = \"xMidYMid meet\",\n  showPath = false,\n\n  // SVG defaults\n  width = \"100%\",\n\n  height = \"100%\",\n  viewBox = \"0 0 100 100\",\n\n  // Marquee defaults\n  baseVelocity = 5,\n\n  direction = \"normal\",\n  easing,\n  slowdownOnHover = false,\n  slowDownFactor = 0.3,\n  slowDownSpringConfig = { damping: 50, stiffness: 400 },\n\n  // Scroll defaults\n  useScrollVelocity = false,\n\n  scrollAwareDirection = false,\n  scrollSpringConfig = { damping: 50, stiffness: 400 },\n  scrollContainer,\n\n  // Items repetition\n  repeat = 3,\n\n  // Drag defaults\n  draggable = false,\n\n  dragSensitivity = 0.2,\n  dragVelocityDecay = 0.96,\n  dragAwareDirection = false,\n  grabCursor = false,\n\n  // Z-index defaults\n  enableRollingZIndex = true,\n\n  // Base z-index value\n  zIndexBase = 1,\n\n  // Range of z-index values to use\n  zIndexRange = 10,\n\n  cssVariableInterpolation = [],\n\n  // Responsive defaults\n  responsive = false,\n  label = \"Images following a curved path. Drag or use the left and right arrow keys.\",\n}) => {\n  const container = useRef(null)\n  const marqueeContainerRef = useRef(null)\n  const baseOffset = useMotionValue(0)\n\n  const pathRef = useRef(null)\n\n  const itemRefs = useRef(new Map())\n  const generatedPathId = useId()\n  const reduceMotion = useReducedMotion()\n\n  // Responsive scaling using direct DOM manipulation (no re-renders)\n  useEffect(() => {\n    if (!responsive) return\n\n    const [, , vbWidth, vbHeight] = viewBox.split(\" \").map(Number)\n    const originalWidth = vbWidth || 100\n    const originalHeight = vbHeight || 100\n\n    const updateScale = () => {\n      const wrapper = container.current\n      const marqueeContainer = marqueeContainerRef.current\n      if (!wrapper || !marqueeContainer) return\n\n      const wrapperWidth = wrapper.clientWidth\n      const wrapperHeight = wrapper.clientHeight\n\n      const scaleX = wrapperWidth / originalWidth\n      const scaleY = wrapperHeight / originalHeight\n      const scale = Math.min(scaleX, scaleY)\n\n      // Calculate the scaled dimensions\n      const scaledWidth = originalWidth * scale\n      const scaledHeight = originalHeight * scale\n\n      // Center the marquee container within the wrapper\n      const offsetX = (wrapperWidth - scaledWidth) / 2\n      const offsetY = (wrapperHeight - scaledHeight) / 2\n\n      // Set fixed dimensions on the container\n      marqueeContainer.style.width = `${originalWidth}px`\n      marqueeContainer.style.height = `${originalHeight}px`\n\n      // Apply scale and position to center\n      marqueeContainer.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`\n      marqueeContainer.style.transformOrigin = \"top left\"\n    }\n\n    updateScale()\n    const observer = new ResizeObserver(updateScale)\n    if (container.current) observer.observe(container.current)\n    window.addEventListener(\"resize\", updateScale)\n    return () => { observer.disconnect(); window.removeEventListener(\"resize\", updateScale); };\n  }, [responsive, viewBox])\n\n  // Create an array of items outside of the render function\n  const items = React.useMemo(() => {\n    const childrenArray = React.Children.toArray(children)\n\n    return childrenArray.flatMap((child, childIndex) =>\n      Array.from({ length: repeat }, (_, repeatIndex) => {\n        const itemIndex = repeatIndex * childrenArray.length + childIndex\n        const key = `${childIndex}-${repeatIndex}`\n        return {\n          child,\n          childIndex,\n          repeatIndex,\n          itemIndex,\n          key,\n        }\n      }));\n  }, [children, repeat])\n\n  // Function to calculate z-index based on offset distance\n  const calculateZIndex = useCallback((offsetDistance) => {\n    if (!enableRollingZIndex) {\n      return undefined\n    }\n\n    // Simple progress-based z-index\n    const normalizedDistance = offsetDistance / 100\n    return Math.floor(zIndexBase + normalizedDistance * zIndexRange);\n  }, [enableRollingZIndex, zIndexBase, zIndexRange])\n\n  // Generate a random ID for the path if not provided\n  const id = pathId || `marquee-path-${generatedPathId.replace(/:/g, \"\")}`\n\n  // Scroll tracking\n const { scrollY } = useScroll({\n  container: scrollContainer || undefined,\n})\n\n  const scrollVelocity = useVelocity(scrollY)\n  const smoothVelocity = useSpring(scrollVelocity, scrollSpringConfig)\n\n  // Hover and drag state tracking\n  const isHoveredRef = useRef(false)\n  const isDragging = useRef(false)\n  const dragVelocity = useRef(0)\n\n  // Direction factor for changing direction based on scroll or drag\n  const directionFactor = useRef(direction === \"normal\" ? 1 : -1)\n\n  // Motion values for animation\n  const hoverFactorValue = useMotionValue(1)\n  const defaultVelocity = useMotionValue(1)\n  const smoothHoverFactor = useSpring(hoverFactorValue, slowDownSpringConfig)\n\n  // Transform scroll velocity into a factor that affects marquee speed\n  const velocityFactor = useTransform(\n    useScrollVelocity ? smoothVelocity : defaultVelocity,\n    [0, 1000],\n    [0, 5],\n    { clamp: false }\n  )\n\n  // Animation frame handler\n  useAnimationFrame((_, delta) => {\n    if (reduceMotion) return\n    if (isDragging.current && draggable) {\n      baseOffset.set(baseOffset.get() + dragVelocity.current)\n\n      // Add decay to dragVelocity\n      dragVelocity.current *= 0.9\n\n      // Stop completely if velocity is very small\n      if (Math.abs(dragVelocity.current) < 0.01) {\n        dragVelocity.current = 0\n      }\n\n      return\n    }\n\n    // Update hover factor\n    if (isHoveredRef.current) {\n      hoverFactorValue.set(slowdownOnHover ? slowDownFactor : 1)\n    } else {\n      hoverFactorValue.set(1)\n    }\n\n    // Calculate regular movement\n    let moveBy =\n      directionFactor.current *\n      baseVelocity *\n      (delta / 1000) *\n      smoothHoverFactor.get()\n\n    // Adjust movement based on scroll velocity if scrollAwareDirection is enabled\n    if (scrollAwareDirection && !isDragging.current) {\n      if (velocityFactor.get() < 0) {\n        directionFactor.current = -1\n      } else if (velocityFactor.get() > 0) {\n        directionFactor.current = 1\n      }\n    }\n\n    moveBy += directionFactor.current * moveBy * velocityFactor.get()\n\n    if (draggable) {\n      moveBy += dragVelocity.current\n\n      // Update direction based on drag direction if dragAwareDirection is true\n      if (dragAwareDirection && Math.abs(dragVelocity.current) > 0.1) {\n        directionFactor.current = Math.sign(dragVelocity.current)\n      }\n\n      // Gradually decay drag velocity back to zero\n      if (!isDragging.current && Math.abs(dragVelocity.current) > 0.01) {\n        dragVelocity.current *= dragVelocityDecay\n      } else if (!isDragging.current) {\n        dragVelocity.current = 0\n      }\n    }\n\n    baseOffset.set(baseOffset.get() + moveBy)\n  })\n\n  // Pointer event handlers for dragging\n  const lastPointerPosition = useRef({ x: 0, y: 0 })\n\n  const handlePointerDown = (e) => {\n    if (!draggable) return\n    ;(e.currentTarget).setPointerCapture(e.pointerId)\n\n    if (grabCursor) {\n      ;(e.currentTarget).style.cursor = \"grabbing\"\n    }\n\n    isDragging.current = true\n    lastPointerPosition.current = { x: e.clientX, y: e.clientY }\n\n    // Pause automatic animation by setting velocity to 0\n    dragVelocity.current = 0\n  }\n\n  const handlePointerMove = (e) => {\n    if (!draggable || !isDragging.current) return\n\n    const currentPosition = { x: e.clientX, y: e.clientY }\n\n    // Calculate movement delta - simplified for path movement\n    const deltaX = currentPosition.x - lastPointerPosition.current.x\n    const deltaY = currentPosition.y - lastPointerPosition.current.y\n\n    // For path following, we use a simple magnitude of movement\n    const delta = Math.sqrt(deltaX * deltaX + deltaY * deltaY)\n    const projectedDelta = deltaX > 0 ? delta : -delta\n\n    // Update drag velocity based on the projected movement\n    dragVelocity.current = projectedDelta * dragSensitivity\n    if (reduceMotion) baseOffset.set(baseOffset.get() + dragVelocity.current)\n\n    // Update last position\n    lastPointerPosition.current = currentPosition\n  }\n\n  const handlePointerUp = (e) => {\n    if (!draggable) return\n    if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId)\n    isDragging.current = false\n\n    if (grabCursor) {\n      ;(e.currentTarget).style.cursor = \"grab\"\n    }\n  }\n\n  return (\n    <div\n      ref={container}\n      role=\"region\"\n      tabIndex={draggable ? 0 : undefined}\n      aria-label={label}\n      onKeyDown={(event) => {\n        if (!draggable || (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\")) return\n        event.preventDefault()\n        baseOffset.set(baseOffset.get() + (event.key === \"ArrowRight\" ? 5 : -5))\n      }}\n      onPointerDown={handlePointerDown}\n      onPointerMove={handlePointerMove}\n      onPointerUp={handlePointerUp}\n      onPointerCancel={handlePointerUp}\n      className={`relative font-body outline-offset-4 focus-visible:outline-2 focus-visible:outline-ring ${className || ''}`}>\n      <div\n        ref={marqueeContainerRef}\n        className=\"relative\"\n        style={{ contain: \"layout style\" }}>\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width={width}\n          height={height}\n          viewBox={viewBox}\n          preserveAspectRatio={preserveAspectRatio}\n          className=\"w-full h-full\">\n          <path\n            id={id}\n            d={path}\n            stroke={showPath ? \"currentColor\" : \"none\"}\n            fill=\"none\"\n            ref={pathRef} />\n        </svg>\n\n        {items.map(({ child, repeatIndex, itemIndex, key }) => (\n          <MarqueePathItem\n            key={key}\n            child={child}\n            repeatIndex={repeatIndex}\n            itemIndex={itemIndex}\n            itemKey={key}\n            baseOffset={baseOffset}\n            itemCount={items.length}\n            easing={easing}\n            calculateZIndex={calculateZIndex}\n            cssVariableInterpolation={cssVariableInterpolation}\n            draggable={draggable}\n            grabCursor={grabCursor}\n            path={path}\n            enableRollingZIndex={enableRollingZIndex}\n            itemRefs={itemRefs}\n            isHoveredRef={isHoveredRef}\n          />\n        ))}\n      </div>\n    </div>\n  );\n}\n\nexport { SvgPathMarquee as MarqueeAlongSvgPath }\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "svg-pixel-reveal",
      "type": "registry:block",
      "dependencies": [
        "gsap"
      ],
      "files": [
        {
          "path": "components/block/svg-pixel-reveal.jsx",
          "target": "@components/block/svg-pixel-reveal.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport gsap from \"gsap\";\nimport { ScrollTrigger } from \"gsap/ScrollTrigger\";\nimport \"@/lib/effects/svg-pixel-reveal/styles.css\";\n\nif (typeof window !== \"undefined\") gsap.registerPlugin(ScrollTrigger);\n\nfunction PixelateSvgFilter({ id, size, crossLayers }) {\n  return (\n    <svg aria-hidden=\"true\" style={{ pointerEvents: \"none\", position: \"absolute\", height: 0, width: 0, overflow: \"hidden\" }}>\n      <defs>\n        <filter id={id} x=\"0\" y=\"0\" width=\"1\" height=\"1\">\n          <feConvolveMatrix kernelMatrix=\"1 1 1 1 1 1 1 1 1\" result=\"AVG\" />\n          <feFlood x=\"1\" y=\"1\" width=\"1\" height=\"1\" />\n          <feComposite operator=\"arithmetic\" k1=\"0\" k2=\"1\" k3=\"0\" k4=\"0\" width={size} height={size} />\n          <feTile result=\"TILE\" />\n          <feComposite in=\"AVG\" in2=\"TILE\" operator=\"in\" />\n          <feMorphology operator=\"dilate\" radius={size / 2} result=\"NORMAL\" />\n\n          {crossLayers && (\n            <>\n              <feConvolveMatrix kernelMatrix=\"1 1 1 1 1 1 1 1 1\" result=\"AVG\" />\n              <feFlood x=\"1\" y=\"1\" width=\"1\" height=\"1\" />\n              <feComposite in2=\"SourceGraphic\" operator=\"arithmetic\" k1=\"0\" k2=\"1\" k3=\"0\" k4=\"0\" width={size / 2} height={size} />\n              <feTile result=\"TILE\" />\n              <feComposite in=\"AVG\" in2=\"TILE\" operator=\"in\" />\n              <feMorphology operator=\"dilate\" radius={size / 2} result=\"FALLBACKX\" />\n\n              <feConvolveMatrix kernelMatrix=\"1 1 1 1 1 1 1 1 1\" result=\"AVG\" />\n              <feFlood x=\"1\" y=\"1\" width=\"1\" height=\"1\" />\n              <feComposite in2=\"SourceGraphic\" operator=\"arithmetic\" k1=\"0\" k2=\"1\" k3=\"0\" k4=\"0\" width={size} height={size / 2} />\n              <feTile result=\"TILE\" />\n              <feComposite in=\"AVG\" in2=\"TILE\" operator=\"in\" />\n              <feMorphology operator=\"dilate\" radius={size / 2} result=\"FALLBACKY\" />\n\n              <feMerge>\n                <feMergeNode in=\"FALLBACKX\" />\n                <feMergeNode in=\"FALLBACKY\" />\n                <feMergeNode in=\"NORMAL\" />\n              </feMerge>\n            </>\n          )}\n\n          {!crossLayers && <feMergeNode in=\"NORMAL\" />}\n        </filter>\n      </defs>\n    </svg>\n  );\n}\n\n/** @param {{src?: string, alt?: string, initialPixelSize?: number, finalPixelSize?: number, start?: string, end?: string,\n * crossLayers?: boolean, style?: import('react').CSSProperties, scroller?: HTMLElement | import('react').RefObject<HTMLElement | null>}} props */\nexport function SvgPixelReveal({\n  src,\n  alt = \"Image\",\n  initialPixelSize = 22,\n  finalPixelSize = 1,\n  start = \"top 50%\",\n  end = \"bottom 35%\",\n  crossLayers = true,\n  style = {},\n  scroller,\n}) {\n  const containerRef = useRef(null);\n  const filterId = useId().replace(/:/g, \"\");\n  const [pixelSize, setPixelSize] = useState(initialPixelSize);\n  const shouldApplyFilter = pixelSize > finalPixelSize + 0.01;\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n\n    const animatedState = { size: initialPixelSize };\n\n    const tween = gsap.to(animatedState, {\n      size: finalPixelSize,\n      duration: 1.0,\n      ease: \"none\",\n      paused: true,\n      onUpdate: () => setPixelSize(animatedState.size),\n    });\n\n    const trigger = ScrollTrigger.create({\n      trigger: container,\n      scroller: scroller?.current !== undefined ? scroller.current : scroller,\n      start,\n      end,\n      animation: tween,\n      invalidateOnRefresh: true,\n    });\n\n    return () => { trigger.kill(); tween.kill(); };\n    }, containerRef);\n    return () => media.revert();\n  }, [end, finalPixelSize, initialPixelSize, start, scroller]);\n\n  return (\n    <div ref={containerRef} style={{ position: \"relative\", ...style }}>\n      <PixelateSvgFilter id={filterId} size={pixelSize} crossLayers={crossLayers} />\n      <div\n        className=\"obsidian-svg-pixel-image\"\n        style={{\n          position: \"relative\", height: \"100%\", width: \"100%\", overflow: \"hidden\",\n          filter: shouldApplyFilter ? `url(#${filterId})` : undefined,\n        }}\n      >\n        <img src={src} alt={alt} style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }} />\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/effects/svg-pixel-reveal/styles.css",
          "target": "@lib/effects/svg-pixel-reveal/styles.css",
          "type": "registry:file",
          "content": "@media (prefers-reduced-motion: reduce) {\n  .obsidian-svg-pixel-image { filter: none !important; }\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "switch",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-switch",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/switch.tsx",
          "target": "@ui/switch.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Switch({\n  className,\n  ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root>) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      className={cn(\n        \"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className={cn(\n          \"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0\"\n        )}\n      />\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { Switch }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "table",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/table.tsx",
          "target": "@ui/table.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Table({ className, ...props }: React.ComponentProps<\"table\">) {\n  return (\n    <div\n      data-slot=\"table-container\"\n      className=\"relative w-full overflow-x-auto\"\n    >\n      <table\n        data-slot=\"table\"\n        className={cn(\"w-full caption-bottom text-sm\", className)}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<\"thead\">) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn(\"[&_tr]:border-b\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<\"tbody\">) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn(\"[&_tr:last-child]:border-0\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<\"tfoot\">) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn(\n        \"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<\"tr\">) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      className={cn(\n        \"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<\"th\">) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      className={cn(\n        \"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<\"td\">) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        \"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<\"caption\">) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn(\"text-muted-foreground mt-4 text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "tabs",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-tabs",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/tabs.tsx",
          "target": "@ui/tabs.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Tabs({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Root>) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      className={cn(\"flex flex-col gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction TabsList({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.List>) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      className={cn(\n        \"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n  return (\n    <TabsPrimitive.Trigger\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        \"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n  return (\n    <TabsPrimitive.Content\n      data-slot=\"tabs-content\"\n      className={cn(\"flex-1 outline-none\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "text-fill-animation",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/text-fill-animation.css",
          "target": "@components/block/text-fill-animation.css",
          "type": "registry:file",
          "content": "@keyframes obsidian-text-fill-color {\n  0% { color: var(--tfa-dim-color); }\n  30% { color: var(--tfa-primary-color); }\n  100% { color: var(--tfa-text-color); }\n}\n\n.obsidian-text-fill .split__wrapper .split-chars {\n  transition: color 0.4s;\n  color: var(--tfa-dim-color);\n}\n\n.obsidian-text-fill .split__wrapper .split-chars.show {\n  animation: obsidian-text-fill-color 0.5s;\n  color: var(--tfa-text-color);\n}\n\n.obsidian-text-fill .tfa-viewport { height: var(--tfa-viewport-height); }\n.obsidian-text-fill .tfa-text-wrapper { width: var(--tfa-text-width); }\n.obsidian-text-fill .tfa-heading { font-size: var(--tfa-text-size); color: var(--tfa-text-color); }\n.obsidian-text-fill .tfa-glow {\n  height: calc(var(--tfa-viewport-height) * 0.7);\n  width: calc(var(--tfa-viewport-height) * 0.7);\n  background: color-mix(in srgb, var(--tfa-primary-color) 5%, transparent);\n}\n\n@media (min-width: 768px) and (max-width: 1024px) {\n  .obsidian-text-fill .tfa-text-wrapper { width: var(--tfa-tablet-text-width); }\n  .obsidian-text-fill .tfa-heading { font-size: var(--tfa-tablet-text-size); }\n}\n\n@media (max-width: 767px) {\n  .obsidian-text-fill .tfa-text-wrapper { width: var(--tfa-mobile-text-width); }\n  .obsidian-text-fill .tfa-heading { font-size: var(--tfa-mobile-text-size); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .obsidian-text-fill { height: auto !important; }\n  .obsidian-text-fill .split__wrapper .split-chars {\n    animation: none;\n    transition: none;\n    color: var(--tfa-text-color);\n  }\n}\n"
        },
        {
          "path": "components/block/text-fill-animation.jsx",
          "target": "@components/block/text-fill-animation.jsx",
          "type": "registry:block",
          "content": "'use client';\n\nimport { useEffect, useId, useRef } from 'react';\nimport gsap from 'gsap';\nimport ScrollTrigger from 'gsap/ScrollTrigger';\nimport SplitText from 'gsap/SplitText';\nimport { cn } from '@/lib/utils';\nimport './text-fill-animation.css';\n\nconst SPLIT_CHARACTER_SELECTOR = '.split-chars';\n\nif (typeof window !== 'undefined') {\n  gsap.registerPlugin(ScrollTrigger, SplitText);\n}\n\n/**\n * @param {{\n *   text?: string, textColor?: string, primaryColor?: string, dimColor?: string,\n *   backgroundColor?: string, className?: string, id?: string,\n *   textSize?: string, textWidth?: string, containerClassName?: string,\n *   mobileTextSize?: string, mobileTextWidth?: string,\n *   tabletTextSize?: string, tabletTextWidth?: string,\n *   scroller?: HTMLElement | Window | import('react').RefObject<HTMLElement | null>,\n *   trigger?: HTMLElement | import('react').RefObject<HTMLElement | null>,\n *   start?: string, end?: string, scrub?: number | boolean,\n *   height?: string | number, viewportHeight?: string, showDetails?: boolean,\n *   style?: import('react').CSSProperties\n * }} props\n */\nexport function TextFillAnimation({\n  text = 'Build thoughtful interfaces, bring your ideas to life, and make every interaction feel right with ObsidianUI.',\n  textColor = 'var(--foreground)',\n  primaryColor = '#ff6b00',\n  dimColor = 'color-mix(in srgb, var(--foreground) 20%, var(--background))',\n  backgroundColor = 'var(--background)',\n  className = '',\n  id,\n  textSize = '5vw',\n  textWidth = '80%',\n  containerClassName = '',\n  mobileTextSize = '8vw',\n  mobileTextWidth = '95%',\n  tabletTextSize = '6.5vw',\n  tabletTextWidth = '88%',\n  scroller,\n  trigger,\n  start = 'top top',\n  end = 'bottom bottom',\n  scrub = 0.25,\n  height = '250vh',\n  viewportHeight = '100vh',\n  showDetails = true,\n  style,\n}) {\n  // State and refs\n  const sectionRef = useRef(null);\n  const textRef = useRef(null);\n  const generatedId = useId();\n\n  // Effects\n  useEffect(() => {\n    const media = gsap.matchMedia();\n    media.add('(prefers-reduced-motion: no-preference)', () => {\n      const textElement = textRef.current;\n\n      if (!textElement) {\n        return;\n      }\n\n      const split = SplitText.create(textElement, {\n        type: 'words chars',\n        aria: 'auto',\n        tag: 'span',\n        charsClass: 'split-chars',\n      });\n\n      gsap.set(textElement, {\n        opacity: 1,\n      });\n\n      const characters = Array.from(\n        textElement.querySelectorAll(SPLIT_CHARACTER_SELECTOR)\n      );\n\n      gsap\n        .timeline({\n          scrollTrigger: {\n            trigger: (trigger?.current !== undefined ? trigger.current : trigger) ?? sectionRef.current,\n            scroller: scroller?.current !== undefined ? scroller.current : scroller,\n            start,\n            end,\n            scrub,\n            invalidateOnRefresh: true,\n          },\n        })\n        .to(\n          characters,\n          {\n            className: 'split-chars show',\n            duration: 0.4,\n            stagger: 0.05,\n            ease: 'power2.inOut',\n          },\n          0\n        );\n\n      return () => {\n        split.revert();\n      };\n    }, sectionRef);\n\n    return () => {\n      media.revert();\n    };\n  }, [\n    text,\n    trigger,\n    scroller,\n    start,\n    end,\n    scrub,\n  ]);\n\n  // Return\n  return (\n    <section\n      id={id ?? `obsidian-text-fill-${generatedId}`}\n      ref={sectionRef}\n      className={cn('obsidian-text-fill relative w-full font-body text-foreground', containerClassName)}\n      style={{\n        backgroundColor,\n        height,\n        '--tfa-viewport-height': viewportHeight,\n        '--tfa-text-color': textColor,\n        '--tfa-primary-color': primaryColor,\n        '--tfa-dim-color': dimColor,\n        '--tfa-text-size': textSize,\n        '--tfa-text-width': textWidth,\n        '--tfa-tablet-text-size': tabletTextSize,\n        '--tfa-tablet-text-width': tabletTextWidth,\n        '--tfa-mobile-text-size': mobileTextSize,\n        '--tfa-mobile-text-width': mobileTextWidth,\n        ...style,\n      }}\n    >\n      <div className=\"tfa-viewport sticky top-0 flex items-center justify-center overflow-x-hidden\">\n        <div aria-hidden=\"true\" className=\"tfa-glow pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full blur-3xl\" />\n\n        {showDetails && (\n          <>\n            <div className=\"absolute left-[5vw] top-[5vh]\">\n              <p className=\"text-xs uppercase tracking-[0.35em] text-muted-foreground max-md:text-sm max-sm:text-xs\">\n                Design Philosophy\n              </p>\n            </div>\n    \n            <div className=\"absolute right-[5vw] top-[5vh] text-right\">\n              <p className=\"text-xs uppercase tracking-[0.35em] text-muted-foreground max-md:text-sm max-sm:text-xs\">\n                Scroll ↓\n              </p>\n            </div>\n    \n            <div className=\"absolute bottom-[6vh] left-[5vw] max-w-55\">\n              <p className=\"text-sm leading-relaxed text-muted-foreground max-md:text-lg max-sm:text-sm\">\n                Less friction.\n                <br />\n                More building.\n              </p>\n            </div>\n    \n            <div className=\"absolute bottom-[6vh] right-[5vw] text-right\">\n              <div className=\"space-y-1\">\n                <p className=\"text-sm text-muted-foreground max-md:text-lg max-sm:text-sm\">\n                  Systems\n                </p>\n                <p className=\"text-sm text-muted-foreground max-md:text-lg max-sm:text-sm\">\n                  Motion\n                </p>\n                <p className=\"text-sm text-muted-foreground max-md:text-lg max-sm:text-sm\">\n                  Clarity\n                </p>\n              </div>\n            </div>\n          </>\n        )}\n\n        <div className=\"split__wrapper tfa-text-wrapper relative z-10 mx-auto text-center\">\n          <h2\n            ref={textRef}\n            className={cn('tfa-heading font-heading font-medium leading-[1.18] tracking-[-0.03em]', className)}\n          >\n            {text}\n          </h2>\n        </div>\n      </div>\n    </section>\n  );\n}\n\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "text-stream",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "gsap",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/text-stream.css",
          "target": "@components/block/text-stream.css",
          "type": "registry:file",
          "content": ".obsidian-text-stream { min-width: 0; }\n\n@media (prefers-reduced-motion: reduce) {\n  .obsidian-text-stream .obsidian-text-stream__viewport {\n    display: flex;\n    align-items: center;\n    mask-image: none !important;\n    -webkit-mask-image: none !important;\n  }\n\n  .obsidian-text-stream .obsidian-text-stream__track {\n    position: relative !important;\n    transform: none !important;\n  }\n\n  .obsidian-text-stream__copy + .obsidian-text-stream__copy { display: none !important; }\n}\n"
        },
        {
          "path": "components/block/text-stream.jsx",
          "target": "@components/block/text-stream.jsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"@/lib/utils\";\nimport \"./text-stream.css\";\n\n/**\n * @param {{\n *   items?: string[], prefix?: string, fontSize?: string, fontWeight?: number,\n *   height?: string | number, paused?: boolean, className?: string,\n *   style?: import('react').CSSProperties,\n *   scroller?: HTMLElement | Window | import('react').RefObject<HTMLElement | null>\n * }} props\n */\nexport function TextStream({\n  items = [],\n  prefix = \"ObsidianUI\",\n  fontSize = \"clamp(1.25rem, 3vw, 2.25rem)\",\n  fontWeight = 200,\n  height = \"100vh\",\n  scroller,\n  paused = false,\n  className,\n  style,\n}) {\n  const trackRef = useRef(null);\n  const contentRef = useRef(null);\n  const containerRef = useRef(null);\n  const [copyCount, setCopyCount] = useState(2);\n  const metricsRef = useRef({\n    currentY: 0,\n    distance: 0,\n    currentVelocity: 0.6,\n    targetVelocity: 0.6,\n    lastScrollDirection: 1,\n  });\n  const scrollTimeoutRef = useRef(null);\n\n  useEffect(() => {\n    const track = trackRef.current;\n    const content = contentRef.current;\n    const container = containerRef.current;\n    if (!track || !content || !container || paused || !items.length) return;\n\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      const baseSpeed = 0.6;\n      const maxBoost = 12;\n      const scrollTarget = (scroller?.current !== undefined ? scroller.current : scroller) ?? window;\n      const readScroll = () => scrollTarget === window ? window.scrollY : scrollTarget.scrollTop;\n      let lastScrollY = readScroll();\n\n      const startAnimation = () => {\n        const distance = content.offsetHeight;\n        const containerHeight = container.offsetHeight;\n        if (!distance || !containerHeight) return;\n\n        const nextCopyCount = Math.max(2, Math.ceil(containerHeight / distance) + 2);\n        setCopyCount((c) => (c === nextCopyCount ? c : nextCopyCount));\n        metricsRef.current.distance = distance;\n\n        metricsRef.current.currentY = gsap.utils.wrap(-distance, 0, metricsRef.current.currentY);\n\n        gsap.set(track, { y: metricsRef.current.currentY });\n      };\n\n      const tick = (_, deltaTime) => {\n        const { distance } = metricsRef.current;\n        if (!distance) return;\n\n        const frameFactor = deltaTime / (1000 / 60);\n        metricsRef.current.currentVelocity = gsap.utils.interpolate(\n          metricsRef.current.currentVelocity,\n          metricsRef.current.targetVelocity,\n          0.14\n        );\n        metricsRef.current.currentY += metricsRef.current.currentVelocity * frameFactor;\n\n        metricsRef.current.currentY = gsap.utils.wrap(-distance, 0, metricsRef.current.currentY);\n\n        gsap.set(track, { y: metricsRef.current.currentY });\n      };\n\n      const applyScrollMotion = (delta) => {\n        if (!delta) return;\n        const direction = delta > 0 ? -1 : 1;\n        const boost = Math.min(maxBoost, baseSpeed + Math.pow(Math.abs(delta), 1.2) * 0.08);\n        metricsRef.current.lastScrollDirection = direction;\n        metricsRef.current.targetVelocity = direction * boost;\n        window.clearTimeout(scrollTimeoutRef.current);\n        scrollTimeoutRef.current = window.setTimeout(() => {\n          metricsRef.current.targetVelocity = metricsRef.current.lastScrollDirection * baseSpeed;\n        }, 120);\n      };\n\n      const handleWheel  = (e) => applyScrollMotion(e.deltaY);\n      const handleScroll = () => {\n        const next = readScroll();\n        applyScrollMotion(next - lastScrollY);\n        lastScrollY = next;\n      };\n\n      startAnimation();\n      gsap.ticker.add(tick);\n\n      const ro = new ResizeObserver(startAnimation);\n      ro.observe(content);\n      ro.observe(container);\n      window.addEventListener(\"resize\", startAnimation);\n      scrollTarget.addEventListener(\"wheel\",  handleWheel,  { passive: true });\n      scrollTarget.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n      return () => {\n        ro.disconnect();\n        window.removeEventListener(\"resize\", startAnimation);\n        scrollTarget.removeEventListener(\"wheel\",  handleWheel);\n        scrollTarget.removeEventListener(\"scroll\", handleScroll);\n        window.clearTimeout(scrollTimeoutRef.current);\n        gsap.ticker.remove(tick);\n      };\n    }, containerRef);\n\n    return () => media.revert();\n  }, [items, paused, scroller]);\n\n  if (!items.length) return null;\n\n  return (\n    <div className={cn(\"obsidian-text-stream font-heading text-foreground\", className)} style={{ display: \"flex\", height, ...style }}>\n      {/* Left — static prefix */}\n      <div style={{ width: \"45%\", display: \"flex\", alignItems: \"center\", justifyContent: \"flex-end\", paddingRight: 8 }}>\n        <p style={{ fontSize, fontWeight, lineHeight: 1, whiteSpace: \"nowrap\", margin: 0 }}>\n          {prefix}\n        </p>\n      </div>\n\n      {/* Right — scrolling marquee */}\n      <div\n        ref={containerRef}\n        className=\"obsidian-text-stream__viewport\"\n        style={{\n          position: \"relative\", width: \"55%\", height: \"100%\", overflow: \"hidden\",\n          maskImage: \"linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2) 48%, rgba(0,0,0,1) 48%, rgba(0,0,0,1) 53%, rgba(0,0,0,0.3) 53%, rgba(0,0,0,0.3) 100%)\",\n          WebkitMaskImage: \"linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2) 48%, rgba(0,0,0,1) 48%, rgba(0,0,0,1) 53%, rgba(0,0,0,0.2) 53%, rgba(0,0,0,0.2) 100%)\",\n        }}\n      >\n        <div\n          ref={trackRef}\n          className=\"obsidian-text-stream__track\"\n          style={{ position: \"absolute\", left: 0, top: 0, display: \"flex\", flexDirection: \"column\", fontSize, fontWeight, lineHeight: 1 }}\n        >\n          {Array.from({ length: copyCount }, (_, copyIndex) => (\n            <div\n              key={copyIndex}\n              ref={copyIndex === 0 ? contentRef : null}\n              className=\"obsidian-text-stream__copy\"\n              style={{ display: \"flex\", flexDirection: \"column\" }}\n              aria-hidden={copyIndex > 0}\n            >\n              {items.map((text, i) => (\n                <div key={`${copyIndex}-${i}`} style={{ padding: \"4px 0 4px 8px\", whiteSpace: \"nowrap\" }}>\n                  {text}\n                </div>\n              ))}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "textarea",
      "type": "registry:ui",
      "dependencies": [
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/textarea.tsx",
          "target": "@ui/textarea.tsx",
          "type": "registry:ui",
          "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n  return (\n    <textarea\n      data-slot=\"textarea\"\n      className={cn(\n        \"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Textarea }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "toggle",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-toggle",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/toggle.tsx",
          "target": "@ui/toggle.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst toggleVariants = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline:\n          \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\",\n      },\n      size: {\n        default: \"h-9 px-2 min-w-9\",\n        sm: \"h-8 px-1.5 min-w-8\",\n        lg: \"h-10 px-2.5 min-w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Toggle({\n  className,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof TogglePrimitive.Root> &\n  VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive.Root\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Toggle, toggleVariants }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "toggle-group",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-toggle",
        "@radix-ui/react-toggle-group",
        "class-variance-authority",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/toggle-group.tsx",
          "target": "@ui/toggle-group.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ToggleGroupPrimitive from \"@radix-ui/react-toggle-group\"\nimport { type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { toggleVariants } from \"@/components/ui/toggle\"\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number\n  }\n>({\n  size: \"default\",\n  variant: \"default\",\n  spacing: 0,\n})\n\nfunction ToggleGroup({\n  className,\n  variant,\n  size,\n  spacing = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number\n  }) {\n  return (\n    <ToggleGroupPrimitive.Root\n      data-slot=\"toggle-group\"\n      data-variant={variant}\n      data-size={size}\n      data-spacing={spacing}\n      style={{ \"--gap\": spacing } as React.CSSProperties}\n      className={cn(\n        \"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs\",\n        className\n      )}\n      {...props}\n    >\n      <ToggleGroupContext.Provider value={{ variant, size, spacing }}>\n        {children}\n      </ToggleGroupContext.Provider>\n    </ToggleGroupPrimitive.Root>\n  )\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &\n  VariantProps<typeof toggleVariants>) {\n  const context = React.useContext(ToggleGroupContext)\n\n  return (\n    <ToggleGroupPrimitive.Item\n      data-slot=\"toggle-group-item\"\n      data-variant={context.variant || variant}\n      data-size={context.size || size}\n      data-spacing={context.spacing}\n      className={cn(\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size,\n        }),\n        \"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10\",\n        \"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </ToggleGroupPrimitive.Item>\n  )\n}\n\nexport { ToggleGroup, ToggleGroupItem }\n"
        },
        {
          "path": "components/ui/toggle.tsx",
          "target": "@ui/toggle.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst toggleVariants = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline:\n          \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\",\n      },\n      size: {\n        default: \"h-9 px-2 min-w-9\",\n        sm: \"h-8 px-1.5 min-w-8\",\n        lg: \"h-10 px-2.5 min-w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Toggle({\n  className,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof TogglePrimitive.Root> &\n  VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive.Root\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Toggle, toggleVariants }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "tooltip",
      "type": "registry:ui",
      "dependencies": [
        "@radix-ui/react-tooltip",
        "clsx",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/ui/tooltip.tsx",
          "target": "@ui/tooltip.tsx",
          "type": "registry:ui",
          "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  )\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  )\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "trading-card",
      "type": "registry:block",
      "dependencies": [
        "motion"
      ],
      "files": [
        {
          "path": "components/block/trading-card.tsx",
          "target": "@components/block/trading-card.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { motion, useAnimationControls } from 'motion/react';\nimport Image from 'next/image';\nimport React, { useRef } from 'react';\n\ninterface TradingCardProps {\n  imageUrl: string;\n  rank: number;\n  name: string;\n  description: string;\n}\n\nconst TradingCard: React.FC<TradingCardProps> = ({ imageUrl, rank, name, description }) => {\n  const cardRef = useRef<HTMLDivElement>(null);\n\n  const backgroundControls = useAnimationControls();\n  const contentControls = useAnimationControls();\n  const cardControls = useAnimationControls();\n\n  const onMouseEnter: React.MouseEventHandler<HTMLDivElement> = () => {\n    contentControls.start({ x: -300 });\n    backgroundControls.start({ scale: 1.05, opacity: 1 });\n  };\n\n  const onMouseLeave = () => {\n    contentControls.start({ x: 0, transition: { delay: 0.5 } });\n    backgroundControls.start({ scale: 0.95, opacity: 0.4 });\n    cardControls.start({ transform: `rotateY(0deg) rotateX(0deg)` });\n  };\n\n  const onMouseMove: React.MouseEventHandler<HTMLDivElement> = (e) => {\n    if (!cardRef.current) return;\n\n    const rect = cardRef.current?.getBoundingClientRect();\n    if (!rect) return;\n\n    const mx = e.clientX - rect.left;\n    const my = e.clientY - rect.top;\n\n    const width = rect.right - rect.left;\n    const height = rect.bottom - rect.top;\n\n    const xd = (mx - width / 2) / 10;\n    const yd = (height / 2 - my) / 10;\n\n    cardRef.current.style.transform = `perspective(1000px) rotateY(${xd}deg) rotateX(${yd}deg)`;\n  };\n\n  return (\n    <motion.div\n      ref={cardRef}\n      onMouseMove={onMouseMove}\n      onMouseEnter={onMouseEnter}\n      onMouseLeave={onMouseLeave}\n      initial={{\n        transform: 'perspective(1000px) rotateX(0deg) rotateY(0deg)'\n      }}\n      style={{\n        transformStyle: 'preserve-3d'\n      }}\n      animate={cardControls}\n      className=\"flex flex-col hover:scale-105 transition-all duration-200 ease-linear items-start justify-end rounded-lg relative shadow-xl overflow-hidden h-[400px] w-[300px] cursor-pointer border-[1px] border-neutral-800\"\n    >\n      <motion.div\n        className=\"h-full w-full absolute z-0\"\n        initial={{\n          opacity: 0.4,\n          scale: 0.95\n        }}\n        animate={backgroundControls}\n        transition={{ duration: 0.7, ease: 'backOut' }}\n      >\n        <div className=\"h-full w-full inset-0 bg-cover bg-center\">\n          <Image src={imageUrl} alt={name} fill className=\"object-cover\" />\n        </div>\n      </motion.div>\n      <div className=\"font-semibold absolute top-5 right-5 z-10 text-white/70\">#{rank}</div>\n      <motion.div\n        animate={contentControls}\n        transition={{ duration: 0.5, ease: 'backOut' }}\n        className=\"p-5 h-full w-full flex flex-col justify-end bg-transparent z-10 opacity-90\"\n      >\n        <div className=\"font-medium\">\n          {name.split(' ').map((word) => {\n            return (\n              <div\n                key={word}\n                className=\"flex flex-col items-start justify-start text-4xl font-bold\"\n              >\n                {word} <br />\n              </div>\n            );\n          })}\n        </div>\n        <p className=\"text-sm text-white/90\">{description}</p>\n      </motion.div>\n    </motion.div>\n  );\n};\n\nexport default TradingCard;\n"
        }
      ]
    },
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "visitor-count",
      "type": "registry:block",
      "dependencies": [
        "clsx",
        "lucide-react",
        "tailwind-merge"
      ],
      "files": [
        {
          "path": "components/block/visitor-count.tsx",
          "target": "@components/block/visitor-count.tsx",
          "type": "registry:block",
          "content": "\"use client\";\n\nimport { Eye } from \"lucide-react\";\nimport { useVisitorCount } from \"@/hooks/use-visitor-count\";\nimport { cn } from \"@/lib/utils\";\n\nexport function VisitorCount({ className }: { className?: string }) {\n  const { count, loading, error } = useVisitorCount();\n  if (error) return null;\n\n  return (\n    <div role=\"status\" className={cn(\"inline-flex items-center gap-2.5 rounded-full bg-muted px-4 py-2.5 text-sm text-muted-foreground\", className)}>\n      <Eye aria-hidden=\"true\" className=\"size-4\" />\n      {loading ? (\n        <span>Loading visitor count…</span>\n      ) : (\n        <span><strong className=\"font-semibold tabular-nums text-foreground\">{count.toLocaleString()}</strong> unique {count === 1 ? \"visitor\" : \"visitors\"}</span>\n      )}\n    </div>\n  );\n}\n"
        },
        {
          "path": "hooks/use-visitor-count.ts",
          "target": "@hooks/use-visitor-count.ts",
          "type": "registry:hook",
          "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { getOrCreateVisitorId } from \"@/lib/visitor-identity\";\n\nexport function useVisitorCount() {\n  const [state, setState] = useState({ count: 0, loading: true, error: null as string | null });\n\n  useEffect(() => {\n    const controller = new AbortController();\n    async function trackVisit() {\n      try {\n        const response = await fetch(\"/api/visitors\", {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify({ fingerprint: getOrCreateVisitorId() }),\n          cache: \"no-store\",\n          signal: controller.signal,\n        });\n        const data = await response.json();\n        if (!response.ok || !data.success || !Number.isSafeInteger(data.uniqueVisitors) || data.uniqueVisitors < 0) {\n          throw new Error(\"Visitor count is temporarily unavailable\");\n        }\n        if (!controller.signal.aborted) {\n          setState({ count: data.uniqueVisitors, loading: false, error: null });\n        }\n      } catch {\n        if (!controller.signal.aborted) {\n          setState({ count: 0, loading: false, error: \"Visitor count is temporarily unavailable\" });\n        }\n      }\n    }\n    void trackVisit();\n    return () => controller.abort();\n  }, []);\n\n  return state;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "target": "@lib/utils.ts",
          "type": "registry:lib",
          "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
        },
        {
          "path": "lib/visitor-identity.ts",
          "target": "@lib/visitor-identity.ts",
          "type": "registry:lib",
          "content": "const STORAGE_KEY = \"obsidianui_visitor_id\";\nlet sessionVisitorId: string | undefined;\n\nexport function getOrCreateVisitorId(): string {\n  if (typeof window === \"undefined\") return \"\";\n  if (sessionVisitorId) return sessionVisitorId;\n  try {\n    const stored = window.localStorage.getItem(STORAGE_KEY);\n    if (stored && /^[a-zA-Z0-9_-]{1,128}$/.test(stored)) {\n      sessionVisitorId = stored;\n      return stored;\n    }\n  } catch {\n    // Storage may be unavailable in private or embedded browsing contexts.\n  }\n  sessionVisitorId = window.crypto.randomUUID();\n  try {\n    window.localStorage.setItem(STORAGE_KEY, sessionVisitorId);\n  } catch {\n    // The memory ID remains stable for this page session.\n  }\n  return sessionVisitorId;\n}\n"
        }
      ],
      "docs": "Provide these application API endpoints before using this component: /api/visitors.",
      "meta": {
        "requiredEndpoints": [
          "/api/visitors"
        ]
      }
    }
  ]
}
