{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "deck",
  "title": "Deck",
  "description": "A Deck component that is the core of the presentation",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/slide-cn/deck.tsx",
      "content": "\"use client\";\nimport { useDeckController } from \"@/components/ui/slide-cn/use-deck-controller\";\nimport { useKeyboardNavigation } from \"@/components/ui/slide-cn/use-keyboard-navigation\";\nimport { NavigationToast } from \"@/components/ui/slide-cn/navigation-toast\";\nimport React from \"react\";\nimport { AnimatePresence } from \"motion/react\";\nimport { SlideNav } from \"@/components/ui/slide-cn/slide-nav\";\nimport { cn } from \"@/lib/utils\";\n\n\n\n/**\n * Deck\n *\n * Root container for a slide presentation.\n *\n * Responsibilities:\n * - Owns viewport height (`h-screen`)\n * - Manages active slide index\n * - Handles global navigation (keyboard, nav UI)\n * - Provides deck state via context\n *\n * Non-responsibilities:\n * - Does NOT manage slide layout or content\n * - Does NOT manage per-slide scroll behavior\n * - Does NOT impose styling on slides\n *\n * Usage:\n * ```tsx\n * <Deck>\n *   <Slide>...</Slide>\n *   <Slide>...</Slide>\n * </Deck>\n * ```\n *\n * Notes:\n * - Deck must be mounted in a client component\n * - Slides are rendered one at a time via AnimatePresence\n * - Height is fixed to the viewport; do not nest Deck inside another Deck\n */\n\n\n\nconst DeckContext = React.createContext<ReturnType<\n\ttypeof useDeckController\n> | null>(null);\n\ntype NavigationToastProps = {\n\tduration?: number;\n\tdesktopMessage?: string;\n\tmobileMessage?: string;\n\tclassName?: string;\n};\n\ntype DeckProps = {\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tshowNavigationToast?: boolean;\n\tnavigationToastProps?: NavigationToastProps;\n};\n\nexport function Deck({ children, className, showNavigationToast = true, navigationToastProps }: DeckProps) {\n\tconst slides = React.Children.toArray(children);\n\tconst deck = useDeckController(slides.length);\n\tuseKeyboardNavigation({\n\t\tonNext: deck.next,\n\t\tonPrev: deck.prev\n\t});\n\n\n\treturn (\n\t\t<DeckContext.Provider\n\t\t\tvalue={deck}\n\t\t>\n\t\t\t<div className={cn(\"absolute inset-0 overflow-hidden touch-pan-y md:touch-none h-screen\", className)}>\n\n\t\t\t\t<SlideNav />\n\t\t\t\t<div className=\"absolute inset-0 overflow-y-auto md:overflow-hidden h-full\">\n\t\t\t\t\t<AnimatePresence mode=\"wait\">\n\t\t\t\t\t\t{slides[deck.index]}\n\t\t\t\t\t</AnimatePresence>\n\t\t\t\t</div>\n\t\t\t\t{showNavigationToast && <NavigationToast {...navigationToastProps} />}\n\n\t\t\t</div>\n\n\t\t</DeckContext.Provider>\n\t);\n}\n\nexport function useDeck() {\n\tconst ctx = React.useContext(DeckContext);\n\tif (!ctx) throw new Error(\"useDeck must be used inside Deck\");\n\treturn ctx;\n}\n\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/deck.tsx"
    },
    {
      "path": "components/ui/slide-cn/slide.tsx",
      "content": "\"use client\";\nimport React from \"react\";\nimport { motion } from \"motion/react\";\nimport { useDeck } from \"@/components/ui/slide-cn/deck\";\nimport { SlideFooter } from \"@/components/ui/slide-cn/slide-footer\";\n\n\n/**\n * Slide\n *\n * Main container for each slide.\n *\n * Responsibilities:\n * - Handles the transition animation between slides\n * - Handles swipe navigation on mobile\n * - Renders a background for the slide if provided, and a footer by default\n *\n * Non Responsibilities:\n * - Does not handle slide layout\n * \n * Usage:\n * ```tsx\n * <Deck>\n * \t<Slide background={<BackgroundComponent/>}>\n * \t\t{slide content}\n * \t</Slide>\n * </Deck>\n * ```\n */\n\n\ntype SlideProps = {\n\tchildren: React.ReactNode;\n\tbackground?: React.ReactNode;\n\tfooter?: React.ReactNode;\n};\n\nexport function Slide({ children, background, footer = <SlideFooter /> }: SlideProps) {\n\tconst deck = useDeck();\n\n\treturn (\n\t\t<motion.div\n\t\t\tdata-slide\n\t\t\tclassName=\"\n        relative w-full h-full\n        overflow-hidden\n        md:absolute md:inset-0\n      \"\n\t\t\texit={{ opacity: 0 }}\n\n\t\t>\n\t\t\t{/* Background layer (viewport-scoped, never scrolls) */}\n\t\t\t{background && (\n\t\t\t\t<div className=\"absolute inset-0 pointer-events-none\">\n\t\t\t\t\t{background}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Scroll layer (mobile scroll lives here) */}\n\t\t\t<motion.div className=\"relative z-10 h-full w-full overflow-y-auto md:overflow-hidden\"\n\t\t\t\tdrag=\"x\"\n\t\t\t\tdragConstraints={{ left: 0, right: 0 }}\n\t\t\t\tdragElastic={0.25}\n\t\t\t\tdragMomentum={false}\n\t\t\t\twhileDrag={{ scale: 1 }}\n\t\t\t\tonDragEnd={(_, info) => {\n\t\t\t\t\tconst swipeDistance = info.offset.x;\n\t\t\t\t\tconst swipeVelocity = info.velocity.x;\n\n\t\t\t\t\tconst DISTANCE_THRESHOLD = 60;\n\t\t\t\t\tconst VELOCITY_THRESHOLD = 600;\n\n\t\t\t\t\tif (Math.abs(info.offset.y) > Math.abs(info.offset.x)) return;\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tswipeDistance > DISTANCE_THRESHOLD ||\n\t\t\t\t\t\tswipeVelocity > VELOCITY_THRESHOLD\n\t\t\t\t\t) {\n\t\t\t\t\t\tdeck.prev();\n\t\t\t\t\t} else if (\n\t\t\t\t\t\tswipeDistance < -DISTANCE_THRESHOLD ||\n\t\t\t\t\t\tswipeVelocity < -DISTANCE_THRESHOLD\n\t\t\t\t\t) {\n\t\t\t\t\t\tdeck.next();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div className=\"h-full w-full flex flex-col\">\n\t\t\t\t\t<div className=\"flex-1\">\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{footer && (\n\t\t\t\t\t\t<div className=\"mt-4\">\n\t\t\t\t\t\t\t{footer}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</motion.div>\n\t\t</motion.div>\n\t);\n}\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/slide.tsx"
    },
    {
      "path": "components/ui/slide-cn/slide-footer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { useDeck } from \"@/components/ui/slide-cn/deck\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\nfunction FooterKeyCap({ children }: { children: React.ReactNode }) {\n\treturn (\n\t\t<span className=\"inline-flex items-center justify-center w-5 h-5 rounded bg-foreground/10 border border-foreground/15 text-[10px] font-mono leading-none\">\n\t\t\t{children}\n\t\t</span>\n\t);\n}\n\n/**\n * SlideFooter\n *\n * Optional footer component for slides that provides contextual metadata\n * and navigation affordances without affecting slide layout.\n *\n * Responsibilities:\n * - Display slide progress (current / total)\n * - Show navigation hints appropriate to device (keyboard vs swipe)\n * - Optionally render lightweight branding attribution\n *\n * Interaction model:\n * - Purely informational (no navigation control)\n * - Adapts copy and hints for mobile vs desktop\n *\n * Usage:\n * ```tsx\n * <Slide footer={<SlideFooter />} />\n *\n * <Slide\n *   footer={<SlideFooter showAdd showHint={false} />}\n * />\n * ```\n *\n * Notes:\n * - Footer is part of content flow (non-sticky)\n * - Does not control slide navigation or state\n * - Intended to remain visually subtle and non-distracting\n */\n\ntype SlideFooterProps = {\n\tclassName?: string;\n\tshowProgress?: boolean;\n\tshowHint?: boolean;\n\tshowAdd?: boolean;\n};\n\nexport function SlideFooter({\n\tclassName,\n\tshowProgress = true,\n\tshowHint = true,\n\tshowAdd = false,\n}: SlideFooterProps) {\n\tconst deck = useDeck();\n\n\t// Using grid layout for precise alignment: Left (Progress), Center (Branding), Right (Hint)\n\treturn (\n\t\t<footer\n\t\t\tclassName={cn(\n\t\t\t\t\"grid grid-cols-3 items-center gap-4 px-6 py-4 w-full\",\n\t\t\t\t\"bg-background/20 backdrop-blur-lg\",\n\t\t\t\t\"text-sm font-medium text-muted-foreground transition-colors\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t>\n\t\t\t{/* Left: Progress */}\n\t\t\t<div className=\"flex justify-start\">\n\t\t\t\t{showProgress && (\n\t\t\t\t\t<span className=\"tabular-nums opacity-75 hover:opacity-100 transition-opacity\">\n\t\t\t\t\t\tSlide {deck.index + 1} <span className=\"text-muted-foreground/40 mx-1\">/</span> {deck.total}\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Center: Branding (Optional) */}\n\t\t\t<div className=\"flex justify-center\">\n\t\t\t\t{showAdd && (\n\t\t\t\t\t<span className=\"opacity-50 hover:opacity-100 transition-opacity flex items-center gap-1.5 text-xs\">\n\t\t\t\t\t\tBuilt with <a href={\"https://slide-cn.com\"} target=\"_blank\" className=\"font-semibold hover:underline\">Slide-CN</a>\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Right: Hint */}\n\t\t\t<div className=\"flex justify-end\">\n\t\t\t\t{showHint && (\n\t\t\t\t\t<span className=\"hidden sm:flex items-center gap-2 opacity-70 hover:opacity-100 transition-opacity\">\n\t\t\t\t\t\t<FooterKeyCap>&#8592;</FooterKeyCap>\n\t\t\t\t\t\t<FooterKeyCap>&#8594;</FooterKeyCap>\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t\t{/* Mobile simplified hint */}\n\t\t\t\t{showHint && (\n\t\t\t\t\t<span className=\"sm:hidden flex items-center gap-1.5 opacity-70 hover:opacity-100 transition-opacity text-xs\">\n\t\t\t\t\t\t<span>Swipe</span>\n\t\t\t\t\t\t<ChevronLeft className=\"w-3 h-3\" />\n\t\t\t\t\t\t<ChevronRight className=\"w-3 h-3\" />\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</footer>\n\t);\n}\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/slide-footer.tsx"
    },
    {
      "path": "components/ui/slide-cn/slide-nav.tsx",
      "content": "\"use client\";\nimport { Button } from \"@/components/ui/button\";\nimport { useDeck } from \"@/components/ui/slide-cn/deck\";\n\n/**\n * SlideNav\n *\n * SlideNav is already included in the deck component by default. You dont need to touch this component unless you want to modify how changing slides works\n */\n\nexport function SlideNav() {\n\tconst deck = useDeck();\n\n\treturn (\n\t\t<>\n\t\t\t{/* LEFT ZONE */}\n\t\t\t<div className=\"hidden md:block pointer-events-none absolute inset-y-0 left-0 w-24\">\n\t\t\t\t<div className=\"group pointer-events-auto h-full w-full flex items-center\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={deck.prev}\n\t\t\t\t\t\tclassName=\"\n\t\t\t\t\t\t\tml-4\n\t\t\t\t\t\t\topacity-0\n\t\t\t\t\t\t\ttransition-opacity\n\t\t\t\t\t\t\tgroup-hover:opacity-100\"\n\t\t\t\t\t>\n\t\t\t\t\t\tPrev\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* RIGHT ZONE */}\n\t\t\t<div className=\"hidden md:block pointer-events-none absolute inset-y-0 right-0 w-24\">\n\t\t\t\t<div className=\"group pointer-events-auto h-full w-full flex items-center justify-end\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={deck.next}\n\t\t\t\t\t\tclassName=\"\n              mr-4\n              opacity-0\n              transition-opacity\n              group-hover:opacity-100\n            \"\n\t\t\t\t\t>\n\t\t\t\t\t\tNext\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/slide-nav.tsx"
    },
    {
      "path": "components/ui/slide-cn/navigation-toast.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\nimport { useDeck } from \"@/components/ui/slide-cn/deck\";\nimport { useIsMobile } from \"@/hooks/use-mobile\";\n\ntype NavigationToastProps = {\n\tduration?: number;\n\tdesktopMessage?: string;\n\tmobileMessage?: string;\n\tclassName?: string;\n};\n\nexport function NavigationToast({\n\tduration = 3000,\n\tdesktopMessage = \"Use arrow keys to navigate\",\n\tmobileMessage = \"Swipe left or right to navigate\",\n\tclassName,\n}: NavigationToastProps) {\n\tconst [visible, setVisible] = React.useState(true);\n\tconst isMobile = useIsMobile();\n\tconst deck = useDeck();\n\tconst initialIndex = React.useRef(deck.index);\n\n\t// Dismiss on navigation\n\tReact.useEffect(() => {\n\t\tif (deck.index !== initialIndex.current) {\n\t\t\tsetVisible(false);\n\t\t}\n\t}, [deck.index]);\n\n\t// Auto-dismiss after duration\n\tReact.useEffect(() => {\n\t\tconst timer = setTimeout(() => setVisible(false), duration);\n\t\treturn () => clearTimeout(timer);\n\t}, [duration]);\n\n\treturn (\n\t\t<AnimatePresence>\n\t\t\t{visible && (\n\t\t\t\t<motion.div\n\t\t\t\t\tinitial={{ opacity: 0, y: 20 }}\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\n\t\t\t\t\texit={{ opacity: 0, y: 10 }}\n\t\t\t\t\ttransition={{ duration: 0.3, ease: \"easeOut\" }}\n\t\t\t\t\tonClick={() => setVisible(false)}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"cursor-pointer\",\n\t\t\t\t\t\t\"absolute bottom-20 left-1/2 -translate-x-1/2 z-50\",\n\t\t\t\t\t\t\"flex items-center gap-3 px-5 py-3 rounded-full\",\n\t\t\t\t\t\t\"bg-foreground/10 backdrop-blur-xl border border-foreground/10\",\n\t\t\t\t\t\t\"text-sm font-medium text-foreground/80\",\n\t\t\t\t\t\t\"shadow-lg\",\n\t\t\t\t\t\tclassName\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{isMobile ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<SwipeIcon />\n\t\t\t\t\t\t\t<span>{mobileMessage}</span>\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<span>{desktopMessage}</span>\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-1.5\">\n\t\t\t\t\t\t\t\t<KeyCap>&#8592;</KeyCap>\n\t\t\t\t\t\t\t\t<KeyCap>&#8594;</KeyCap>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</>\n\t\t\t\t\t)}\n\t\t\t\t</motion.div>\n\t\t\t)}\n\t\t</AnimatePresence>\n\t);\n}\n\nfunction KeyCap({ children }: { children: React.ReactNode }) {\n\treturn (\n\t\t<span className=\"inline-flex items-center justify-center w-7 h-7 rounded-md bg-foreground/10 border border-foreground/15 text-xs font-mono shadow-sm\">\n\t\t\t{children}\n\t\t</span>\n\t);\n}\n\nfunction SwipeIcon() {\n\treturn (\n\t\t<svg\n\t\t\twidth=\"24\"\n\t\t\theight=\"24\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"1.5\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName=\"opacity-70\"\n\t\t>\n\t\t\t<path d=\"M5 12h14\" />\n\t\t\t<path d=\"M2 12l3-3M2 12l3 3\" />\n\t\t\t<path d=\"M22 12l-3-3M22 12l-3 3\" />\n\t\t</svg>\n\t);\n}\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/navigation-toast.tsx"
    },
    {
      "path": "components/ui/slide-cn/use-deck-controller.ts",
      "content": "\"use client\";\nimport { useCallback, useState } from 'react';\n\nexport function useDeckController(slideCount: number) {\n\tconst [index, setIndex] = useState(0);\n\n\tconst clamp = useCallback(\n\t\t(slideIndex: number) => {\n\t\t\tif (slideIndex < 0) return 0;\n\t\t\tif (slideIndex >= slideCount) return slideCount - 1;\n\t\t\treturn slideIndex;\n\t\t},\n\t\t[slideCount]\n\t);\n\n\tconst next = useCallback(() => {\n\t\tsetIndex((i) => clamp(i + 1));\n\t}, [clamp]);\n\n\tconst prev = useCallback(() => {\n\t\tsetIndex((i) => clamp(i - 1));\n\t}, [clamp]);\n\n\tconst goTo = useCallback(\n\t\t(target: number) => {\n\t\t\tsetIndex(clamp(target));\n\t\t},\n\t\t[clamp]\n\t);\n\n\treturn {\n\t\tindex,\n\t\tnext,\n\t\tprev,\n\t\tgoTo,\n\t\ttotal: slideCount,\n\t};\n}\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/use-deck-controller.ts"
    },
    {
      "path": "components/ui/slide-cn/use-keyboard-navigation.ts",
      "content": "import { useEffect } from \"react\";\n\ntype KeyboardNavigationOptions = {\n\tonNext: () => void;\n\tonPrev: () => void;\n\tdisabled?: boolean;\n};\n\nexport function useKeyboardNavigation({\n\tonNext,\n\tonPrev,\n\tdisabled = false,\n}: KeyboardNavigationOptions) {\n\tuseEffect(() => {\n\t\tif (disabled) return;\n\n\t\tconst handler = (e: KeyboardEvent) => {\n\t\t\t// Ignore key repeat (holding down key)\n\t\t\tif (e.repeat) return;\n\n\t\t\tif (e.key === \"ArrowRight\" || e.key === \" \") {\n\t\t\t\te.preventDefault();\n\t\t\t\tonNext();\n\t\t\t}\n\n\t\t\tif (e.key === \"ArrowLeft\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tonPrev();\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"keydown\", handler);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", handler);\n\t\t};\n\t}, [onNext, onPrev, disabled]);\n}\n",
      "type": "registry:component",
      "target": "components/ui/slide-cn/use-keyboard-navigation.ts"
    }
  ],
  "type": "registry:component"
}