{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "open-folder",
  "title": "Open Folder",
  "description": "Lid opens with 3D perspective and floating cards emerge on hover.",
  "files": [
    {
      "path": "components/library/open-folder.tsx",
      "content": "\"use client\"\n\nimport { motion } from \"motion/react\"\nimport { useId, useState } from \"react\"\n\nconst spring = { type: \"spring\" as const, stiffness: 500, damping: 45, mass: 1 }\n\nconst floatingCardVariants = {\n    hidden: {\n        y: 0,\n        x: \"-50%\",\n        rotate: 0,\n        opacity: 0,\n        pointerEvents: \"none\" as const,\n    },\n    left: {\n        y: -40,\n        x: \"-85%\",\n        rotate: -18,\n        opacity: 1,\n        pointerEvents: \"auto\" as const,\n    },\n    center: {\n        y: -48,\n        x: \"-50%\",\n        rotate: 5,\n        opacity: 1,\n        pointerEvents: \"auto\" as const,\n    },\n    right: {\n        y: -35,\n        x: \"-15%\",\n        rotate: 22,\n        opacity: 1,\n        pointerEvents: \"auto\" as const,\n    },\n}\n\nconst DEFAULT_SIZE = 80\n\n// ─── Color Themes ────────────────────────────────────────────────────────────\n\ntype ColorTheme = {\n    front: [string, string, string, string, string]\n    back: [string, string, string, string, string]\n}\n\nconst colorThemes = {\n    black: {\n        front: [\"#4A4A4A\", \"#2C2C2C\", \"#1A1A1A\", \"#0D0D0D\", \"#1F1F1F\"],\n        back: [\"#525252\", \"#3D3D3D\", \"#252525\", \"#181818\", \"#0E0E0E\"],\n    },\n    green: {\n        front: [\"#7A8B6F\", \"#657A58\", \"#566B4A\", \"#465A3C\", \"#526645\"],\n        back: [\"#5C6B50\", \"#4F5E44\", \"#435139\", \"#38452F\", \"#2E3926\"],\n    },\n    blue: {\n        front: [\"#6B8DB5\", \"#4A7099\", \"#3A5F85\", \"#2B4D6E\", \"#35587A\"],\n        back: [\"#4A6A8A\", \"#3D5A78\", \"#314B65\", \"#263D53\", \"#1E3244\"],\n    },\n    purple: {\n        front: [\"#8B7AAF\", \"#7460A0\", \"#604D8D\", \"#4D3B78\", \"#5A4585\"],\n        back: [\"#6B5A8A\", \"#5C4D78\", \"#4D4065\", \"#3E3353\", \"#322944\"],\n    },\n} satisfies Record<string, ColorTheme>\n\nexport type OpenFolderColor = keyof typeof colorThemes\n\n// ─── SVG Paths ───────────────────────────────────────────────────────────────\n\nconst BACK_PATH = \"M4.95238 36H39.6834H40.5288C42.7478 36 44.5467 34.2011 44.5467 31.9821V31.8154L44.5714 15.399V10.2488L44.5659 9.09706C44.5553 6.88562 42.7595 5.09852 40.5481 5.09852H39.619H23.2092C21.8597 5.09852 20.6004 4.42108 19.8567 3.29507L18.8714 1.80344C18.1277 0.677438 16.8683 0 15.5189 0H4.01786C1.79886 0 0 1.79886 0 4.01786V30.8498V31.9821C0 34.2011 1.79886 36 4.01786 36H4.95238Z\"\n\nconst FRONT_PATH = \"M4.95238 35.9983H39.6834H40.6003C42.7798 35.9983 44.5467 34.2315 44.5467 32.0519L44.5714 16.5699V11.7128L44.567 10.853C44.5558 8.64207 42.7602 6.85567 40.5492 6.85567H39.619H21.0479L4.0179 6.85551C1.79888 6.85549 0 8.65435 0 10.8734V31.1412V31.9805C0 34.1995 1.79886 35.9983 4.01786 35.9983H4.95238Z\"\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nexport const OpenFolder = ({\n    size = DEFAULT_SIZE,\n    color = \"black\",\n    content = [],\n}: {\n    size?: number\n    color?: OpenFolderColor\n    content?: string[]\n}) => {\n    return (\n        <div>\n            <FolderPreview size={size} color={color} content={content} />\n        </div>\n    )\n}\n\nconst Folder2Back = ({ size, color = \"black\" }: { size: number; color?: OpenFolderColor }) => {\n    const svgWidth = size\n    const svgHeight = size * (36 / 45)\n    const uid = useId()\n    const theme = colorThemes[color] ?? colorThemes.black\n\n    const backId = `f2Back-${uid}`\n    const shineId = `f2BackShine-${uid}`\n    const edgeId = `f2BackEdge-${uid}`\n    const vignetteId = `f2BackVignette-${uid}`\n\n    return (\n        <div className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\">\n            <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={svgWidth}\n                height={svgHeight}\n                viewBox=\"0 0 45 36\"\n                fill=\"none\"\n            >\n                <defs>\n                    <linearGradient id={backId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                        <stop offset=\"0%\" stopColor={theme.back[0]} />\n                        <stop offset=\"15%\" stopColor={theme.back[1]} />\n                        <stop offset=\"45%\" stopColor={theme.back[2]} />\n                        <stop offset=\"75%\" stopColor={theme.back[3]} />\n                        <stop offset=\"100%\" stopColor={theme.back[4]} />\n                    </linearGradient>\n                    <linearGradient id={shineId} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0.6\">\n                        <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.14)\" />\n                        <stop offset=\"30%\" stopColor=\"rgba(255,255,255,0.04)\" />\n                        <stop offset=\"60%\" stopColor=\"rgba(255,255,255,0)\" />\n                        <stop offset=\"85%\" stopColor=\"rgba(255,255,255,0.06)\" />\n                        <stop offset=\"100%\" stopColor=\"rgba(0,0,0,0.2)\" />\n                    </linearGradient>\n                    <linearGradient id={edgeId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                        <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.22)\" />\n                        <stop offset=\"8%\" stopColor=\"rgba(255,255,255,0.05)\" />\n                        <stop offset=\"100%\" stopColor=\"rgba(0,0,0,0)\" />\n                    </linearGradient>\n                    <radialGradient id={vignetteId} cx=\"0.5\" cy=\"0.3\" r=\"0.7\">\n                        <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.05)\" />\n                        <stop offset=\"100%\" stopColor=\"rgba(0,0,0,0.18)\" />\n                    </radialGradient>\n                </defs>\n                <path d={BACK_PATH} fill={`url(#${backId})`} />\n                <path d={BACK_PATH} fill={`url(#${shineId})`} />\n                <path d={BACK_PATH} fill={`url(#${edgeId})`} />\n                <path d={BACK_PATH} fill={`url(#${vignetteId})`} />\n                <path d={BACK_PATH} fill=\"none\" stroke=\"rgba(255,255,255,0.08)\" strokeWidth=\"0.3\" />\n            </svg>\n        </div>\n    )\n}\n\nconst Folder2Front = ({ hovered, size, color = \"black\" }: { hovered: boolean; size: number; color?: OpenFolderColor }) => {\n    const svgWidth = size\n    const svgHeight = size * (36 / 45)\n    const uid = useId()\n    const theme = colorThemes[color] ?? colorThemes.black\n\n    const frontId = `f2Front-${uid}`\n    const frontShineId = `f2FrontShine-${uid}`\n    const frontEdgeId = `f2FrontEdge-${uid}`\n    const frontVignetteId = `f2FrontVignette-${uid}`\n\n    return (\n        <motion.div\n            className=\"absolute z-20 top-1/2 left-1/2\"\n            style={{\n                transformOrigin: \"50% 100%\",\n                x: \"-50%\",\n                y: \"-50%\",\n            }}\n            animate={{\n                rotateX: hovered ? -35 : 0,\n            }}\n            transition={spring}\n        >\n            <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={svgWidth}\n                height={svgHeight}\n                viewBox=\"0 0 45 36\"\n                fill=\"none\"\n            >\n                <defs>\n                    <linearGradient id={frontId} x1=\"0\" y1=\"0\" x2=\"0.3\" y2=\"1\">\n                        <stop offset=\"0%\" stopColor={theme.front[0]} />\n                        <stop offset=\"20%\" stopColor={theme.front[1]} />\n                        <stop offset=\"50%\" stopColor={theme.front[2]} />\n                        <stop offset=\"80%\" stopColor={theme.front[3]} />\n                        <stop offset=\"100%\" stopColor={theme.front[4]} />\n                    </linearGradient>\n                    <linearGradient id={frontShineId} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0.5\">\n                        <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.18)\" />\n                        <stop offset=\"25%\" stopColor=\"rgba(255,255,255,0.06)\" />\n                        <stop offset=\"55%\" stopColor=\"rgba(255,255,255,0)\" />\n                        <stop offset=\"80%\" stopColor=\"rgba(255,255,255,0.04)\" />\n                        <stop offset=\"100%\" stopColor=\"rgba(0,0,0,0.15)\" />\n                    </linearGradient>\n                    <linearGradient id={frontEdgeId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                        <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.25)\" />\n                        <stop offset=\"6%\" stopColor=\"rgba(255,255,255,0.08)\" />\n                        <stop offset=\"100%\" stopColor=\"rgba(0,0,0,0)\" />\n                    </linearGradient>\n                    <radialGradient id={frontVignetteId} cx=\"0.4\" cy=\"0.35\" r=\"0.65\">\n                        <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.06)\" />\n                        <stop offset=\"100%\" stopColor=\"rgba(0,0,0,0.14)\" />\n                    </radialGradient>\n                </defs>\n                <path d={FRONT_PATH} fill={`url(#${frontId})`} />\n                <path d={FRONT_PATH} fill={`url(#${frontShineId})`} />\n                <path d={FRONT_PATH} fill={`url(#${frontEdgeId})`} />\n                <path d={FRONT_PATH} fill={`url(#${frontVignetteId})`} />\n                <path d={FRONT_PATH} fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" strokeWidth=\"0.3\" />\n            </svg>\n        </motion.div>\n    )\n}\n\nconst Folder2Floating = ({ hovered, variant, delay = 0, size, content = \"\" }: { hovered: boolean; variant: \"left\" | \"center\" | \"right\"; delay?: number; size: number; content?: string }) => {\n    const scale = size / DEFAULT_SIZE\n    const cardWidth = 40 * scale\n    const cardHeight = 50 * scale\n\n    return (\n        <motion.div\n            className=\"absolute overflow-hidden rounded-lg bg-white z-10 origin-bottom shadow-[0_2px_10px_rgba(0,0,0,0.15)]\"\n            style={{\n                width: cardWidth,\n                height: cardHeight,\n                left: \"50%\",\n                bottom: \"15%\",\n            }}\n            initial=\"hidden\"\n            animate={hovered ? variant : \"hidden\"}\n            variants={floatingCardVariants}\n            transition={{ ...spring, delay: hovered ? delay : 0 }}\n        >\n            <div\n                className=\"relative h-full w-full overflow-hidden whitespace-pre-wrap\n                    [-webkit-mask-image:linear-gradient(to_bottom,black_60%,transparent_100%)]\"\n                style={{\n                    fontSize: 7 * scale,\n                    fontWeight: 400,\n                    lineHeight: 1.4,\n                }}\n            >\n                <p className=\"text-black p-1.5 leading-tight\">{content}</p>\n            </div>\n        </motion.div>\n    )\n}\n\nconst FolderPreview = ({ size = DEFAULT_SIZE, color = \"black\", content = [] }: { size?: number; color?: OpenFolderColor; content?: string[] }) => {\n    const [hovered, setHovered] = useState(false)\n\n    return (\n        <motion.div\n            className=\"relative cursor-pointer\"\n            style={{\n                width: size,\n                height: size,\n                perspective: size * 3,\n            }}\n            onHoverStart={() => setHovered(true)}\n            onHoverEnd={() => setHovered(false)}\n        >\n            {/* Back Folder */}\n            <Folder2Back size={size} color={color} />\n\n            {/* Floating Cards - fan out on hover */}\n            <Folder2Floating hovered={hovered} variant=\"left\" delay={0} size={size} content={content[0]} />\n            <Folder2Floating hovered={hovered} variant=\"right\" delay={0.1} size={size} content={content[2]} />\n            <Folder2Floating hovered={hovered} variant=\"center\" delay={0.05} size={size} content={content[1]} />\n\n            {/* Front Folder - opens on hover */}\n            <Folder2Front hovered={hovered} size={size} color={color} />\n        </motion.div>\n    )\n}",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}