reborn-ui 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +47 -0
  2. package/dist/index.js +871 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +40 -0
  5. package/registry/.gitkeep +2 -0
  6. package/registry/components/animate-grid.json +18 -0
  7. package/registry/components/animated-beam.json +16 -0
  8. package/registry/components/animated-circular-progressbar.json +16 -0
  9. package/registry/components/animated-list.json +22 -0
  10. package/registry/components/animated-testimonials.json +18 -0
  11. package/registry/components/animated-tooltip.json +18 -0
  12. package/registry/components/apple-card-carousel.json +35 -0
  13. package/registry/components/aurora-background.json +16 -0
  14. package/registry/components/balance-slider.json +16 -0
  15. package/registry/components/bending-gallery.json +18 -0
  16. package/registry/components/bento-grid.json +24 -0
  17. package/registry/components/bg-black-hole.json +14 -0
  18. package/registry/components/bg-bubbles.json +18 -0
  19. package/registry/components/bg-falling-stars.json +16 -0
  20. package/registry/components/bg-neural.json +14 -0
  21. package/registry/components/bg-particle-whirlpool.json +18 -0
  22. package/registry/components/bg-silk.json +16 -0
  23. package/registry/components/bg-stars.json +18 -0
  24. package/registry/components/bg-stractium.json +16 -0
  25. package/registry/components/blur-reveal.json +18 -0
  26. package/registry/components/book.json +28 -0
  27. package/registry/components/border-beam.json +16 -0
  28. package/registry/components/box-reveal.json +18 -0
  29. package/registry/components/card-3d.json +24 -0
  30. package/registry/components/card-spotlight.json +16 -0
  31. package/registry/components/carousel-3d.json +15 -0
  32. package/registry/components/color-picker.json +26 -0
  33. package/registry/components/colourful-text.json +18 -0
  34. package/registry/components/compare.json +22 -0
  35. package/registry/components/confetti.json +22 -0
  36. package/registry/components/container-scroll.json +26 -0
  37. package/registry/components/container-text-flip.json +19 -0
  38. package/registry/components/cosmic-portal.json +18 -0
  39. package/registry/components/direction-aware-hover.json +16 -0
  40. package/registry/components/dock.json +32 -0
  41. package/registry/components/expandable-gallery.json +16 -0
  42. package/registry/components/file-tree.json +28 -0
  43. package/registry/components/file-upload.json +22 -0
  44. package/registry/components/flickering-grid.json +16 -0
  45. package/registry/components/flip-card.json +16 -0
  46. package/registry/components/flip-words.json +16 -0
  47. package/registry/components/fluid-cursor.json +16 -0
  48. package/registry/components/focus.json +16 -0
  49. package/registry/components/github-globe.json +23 -0
  50. package/registry/components/glare-card.json +18 -0
  51. package/registry/components/globe.json +19 -0
  52. package/registry/components/glow-border.json +16 -0
  53. package/registry/components/glowing-effect.json +18 -0
  54. package/registry/components/gradient-button.json +16 -0
  55. package/registry/components/halo-search.json +16 -0
  56. package/registry/components/hyper-text.json +19 -0
  57. package/registry/components/icon-cloud.json +16 -0
  58. package/registry/components/image-trail-cursor.json +22 -0
  59. package/registry/components/images-slider.json +18 -0
  60. package/registry/components/infinite-grid.json +47 -0
  61. package/registry/components/input.json +18 -0
  62. package/registry/components/interactive-grid-pattern.json +16 -0
  63. package/registry/components/interactive-hover-button.json +16 -0
  64. package/registry/components/iphone-mockup.json +16 -0
  65. package/registry/components/lamp-effect.json +16 -0
  66. package/registry/components/lens.json +18 -0
  67. package/registry/components/letter-pullup.json +18 -0
  68. package/registry/components/light-speed.json +31 -0
  69. package/registry/components/line-shadow-text.json +16 -0
  70. package/registry/components/link-preview.json +16 -0
  71. package/registry/components/liquid-background.json +18 -0
  72. package/registry/components/liquid-glass.json +16 -0
  73. package/registry/components/liquid-logo.json +24 -0
  74. package/registry/components/logo-cloud.json +24 -0
  75. package/registry/components/logo-origami.json +22 -0
  76. package/registry/components/marquee.json +20 -0
  77. package/registry/components/meteors.json +16 -0
  78. package/registry/components/morphing-tabs.json +16 -0
  79. package/registry/components/morphing-text.json +16 -0
  80. package/registry/components/multi-step-loader.json +16 -0
  81. package/registry/components/neon-border.json +16 -0
  82. package/registry/components/number-ticker.json +18 -0
  83. package/registry/components/orbit.json +16 -0
  84. package/registry/components/particle-image.json +24 -0
  85. package/registry/components/particles-bg.json +18 -0
  86. package/registry/components/pattern-background.json +18 -0
  87. package/registry/components/photo-gallery.json +16 -0
  88. package/registry/components/radiant-text.json +16 -0
  89. package/registry/components/rainbow-button.json +16 -0
  90. package/registry/components/ripple-button.json +16 -0
  91. package/registry/components/ripple.json +24 -0
  92. package/registry/components/safari-mockup.json +16 -0
  93. package/registry/components/scratch-to-reveal.json +18 -0
  94. package/registry/components/scroll-island.json +20 -0
  95. package/registry/components/shader-toy.json +22 -0
  96. package/registry/components/shimmer-button.json +16 -0
  97. package/registry/components/sleek-line-cursor.json +12 -0
  98. package/registry/components/smooth-cursor.json +23 -0
  99. package/registry/components/snowfall-bg.json +18 -0
  100. package/registry/components/sparkles-text.json +18 -0
  101. package/registry/components/sparkles.json +18 -0
  102. package/registry/components/spinning-text.json +18 -0
  103. package/registry/components/spline.json +23 -0
  104. package/registry/components/spring-calendar.json +22 -0
  105. package/registry/components/svg-mask.json +16 -0
  106. package/registry/components/tailed-cursor.json +14 -0
  107. package/registry/components/testimonial-slider.json +16 -0
  108. package/registry/components/tetris.json +19 -0
  109. package/registry/components/text-3d.json +16 -0
  110. package/registry/components/text-generate-effect.json +16 -0
  111. package/registry/components/text-glitch.json +12 -0
  112. package/registry/components/text-highlight.json +16 -0
  113. package/registry/components/text-hover-effect.json +16 -0
  114. package/registry/components/text-reveal-card.json +20 -0
  115. package/registry/components/text-reveal.json +18 -0
  116. package/registry/components/text-scroll-reveal.json +20 -0
  117. package/registry/components/timeline.json +18 -0
  118. package/registry/components/tracing-beam.json +19 -0
  119. package/registry/components/vanishing-input.json +18 -0
  120. package/registry/components/video-text.json +16 -0
  121. package/registry/components/vortex.json +19 -0
  122. package/registry/components/warp-background.json +22 -0
  123. package/registry/components/wavy-background.json +19 -0
  124. package/registry/components/world-map.json +19 -0
  125. package/registry/registry.json +2007 -0
  126. package/templates/composables/useMouseState.ts +21 -0
  127. package/templates/lib/utils.ts +13 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/add.ts","../src/utils/fs.ts","../src/utils/pm.ts","../src/utils/registry.ts","../src/commands/build.ts","../src/utils/imports.ts","../src/commands/init.ts","../src/utils/templates.ts"],"sourcesContent":["import { Command } from \"commander\";\r\nimport { addCommand } from \"./commands/add.js\";\r\nimport { buildCommand } from \"./commands/build.js\";\r\nimport { initCommand } from \"./commands/init.js\";\r\n\r\nconst program = new Command()\r\n .name(\"reborn-ui\")\r\n .description(\"Reborn UI - 组件安装与 registry 辅助 CLI\")\r\n .version(\"0.1.0\");\r\n\r\nprogram.addCommand(initCommand());\r\nprogram.addCommand(addCommand());\r\nprogram.addCommand(buildCommand());\r\n\r\nawait program.parseAsync(process.argv);\r\n\r\n\r\n","import path from \"node:path\";\r\nimport { Command } from \"commander\";\r\nimport prompts from \"prompts\";\r\nimport type { PackageManager, RegistryComponent } from \"../types.js\";\r\nimport { ensureDir, pathExists, writeTextFile } from \"../utils/fs.js\";\r\nimport {\r\n detectPackageManager,\r\n getMissingDeps,\r\n installDeps,\r\n readPackageJson,\r\n} from \"../utils/pm.js\";\r\nimport { defaultConfig, loadConfigCompat, loadRegistry } from \"../utils/registry.js\";\r\n\r\nfunction rewriteImports(params: {\r\n content: string;\r\n aliasSymbol: string;\r\n}) {\r\n const { content, aliasSymbol } = params;\r\n if (!aliasSymbol || aliasSymbol === \"@\") return content;\r\n\r\n // 仅替换项目根目录映射符号:把 \"@/xxx\" -> \"<aliasSymbol>/xxx\"\r\n // 注意:不要影响 scoped package(@scope/name),这里只替换 \"@/\" 前缀。\r\n return content.replaceAll(\"@/\", `${aliasSymbol}/`);\r\n}\r\n\r\nasync function writeComponentFiles(params: {\r\n cwd: string;\r\n componentsDir: string;\r\n aliasSymbol: string;\r\n component: RegistryComponent;\r\n overwrite?: boolean;\r\n}) {\r\n const { cwd, componentsDir, aliasSymbol, component, overwrite } = params;\r\n const baseDir = path.join(cwd, componentsDir, component.name);\r\n await ensureDir(baseDir);\r\n\r\n for (const f of component.files) {\r\n const target = path.join(baseDir, ...f.path.split(\"/\"));\r\n if (!overwrite && (await pathExists(target))) continue;\r\n const nextContent = rewriteImports({ content: f.content, aliasSymbol });\r\n await writeTextFile(target, nextContent);\r\n }\r\n\r\n return baseDir;\r\n}\r\n\r\nexport function addCommand() {\r\n const cmd = new Command(\"add\")\r\n .description(\"向项目中添加组件与相关依赖\")\r\n .argument(\"[components...]\", \"组件名(可多个)\")\r\n .option(\"--cwd <path>\", \"目标项目目录\", process.cwd())\r\n .option(\"--pm <pm>\", \"包管理器:pnpm|npm|yarn|bun\")\r\n .option(\"--yes\", \"跳过交互\", false)\r\n .option(\"--overwrite\", \"覆盖已存在文件\", false)\r\n .option(\"--config <path>\", \"配置文件路径(相对 cwd)\", \"components.json\")\r\n .option(\"--registry <pkgOrPath>\", \"覆盖配置里的 registry\")\r\n .option(\"--components-dir <path>\", \"覆盖配置里的 componentsDir\")\r\n .option(\"--lib-dir <path>\", \"覆盖配置里的 libDir\")\r\n .option(\"--alias-symbol <symbol>\", \"覆盖配置里的 aliasSymbol(默认 @)\")\r\n .action(async (components: string[], opts) => {\r\n const cwd = path.resolve(opts.cwd);\r\n const pm: PackageManager =\r\n opts.pm ?? (await detectPackageManager(cwd));\r\n\r\n const cfg = (await loadConfigCompat(cwd, opts.config)) ?? defaultConfig();\r\n if (opts.registry) cfg.registry = opts.registry;\r\n if (opts.componentsDir) cfg.componentsDir = opts.componentsDir;\r\n if (opts.libDir) cfg.libDir = opts.libDir;\r\n if (opts.aliasSymbol) cfg.aliasSymbol = opts.aliasSymbol;\r\n\r\n const registry = await loadRegistry({ cwd, registry: cfg.registry });\r\n\r\n let targets = components ?? [];\r\n if (!targets.length) {\r\n if (opts.yes) {\r\n throw new Error(\"未指定组件名;请传入组件参数或去掉 --yes 以交互选择。\");\r\n }\r\n const choices = registry.components.map((c) => ({\r\n title: c.name,\r\n value: c.name,\r\n }));\r\n const res = await prompts(\r\n [\r\n {\r\n type: \"multiselect\",\r\n name: \"selected\",\r\n message: \"选择要添加的组件\",\r\n choices,\r\n min: 1,\r\n },\r\n ],\r\n {\r\n onCancel: () => {\r\n throw new Error(\"已取消\");\r\n },\r\n },\r\n );\r\n targets = res.selected ?? [];\r\n }\r\n\r\n const pkg = await readPackageJson(cwd);\r\n const allDeps = new Set<string>();\r\n\r\n for (const name of targets) {\r\n const c = registry.components.find((x) => x.name === name);\r\n if (!c) throw new Error(`registry 中不存在该组件:${name}`);\r\n\r\n const outDir = await writeComponentFiles({\r\n cwd,\r\n componentsDir: cfg.componentsDir,\r\n aliasSymbol: cfg.aliasSymbol ?? \"@\",\r\n component: c,\r\n overwrite: opts.overwrite,\r\n });\r\n\r\n // eslint-disable-next-line no-console\r\n console.log(`已写入组件:${name} -> ${path.relative(cwd, outDir)}`);\r\n\r\n for (const dep of c.dependencies) allDeps.add(dep);\r\n }\r\n\r\n const missing = getMissingDeps(pkg, [...allDeps]);\r\n await installDeps({ cwd, pm, deps: missing });\r\n\r\n // eslint-disable-next-line no-console\r\n console.log(\r\n `完成:添加 ${targets.length} 个组件;补装依赖 ${missing.length} 个(pm=${pm})`,\r\n );\r\n });\r\n\r\n return cmd;\r\n}\r\n\r\n\r\n","import { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function pathExists(p: string) {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDir(dirPath: string) {\n await fs.mkdir(dirPath, { recursive: true });\n}\n\nexport async function readJsonFile<T>(filePath: string): Promise<T> {\n const raw = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n}\n\nexport async function writeJsonFile(filePath: string, data: unknown) {\n await ensureDir(path.dirname(filePath));\n await fs.writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport async function writeTextFile(filePath: string, content: string) {\n await ensureDir(path.dirname(filePath));\n await fs.writeFile(filePath, content, \"utf8\");\n}\n\nexport async function listFilesRecursive(\n dirPath: string,\n opts?: { ignoreDirNames?: Set<string> },\n) {\n const ignoreDirNames =\n opts?.ignoreDirNames ??\n new Set([\"node_modules\", \".git\", \".nuxt\", \"dist\", \".output\"]);\n\n const out: string[] = [];\n\n async function walk(current: string) {\n const entries = await fs.readdir(current, { withFileTypes: true });\n for (const entry of entries) {\n const p = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (ignoreDirNames.has(entry.name)) continue;\n await walk(p);\n } else if (entry.isFile()) {\n out.push(p);\n }\n }\n }\n\n await walk(dirPath);\n return out;\n}\n\nexport async function copyDirRecursive(params: {\n fromDir: string;\n toDir: string;\n overwrite?: boolean;\n ignoreFileNames?: Set<string>;\n}) {\n const { fromDir, toDir, overwrite } = params;\n const ignoreFileNames = params.ignoreFileNames ?? new Set([\".DS_Store\"]);\n\n const entries = await fs.readdir(fromDir, { withFileTypes: true });\n await ensureDir(toDir);\n\n for (const entry of entries) {\n if (ignoreFileNames.has(entry.name)) continue;\n const from = path.join(fromDir, entry.name);\n const to = path.join(toDir, entry.name);\n\n if (entry.isDirectory()) {\n await copyDirRecursive({ fromDir: from, toDir: to, overwrite, ignoreFileNames });\n continue;\n }\n\n if (!entry.isFile()) continue;\n\n if (!overwrite) {\n try {\n await fs.access(to);\n continue; // 已存在则跳过\n } catch {\n // not exists\n }\n }\n\n await ensureDir(path.dirname(to));\n await fs.copyFile(from, to);\n }\n}\n\nexport function sha1(text: string) {\n return createHash(\"sha1\").update(text).digest(\"hex\");\n}\n\n\n","import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { execa } from \"execa\";\r\nimport type { PackageManager } from \"../types.js\";\r\nimport { pathExists, readJsonFile, writeJsonFile } from \"./fs.js\";\r\n\r\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\r\n if (await pathExists(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\r\n if (await pathExists(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\r\n if (await pathExists(path.join(cwd, \"package-lock.json\"))) return \"npm\";\r\n if (await pathExists(path.join(cwd, \"bun.lockb\"))) return \"bun\";\r\n return \"pnpm\";\r\n}\r\n\r\nexport async function readPackageJson(\r\n cwd: string,\r\n): Promise<Record<string, any> & { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }> {\r\n return await readJsonFile(path.join(cwd, \"package.json\"));\r\n}\r\n\r\nexport async function writePackageJson(cwd: string, pkg: unknown) {\r\n await writeJsonFile(path.join(cwd, \"package.json\"), pkg);\r\n}\r\n\r\nexport function getMissingDeps(\r\n pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> },\r\n deps: string[],\r\n) {\r\n const existing = new Set([\r\n ...Object.keys(pkg.dependencies ?? {}),\r\n ...Object.keys(pkg.devDependencies ?? {}),\r\n ]);\r\n return deps.filter((d) => !existing.has(d));\r\n}\r\n\r\nexport async function installDeps(params: {\r\n cwd: string;\r\n pm: PackageManager;\r\n deps: string[];\r\n dev?: boolean;\r\n}) {\r\n const { cwd, pm, deps, dev } = params;\r\n if (!deps.length) return;\r\n\r\n const args: string[] = [];\r\n if (pm === \"pnpm\") args.push(\"add\");\r\n else if (pm === \"npm\") args.push(\"install\");\r\n else if (pm === \"yarn\") args.push(\"add\");\r\n else if (pm === \"bun\") args.push(\"add\");\r\n\r\n if (dev) {\r\n if (pm === \"npm\") args.push(\"--save-dev\");\r\n else args.push(\"-D\");\r\n }\r\n\r\n args.push(...deps);\r\n\r\n await execa(pm, args, { cwd, stdio: \"inherit\" });\r\n}\r\n\r\nexport async function ensureJsonFile(cwd: string, relPath: string, defaultJson: unknown) {\r\n const p = path.join(cwd, relPath);\r\n if (await pathExists(p)) return;\r\n await fs.mkdir(path.dirname(p), { recursive: true });\r\n await fs.writeFile(p, JSON.stringify(defaultJson, null, 2) + \"\\n\", \"utf8\");\r\n}\r\n\r\n\r\n","import path from \"node:path\";\nimport fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { fileURLToPath } from \"node:url\";\nimport type { CliConfig, RegistryFile } from \"../types.js\";\nimport { pathExists, readJsonFile } from \"./fs.js\";\n\nexport const DEFAULT_CONFIG_PATH = \"components.json\";\n\nfunction findNearestPackageRoot(from: string) {\n // from: import.meta.url\n let dir = path.dirname(fileURLToPath(from));\n while (true) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return dir;\n dir = parent;\n }\n}\n\nexport function defaultConfig(): CliConfig {\n return {\n schemaVersion: 1,\n componentsDir: \"components\",\n libDir: \"lib\",\n composablesDir: \"composables\",\n // 默认使用 CLI 包内置的 registry(随 reborn-ui 一起发布)\n registry: \"builtin\",\n // 项目根目录映射别名符号(默认 @)\n aliasSymbol: \"@\",\n };\n}\n\nexport async function loadConfig(cwd: string, configPath = DEFAULT_CONFIG_PATH) {\n const abs = path.isAbsolute(configPath)\n ? configPath\n : path.join(cwd, configPath);\n if (!(await pathExists(abs))) return null;\n return await readJsonFile<CliConfig>(abs);\n}\n\nexport function resolveRegistryJsonPath(params: { cwd: string; registry: string }) {\n const { cwd, registry } = params;\n\n // 0) 内置 registry\n if (!registry || registry === \"builtin\") {\n // 注意:CLI 经过打包后可能变成单文件(例如 dist/index.js),\n // import.meta.url 的相对基准会变化,不能用固定的 ../../registry/...\n // 这里通过向上查找最近的 package.json 来定位包根目录。\n const pkgRoot = findNearestPackageRoot(import.meta.url);\n return path.join(pkgRoot, \"registry\", \"registry.json\");\n }\n\n // 1) 如果是文件路径(包含分隔符或 .json),优先当成路径处理\n const looksLikePath =\n registry.includes(\"/\") ||\n registry.includes(\"\\\\\") ||\n registry.endsWith(\".json\");\n\n if (looksLikePath) {\n const abs = path.isAbsolute(registry) ? registry : path.join(cwd, registry);\n return abs;\n }\n\n // 2) 当成包名处理:默认读取 <pkg>/registry/registry.json\n const require = createRequire(import.meta.url);\n // 不显式绑定 cwd,避免 npx 场景下找不到 registry 包\n return require.resolve(`${registry}/registry/registry.json`);\n}\n\nexport async function loadRegistry(params: { cwd: string; registry: string }) {\n const jsonPath = resolveRegistryJsonPath(params);\n return await readJsonFile<RegistryFile>(jsonPath);\n}\n\n// 兼容旧配置文件名:shadcn-docs.json\nexport async function loadConfigCompat(cwd: string, configPath?: string) {\n if (configPath) return await loadConfig(cwd, configPath);\n const primary = await loadConfig(cwd, DEFAULT_CONFIG_PATH);\n if (primary) return primary;\n return await loadConfig(cwd, \"shadcn-docs.json\");\n}\n\n\n","import path from \"node:path\";\r\nimport fs from \"node:fs/promises\";\r\nimport fssync from \"node:fs\";\r\nimport { Command } from \"commander\";\r\nimport type { RegistryComponent, RegistryFile } from \"../types.js\";\r\nimport { listFilesRecursive, sha1, writeJsonFile } from \"../utils/fs.js\";\r\nimport { extractNpmDependenciesFromText } from \"../utils/imports.js\";\r\n\r\nfunction isAllowedFile(filePath: string) {\r\n const ext = path.extname(filePath).toLowerCase();\r\n return [\r\n \".vue\",\r\n \".ts\",\r\n \".js\",\r\n \".json\",\r\n \".css\",\r\n \".md\",\r\n \".svg\",\r\n ].includes(ext);\r\n}\r\n\r\nfunction findWorkspaceRoot(startDir: string) {\r\n let current = path.resolve(startDir);\r\n for (;;) {\r\n const marker = path.join(current, \"pnpm-workspace.yaml\");\r\n if (fssync.existsSync(marker)) return current;\r\n const parent = path.dirname(current);\r\n if (parent === current) return startDir;\r\n current = parent;\r\n }\r\n}\r\n\r\nexport function buildCommand() {\r\n const cmd = new Command(\"build\")\r\n .description(\"(内部)扫描组件源码并生成 registry JSON\")\r\n .option(\"--root <path>\", \"仓库根目录(默认自动向上查找 pnpm-workspace.yaml)\")\r\n .option(\r\n \"--source <path>\",\r\n \"组件源码目录(相对 root)\",\r\n \"components/content/reborn\",\r\n )\r\n .option(\r\n \"--out <path>\",\r\n \"输出 registry.json 路径(相对 root)\",\r\n \"packages/cli/registry/registry.json\",\r\n )\r\n .option(\r\n \"--also-out <path>\",\r\n \"额外再输出一份 registry.json(可重复传参)\",\r\n (val, acc: string[]) => {\r\n acc.push(val);\r\n return acc;\r\n },\r\n [],\r\n )\r\n .action(async (opts) => {\r\n const rootDir = opts.root\r\n ? path.resolve(opts.root)\r\n : findWorkspaceRoot(process.cwd());\r\n const sourceDir = path.join(rootDir, opts.source);\r\n const outPath = path.join(rootDir, opts.out);\r\n const alsoOutPaths: string[] = (opts.alsoOut ?? []).map((p: string) =>\r\n path.join(rootDir, p),\r\n );\r\n\r\n const dirents = await fs.readdir(sourceDir, { withFileTypes: true });\r\n const componentDirs = dirents\r\n .filter((d) => d.isDirectory())\r\n .map((d) => path.join(sourceDir, d.name))\r\n .sort((a, b) => a.localeCompare(b));\r\n\r\n const components: RegistryComponent[] = [];\r\n\r\n for (const absComponentDir of componentDirs) {\r\n const name = path.basename(absComponentDir);\r\n const absFiles = (await listFilesRecursive(absComponentDir)).filter(\r\n isAllowedFile,\r\n );\r\n\r\n const files: RegistryComponent[\"files\"] = [];\r\n const depSet = new Set<string>();\r\n\r\n for (const absFile of absFiles) {\r\n const rel = path\r\n .relative(absComponentDir, absFile)\r\n .split(path.sep)\r\n .join(\"/\");\r\n const content = await fs.readFile(absFile, \"utf8\");\r\n files.push({ path: rel, content });\r\n\r\n // 只从代码文件里抽依赖\r\n const ext = path.extname(absFile).toLowerCase();\r\n if (ext === \".ts\" || ext === \".js\" || ext === \".vue\") {\r\n for (const dep of extractNpmDependenciesFromText(content)) {\r\n depSet.add(dep);\r\n }\r\n }\r\n }\r\n\r\n components.push({\r\n name,\r\n dependencies: [...depSet].sort(),\r\n files,\r\n });\r\n }\r\n\r\n const registry: RegistryFile = {\r\n schemaVersion: 1,\r\n generatedAt: new Date().toISOString(),\r\n source: {\r\n rootDir: rootDir.split(path.sep).join(\"/\"),\r\n componentsDir: opts.source,\r\n },\r\n components,\r\n };\r\n\r\n async function writeOut(targetRegistryPath: string) {\r\n const outComponentsDir = path.join(\r\n path.dirname(targetRegistryPath),\r\n \"components\",\r\n );\r\n await writeJsonFile(targetRegistryPath, registry);\r\n\r\n // 额外输出每个组件的 json,方便调试/按需读取\r\n await fs.mkdir(outComponentsDir, { recursive: true });\r\n for (const c of components) {\r\n await writeJsonFile(\r\n path.join(outComponentsDir, `${c.name}.json`),\r\n {\r\n ...c,\r\n fileCount: c.files.length,\r\n contentHash: sha1(JSON.stringify(c.files.map((f) => f.content))),\r\n },\r\n );\r\n }\r\n }\r\n\r\n await writeOut(outPath);\r\n for (const p of alsoOutPaths) await writeOut(p);\r\n\r\n // eslint-disable-next-line no-console\r\n console.log(\r\n `registry 已生成:${path.relative(process.cwd(), outPath)}(${components.length} 个组件)`,\r\n );\r\n });\r\n\r\n return cmd;\r\n}\r\n\r\n\r\n","const IMPORT_RE =\r\n /\\bfrom\\s+[\"']([^\"']+)[\"']|\\bimport\\(\\s*[\"']([^\"']+)[\"']\\s*\\)|\\brequire\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/g;\r\n\r\nfunction normalizePackageName(specifier: string) {\r\n // ignore relative/alias/virtual\r\n if (\r\n specifier.startsWith(\".\") ||\r\n specifier.startsWith(\"/\") ||\r\n specifier.startsWith(\"@/\") ||\r\n specifier.startsWith(\"~/\") ||\r\n specifier.startsWith(\"#\") ||\r\n specifier.startsWith(\"virtual:\")\r\n ) {\r\n return null;\r\n }\r\n\r\n // scoped package: @scope/name[/...]\r\n if (specifier.startsWith(\"@\")) {\r\n const parts = specifier.split(\"/\");\r\n if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;\r\n return specifier;\r\n }\r\n\r\n // normal package: name[/...]\r\n return specifier.split(\"/\")[0] ?? null;\r\n}\r\n\r\nexport function extractNpmDependenciesFromText(text: string) {\r\n const out = new Set<string>();\r\n\r\n for (const match of text.matchAll(IMPORT_RE)) {\r\n const spec = match[1] ?? match[2] ?? match[3];\r\n if (!spec) continue;\r\n const pkg = normalizePackageName(spec);\r\n if (!pkg) continue;\r\n out.add(pkg);\r\n }\r\n\r\n // 常见“框架自带/不应自动安装”的虚拟模块\r\n out.delete(\"nuxt\");\r\n out.delete(\"vue\");\r\n\r\n return [...out].sort();\r\n}\r\n\r\n\r\n","import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport type { CliConfig, PackageManager } from \"../types.js\";\nimport { ensureDir, listFilesRecursive, pathExists, writeJsonFile, writeTextFile } from \"../utils/fs.js\";\nimport {\n detectPackageManager,\n getMissingDeps,\n installDeps,\n readPackageJson,\n} from \"../utils/pm.js\";\nimport { DEFAULT_CONFIG_PATH, defaultConfig, loadConfigCompat } from \"../utils/registry.js\";\nimport { copyTemplateSubdir } from \"../utils/templates.js\";\n\nfunction looksLikeNuxtProject(cwd: string) {\n return Promise.all([\n pathExists(path.join(cwd, \"nuxt.config.ts\")),\n pathExists(path.join(cwd, \"nuxt.config.js\")),\n pathExists(path.join(cwd, \"nuxt.config.mjs\")),\n ]).then((arr) => arr.some(Boolean));\n}\n\nfunction isTailwindConfig(content: string) {\n return content.includes(\"tailwind\") || content.includes(\"content:\");\n}\n\nfunction makeDefaultTailwindConfig(params: {\n contentGlobs: string[];\n}) {\n const { contentGlobs } = params;\n const globs = contentGlobs.map((g) => ` \"${g}\",`).join(\"\\n\");\n return `/** @type {import('tailwindcss').Config} */\\nexport default {\\n darkMode: ['class'],\\n content: [\\n${globs}\\n ],\\n theme: {\\n extend: {\\n colors: {\\n border: 'hsl(var(--border))',\\n input: 'hsl(var(--input))',\\n ring: 'hsl(var(--ring))',\\n background: 'hsl(var(--background))',\\n foreground: 'hsl(var(--foreground))',\\n primary: {\\n DEFAULT: 'hsl(var(--primary))',\\n foreground: 'hsl(var(--primary-foreground))',\\n },\\n secondary: {\\n DEFAULT: 'hsl(var(--secondary))',\\n foreground: 'hsl(var(--secondary-foreground))',\\n },\\n destructive: {\\n DEFAULT: 'hsl(var(--destructive))',\\n foreground: 'hsl(var(--destructive-foreground))',\\n },\\n muted: {\\n DEFAULT: 'hsl(var(--muted))',\\n foreground: 'hsl(var(--muted-foreground))',\\n },\\n accent: {\\n DEFAULT: 'hsl(var(--accent))',\\n foreground: 'hsl(var(--accent-foreground))',\\n },\\n popover: {\\n DEFAULT: 'hsl(var(--popover))',\\n foreground: 'hsl(var(--popover-foreground))',\\n },\\n card: {\\n DEFAULT: 'hsl(var(--card))',\\n foreground: 'hsl(var(--card-foreground))',\\n },\\n },\\n borderRadius: {\\n xl: 'calc(var(--radius) + 4px)',\\n lg: 'var(--radius)',\\n md: 'calc(var(--radius) - 2px)',\\n sm: 'calc(var(--radius) - 4px)',\\n },\\n },\\n },\\n plugins: [],\\n};\\n`;\n}\n\nfunction patchTailwindContentArray(existing: string, wantedGlobs: string[]) {\n // 尽量保守:只在发现 content: [ ... ] 时往里补缺失项\n const m = existing.match(/content\\s*:\\s*\\[([\\s\\S]*?)\\]/m);\n if (!m) return null;\n const inner = m[1] ?? \"\";\n\n const missing = wantedGlobs.filter((g) => !inner.includes(g));\n if (!missing.length) return existing;\n\n const insertion = missing.map((g) => ` \"${g}\",`).join(\"\\n\");\n const replaced = existing.replace(\n /content\\s*:\\s*\\[([\\s\\S]*?)\\]/m,\n (full) => {\n // 在 ] 前插入\n return full.replace(/\\]\\s*$/, `${insertion}\\n ]`);\n },\n );\n return replaced;\n}\n\nfunction defaultCssVariables() {\n return `@tailwind base;\\n@tailwind components;\\n@tailwind utilities;\\n\\n@layer base {\\n :root {\\n --background: 0 0% 100%;\\n --foreground: 222.2 84% 4.9%;\\n\\n --card: 0 0% 100%;\\n --card-foreground: 222.2 84% 4.9%;\\n\\n --popover: 0 0% 100%;\\n --popover-foreground: 222.2 84% 4.9%;\\n\\n --primary: 222.2 47.4% 11.2%;\\n --primary-foreground: 210 40% 98%;\\n\\n --secondary: 210 40% 96.1%;\\n --secondary-foreground: 222.2 47.4% 11.2%;\\n\\n --muted: 210 40% 96.1%;\\n --muted-foreground: 215.4 16.3% 46.9%;\\n\\n --accent: 210 40% 96.1%;\\n --accent-foreground: 222.2 47.4% 11.2%;\\n\\n --destructive: 0 84.2% 60.2%;\\n --destructive-foreground: 210 40% 98%;\\n\\n --border: 214.3 31.8% 91.4%;\\n --input: 214.3 31.8% 91.4%;\\n --ring: 222.2 84% 4.9%;\\n\\n --radius: 0.5rem;\\n }\\n\\n .dark {\\n --background: 222.2 84% 4.9%;\\n --foreground: 210 40% 98%;\\n\\n --card: 222.2 84% 4.9%;\\n --card-foreground: 210 40% 98%;\\n\\n --popover: 222.2 84% 4.9%;\\n --popover-foreground: 210 40% 98%;\\n\\n --primary: 210 40% 98%;\\n --primary-foreground: 222.2 47.4% 11.2%;\\n\\n --secondary: 217.2 32.6% 17.5%;\\n --secondary-foreground: 210 40% 98%;\\n\\n --muted: 217.2 32.6% 17.5%;\\n --muted-foreground: 215 20.2% 65.1%;\\n\\n --accent: 217.2 32.6% 17.5%;\\n --accent-foreground: 210 40% 98%;\\n\\n --destructive: 0 62.8% 30.6%;\\n --destructive-foreground: 210 40% 98%;\\n\\n --border: 217.2 32.6% 17.5%;\\n --input: 217.2 32.6% 17.5%;\\n --ring: 212.7 26.8% 83.9%;\\n }\\n}\\n`;\n}\n\nfunction cnUtilsTs(params: { importPath?: string }) {\n // importPath 预留:如果用户想从别处导入 clsx/twMerge\n void params;\n return `import { type ClassValue, clsx } from \"clsx\";\\nimport { twMerge } from \"tailwind-merge\";\\n\\nexport function cn(...inputs: ClassValue[]) {\\n return twMerge(clsx(inputs));\\n}\\n`;\n}\n\nasync function patchNuxtConfigAddCss(params: { cwd: string; cssPath: string }) {\n const { cwd, cssPath } = params;\n const nuxtConfigPath = path.join(cwd, \"nuxt.config.ts\");\n if (!(await pathExists(nuxtConfigPath))) return false;\n const raw = await fs.readFile(nuxtConfigPath, \"utf8\");\n if (raw.includes(cssPath)) return true;\n\n // 1) 已有 css: [] -> 补进去\n if (raw.match(/\\bcss\\s*:\\s*\\[/)) {\n const patched = raw.replace(/\\bcss\\s*:\\s*\\[/, `css: [\"${cssPath}\", `);\n await fs.writeFile(nuxtConfigPath, patched, \"utf8\");\n return true;\n }\n\n // 2) 没有 css 字段:在 defineNuxtConfig({ 后插入\n const m = raw.match(/defineNuxtConfig\\(\\s*\\{\\s*/);\n if (!m) return false;\n const patched = raw.replace(\n /defineNuxtConfig\\(\\s*\\{\\s*/,\n (s) => `${s}\\n css: [\"${cssPath}\"],\\n`,\n );\n await fs.writeFile(nuxtConfigPath, patched, \"utf8\");\n return true;\n}\n\nasync function rewriteAliasInDir(params: {\n cwd: string;\n targetDir: string;\n aliasSymbol: string;\n}) {\n const { cwd, targetDir, aliasSymbol } = params;\n if (!aliasSymbol || aliasSymbol === \"@\") return;\n const absDir = path.join(cwd, targetDir);\n if (!(await pathExists(absDir))) return;\n\n const files = await listFilesRecursive(absDir);\n for (const f of files) {\n const ext = path.extname(f).toLowerCase();\n // 只处理常见文本文件,避免误改二进制\n if (![\".ts\", \".tsx\", \".js\", \".jsx\", \".vue\", \".mjs\", \".cjs\"].includes(ext)) continue;\n const raw = await fs.readFile(f, \"utf8\");\n const next = raw.replaceAll(\"@/\", `${aliasSymbol}/`);\n if (next !== raw) await fs.writeFile(f, next, \"utf8\");\n }\n}\n\nexport function initCommand() {\n const cmd = new Command(\"init\")\n .description(\"初始化新项目:安装依赖、生成 cn/utils、配置 tailwind 与 CSS 变量,并写入 components.json\")\n .option(\"--cwd <path>\", \"目标项目目录\", process.cwd())\n .option(\"--pm <pm>\", \"包管理器:pnpm|npm|yarn|bun\")\n .option(\"--yes\", \"跳过交互,使用默认值\", false)\n .option(\"--overwrite\", \"覆盖已存在的模板文件(lib/composables)\", false)\n .option(\n \"--config <path>\",\n \"配置文件路径(相对 cwd)\",\n DEFAULT_CONFIG_PATH,\n )\n .option(\n \"--components-dir <path>\",\n \"组件写入目录\",\n defaultConfig().componentsDir,\n )\n .option(\n \"--lib-dir <path>\",\n \"lib 目录(cn/utils)\",\n defaultConfig().libDir,\n )\n .option(\n \"--composables-dir <path>\",\n \"composables 目录\",\n defaultConfig().composablesDir,\n )\n .option(\n \"--alias-symbol <symbol>\",\n \"项目根目录映射别名符号(默认 @)\",\n defaultConfig().aliasSymbol,\n )\n .option(\"--registry <pkgOrPath>\", \"registry 来源(默认 builtin)\", defaultConfig().registry)\n .action(async (opts) => {\n const cwd = path.resolve(opts.cwd);\n\n // eslint-disable-next-line no-console\n console.log(\n \"提示:项目中有部分依赖需要使用@/ 路径进行配置,请您安装到项目中配置好的路径下,请慎重选择文件位置\",\n );\n\n const pkgPath = path.join(cwd, \"package.json\");\n if (!(await pathExists(pkgPath))) {\n throw new Error(`未找到 package.json:${pkgPath}`);\n }\n\n const pm: PackageManager =\n opts.pm ?? (await detectPackageManager(cwd));\n\n const cfg: CliConfig = {\n schemaVersion: 1,\n componentsDir: opts.componentsDir,\n libDir: opts.libDir,\n composablesDir: opts.composablesDir,\n aliasSymbol: opts.aliasSymbol,\n registry: opts.registry,\n };\n\n // 如果已存在 components.json,默认沿用(除非用户显式传参覆盖)\n const existing = await loadConfigCompat(cwd);\n if (existing) {\n cfg.componentsDir = opts.componentsDir ?? existing.componentsDir ?? cfg.componentsDir;\n cfg.libDir = opts.libDir ?? existing.libDir ?? cfg.libDir;\n cfg.composablesDir = opts.composablesDir ?? existing.composablesDir ?? cfg.composablesDir;\n cfg.aliasSymbol = opts.aliasSymbol ?? existing.aliasSymbol ?? cfg.aliasSymbol;\n cfg.registry = opts.registry ?? existing.registry ?? cfg.registry;\n }\n\n if (!opts.yes) {\n const nuxt = await looksLikeNuxtProject(cwd);\n const res = await prompts(\n [\n {\n type: \"text\",\n name: \"componentsDir\",\n message: \"组件目录(componentsDir)\",\n initial: cfg.componentsDir,\n },\n {\n type: \"text\",\n name: \"libDir\",\n message: \"lib 目录(libDir,用于 cn/utils)\",\n initial: cfg.libDir,\n },\n {\n type: \"text\",\n name: \"composablesDir\",\n message: \"composables 目录(composablesDir)\",\n initial: cfg.composablesDir,\n },\n {\n type: \"text\",\n name: \"aliasSymbol\",\n message: \"基于项目根目录的“映射”的符号是?(aliasSymbol)\",\n initial: cfg.aliasSymbol ?? \"@\",\n },\n {\n type: \"text\",\n name: \"registry\",\n message: \"registry(包名或路径)\",\n initial: cfg.registry,\n },\n {\n type: \"confirm\",\n name: \"installBaseDeps\",\n message: `安装基础依赖(clsx、tailwind-merge、tailwindcss 等)?`,\n initial: true,\n },\n {\n type: nuxt ? \"confirm\" : null,\n name: \"installDocs\",\n message: `检测到 Nuxt:若缺失则安装 shadcn-docs-nuxt?`,\n initial: true,\n },\n ],\n {\n onCancel: () => {\n throw new Error(\"已取消\");\n },\n },\n );\n\n cfg.componentsDir = res.componentsDir ?? cfg.componentsDir;\n cfg.libDir = res.libDir ?? cfg.libDir;\n cfg.composablesDir = res.composablesDir ?? cfg.composablesDir;\n cfg.aliasSymbol = res.aliasSymbol ?? cfg.aliasSymbol;\n cfg.registry = res.registry ?? cfg.registry;\n\n // 写配置\n const cfgPath = path.join(cwd, opts.config);\n await writeJsonFile(cfgPath, cfg);\n\n const pkg = await readPackageJson(cwd);\n const wantDeps: string[] = [];\n if (res.installBaseDeps)\n wantDeps.push(\"clsx\", \"tailwind-merge\", \"tailwindcss\", \"postcss\", \"autoprefixer\");\n if (res.installDocs) wantDeps.push(\"shadcn-docs-nuxt\");\n const missing = getMissingDeps(pkg, wantDeps);\n // tailwind 相关更适合 devDependencies,但为简化:统一装到 dependencies(pnpm add)\n await installDeps({ cwd, pm, deps: missing });\n\n // 复制模板:lib/ + composables/\n await copyTemplateSubdir({\n subdir: \"lib\",\n cwd,\n targetDir: cfg.libDir,\n overwrite: opts.overwrite,\n });\n await copyTemplateSubdir({\n subdir: \"composables\",\n cwd,\n targetDir: cfg.composablesDir,\n overwrite: opts.overwrite,\n });\n // 仅替换 \"@/...\" 的别名符号,不改其它路径内容\n await rewriteAliasInDir({ cwd, targetDir: cfg.libDir, aliasSymbol: cfg.aliasSymbol ?? \"@\" });\n await rewriteAliasInDir({\n cwd,\n targetDir: cfg.composablesDir,\n aliasSymbol: cfg.aliasSymbol ?? \"@\",\n });\n\n // 兜底:如果用户模板里没有 utils.ts,则生成 cn\n const libUtilsPath = path.join(cwd, cfg.libDir, \"utils.ts\");\n if (!(await pathExists(libUtilsPath))) await writeTextFile(libUtilsPath, cnUtilsTs({}));\n\n // 写 CSS 变量文件\n const cssRel = \"assets/css/reborn-ui.css\";\n const cssAbs = path.join(cwd, cssRel);\n if (!(await pathExists(cssAbs))) {\n await writeTextFile(cssAbs, defaultCssVariables());\n }\n\n // tailwind.config.js:存在则补 content;不存在则生成\n const contentGlobs = [\n \"./components/**/*.{vue,js,ts}\",\n \"./layouts/**/*.{vue,js,ts}\",\n \"./pages/**/*.{vue,js,ts}\",\n \"./plugins/**/*.{js,ts}\",\n \"./app.vue\",\n \"./error.vue\",\n \"./content/**/*.{md,yml,yaml,json}\",\n `./${cfg.componentsDir}/**/*.{vue,js,ts}`,\n `./${cfg.composablesDir}/**/*.{js,ts}`,\n ];\n const twPath = path.join(cwd, \"tailwind.config.js\");\n if (await pathExists(twPath)) {\n const rawTw = await fs.readFile(twPath, \"utf8\");\n if (isTailwindConfig(rawTw)) {\n const patched = patchTailwindContentArray(rawTw, contentGlobs);\n if (patched && patched !== rawTw) {\n await fs.writeFile(twPath, patched, \"utf8\");\n }\n }\n } else {\n await writeTextFile(\n twPath,\n makeDefaultTailwindConfig({ contentGlobs }),\n );\n }\n\n // Nuxt:把 CSS 文件加入 nuxt.config.ts(尽量自动化)\n await patchNuxtConfigAddCss({ cwd, cssPath: `~/${cssRel}` });\n\n // eslint-disable-next-line no-console\n console.log(\n `已写入配置:${path.relative(process.cwd(), cfgPath)};并生成 cn/utils、tailwind 配置与 CSS 变量(pm=${pm})`,\n );\n return;\n }\n\n // --yes:直接写默认配置并安装基础依赖(缺失则补)\n const cfgPath = path.join(cwd, opts.config);\n await ensureDir(path.dirname(cfgPath));\n await fs.writeFile(cfgPath, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf8\");\n\n const pkg = await readPackageJson(cwd);\n const wantDeps = [\"clsx\", \"tailwind-merge\", \"tailwindcss\", \"postcss\", \"autoprefixer\"];\n if (await looksLikeNuxtProject(cwd)) wantDeps.push(\"shadcn-docs-nuxt\");\n const missing = getMissingDeps(pkg, wantDeps);\n await installDeps({ cwd, pm, deps: missing });\n\n // 复制模板:lib/ + composables/\n await copyTemplateSubdir({\n subdir: \"lib\",\n cwd,\n targetDir: cfg.libDir,\n overwrite: opts.overwrite,\n });\n await copyTemplateSubdir({\n subdir: \"composables\",\n cwd,\n targetDir: cfg.composablesDir,\n overwrite: opts.overwrite,\n });\n // 仅替换 \"@/...\" 的别名符号,不改其它路径内容\n await rewriteAliasInDir({ cwd, targetDir: cfg.libDir, aliasSymbol: cfg.aliasSymbol ?? \"@\" });\n await rewriteAliasInDir({\n cwd,\n targetDir: cfg.composablesDir,\n aliasSymbol: cfg.aliasSymbol ?? \"@\",\n });\n\n // 兜底:如果用户模板里没有 utils.ts,则生成 cn\n const libUtilsPath = path.join(cwd, cfg.libDir, \"utils.ts\");\n if (!(await pathExists(libUtilsPath))) await writeTextFile(libUtilsPath, cnUtilsTs({}));\n\n // 写 CSS 变量文件\n const cssRel = \"assets/css/reborn-ui.css\";\n const cssAbs = path.join(cwd, cssRel);\n if (!(await pathExists(cssAbs))) {\n await writeTextFile(cssAbs, defaultCssVariables());\n }\n\n // tailwind.config.js:存在则补 content;不存在则生成\n const contentGlobs = [\n \"./components/**/*.{vue,js,ts}\",\n \"./layouts/**/*.{vue,js,ts}\",\n \"./pages/**/*.{vue,js,ts}\",\n \"./plugins/**/*.{js,ts}\",\n \"./app.vue\",\n \"./error.vue\",\n \"./content/**/*.{md,yml,yaml,json}\",\n `./${cfg.componentsDir}/**/*.{vue,js,ts}`,\n `./${cfg.composablesDir}/**/*.{js,ts}`,\n ];\n const twPath = path.join(cwd, \"tailwind.config.js\");\n if (await pathExists(twPath)) {\n const rawTw = await fs.readFile(twPath, \"utf8\");\n const patched = patchTailwindContentArray(rawTw, contentGlobs);\n if (patched && patched !== rawTw) await fs.writeFile(twPath, patched, \"utf8\");\n } else {\n await writeTextFile(twPath, makeDefaultTailwindConfig({ contentGlobs }));\n }\n\n await patchNuxtConfigAddCss({ cwd, cssPath: `~/${cssRel}` });\n\n // eslint-disable-next-line no-console\n console.log(\n `已初始化:${path.relative(process.cwd(), cfgPath)};并生成 cn/utils、tailwind 配置与 CSS 变量(pm=${pm})`,\n );\n });\n\n return cmd;\n}\n\n\n","import path from \"node:path\";\nimport fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { pathExists } from \"./fs.js\";\nimport { copyDirRecursive } from \"./fs.js\";\n\nfunction findNearestPackageRoot(from: string) {\n // from: import.meta.url\n let dir = path.dirname(fileURLToPath(from));\n while (true) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return dir;\n dir = parent;\n }\n}\n\nexport function getTemplatesRootDir() {\n // 注意:CLI 打包后可能变成单文件(dist/index.js),此时 import.meta.url 的相对基准会变化。\n // 通过向上查找最近的 package.json 来定位包根目录。\n const pkgRoot = findNearestPackageRoot(import.meta.url);\n return path.join(pkgRoot, \"templates\");\n}\n\nexport async function copyTemplateSubdir(params: {\n subdir: \"lib\" | \"composables\";\n cwd: string;\n targetDir: string;\n overwrite?: boolean;\n}) {\n const templatesRoot = getTemplatesRootDir();\n const fromDir = path.join(templatesRoot, params.subdir);\n if (!(await pathExists(fromDir))) return false;\n\n const toDir = path.join(params.cwd, params.targetDir);\n await copyDirRecursive({ fromDir, toDir, overwrite: params.overwrite });\n return true;\n}\n\n\n\n\n\n\n"],"mappings":"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","names":["Command","path","fs","path","path","path","fs","path","fs","require","path","path","fs","Command","path","Command","fs","path","fs","Command","prompts","path","fs","fileURLToPath","findNearestPackageRoot","path","fileURLToPath","fs","path","fs","patched","Command","prompts","cfgPath","pkg","wantDeps","missing","libUtilsPath","cssRel","cssAbs","contentGlobs","twPath","Command"]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "reborn-ui",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "bin": {
6
+ "reborn-ui": "dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "registry",
11
+ "templates",
12
+ "README.md"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "cli": "tsx src/index.ts",
19
+ "build": "tsup",
20
+ "dev": "tsx src/index.ts",
21
+ "clean": "rimraf dist",
22
+ "prepare": "tsup",
23
+ "registry:build": "tsx src/index.ts build --root ../..",
24
+ "prepack": "npm run build && npm run registry:build",
25
+ "prepublishOnly": "npm run build && npm run registry:build"
26
+ },
27
+ "dependencies": {
28
+ "commander": "^14.0.0",
29
+ "execa": "^9.6.0",
30
+ "prompts": "^2.4.2"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.10.7",
34
+ "@types/prompts": "^2.4.9",
35
+ "rimraf": "^6.0.1",
36
+ "tsup": "^8.5.0",
37
+ "tsx": "^4.20.0",
38
+ "typescript": "^5.8.3"
39
+ }
40
+ }
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "animate-grid",
3
+ "dependencies": [
4
+ "@vueuse/core"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "AnimateGrid.vue",
9
+ "content": "<template>\r\n <div :class=\"cn('relative block', props.class)\">\r\n <div\r\n :class=\"\r\n cn(\r\n 'relative grid w-full max-w-full items-center justify-center',\r\n props.cards.length < 4 ? `grid-cols-${props.cards.length}` : 'grid-cols-4',\r\n )\r\n \"\r\n :style=\"{\r\n transform: `perspective(${props.perspective}px) rotateX(${props.rotateX}deg) rotateY(${props.rotateY}deg)`,\r\n }\"\r\n >\r\n <div\r\n v-for=\"(item, index) in props.cards\"\r\n :key=\"index\"\r\n ref=\"card\"\r\n class=\"card block rounded border border-transparent px-3 py-5 transition-all duration-200\"\r\n :style=\"{ zIndex: index + 1 }\"\r\n >\r\n <slot\r\n name=\"logo\"\r\n :logo=\"item.logo\"\r\n :index=\"index\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { onMounted, ref, watch } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useMouseInElement, useDebounceFn } from \"@vueuse/core\";\r\n\r\nconst card = ref<HTMLElement[]>();\r\ninterface Cards {\r\n logo: string;\r\n}\r\ninterface Props {\r\n class?: string;\r\n textGlowStartColor?: string;\r\n perspective?: number;\r\n textGlowEndColor?: string;\r\n cards: Cards[];\r\n rotateX?: number;\r\n rotateY?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n textGlowStartColor: \"#38ef7d80\",\r\n textGlowEndColor: \"#38ef7d\",\r\n perspective: 600,\r\n rotateX: -1,\r\n rotateY: -15,\r\n});\r\n\r\nfunction adjacentCardItems(i: number): HTMLElement[] {\r\n return [i - 1, i + 1, i - 4, i + 4]\r\n .filter((index) => {\r\n if (index < 0 || index > 15) return false;\r\n if (i % 4 === 0 && index === i - 1) return false;\r\n if (i % 4 === 3 && index === i + 1) return false;\r\n return true;\r\n })\r\n .map((index) => card.value?.[index]) as HTMLElement[];\r\n}\r\n\r\nfunction removeCardClasses(el: HTMLElement, adjacentCards: HTMLElement[]) {\r\n el.classList.remove(\"card-raised-big\");\r\n adjacentCards.forEach((adjacentCard) => {\r\n adjacentCard?.classList.remove(\"card-raised-small\");\r\n });\r\n}\r\nonMounted(() => {\r\n card.value?.forEach((el, i) => {\r\n const { isOutside } = useMouseInElement(el);\r\n const adjacentCards = adjacentCardItems(i);\r\n // add class when mouse is inside the element\r\n const removeClasses = useDebounceFn(() => removeCardClasses(el, adjacentCards), 200);\r\n watch(isOutside, (isOutside) => {\r\n if (!isOutside) {\r\n el.classList.add(\"card-raised-big\");\r\n adjacentCards.forEach((adjacentCard) => {\r\n adjacentCard?.classList.add(\"card-raised-small\");\r\n });\r\n } else {\r\n removeClasses();\r\n }\r\n });\r\n });\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.logo svg {\r\n max-height: 100%;\r\n max-width: 100%;\r\n display: block;\r\n margin: 0 auto;\r\n}\r\n\r\n.grid-transform:before {\r\n content: \"\";\r\n z-index: -1;\r\n left: -10%;\r\n top: -10%;\r\n position: absolute;\r\n width: 150%;\r\n height: 120%;\r\n background: radial-gradient(circle, #d9fbe8 0%, white 70%, transparent 100%);\r\n opacity: 0.5;\r\n}\r\n\r\n.dark .grid-transform:before {\r\n background: radial-gradient(circle, #1f2937 0%, #020420 70%, transparent 100%);\r\n}\r\n\r\n.card {\r\n box-shadow:\r\n 2px 2px 5px rgba(217, 251, 232, 0.5),\r\n 3px 3px 10px rgba(217, 251, 232, 0.5),\r\n 6px 6px 20px rgba(217, 251, 232, 0.1);\r\n}\r\n\r\n.dark .card {\r\n box-shadow:\r\n 2px 2px 5px rgba(31, 41, 55, 0.2),\r\n 3px 3px 10px rgba(31, 41, 55, 0.2),\r\n 6px 6px 20px rgba(31, 41, 55, 0.1);\r\n}\r\n\r\n.card svg {\r\n opacity: 0.7;\r\n transition: 0.2s;\r\n}\r\n\r\n.dark .card:hover {\r\n box-shadow:\r\n 3px 3px 5px rgba(31, 41, 55, 1),\r\n 5px 5px 10px rgba(31, 41, 55, 1),\r\n 10px 10px 20px rgba(31, 41, 55, 1);\r\n}\r\n\r\n.card:hover svg {\r\n opacity: 1;\r\n}\r\n\r\n.card svg {\r\n shape-rendering: geometricPrecision;\r\n}\r\n\r\n.card-raised-small {\r\n border: 1px solid rgba(0, 193, 106, 0.3);\r\n transform: scale(1.05) translateX(-5px) translateY(-5px) translateZ(0);\r\n animation: text-glow-small 1.5s alternate infinite ease-in-out;\r\n}\r\n\r\n.card-raised-big {\r\n border: 1px solid rgba(0, 193, 106, 0.5);\r\n background-color: white;\r\n transform: scale(1.15) translateX(-20px) translateY(-20px) translateZ(15px);\r\n animation: text-glow 1.5s alternate infinite ease-in-out;\r\n}\r\n\r\n.dark .card-raised-big {\r\n border: 1px solid rgba(217, 251, 232, 0.3);\r\n background-color: #020420;\r\n}\r\n\r\n.dark .card-raised-small {\r\n border: 1px solid rgba(217, 251, 232, 0.2);\r\n transform: scale(1.05) translateX(-5px) translateY(-5px) translateZ(0);\r\n}\r\n\r\n@keyframes text-glow {\r\n 0% {\r\n filter: drop-shadow(0px 0px 2px v-bind(textGlowStartColor));\r\n }\r\n\r\n 100% {\r\n filter: drop-shadow(0px 1px 8px v-bind(textGlowEndColor));\r\n }\r\n}\r\n\r\n@keyframes text-glow-small {\r\n 0% {\r\n filter: drop-shadow(0px 0px 2px rgba(56, 239, 125, 0.1));\r\n }\r\n\r\n 100% {\r\n filter: drop-shadow(0px 1px 4px rgba(56, 239, 125, 0.5));\r\n }\r\n}\r\n</style>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as AnimateGrid } from \"./AnimateGrid.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "aed2bdf6e98c2170c2efeffe0b9ae9ba4295ad20"
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "animated-beam",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "AnimatedBeam.vue",
7
+ "content": "<template>\r\n <svg\r\n fill=\"none\"\r\n :width=\"svgDimensions.width\"\r\n :height=\"svgDimensions.height\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n :class=\"cn('pointer-events-none absolute left-0 top-0 transform-gpu stroke-2', $props.class)\"\r\n :viewBox=\"`0 0 ${svgDimensions.width} ${svgDimensions.height}`\"\r\n >\r\n <path\r\n :d=\"pathD\"\r\n :stroke=\"pathColor\"\r\n :stroke-width=\"pathWidth\"\r\n :stroke-opacity=\"pathOpacity\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n :d=\"pathD\"\r\n :stroke-width=\"pathWidth\"\r\n :stroke=\"`url(#${id})`\"\r\n stroke-opacity=\"1\"\r\n stroke-linecap=\"round\"\r\n />\r\n <defs>\r\n <linearGradient\r\n :id=\"id\"\r\n gradientUnits=\"userSpaceOnUse\"\r\n x1=\"0%\"\r\n x2=\"0%\"\r\n y1=\"0%\"\r\n y2=\"0%\"\r\n >\r\n <stop\r\n :stop-color=\"gradientStartColor\"\r\n stop-opacity=\"0\"\r\n />\r\n <stop :stop-color=\"gradientStartColor\" />\r\n <stop\r\n offset=\"32.5%\"\r\n :stop-color=\"gradientStopColor\"\r\n />\r\n <stop\r\n offset=\"100%\"\r\n :stop-color=\"gradientStopColor\"\r\n stop-opacity=\"0\"\r\n />\r\n <animate\r\n v-if=\"!isVertical\"\r\n attributeName=\"x1\"\r\n :values=\"x1\"\r\n :dur=\"`${duration}s`\"\r\n keyTimes=\"0; 1\"\r\n keySplines=\"0.16 1 0.3 1\"\r\n calcMode=\"spline\"\r\n repeatCount=\"indefinite\"\r\n />\r\n <animate\r\n v-if=\"!isVertical\"\r\n attributeName=\"x2\"\r\n :values=\"x2\"\r\n :dur=\"`${duration}s`\"\r\n keyTimes=\"0; 1\"\r\n keySplines=\"0.16 1 0.3 1\"\r\n calcMode=\"spline\"\r\n repeatCount=\"indefinite\"\r\n />\r\n <animate\r\n v-if=\"isVertical\"\r\n attributeName=\"y1\"\r\n :values=\"y1\"\r\n :dur=\"`${duration}s`\"\r\n keyTimes=\"0; 1\"\r\n keySplines=\"0.16 1 0.3 1\"\r\n calcMode=\"spline\"\r\n repeatCount=\"indefinite\"\r\n />\r\n <animate\r\n v-if=\"isVertical\"\r\n attributeName=\"y2\"\r\n :values=\"y2\"\r\n :dur=\"`${duration}s`\"\r\n keyTimes=\"0; 1\"\r\n keySplines=\"0.16 1 0.3 1\"\r\n calcMode=\"spline\"\r\n repeatCount=\"indefinite\"\r\n />\r\n </linearGradient>\r\n </defs>\r\n </svg>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { onBeforeUnmount, ref, watchEffect } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ntype AnimatedBeamProps = {\r\n class?: string;\r\n containerRef: HTMLElement;\r\n fromRef: HTMLElement;\r\n toRef: HTMLElement;\r\n curvature?: number;\r\n reverse?: boolean;\r\n pathColor?: string;\r\n pathWidth?: number;\r\n pathOpacity?: number;\r\n gradientStartColor?: string;\r\n gradientStopColor?: string;\r\n delay?: number;\r\n duration?: number;\r\n startXOffset?: number;\r\n startYOffset?: number;\r\n endXOffset?: number;\r\n endYOffset?: number;\r\n};\r\n\r\nconst props = withDefaults(defineProps<AnimatedBeamProps>(), {\r\n curvature: 0,\r\n reverse: false,\r\n duration: Math.random() * 3 + 4,\r\n delay: 0,\r\n pathColor: \"gray\",\r\n pathWidth: 2,\r\n pathOpacity: 0.2,\r\n gradientStartColor: \"#FFAA40\",\r\n gradientStopColor: \"#9C40FF\",\r\n startXOffset: 0,\r\n startYOffset: 0,\r\n endXOffset: 0,\r\n endYOffset: 0,\r\n});\r\n\r\nconst id = \"beam-\" + Math.random().toString(36).substring(2, 10);\r\nconst isVertical = ref(false);\r\nconst isRightToLeft = ref(false);\r\nconst isBottomToTop = ref(false);\r\nconst x1 = computed(() => {\r\n const direction = props.reverse ? !isRightToLeft.value : isRightToLeft.value;\r\n return direction ? \"90%; -10%;\" : \"10%; 110%;\";\r\n});\r\nconst x2 = computed(() => {\r\n const direction = props.reverse ? !isRightToLeft.value : isRightToLeft.value;\r\n return direction ? \"100%; 0%;\" : \"0%; 100%;\";\r\n});\r\nconst y1 = computed(() => {\r\n const direction = props.reverse ? !isBottomToTop.value : isBottomToTop.value;\r\n return direction ? \"90%; -10%;\" : \"10%; 110%;\";\r\n});\r\nconst y2 = computed(() => {\r\n const direction = props.reverse ? !isBottomToTop.value : isBottomToTop.value;\r\n return direction ? \"100%; 0%;\" : \"0%; 100%;\";\r\n});\r\n\r\nconst pathD = ref(\"\");\r\nconst svgDimensions = ref<{ width: number; height: number }>({\r\n width: 0,\r\n height: 0,\r\n});\r\n\r\nlet resizeObserver: ResizeObserver | undefined = undefined;\r\n\r\nconst { stop: stopEffect } = watchEffect(effect);\r\n\r\nfunction effect() {\r\n if (resizeObserver == undefined && props.containerRef != null) {\r\n resizeObserver = new ResizeObserver(() => {\r\n updatePath();\r\n });\r\n resizeObserver.observe(props.containerRef);\r\n\r\n stopEffect();\r\n }\r\n}\r\n\r\n// Function to update the path based on the positions of the elements\r\nfunction updatePath() {\r\n if (props.containerRef && props.fromRef && props.toRef) {\r\n const containerRect = props.containerRef.getBoundingClientRect();\r\n const rectA = props.fromRef.getBoundingClientRect();\r\n const rectB = props.toRef.getBoundingClientRect();\r\n\r\n const svgWidth = containerRect.width;\r\n const svgHeight = containerRect.height;\r\n svgDimensions.value = { width: svgWidth, height: svgHeight };\r\n\r\n const startX = rectA.left - containerRect.left + rectA.width / 2 + (props.startXOffset ?? 0);\r\n const startY = rectA.top - containerRect.top + rectA.height / 2 + (props.startYOffset ?? 0);\r\n const endX = rectB.left - containerRect.left + rectB.width / 2 + (props.endXOffset ?? 0);\r\n const endY = rectB.top - containerRect.top + rectB.height / 2 + (props.endYOffset ?? 0);\r\n\r\n // Check if the light beam is in a vertical direction (the distance in the y-direction is greater than the distance in the x-direction).\r\n isVertical.value = Math.abs(endY - startY) > Math.abs(endX - startX);\r\n\r\n // Determine the animation direction based on the position relationship between the starting point and the endpoint\r\n isRightToLeft.value = endX < startX;\r\n isBottomToTop.value = endY < startY;\r\n\r\n const controlY = startY - (props.curvature ?? 0);\r\n const d = `M ${startX},${startY} Q ${(startX + endX) / 2},${controlY} ${endX},${endY}`;\r\n pathD.value = d;\r\n }\r\n}\r\n\r\nonBeforeUnmount(() => {\r\n resizeObserver?.disconnect();\r\n});\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as AnimatedBeam } from \"./AnimatedBeam.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "48103000a74d44d35d572e5cb464df704ea8af6d"
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "animated-circular-progressbar",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "AnimatedCircularProgressBar.vue",
7
+ "content": "<template>\r\n <div\r\n class=\"progress-circle-base\"\r\n :class=\"cn('relative size-40 text-2xl font-semibold', props.class)\"\r\n >\r\n <svg\r\n fill=\"none\"\r\n class=\"size-full\"\r\n stroke-width=\"2\"\r\n viewBox=\"0 0 100 100\"\r\n >\r\n <circle\r\n v-if=\"currentPercent <= 90 && currentPercent >= 0\"\r\n cx=\"50\"\r\n cy=\"50\"\r\n r=\"45\"\r\n :stroke-width=\"circleStrokeWidth\"\r\n stroke-dashoffset=\"0\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n class=\"gauge-secondary-stroke opacity-100\"\r\n />\r\n <circle\r\n cx=\"50\"\r\n cy=\"50\"\r\n r=\"45\"\r\n :stroke-width=\"circleStrokeWidth\"\r\n stroke-dashoffset=\"0\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n class=\"gauge-primary-stroke opacity-100\"\r\n />\r\n </svg>\r\n <span\r\n v-if=\"showPercentage\"\r\n :data-current-value=\"currentPercent\"\r\n class=\"absolute inset-0 m-auto size-fit delay-0 duration-1000 ease-linear animate-in fade-in\"\r\n >\r\n {{ currentPercent }}\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n max?: number;\r\n value?: number;\r\n min?: number;\r\n gaugePrimaryColor?: string;\r\n gaugeSecondaryColor?: string;\r\n class?: string;\r\n circleStrokeWidth?: number;\r\n showPercentage?: boolean;\r\n duration?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n max: 100,\r\n min: 0,\r\n value: 0,\r\n gaugePrimaryColor: \"rgb(79 70 229)\",\r\n gaugeSecondaryColor: \"rgba(0, 0, 0, 0.1)\",\r\n circleStrokeWidth: 10,\r\n showPercentage: true,\r\n duration: 1,\r\n});\r\n\r\nconst circumference = 2 * Math.PI * 45;\r\nconst percentPx = circumference / 100;\r\n\r\nconst currentPercent = computed(() => ((props.value - props.min) / (props.max - props.min)) * 100);\r\nconst percentageInPx = computed(() => `${percentPx}px`);\r\nconst durationInSeconds = computed(() => `${props.duration}s`);\r\n</script>\r\n\r\n<style scoped lang=\"css\">\r\n.progress-circle-base {\r\n --circle-size: 100px;\r\n --circumference: v-bind(circumference);\r\n --percent-to-px: v-bind(percentageInPx);\r\n --gap-percent: 5;\r\n --offset-factor: 0;\r\n --transition-step: 200ms;\r\n --percent-to-deg: 3.6deg;\r\n transform: translateZ(0);\r\n}\r\n\r\n.gauge-primary-stroke {\r\n stroke: v-bind(gaugePrimaryColor);\r\n --stroke-percent: v-bind(currentPercent);\r\n stroke-dasharray: calc(var(--stroke-percent) * var(--percent-to-px)) var(--circumference);\r\n transition:\r\n v-bind(durationInSeconds) ease,\r\n stroke v-bind(durationInSeconds) ease;\r\n transition-property: stroke-dasharray, transform;\r\n transform: rotate(\r\n calc(-90deg + var(--gap-percent) * var(--offset-factor) * var(--percent-to-deg))\r\n );\r\n transform-origin: calc(var(--circle-size) / 2) calc(var(--circle-size) / 2);\r\n}\r\n\r\n.gauge-secondary-stroke {\r\n stroke: v-bind(gaugeSecondaryColor);\r\n --stroke-percent: 90 - v-bind(currentPercent);\r\n --offset-factor-secondary: calc(1 - var(--offset-factor));\r\n stroke-dasharray: calc(var(--stroke-percent) * var(--percent-to-px)) var(--circumference);\r\n transform: rotate(\r\n calc(\r\n 1turn - 90deg -\r\n (var(--gap-percent) * var(--percent-to-deg) * var(--offset-factor-secondary))\r\n )\r\n )\r\n scaleY(-1);\r\n transition: all v-bind(durationInSeconds) ease;\r\n transform-origin: calc(var(--circle-size) / 2) calc(var(--circle-size) / 2);\r\n}\r\n</style>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as AnimatedCircularProgressBar } from \"./AnimatedCircularProgressBar.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "08bfea84c594dffa9ba822f7278736d4f46e014d"
16
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "animated-list",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "AnimatedList.vue",
9
+ "content": "<template>\r\n <div :class=\"cn('flex flex-col items-center gap-4', $props.class)\">\r\n <transition-group\r\n name=\"list\"\r\n tag=\"div\"\r\n class=\"flex flex-col-reverse items-center gap-3\"\r\n move-class=\"move\"\r\n >\r\n <!-- Only render the items up to the current index -->\r\n <Motion\r\n v-for=\"data in itemsToShow\"\r\n :key=\"data.id\"\r\n as=\"div\"\r\n :initial=\"{ scale: 0, opacity: 0 }\"\r\n :animate=\"{\r\n scale: 1,\r\n opacity: 1,\r\n y: 0,\r\n }\"\r\n :exit=\"{\r\n scale: 0,\r\n opacity: 0,\r\n y: 0,\r\n }\"\r\n :transition=\"{\r\n type: 'spring',\r\n stiffness: 350,\r\n damping: 40,\r\n }\"\r\n :class=\"cn('mx-auto w-full')\"\r\n >\r\n <component :is=\"data.node\" />\r\n </Motion>\r\n </transition-group>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { Motion } from \"motion-v\";\r\nimport { computed, onMounted, ref, useSlots } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n class?: string;\r\n delay?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n delay: 1000,\r\n});\r\n\r\nconst slots = useSlots();\r\nconst displayedItems = ref<{ node: unknown; id: string }[]>([]);\r\nconst nextIndex = ref(0);\r\n\r\nonMounted(startLoop);\r\n\r\nasync function startLoop() {\r\n const notifications = slots.default ? (slots.default()[0].children ?? []) : [];\r\n if (!notifications.length) return;\r\n\r\n while (displayedItems.value.length < notifications.length) {\r\n displayedItems.value.push({\r\n node: notifications[nextIndex.value],\r\n id: `${nextIndex.value}-${Date.now()}`,\r\n });\r\n nextIndex.value = (nextIndex.value + 1) % notifications.length;\r\n await wait(props.delay);\r\n }\r\n\r\n while (true) {\r\n displayedItems.value.shift();\r\n displayedItems.value.push({\r\n node: notifications[nextIndex.value],\r\n id: `${nextIndex.value}-${Date.now()}`,\r\n });\r\n nextIndex.value = (nextIndex.value + 1) % notifications.length;\r\n await wait(props.delay);\r\n }\r\n}\r\n\r\nconst itemsToShow = computed(() => displayedItems.value);\r\n\r\nasync function wait(ms: number) {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.move {\r\n transition: transform 0.4s ease-out;\r\n}\r\n</style>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as AnimatedList } from \"./AnimatedList.vue\";\r\nexport { default as Notification } from \"./Notification.vue\";\r\n"
14
+ },
15
+ {
16
+ "path": "Notification.vue",
17
+ "content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'relative mx-auto min-h-fit w-full max-w-[400px] cursor-pointer overflow-hidden rounded-2xl p-4',\r\n // animation styles\r\n 'transition-all duration-200 ease-in-out hover:scale-[103%]',\r\n // light styles\r\n 'bg-white [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]',\r\n // dark styles\r\n 'transform-gpu dark:bg-transparent dark:backdrop-blur-md dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]',\r\n )\r\n \"\r\n >\r\n <div class=\"flex flex-row items-center gap-3\">\r\n <div\r\n class=\"flex size-10 items-center justify-center rounded-2xl\"\r\n :style=\"`background-color: ${color}`\"\r\n >\r\n <span class=\"text-lg\">{{ icon }}</span>\r\n </div>\r\n <div class=\"flex flex-col overflow-hidden\">\r\n <div class=\"flex flex-row items-center whitespace-pre text-lg font-medium dark:text-white\">\r\n <span class=\"text-sm sm:text-lg\">{{ name }}</span>\r\n <span class=\"mx-1\">·</span>\r\n <span class=\"text-xs text-gray-500\">{{ time }}</span>\r\n </div>\r\n <p class=\"text-sm font-normal dark:text-white/60\">{{ description }}</p>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ntype NotificationProps = {\r\n name?: string;\r\n description?: string;\r\n time?: string;\r\n icon?: string;\r\n color?: string;\r\n};\r\n\r\nwithDefaults(defineProps<NotificationProps>(), {\r\n name: \"\",\r\n description: \"\",\r\n time: \"\",\r\n icon: \"\",\r\n color: \"\",\r\n});\r\n</script>\r\n"
18
+ }
19
+ ],
20
+ "fileCount": 3,
21
+ "contentHash": "dd6d4a2257148e8e166fa1b2588dc2c3c0f6d028"
22
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "animated-testimonials",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "AnimatedTestimonials.vue",
9
+ "content": "<template>\r\n <div class=\"mx-auto max-w-sm px-4 py-20 font-sans antialiased lg:px-12 md:max-w-4xl md:px-8\">\r\n <div class=\"relative grid grid-cols-1 gap-20 md:grid-cols-2\">\r\n <div>\r\n <div class=\"relative h-80 w-full\">\r\n <AnimatePresence>\r\n <Motion\r\n v-for=\"(testimonial, index) in props.testimonials\"\r\n :key=\"testimonial.image\"\r\n as=\"div\"\r\n :initial=\"{\r\n opacity: 0,\r\n scale: 0.9,\r\n z: -100,\r\n rotate: randomRotateY(),\r\n }\"\r\n :animate=\"{\r\n opacity: isActive(index) ? 1 : 0.7,\r\n scale: isActive(index) ? 1 : 0.95,\r\n z: isActive(index) ? 0 : -100,\r\n rotate: isActive(index) ? 0 : randomRotateY(),\r\n zIndex: isActive(index) ? 40 : testimonials.length + 2 - index,\r\n y: isActive(index) ? [0, -80, 0] : 0,\r\n }\"\r\n :exit=\"{\r\n opacity: 0,\r\n scale: 0.9,\r\n z: 100,\r\n rotate: randomRotateY(),\r\n }\"\r\n :transition=\"{\r\n duration: 0.4,\r\n ease: 'easeInOut',\r\n }\"\r\n class=\"absolute inset-0 origin-bottom\"\r\n >\r\n <NuxtImg\r\n :src=\"testimonial.image\"\r\n :alt=\"testimonial.name\"\r\n width=\"500\"\r\n height=\"500\"\r\n :draggable=\"false\"\r\n class=\"size-full rounded-3xl object-cover object-center\"\r\n />\r\n </Motion>\r\n </AnimatePresence>\r\n </div>\r\n </div>\r\n <div class=\"flex flex-col justify-between py-4\">\r\n <Motion\r\n :key=\"active\"\r\n as=\"div\"\r\n :initial=\"{\r\n y: 20,\r\n opacity: 0,\r\n }\"\r\n :animate=\"{\r\n y: 0,\r\n opacity: 1,\r\n }\"\r\n :exit=\"{\r\n y: -20,\r\n opacity: 0,\r\n }\"\r\n :transition=\"{\r\n duration: 0.2,\r\n ease: 'easeInOut',\r\n }\"\r\n >\r\n <h3 class=\"text-2xl font-bold text-black dark:text-white\">\r\n {{ props.testimonials[active].name }}\r\n </h3>\r\n <p class=\"text-sm text-gray-500 dark:text-neutral-500\">\r\n {{ props.testimonials[active].designation }}\r\n </p>\r\n <Motion\r\n as=\"p\"\r\n class=\"mt-8 text-lg text-gray-500 dark:text-neutral-300\"\r\n >\r\n <Motion\r\n v-for=\"(word, index) in activeTestimonialQuote\"\r\n :key=\"index\"\r\n as=\"span\"\r\n :initial=\"{\r\n filter: 'blur(10px)',\r\n opacity: 0,\r\n y: 5,\r\n }\"\r\n :animate=\"{\r\n filter: 'blur(0px)',\r\n opacity: 1,\r\n y: 0,\r\n }\"\r\n :transition=\"{\r\n duration: 0.2,\r\n ease: 'easeInOut',\r\n delay: 0.02 * index,\r\n }\"\r\n class=\"inline-block\"\r\n >\r\n {{ word }}&nbsp;\r\n </Motion>\r\n </Motion>\r\n </Motion>\r\n <div class=\"flex gap-4 pt-12 md:pt-0\">\r\n <button\r\n class=\"group/button flex size-7 items-center justify-center rounded-full bg-gray-100 dark:bg-neutral-800\"\r\n @click=\"handlePrev\"\r\n >\r\n <Icon\r\n name=\"lucide:arrow-left\"\r\n class=\"size-5 text-black transition-transform duration-300 group-hover/button:rotate-12 dark:text-neutral-400\"\r\n />\r\n </button>\r\n <button\r\n class=\"group/button flex size-7 items-center justify-center rounded-full bg-gray-100 dark:bg-neutral-800\"\r\n @click=\"handleNext\"\r\n >\r\n <Icon\r\n name=\"lucide:arrow-right\"\r\n class=\"size-5 text-black transition-transform duration-300 group-hover/button:-rotate-12 dark:text-neutral-400\"\r\n />\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { Motion } from \"motion-v\";\r\n\r\ninterface Testimonial {\r\n quote: string;\r\n name: string;\r\n designation: string;\r\n image: string;\r\n}\r\ninterface Props {\r\n testimonials?: Testimonial[];\r\n autoplay?: boolean;\r\n duration?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n testimonials: () => [],\r\n autoplay: () => false,\r\n duration: 5000,\r\n});\r\n\r\nconst active = ref(0);\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nconst interval = ref<any>();\r\n\r\nconst activeTestimonialQuote = computed(() => {\r\n return props.testimonials[active.value].quote.split(\" \");\r\n});\r\n\r\nonMounted(() => {\r\n if (props.autoplay) {\r\n interval.value = setInterval(handleNext, props.duration);\r\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n if (!interval.value) {\r\n clearInterval(interval.value);\r\n }\r\n});\r\n\r\nfunction handleNext() {\r\n active.value = (active.value + 1) % props.testimonials.length;\r\n}\r\n\r\nfunction handlePrev() {\r\n active.value = (active.value - 1 + props.testimonials.length) % props.testimonials.length;\r\n}\r\n\r\nfunction isActive(index: number) {\r\n return active.value === index;\r\n}\r\n\r\nfunction randomRotateY() {\r\n return Math.floor(Math.random() * 21) - 10;\r\n}\r\n</script>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as AnimatedTestimonials } from \"./AnimatedTestimonials.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "f660ab172808ae3ca330384ab1ec91925dd9c0fc"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "animated-tooltip",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "AnimatedTooltip.vue",
9
+ "content": "<template>\r\n <div\r\n v-for=\"item in items\"\r\n :key=\"item.id\"\r\n class=\"group relative -mr-4\"\r\n @mouseenter=\"(e) => handleMouseEnter(e, item.id)\"\r\n @mouseleave=\"hoveredIndex = null\"\r\n @mousemove=\"handleMouseMove\"\r\n >\r\n <!-- Tooltip -->\r\n <Motion\r\n v-if=\"hoveredIndex === item.id\"\r\n :initial=\"{\r\n opacity: 0,\r\n y: 20,\r\n scale: 0.6,\r\n }\"\r\n :animate=\"{\r\n opacity: 1,\r\n y: 0,\r\n scale: 1,\r\n }\"\r\n :transition=\"{\r\n type: 'spring',\r\n stiffness: 260,\r\n damping: 10,\r\n }\"\r\n :exit=\"{\r\n opacity: 0,\r\n y: 20,\r\n scale: 0.6,\r\n }\"\r\n :style=\"{\r\n translateX: `${translation}px`,\r\n rotate: `${rotation}deg`,\r\n }\"\r\n class=\"absolute left-1/2 -top-16 z-50 flex -translate-x-1/2 flex-col items-center justify-center whitespace-nowrap rounded-md bg-black px-4 py-2 text-xs shadow-xl\"\r\n >\r\n <div\r\n class=\"absolute right-1/2 translate-x-1/2 -bottom-px z-30 h-px w-2/5 me-1 bg-gradient-to-r from-transparent via-emerald-500 to-transparent\"\r\n />\r\n <div\r\n class=\"absolute left-1/2 -translate-x-1/2 -bottom-px z-30 h-px w-2/5 ms-1 bg-gradient-to-r from-transparent via-sky-500 to-transparent\"\r\n />\r\n <div class=\"relative z-30 text-base font-bold text-white\">\r\n {{ item.name }}\r\n </div>\r\n <div class=\"text-xs text-white\">{{ item.designation }}</div>\r\n </Motion>\r\n\r\n <!-- Avatar Image -->\r\n <img\r\n :src=\"item.image\"\r\n :alt=\"item.name\"\r\n class=\"relative !m-0 size-14 rounded-full border-2 border-white object-cover object-top !p-0 transition duration-500 group-hover:z-30 group-hover:scale-105\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref } from \"vue\";\r\nimport { Motion } from \"motion-v\";\r\n\r\ninterface Item {\r\n id: number;\r\n name: string;\r\n designation: string;\r\n image: string;\r\n}\r\n\r\ndefineProps<{\r\n items: Item[];\r\n}>();\r\n\r\nconst hoveredIndex = ref<number | null>(null);\r\nconst mouseX = ref<number>(0);\r\n\r\n// Calculate rotation and translation based on mouse position\r\nconst rotation = computed<number>(() => {\r\n const x = mouseX.value;\r\n return (x / 100) * 50;\r\n});\r\n\r\nconst translation = computed<number>(() => {\r\n const x = mouseX.value;\r\n return (x / 100) * 50;\r\n});\r\n\r\n// Handle initial mouse position and hover\r\nfunction handleMouseEnter(event: MouseEvent, itemId: number) {\r\n hoveredIndex.value = itemId;\r\n // Calculate initial position immediately\r\n const rect = (event.target as HTMLElement)?.getBoundingClientRect();\r\n const halfWidth = rect.width / 2;\r\n mouseX.value = event.clientX - rect.left - halfWidth;\r\n}\r\n\r\n// Handle mouse movement\r\nfunction handleMouseMove(event: MouseEvent) {\r\n const rect = (event.target as HTMLElement)?.getBoundingClientRect();\r\n const halfWidth = rect.width / 2;\r\n mouseX.value = event.clientX - rect.left - halfWidth;\r\n}\r\n</script>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as AnimatedTooltip } from \"./AnimatedTooltip.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "ddacc08a121e54bdc3aea7c291182fe69614b89e"
18
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "apple-card-carousel",
3
+ "dependencies": [
4
+ "@vueuse/core",
5
+ "motion-v"
6
+ ],
7
+ "files": [
8
+ {
9
+ "path": "AppleBlurImage.vue",
10
+ "content": "<template>\r\n <img\r\n :class=\"\r\n cn(\r\n 'transition duration-300',\r\n isLoading ? 'blur-sm' : 'blur-0',\r\n props.class,\r\n fill ? 'h-full w-full' : '',\r\n )\r\n \"\r\n :src=\"src\"\r\n :width=\"width\"\r\n :height=\"height\"\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n :alt=\"alt\"\r\n @load=\"handleLoad\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n height?: number | string;\r\n width?: number | string;\r\n src: string;\r\n class?: string;\r\n alt?: string;\r\n fill?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n height: undefined,\r\n width: undefined,\r\n class: \"\",\r\n alt: \"Background of a beautiful view\",\r\n fill: false,\r\n});\r\n\r\nconst isLoading = ref(true);\r\n\r\nfunction handleLoad() {\r\n isLoading.value = false;\r\n}\r\n</script>\r\n"
11
+ },
12
+ {
13
+ "path": "AppleCard.vue",
14
+ "content": "<template>\r\n <Teleport to=\"body\">\r\n <AnimatePresence>\r\n <div\r\n v-if=\"open\"\r\n class=\"fixed inset-0 z-50 h-screen overflow-auto\"\r\n >\r\n <Motion\r\n as=\"div\"\r\n :initial=\"{ opacity: 0 }\"\r\n :animate=\"{ opacity: 1 }\"\r\n :exit=\"{ opacity: 0 }\"\r\n class=\"fixed inset-0 size-full bg-black/80 backdrop-blur-lg\"\r\n />\r\n <Motion\r\n ref=\"containerRef\"\r\n as=\"div\"\r\n :initial=\"{ opacity: 0 }\"\r\n :animate=\"{ opacity: 1 }\"\r\n :exit=\"{ opacity: 0 }\"\r\n :layout-id=\"layout ? `card-${card.title}` : undefined\"\r\n class=\"relative z-[60] mx-auto my-10 h-fit max-w-5xl rounded-3xl bg-white p-4 font-sans md:p-10 dark:bg-neutral-900\"\r\n >\r\n <button\r\n class=\"sticky right-0 top-4 ml-auto flex size-8 items-center justify-center rounded-full bg-black dark:bg-white\"\r\n @click=\"handleClose\"\r\n >\r\n <Icon\r\n name=\"tabler:x\"\r\n class=\"size-6 text-neutral-100 dark:text-neutral-900\"\r\n />\r\n </button>\r\n <Motion\r\n as=\"div\"\r\n :layout-id=\"layout ? `category-${card.title}` : undefined\"\r\n class=\"text-base font-medium text-black dark:text-white\"\r\n >\r\n {{ card.category }}\r\n </Motion>\r\n <Motion\r\n as=\"div\"\r\n :layout-id=\"layout ? `title-${card.title}` : undefined\"\r\n class=\"mt-4 text-2xl font-semibold text-neutral-700 md:text-5xl dark:text-white\"\r\n >\r\n {{ card.title }}\r\n </Motion>\r\n <div class=\"py-10\">\r\n <slot></slot>\r\n </div>\r\n </Motion>\r\n </div>\r\n </AnimatePresence>\r\n </Teleport>\r\n\r\n <Motion\r\n :layout-id=\"layout ? `card-${card.title}` : undefined\"\r\n class=\"relative z-10 flex h-80 w-56 flex-col items-start justify-start overflow-hidden rounded-3xl bg-gray-100 md:h-[40rem] md:w-96 dark:bg-neutral-900\"\r\n @click=\"handleOpen\"\r\n >\r\n <div\r\n class=\"pointer-events-none absolute inset-x-0 top-0 z-30 h-full bg-gradient-to-b from-black/50 via-transparent to-transparent\"\r\n />\r\n <div class=\"relative z-40 p-8\">\r\n <Motion\r\n :layout-id=\"layout ? `category-${card.category}` : undefined\"\r\n class=\"text-left font-sans text-sm font-medium text-white md:text-base\"\r\n >\r\n {{ card.category }}\r\n </Motion>\r\n <Motion\r\n :layout-id=\"layout ? `title-${card.title}` : undefined\"\r\n class=\"mt-2 max-w-xs text-left font-sans text-xl font-semibold text-white [text-wrap:balance] md:text-3xl\"\r\n >\r\n {{ card.title }}\r\n </Motion>\r\n </div>\r\n <AppleBlurImage\r\n :src=\"card.src\"\r\n :alt=\"card.title\"\r\n class=\"absolute inset-0 z-10 object-cover\"\r\n :fill=\"true\"\r\n />\r\n </Motion>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, onMounted, inject, onUnmounted, watch } from \"vue\";\r\nimport { Motion, AnimatePresence } from \"motion-v\";\r\nimport { onClickOutside } from \"@vueuse/core\";\r\nimport { CarouselKey } from \"./AppleCarouselContext\";\r\n\r\ninterface Card {\r\n src: string;\r\n title: string;\r\n category: string;\r\n}\r\n\r\ninterface Props {\r\n card: Card;\r\n index: number;\r\n layout?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n layout: false,\r\n});\r\n\r\nconst open = ref(false);\r\nconst containerRef = ref<HTMLDivElement | null>(null);\r\nconst carouselContext = inject(CarouselKey);\r\n\r\nif (!carouselContext) {\r\n throw new Error(\"Card must be used within a Carousel\");\r\n}\r\n\r\nconst { onCardClose, currentIndex } = carouselContext;\r\n\r\nfunction handleKeyDown(event: KeyboardEvent) {\r\n if (event.key === \"Escape\") {\r\n handleClose();\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n window.addEventListener(\"keydown\", handleKeyDown);\r\n});\r\n\r\nonUnmounted(() => {\r\n window.removeEventListener(\"keydown\", handleKeyDown);\r\n});\r\n\r\nwatch(open, (newVal) => {\r\n if (newVal) {\r\n document.body.style.overflow = \"hidden\";\r\n } else {\r\n document.body.style.overflow = \"auto\";\r\n }\r\n});\r\n\r\nonClickOutside(containerRef, () => handleClose());\r\n\r\nfunction handleOpen() {\r\n open.value = true;\r\n}\r\n\r\nfunction handleClose() {\r\n open.value = false;\r\n onCardClose(props.index);\r\n}\r\n</script>\r\n"
15
+ },
16
+ {
17
+ "path": "AppleCardCarousel.vue",
18
+ "content": "<template>\r\n <div class=\"relative w-full\">\r\n <div\r\n ref=\"carouselRef\"\r\n class=\"flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 [scrollbar-width:none] md:py-20\"\r\n @scroll=\"checkScrollability\"\r\n >\r\n <div\r\n :class=\"cn('absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l')\"\r\n ></div>\r\n\r\n <div :class=\"cn('flex flex-row justify-start gap-4 pl-4', 'mx-auto max-w-7xl')\">\r\n <slot></slot>\r\n </div>\r\n </div>\r\n <div class=\"mr-10 flex justify-end gap-2\">\r\n <button\r\n class=\"relative z-40 flex size-10 items-center justify-center rounded-full bg-gray-100 disabled:opacity-50\"\r\n :disabled=\"!canScrollLeft\"\r\n @click=\"scrollLeft\"\r\n >\r\n <Icon\r\n name=\"tabler:arrow-narrow-left\"\r\n class=\"size-6 text-gray-500\"\r\n />\r\n </button>\r\n <button\r\n class=\"relative z-40 flex size-10 items-center justify-center rounded-full bg-gray-100 disabled:opacity-50\"\r\n :disabled=\"!canScrollRight\"\r\n @click=\"scrollRight\"\r\n >\r\n <Icon\r\n name=\"tabler:arrow-narrow-right\"\r\n class=\"size-6 text-gray-500\"\r\n />\r\n </button>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, onMounted, watch, provide, computed } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { CarouselKey } from \"./AppleCarouselContext\";\r\n\r\ninterface Props {\r\n initialScroll?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n initialScroll: 0,\r\n});\r\n\r\nconst carouselRef = ref<HTMLDivElement | null>(null);\r\nconst canScrollLeft = ref(false);\r\nconst canScrollRight = ref(true);\r\nconst currentIndex = ref(0);\r\n\r\nonMounted(() => {\r\n if (carouselRef.value) {\r\n carouselRef.value.scrollLeft = props.initialScroll;\r\n checkScrollability();\r\n }\r\n});\r\n\r\nwatch(\r\n () => props.initialScroll,\r\n (newVal) => {\r\n if (carouselRef.value) {\r\n carouselRef.value.scrollLeft = newVal;\r\n checkScrollability();\r\n }\r\n },\r\n);\r\n\r\nfunction checkScrollability() {\r\n if (carouselRef.value) {\r\n const { scrollLeft, scrollWidth, clientWidth } = carouselRef.value;\r\n canScrollLeft.value = scrollLeft > 0;\r\n canScrollRight.value = scrollLeft < scrollWidth - clientWidth;\r\n }\r\n}\r\n\r\nfunction scrollLeft() {\r\n if (carouselRef.value) {\r\n carouselRef.value.scrollBy({ left: -300, behavior: \"smooth\" });\r\n }\r\n}\r\n\r\nfunction scrollRight() {\r\n if (carouselRef.value) {\r\n carouselRef.value.scrollBy({ left: 300, behavior: \"smooth\" });\r\n }\r\n}\r\n\r\nfunction handleCardClose(index: number) {\r\n if (carouselRef.value) {\r\n const cardWidth = isMobile.value ? 230 : 384; // (md:w-96)\r\n const gap = isMobile.value ? 4 : 8;\r\n const scrollPosition = (cardWidth + gap) * (index + 1);\r\n carouselRef.value.scrollTo({\r\n left: scrollPosition,\r\n behavior: \"smooth\",\r\n });\r\n currentIndex.value = index;\r\n }\r\n}\r\n\r\nconst isMobile = computed(() => {\r\n return window && window.innerWidth < 768;\r\n});\r\n\r\nprovide(CarouselKey, {\r\n onCardClose: handleCardClose,\r\n currentIndex,\r\n});\r\n</script>\r\n"
19
+ },
20
+ {
21
+ "path": "AppleCarouselContext.ts",
22
+ "content": "import { type InjectionKey, type Ref } from \"vue\";\r\n\r\nexport interface CarouselContextType {\r\n onCardClose: (index: number) => void;\r\n currentIndex: Ref<number>;\r\n}\r\n\r\nexport const CarouselKey = Symbol() as InjectionKey<CarouselContextType>;\r\n"
23
+ },
24
+ {
25
+ "path": "AppleCarouselItem.vue",
26
+ "content": "<template>\r\n <Motion\r\n as=\"div\"\r\n :initial=\"{\r\n opacity: 0,\r\n y: 20,\r\n }\"\r\n :animate=\"{\r\n opacity: 1,\r\n y: 0,\r\n transition: {\r\n duration: 0.5,\r\n delay: 0.2 * index,\r\n ease: 'easeOut',\r\n once: true,\r\n },\r\n }\"\r\n class=\"rounded-3xl last:pr-[5%] md:last:pr-[33%]\"\r\n >\r\n <slot></slot>\r\n </Motion>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { Motion } from \"motion-v\";\r\n\r\ninterface Props {\r\n index: number;\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n"
27
+ },
28
+ {
29
+ "path": "index.ts",
30
+ "content": "export { default as AppleCardCarousel } from \"./AppleCardCarousel.vue\";\r\nexport { default as AppleCarouselItem } from \"./AppleCarouselItem.vue\";\r\nexport { default as AppleCard } from \"./AppleCard.vue\";\r\nexport { default as AppleBlurImage } from \"./AppleBlurImage.vue\";\r\nexport * from \"./AppleCarouselContext\";\r\n"
31
+ }
32
+ ],
33
+ "fileCount": 6,
34
+ "contentHash": "82edd74b20d26c18a1fa5f94a374cb842a0f125c"
35
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "aurora-background",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "AuroraBackground.vue",
7
+ "content": "<template>\r\n <main>\r\n <div\r\n v-bind=\"props\"\r\n :class=\"\r\n cn(\r\n 'transition-bg relative flex h-[100vh] flex-col items-center justify-center bg-zinc-50 text-slate-950 dark:bg-zinc-900',\r\n props.class,\r\n )\r\n \"\r\n >\r\n <div\r\n :style=\"styles\"\r\n class=\"absolute inset-0 overflow-hidden\"\r\n >\r\n <div\r\n :class=\"\r\n cn(\r\n `after:animate-aurora pointer-events-none absolute -inset-[10px] [background-image:var(--white-gradient),var(--aurora)] [background-size:300%,_200%] [background-position:50%_50%,50%_50%] opacity-50 blur-[10px] invert filter will-change-transform [--aurora:repeating-linear-gradient(100deg,var(--blue-500)_10%,var(--indigo-300)_15%,var(--blue-300)_20%,var(--violet-200)_25%,var(--blue-400)_30%)] [--dark-gradient:repeating-linear-gradient(100deg,var(--black)_0%,var(--black)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--black)_16%)] [--white-gradient:repeating-linear-gradient(100deg,var(--white)_0%,var(--white)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--white)_16%)] after:absolute after:inset-0 after:[background-image:var(--white-gradient),var(--aurora)] after:[background-size:200%,_100%] after:[background-attachment:fixed] after:mix-blend-difference after:content-[''] dark:[background-image:var(--dark-gradient),var(--aurora)] dark:invert-0 after:dark:[background-image:var(--dark-gradient),var(--aurora)]`,\r\n props.radialGradient &&\r\n `[mask-image:radial-gradient(ellipse_at_100%_0%,black_10%,var(--transparent)_70%)]`,\r\n )\r\n \"\r\n />\r\n </div>\r\n <slot />\r\n </div>\r\n </main>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport { computed } from \"vue\";\r\n\r\ninterface AuroraBackgroundProps {\r\n radialGradient?: boolean;\r\n class?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<AuroraBackgroundProps>(), {\r\n radialGradient: true,\r\n});\r\n\r\nconst styles = computed(() => {\r\n return {\r\n \"--aurora\":\r\n \"repeating-linear-gradient(100deg,#3b82f6_10%,#a5b4fc_15%,#93c5fd_20%,#ddd6fe_25%,#60a5fa_30%)\",\r\n \"--dark-gradient\":\r\n \"repeating-linear-gradient(100deg,#000_0%,#000_7%,transparent_10%,transparent_12%,#000_16%)\",\r\n \"--white-gradient\":\r\n \"repeating-linear-gradient(100deg,#fff_0%,#fff_7%,transparent_10%,transparent_12%,#fff_16%)\",\r\n\r\n \"--blue-300\": \"#93c5fd\",\r\n \"--blue-400\": \"#60a5fa\",\r\n \"--blue-500\": \"#3b82f6\",\r\n \"--indigo-300\": \"#a5b4fc\",\r\n \"--violet-200\": \"#ddd6fe\",\r\n \"--black\": \"#000\",\r\n \"--white\": \"#fff\",\r\n \"--transparent\": \"transparent\",\r\n \"--animate-aurora\": \"aurora 60s linear infinite\",\r\n };\r\n});\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as AuroraBackground } from \"./AuroraBackground.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "e5adb4691fb18fe72332491030138d006279efe4"
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "balance-slider",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "BalanceSlider.vue",
7
+ "content": "<template>\r\n <div\r\n class=\"slider-container relative mx-auto my-0 grid place-items-center overflow-hidden\"\r\n :style=\"sliderStyles\"\r\n @mouseenter=\"active = 1\"\r\n @mouseleave=\"active = 0\"\r\n @focusin=\"active = 1\"\r\n @focusout=\"active = 0\"\r\n @touchstart=\"active = 1\"\r\n @touchend=\"active = 0\"\r\n >\r\n <input\r\n id=\"track\"\r\n v-model=\"value\"\r\n type=\"range\"\r\n min=\"0\"\r\n max=\"100\"\r\n class=\"size-full touch-none opacity-0 hover:cursor-grab focus-visible:outline-offset-4 focus-visible:outline-transparent active:cursor-grabbing\"\r\n />\r\n <div\r\n aria-hidden=\"true\"\r\n :class=\"\r\n cn('slider-value-labels pointer-events-none absolute inset-x-0 top-0 z-[2] h-1/2 text-base')\r\n \"\r\n :style=\"sliderLabelStyles\"\r\n ></div>\r\n <div\r\n class=\"slider-track pointer-events-none absolute bottom-0 w-full\"\r\n :style=\"sliderTrackStyles\"\r\n >\r\n <div\r\n class=\"slider-indicator absolute top-1/2 z-[2] h-3/4 w-1 -translate-x-1/2 -translate-y-1/2 rounded-sm\"\r\n ></div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n initialValue?: number;\r\n leftColor?: string;\r\n rightColor?: string;\r\n minShiftLimit?: number;\r\n maxShiftLimit?: number;\r\n leftContent?: string;\r\n rightContent?: string;\r\n indicatorColor?: string;\r\n borderRadius?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n initialValue: 50,\r\n minShiftLimit: 40,\r\n maxShiftLimit: 68,\r\n leftContent: \"LEFT\",\r\n rightContent: \"RIGHT\",\r\n leftColor: \"#e68a00\",\r\n rightColor: \"#ffffff\",\r\n indicatorColor: \"#FFFFFF\",\r\n borderRadius: 8,\r\n});\r\n\r\nconst value = ref(props.initialValue);\r\nconst active = ref(0);\r\n\r\nconst shift = computed(() =>\r\n value.value > props.minShiftLimit && value.value < props.maxShiftLimit ? 1 : 0,\r\n);\r\n\r\nconst sliderStyles = computed(() => ({\r\n \"--value\": value.value,\r\n \"--shift\": shift.value,\r\n \"--active\": active.value,\r\n \"--leftContent\": `\"${props.leftContent} \"`,\r\n \"--rightContent\": `\" ${props.rightContent}\"`,\r\n \"--indicatorColor\": indicatorColorHsl.value,\r\n}));\r\n\r\nconst sliderLabelStyles = computed(() => ({\r\n \"--shift\": shift.value,\r\n}));\r\n\r\nconst sliderTrackStyles = computed(() => ({\r\n \"--value\": value.value,\r\n \"--shift\": shift.value,\r\n \"--leftColor\": leftColorHsl.value,\r\n \"--rightColor\": rightColorHsl.value,\r\n}));\r\n\r\nconst leftColorHsl = computed(() => {\r\n const [h, s, l] = hexToHsl(props.leftColor);\r\n const alpha = 0.4;\r\n const lightness = 24 + (30 * (100 - value.value)) / 100;\r\n return `hsl(${h} ${s}% ${lightness}% / ${alpha})`;\r\n});\r\n\r\nconst rightColorHsl = computed(() => {\r\n const [h, s, l] = hexToHsl(props.rightColor);\r\n const alpha = 0.1 + (0.4 * (100 - value.value)) / 100;\r\n return `hsl(${h} ${s}% ${l}% / ${alpha})`;\r\n});\r\n\r\nconst indicatorColorHsl = computed(() => {\r\n const [h, s, l] = hexToHsl(props.indicatorColor); // Base color as hex, here white (#ffffff)\r\n const activeAlpha = active.value * 0.5 + 0.5; // Calculate alpha based on active state\r\n return `hsl(${h} ${s}% ${l}% / ${activeAlpha})`;\r\n});\r\n\r\nconst borderRadiusInPx = computed(() => `${props.borderRadius}px`);\r\n\r\nfunction hexToHsl(hex: string): [number, number, number] {\r\n // Remove \"#\" if present\r\n hex = hex.replace(/^#/, \"\");\r\n\r\n // Parse r, g, b values\r\n let r = parseInt(hex.substring(0, 2), 16) / 255;\r\n let g = parseInt(hex.substring(2, 4), 16) / 255;\r\n let b = parseInt(hex.substring(4, 6), 16) / 255;\r\n\r\n // Find min and max values of r, g, b\r\n let max = Math.max(r, g, b),\r\n min = Math.min(r, g, b);\r\n let h = 0,\r\n s = 0,\r\n l = (max + min) / 2;\r\n\r\n if (max != min) {\r\n let d = max - min;\r\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\r\n switch (max) {\r\n case r:\r\n h = (g - b) / d + (g < b ? 6 : 0);\r\n break;\r\n case g:\r\n h = (b - r) / d + 2;\r\n break;\r\n case b:\r\n h = (r - g) / d + 4;\r\n break;\r\n }\r\n h /= 6;\r\n }\r\n\r\n return [h * 360, s * 100, l * 100];\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.slider-container {\r\n --speed: 0.65s;\r\n --update: 0s;\r\n --timing: linear(\r\n 0,\r\n 0.5007 7.21%,\r\n 0.7803 12.29%,\r\n 0.8883 14.93%,\r\n 0.9724 17.63%,\r\n 1.0343 20.44%,\r\n 1.0754 23.44%,\r\n 1.0898 25.22%,\r\n 1.0984 27.11%,\r\n 1.1014 29.15%,\r\n 1.0989 31.4%,\r\n 1.0854 35.23%,\r\n 1.0196 48.86%,\r\n 1.0043 54.06%,\r\n 0.9956 59.6%,\r\n 0.9925 68.11%,\r\n 1\r\n );\r\n}\r\n\r\n.slider-value-labels {\r\n transform: translateY(calc(var(--shift, 0) * 50%));\r\n transition: transform var(--speed) var(--timing);\r\n counter-reset: low var(--value) high calc(100 - var(--value));\r\n}\r\n\r\n.slider-value-labels::after,\r\n.slider-value-labels::before {\r\n font-variant: tabular-nums;\r\n position: absolute;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n font-weight: bold;\r\n color: white;\r\n font-family: monospace;\r\n}\r\n\r\n.slider-value-labels::before {\r\n --range: calc((70 - (var(--value) / 100 * 10)) * 1%);\r\n color: hsl(24 74% 54%);\r\n content: var(--leftContent) counter(low) \"%\";\r\n mask: linear-gradient(90deg, hsl(0 0% 100% / 0.6) var(--range), hsl(0 0% 100% / 1) var(--range));\r\n left: 0.5rem;\r\n}\r\n\r\n.slider-value-labels::after {\r\n --range: calc((50 - (var(--value) / 100 * 10)) * 1%);\r\n content: counter(high) \"% \" var(--rightContent);\r\n mask: linear-gradient(90deg, hsl(0 0% 100% / 1) var(--range), hsl(0 0% 100% / 0.5) var(--range));\r\n right: 0.5rem;\r\n}\r\n\r\n.slider-track {\r\n height: calc(50% + (var(--shift) * 50%));\r\n transition: height var(--speed) var(--timing);\r\n}\r\n\r\n.slider-track::before {\r\n content: \"\";\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n left: 0;\r\n width: calc(var(--value, 0) * 1% - 0.5rem);\r\n background: var(--leftColor);\r\n border-radius: v-bind(borderRadiusInPx);\r\n transition: width var(--update);\r\n}\r\n\r\n.slider-track::after {\r\n content: \"\";\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n right: 0;\r\n width: calc((100 - var(--value, 0)) * 1% - 0.5rem);\r\n background: var(--rightColor);\r\n border-radius: v-bind(borderRadiusInPx);\r\n transition: width var(--update);\r\n}\r\n\r\n.slider-indicator {\r\n background: var(--indicatorColor);\r\n left: calc(var(--value, 0) * 1%);\r\n transition: left var(--update);\r\n}\r\n\r\n/* Range input styles */\r\n[type=\"range\"]::-webkit-slider-thumb {\r\n appearance: none;\r\n height: 120px;\r\n width: 40px;\r\n margin-top: 0px;\r\n opacity: 1;\r\n}\r\n\r\n[type=\"range\"]::-webkit-slider-runnable-track {\r\n height: 120px;\r\n background: hsl(10 80% 50% / 0.5);\r\n margin-top: -60px;\r\n box-shadow:\r\n 1px 1px 1px #000000,\r\n 0px 0px 1px #0d0d0d;\r\n}\r\n\r\n[type=\"range\"]::-moz-range-track {\r\n height: 120px;\r\n background: hsl(10 80% 50% / 0.5);\r\n margin-top: -60px;\r\n box-shadow:\r\n 1px 1px 1px #000000,\r\n 0px 0px 1px #0d0d0d;\r\n}\r\n</style>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as BalanceSlider } from \"./BalanceSlider.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "511269f6a6bdd94fdaa1df002128a40f6376aafe"
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "bending-gallery",
3
+ "dependencies": [
4
+ "ogl"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "BendingGallery.vue",
9
+ "content": "<template>\r\n <div\r\n ref=\"containerRef\"\r\n class=\"w-full h-full overflow-hidden cursor-grab active:cursor-grabbing\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n\r\nimport { Camera, Mesh, Plane, Program, Renderer, Texture, Transform } from \"ogl\";\r\nimport { onBeforeUnmount, onMounted, ref, watch } from \"vue\";\r\n\r\ntype GL = Renderer[\"gl\"];\r\n\r\n// Props definition\r\ninterface CircularGalleryProps {\r\n items?: { image: string; text: string }[];\r\n bend?: number;\r\n textColor?: string;\r\n borderRadius?: number;\r\n font?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<CircularGalleryProps>(), {\r\n bend: 3,\r\n textColor: \"#ffffff\",\r\n borderRadius: 0.05,\r\n font: \"bold 30px DM Sans\",\r\n});\r\n\r\n// Refs\r\nconst containerRef = ref<HTMLDivElement | null>(null);\r\nlet app: App | null = null;\r\n\r\n// Utility functions\r\nfunction debounce<T extends (...args: any[]) => void>(func: T, wait: number) {\r\n let timeout: number;\r\n return function (this: any, ...args: Parameters<T>) {\r\n window.clearTimeout(timeout);\r\n timeout = window.setTimeout(() => func.apply(this, args), wait);\r\n };\r\n}\r\n\r\nfunction lerp(p1: number, p2: number, t: number): number {\r\n return p1 + (p2 - p1) * t;\r\n}\r\n\r\nfunction autoBind(instance: any): void {\r\n const proto = Object.getPrototypeOf(instance);\r\n Object.getOwnPropertyNames(proto).forEach((key) => {\r\n if (key !== \"constructor\" && typeof instance[key] === \"function\") {\r\n instance[key] = instance[key].bind(instance);\r\n }\r\n });\r\n}\r\n\r\nfunction getFontSize(font: string): number {\r\n const match = font.match(/(\\d+)px/);\r\n return match ? Number.parseInt(match[1], 10) : 30;\r\n}\r\n\r\nfunction createTextTexture(\r\n gl: GL,\r\n text: string,\r\n font: string = \"bold 30px monospace\",\r\n color: string = \"black\",\r\n): { texture: Texture; width: number; height: number } {\r\n const canvas = document.createElement(\"canvas\");\r\n const context = canvas.getContext(\"2d\");\r\n if (!context) throw new Error(\"Could not get 2d context\");\r\n\r\n context.font = font;\r\n const metrics = context.measureText(text);\r\n const textWidth = Math.ceil(metrics.width);\r\n const fontSize = getFontSize(font);\r\n const textHeight = Math.ceil(fontSize * 1.2);\r\n\r\n canvas.width = textWidth + 20;\r\n canvas.height = textHeight + 20;\r\n\r\n context.font = font;\r\n context.fillStyle = color;\r\n context.textBaseline = \"middle\";\r\n context.textAlign = \"center\";\r\n context.clearRect(0, 0, canvas.width, canvas.height);\r\n context.fillText(text, canvas.width / 2, canvas.height / 2);\r\n\r\n const texture = new Texture(gl, { generateMipmaps: false });\r\n texture.image = canvas;\r\n return { texture, width: canvas.width, height: canvas.height };\r\n}\r\n\r\n// Classes\r\ninterface TitleProps {\r\n gl: GL;\r\n plane: Mesh;\r\n renderer: Renderer;\r\n text: string;\r\n textColor?: string;\r\n font?: string;\r\n}\r\n\r\nclass Title {\r\n gl: GL;\r\n plane: Mesh;\r\n renderer: Renderer;\r\n text: string;\r\n textColor: string;\r\n font: string;\r\n mesh!: Mesh;\r\n\r\n constructor({\r\n gl,\r\n plane,\r\n renderer,\r\n text,\r\n textColor = \"#545050\",\r\n font = \"30px sans-serif\",\r\n }: TitleProps) {\r\n autoBind(this);\r\n this.gl = gl;\r\n this.plane = plane;\r\n this.renderer = renderer;\r\n this.text = text;\r\n this.textColor = textColor;\r\n this.font = font;\r\n this.createMesh();\r\n }\r\n\r\n createMesh() {\r\n const { texture, width, height } = createTextTexture(\r\n this.gl,\r\n this.text,\r\n this.font,\r\n this.textColor,\r\n );\r\n const geometry = new Plane(this.gl);\r\n const program = new Program(this.gl, {\r\n vertex: `\r\n attribute vec3 position;\r\n attribute vec2 uv;\r\n uniform mat4 modelViewMatrix;\r\n uniform mat4 projectionMatrix;\r\n varying vec2 vUv;\r\n void main() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n }\r\n `,\r\n fragment: `\r\n precision highp float;\r\n uniform sampler2D tMap;\r\n varying vec2 vUv;\r\n void main() {\r\n vec4 color = texture2D(tMap, vUv);\r\n if (color.a < 0.1) discard;\r\n gl_FragColor = color;\r\n }\r\n `,\r\n uniforms: { tMap: { value: texture } },\r\n transparent: true,\r\n });\r\n this.mesh = new Mesh(this.gl, { geometry, program });\r\n const aspect = width / height;\r\n const textHeightScaled = this.plane.scale.y * 0.15;\r\n const textWidthScaled = textHeightScaled * aspect;\r\n this.mesh.scale.set(textWidthScaled, textHeightScaled, 1);\r\n this.mesh.position.y = -this.plane.scale.y * 0.5 - textHeightScaled * 0.5 - 0.05;\r\n this.mesh.setParent(this.plane);\r\n }\r\n}\r\n\r\ninterface ScreenSize {\r\n width: number;\r\n height: number;\r\n}\r\n\r\ninterface Viewport {\r\n width: number;\r\n height: number;\r\n}\r\n\r\ninterface MediaProps {\r\n geometry: Plane;\r\n gl: GL;\r\n image: string;\r\n index: number;\r\n length: number;\r\n renderer: Renderer;\r\n scene: Transform;\r\n screen: ScreenSize;\r\n text: string;\r\n viewport: Viewport;\r\n bend: number;\r\n textColor: string;\r\n borderRadius?: number;\r\n font?: string;\r\n}\r\n\r\nclass Media {\r\n extra: number = 0;\r\n geometry: Plane;\r\n gl: GL;\r\n image: string;\r\n index: number;\r\n length: number;\r\n renderer: Renderer;\r\n scene: Transform;\r\n screen: ScreenSize;\r\n text: string;\r\n viewport: Viewport;\r\n bend: number;\r\n textColor: string;\r\n borderRadius: number;\r\n font?: string;\r\n program!: Program;\r\n plane!: Mesh;\r\n title!: Title;\r\n scale!: number;\r\n padding!: number;\r\n width!: number;\r\n widthTotal!: number;\r\n x!: number;\r\n speed: number = 0;\r\n isBefore: boolean = false;\r\n isAfter: boolean = false;\r\n\r\n constructor({\r\n geometry,\r\n gl,\r\n image,\r\n index,\r\n length,\r\n renderer,\r\n scene,\r\n screen,\r\n text,\r\n viewport,\r\n bend,\r\n textColor,\r\n borderRadius = 0,\r\n font,\r\n }: MediaProps) {\r\n this.geometry = geometry;\r\n this.gl = gl;\r\n this.image = image;\r\n this.index = index;\r\n this.length = length;\r\n this.renderer = renderer;\r\n this.scene = scene;\r\n this.screen = screen;\r\n this.text = text;\r\n this.viewport = viewport;\r\n this.bend = bend;\r\n this.textColor = textColor;\r\n this.borderRadius = borderRadius;\r\n this.font = font;\r\n this.createShader();\r\n this.createMesh();\r\n this.createTitle();\r\n this.onResize();\r\n }\r\n\r\n createShader() {\r\n const texture = new Texture(this.gl, { generateMipmaps: false });\r\n this.program = new Program(this.gl, {\r\n depthTest: false,\r\n depthWrite: false,\r\n vertex: `\r\n precision highp float;\r\n attribute vec3 position;\r\n attribute vec2 uv;\r\n uniform mat4 modelViewMatrix;\r\n uniform mat4 projectionMatrix;\r\n uniform float uTime;\r\n uniform float uSpeed;\r\n varying vec2 vUv;\r\n void main() {\r\n vUv = uv;\r\n vec3 p = position;\r\n p.z = (sin(p.x * 4.0 + uTime) * 1.5 + cos(p.y * 2.0 + uTime) * 1.5) * (0.1 + uSpeed * 0.5);\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);\r\n }\r\n `,\r\n fragment: `\r\n precision highp float;\r\n uniform vec2 uImageSizes;\r\n uniform vec2 uPlaneSizes;\r\n uniform sampler2D tMap;\r\n uniform float uBorderRadius;\r\n varying vec2 vUv;\r\n \r\n // Rounded box SDF for UV space\r\n float roundedBoxSDF(vec2 p, vec2 b, float r) {\r\n vec2 d = abs(p) - b;\r\n return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0) - r;\r\n }\r\n \r\n void main() {\r\n vec2 ratio = vec2(\r\n min((uPlaneSizes.x / uPlaneSizes.y) / (uImageSizes.x / uImageSizes.y), 1.0),\r\n min((uPlaneSizes.y / uPlaneSizes.x) / (uImageSizes.y / uImageSizes.x), 1.0)\r\n );\r\n vec2 uv = vec2(\r\n vUv.x * ratio.x + (1.0 - ratio.x) * 0.5,\r\n vUv.y * ratio.y + (1.0 - ratio.y) * 0.5\r\n );\r\n vec4 color = texture2D(tMap, uv);\r\n \r\n // Apply rounded corners (assumes vUv in [0,1])\r\n float d = roundedBoxSDF(vUv - 0.5, vec2(0.5 - uBorderRadius), uBorderRadius);\r\n if(d > 0.0) {\r\n discard;\r\n }\r\n \r\n gl_FragColor = vec4(color.rgb, 1.0);\r\n }\r\n `,\r\n uniforms: {\r\n tMap: { value: texture },\r\n uPlaneSizes: { value: [0, 0] },\r\n uImageSizes: { value: [0, 0] },\r\n uSpeed: { value: 0 },\r\n uTime: { value: 100 * Math.random() },\r\n uBorderRadius: { value: this.borderRadius },\r\n },\r\n transparent: true,\r\n });\r\n const img = new Image();\r\n img.crossOrigin = \"anonymous\";\r\n img.src = this.image;\r\n img.onload = () => {\r\n texture.image = img;\r\n this.program.uniforms.uImageSizes.value = [img.naturalWidth, img.naturalHeight];\r\n };\r\n }\r\n\r\n createMesh() {\r\n this.plane = new Mesh(this.gl, {\r\n geometry: this.geometry,\r\n program: this.program,\r\n });\r\n this.plane.setParent(this.scene);\r\n }\r\n\r\n createTitle() {\r\n this.title = new Title({\r\n gl: this.gl,\r\n plane: this.plane,\r\n renderer: this.renderer,\r\n text: this.text,\r\n textColor: this.textColor,\r\n font: this.font,\r\n });\r\n }\r\n\r\n update(scroll: { current: number; last: number }, direction: \"right\" | \"left\") {\r\n this.plane.position.x = this.x - scroll.current - this.extra;\r\n\r\n const x = this.plane.position.x;\r\n const H = this.viewport.width / 2;\r\n\r\n if (this.bend === 0) {\r\n this.plane.position.y = 0;\r\n this.plane.rotation.z = 0;\r\n } else {\r\n const B_abs = Math.abs(this.bend);\r\n const R = (H * H + B_abs * B_abs) / (2 * B_abs);\r\n const effectiveX = Math.min(Math.abs(x), H);\r\n\r\n const arc = R - Math.sqrt(R * R - effectiveX * effectiveX);\r\n if (this.bend > 0) {\r\n this.plane.position.y = -arc;\r\n this.plane.rotation.z = -Math.sign(x) * Math.asin(effectiveX / R);\r\n } else {\r\n this.plane.position.y = arc;\r\n this.plane.rotation.z = Math.sign(x) * Math.asin(effectiveX / R);\r\n }\r\n }\r\n\r\n this.speed = scroll.current - scroll.last;\r\n this.program.uniforms.uTime.value += 0.04;\r\n this.program.uniforms.uSpeed.value = this.speed;\r\n\r\n const planeOffset = this.plane.scale.x / 2;\r\n const viewportOffset = this.viewport.width / 2;\r\n this.isBefore = this.plane.position.x + planeOffset < -viewportOffset;\r\n this.isAfter = this.plane.position.x - planeOffset > viewportOffset;\r\n if (direction === \"right\" && this.isBefore) {\r\n this.extra -= this.widthTotal;\r\n this.isBefore = this.isAfter = false;\r\n }\r\n if (direction === \"left\" && this.isAfter) {\r\n this.extra += this.widthTotal;\r\n this.isBefore = this.isAfter = false;\r\n }\r\n }\r\n\r\n onResize({ screen, viewport }: { screen?: ScreenSize; viewport?: Viewport } = {}) {\r\n if (screen) this.screen = screen;\r\n if (viewport) {\r\n this.viewport = viewport;\r\n if (this.plane.program.uniforms.uViewportSizes) {\r\n this.plane.program.uniforms.uViewportSizes.value = [\r\n this.viewport.width,\r\n this.viewport.height,\r\n ];\r\n }\r\n }\r\n this.scale = this.screen.height / 1500;\r\n this.plane.scale.y = (this.viewport.height * (900 * this.scale)) / this.screen.height;\r\n this.plane.scale.x = (this.viewport.width * (700 * this.scale)) / this.screen.width;\r\n this.plane.program.uniforms.uPlaneSizes.value = [this.plane.scale.x, this.plane.scale.y];\r\n this.padding = 2;\r\n this.width = this.plane.scale.x + this.padding;\r\n this.widthTotal = this.width * this.length;\r\n this.x = this.width * this.index;\r\n }\r\n}\r\n\r\ninterface AppConfig {\r\n items?: { image: string; text: string }[];\r\n bend?: number;\r\n textColor?: string;\r\n borderRadius?: number;\r\n font?: string;\r\n}\r\n\r\nclass App {\r\n container: HTMLElement;\r\n scroll: {\r\n ease: number;\r\n current: number;\r\n target: number;\r\n last: number;\r\n position?: number;\r\n };\r\n onCheckDebounce: (...args: any[]) => void;\r\n renderer!: Renderer;\r\n gl!: GL;\r\n camera!: Camera;\r\n scene!: Transform;\r\n planeGeometry!: Plane;\r\n medias: Media[] = [];\r\n mediasImages: { image: string; text: string }[] = [];\r\n screen!: { width: number; height: number };\r\n viewport!: { width: number; height: number };\r\n raf: number = 0;\r\n\r\n boundOnResize!: () => void;\r\n boundOnWheel!: () => void;\r\n boundOnTouchDown!: (e: MouseEvent | TouchEvent) => void;\r\n boundOnTouchMove!: (e: MouseEvent | TouchEvent) => void;\r\n boundOnTouchUp!: () => void;\r\n\r\n isDown: boolean = false;\r\n start: number = 0;\r\n\r\n constructor(\r\n container: HTMLElement,\r\n {\r\n items,\r\n bend = 1,\r\n textColor = \"#ffffff\",\r\n borderRadius = 0,\r\n font = \"bold 30px DM Sans\",\r\n }: AppConfig,\r\n ) {\r\n this.container = container;\r\n this.scroll = { ease: 0.05, current: 0, target: 0, last: 0 };\r\n this.onCheckDebounce = debounce(this.onCheck.bind(this), 200);\r\n this.createRenderer();\r\n this.createCamera();\r\n this.createScene();\r\n this.onResize();\r\n this.createGeometry();\r\n this.createMedias(items, bend, textColor, borderRadius, font);\r\n this.update();\r\n this.addEventListeners();\r\n }\r\n\r\n createRenderer() {\r\n this.renderer = new Renderer({ alpha: true });\r\n this.gl = this.renderer.gl;\r\n this.gl.clearColor(0, 0, 0, 0);\r\n this.container.appendChild(this.renderer.gl.canvas as HTMLCanvasElement);\r\n }\r\n\r\n createCamera() {\r\n this.camera = new Camera(this.gl);\r\n this.camera.fov = 45;\r\n this.camera.position.z = 20;\r\n }\r\n\r\n createScene() {\r\n this.scene = new Transform();\r\n }\r\n\r\n createGeometry() {\r\n this.planeGeometry = new Plane(this.gl, {\r\n heightSegments: 50,\r\n widthSegments: 100,\r\n });\r\n }\r\n\r\n createMedias(\r\n items: { image: string; text: string }[] | undefined,\r\n bend: number = 1,\r\n textColor: string,\r\n borderRadius: number,\r\n font: string,\r\n ) {\r\n const defaultItems = [\r\n {\r\n image: `https://picsum.photos/seed/1/800/600?grayscale`,\r\n text: \"Bridge\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/2/800/600?grayscale`,\r\n text: \"Desk Setup\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/3/800/600?grayscale`,\r\n text: \"Waterfall\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/4/800/600?grayscale`,\r\n text: \"Strawberries\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/5/800/600?grayscale`,\r\n text: \"Deep Diving\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/16/800/600?grayscale`,\r\n text: \"Train Track\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/17/800/600?grayscale`,\r\n text: \"Santorini\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/8/800/600?grayscale`,\r\n text: \"Blurry Lights\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/9/800/600?grayscale`,\r\n text: \"New York\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/10/800/600?grayscale`,\r\n text: \"Good Boy\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/21/800/600?grayscale`,\r\n text: \"Coastline\",\r\n },\r\n {\r\n image: `https://picsum.photos/seed/12/800/600?grayscale`,\r\n text: \"Palm Trees\",\r\n },\r\n ];\r\n const galleryItems = items && items.length ? items : defaultItems;\r\n this.mediasImages = galleryItems.concat(galleryItems);\r\n this.medias = this.mediasImages.map((data, index) => {\r\n return new Media({\r\n geometry: this.planeGeometry,\r\n gl: this.gl,\r\n image: data.image,\r\n index,\r\n length: this.mediasImages.length,\r\n renderer: this.renderer,\r\n scene: this.scene,\r\n screen: this.screen,\r\n text: data.text,\r\n viewport: this.viewport,\r\n bend,\r\n textColor,\r\n borderRadius,\r\n font,\r\n });\r\n });\r\n }\r\n\r\n onTouchDown(e: MouseEvent | TouchEvent) {\r\n this.isDown = true;\r\n this.scroll.position = this.scroll.current;\r\n this.start = \"touches\" in e ? e.touches[0].clientX : e.clientX;\r\n }\r\n\r\n onTouchMove(e: MouseEvent | TouchEvent) {\r\n if (!this.isDown) return;\r\n const x = \"touches\" in e ? e.touches[0].clientX : e.clientX;\r\n const distance = (this.start - x) * 0.05;\r\n this.scroll.target = (this.scroll.position ?? 0) + distance;\r\n }\r\n\r\n onTouchUp() {\r\n this.isDown = false;\r\n this.onCheck();\r\n }\r\n\r\n onWheel() {\r\n this.scroll.target += 2;\r\n this.onCheckDebounce();\r\n }\r\n\r\n onCheck() {\r\n if (!this.medias || !this.medias[0]) return;\r\n const width = this.medias[0].width;\r\n const itemIndex = Math.round(Math.abs(this.scroll.target) / width);\r\n const item = width * itemIndex;\r\n this.scroll.target = this.scroll.target < 0 ? -item : item;\r\n }\r\n\r\n onResize() {\r\n this.screen = {\r\n width: this.container.clientWidth,\r\n height: this.container.clientHeight,\r\n };\r\n this.renderer.setSize(this.screen.width, this.screen.height);\r\n this.camera.perspective({\r\n aspect: this.screen.width / this.screen.height,\r\n });\r\n const fov = (this.camera.fov * Math.PI) / 180;\r\n const height = 2 * Math.tan(fov / 2) * this.camera.position.z;\r\n const width = height * this.camera.aspect;\r\n this.viewport = { width, height };\r\n if (this.medias) {\r\n this.medias.forEach((media) =>\r\n media.onResize({ screen: this.screen, viewport: this.viewport }),\r\n );\r\n }\r\n }\r\n\r\n update() {\r\n this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease);\r\n const direction = this.scroll.current > this.scroll.last ? \"right\" : \"left\";\r\n if (this.medias) {\r\n this.medias.forEach((media) => media.update(this.scroll, direction));\r\n }\r\n this.renderer.render({ scene: this.scene, camera: this.camera });\r\n this.scroll.last = this.scroll.current;\r\n this.raf = window.requestAnimationFrame(this.update.bind(this));\r\n }\r\n\r\n addEventListeners() {\r\n this.boundOnResize = this.onResize.bind(this);\r\n this.boundOnWheel = this.onWheel.bind(this);\r\n this.boundOnTouchDown = this.onTouchDown.bind(this);\r\n this.boundOnTouchMove = this.onTouchMove.bind(this);\r\n this.boundOnTouchUp = this.onTouchUp.bind(this);\r\n window.addEventListener(\"resize\", this.boundOnResize);\r\n window.addEventListener(\"mousewheel\", this.boundOnWheel);\r\n window.addEventListener(\"wheel\", this.boundOnWheel);\r\n window.addEventListener(\"mousedown\", this.boundOnTouchDown);\r\n window.addEventListener(\"mousemove\", this.boundOnTouchMove);\r\n window.addEventListener(\"mouseup\", this.boundOnTouchUp);\r\n window.addEventListener(\"touchstart\", this.boundOnTouchDown);\r\n window.addEventListener(\"touchmove\", this.boundOnTouchMove);\r\n window.addEventListener(\"touchend\", this.boundOnTouchUp);\r\n }\r\n\r\n destroy() {\r\n window.cancelAnimationFrame(this.raf);\r\n window.removeEventListener(\"resize\", this.boundOnResize);\r\n window.removeEventListener(\"mousewheel\", this.boundOnWheel);\r\n window.removeEventListener(\"wheel\", this.boundOnWheel);\r\n window.removeEventListener(\"mousedown\", this.boundOnTouchDown);\r\n window.removeEventListener(\"mousemove\", this.boundOnTouchMove);\r\n window.removeEventListener(\"mouseup\", this.boundOnTouchUp);\r\n window.removeEventListener(\"touchstart\", this.boundOnTouchDown);\r\n window.removeEventListener(\"touchmove\", this.boundOnTouchMove);\r\n window.removeEventListener(\"touchend\", this.boundOnTouchUp);\r\n if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) {\r\n this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas as HTMLCanvasElement);\r\n }\r\n }\r\n}\r\n\r\n// Vue lifecycle hooks\r\nonMounted(() => {\r\n if (!containerRef.value) return;\r\n\r\n app = new App(containerRef.value, {\r\n items: props.items,\r\n bend: props.bend,\r\n textColor: props.textColor,\r\n borderRadius: props.borderRadius,\r\n font: props.font,\r\n });\r\n});\r\n\r\n// Watch for props changes\r\nwatch([() => props.bend, () => props.textColor, () => props.borderRadius, () => props.font], () => {\r\n // Recreate the app with new props\r\n if (app) {\r\n app.destroy();\r\n app = null;\r\n }\r\n\r\n if (containerRef.value) {\r\n app = new App(containerRef.value, {\r\n items: props.items,\r\n bend: props.bend,\r\n textColor: props.textColor,\r\n borderRadius: props.borderRadius,\r\n font: props.font,\r\n });\r\n }\r\n});\r\n\r\n// Watch for items changes (need to handle this separately to deep watch)\r\nwatch(\r\n () => props.items,\r\n () => {\r\n if (app) {\r\n app.destroy();\r\n app = null;\r\n }\r\n\r\n if (containerRef.value) {\r\n app = new App(containerRef.value, {\r\n items: props.items,\r\n bend: props.bend,\r\n textColor: props.textColor,\r\n borderRadius: props.borderRadius,\r\n font: props.font,\r\n });\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonBeforeUnmount(() => {\r\n if (app) {\r\n app.destroy();\r\n app = null;\r\n }\r\n});\r\n</script>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as BendingGallery } from \"./BendingGallery.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "d541653b83407e9779bcdca72c23518f4b6dc4b8"
18
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "bento-grid",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "BentoGrid.vue",
7
+ "content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'grid md:auto-rows-[18rem] grid-cols-1 md:grid-cols-3 gap-4 max-w-7xl mx-auto ',\r\n props.class,\r\n )\r\n \"\r\n >\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { HTMLAttributes } from \"vue\";\r\n\r\ninterface Props {\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "BentoGridCard.vue",
11
+ "content": "<template>\r\n <div\r\n :key=\"name\"\r\n :class=\"\r\n cn(\r\n 'group relative col-span-3 flex flex-col justify-end overflow-hidden rounded-xl',\r\n // light styles\r\n 'bg-white [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]',\r\n // dark styles\r\n 'transform-gpu dark:bg-black dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]',\r\n props.class,\r\n )\r\n \"\r\n >\r\n <slot name=\"background\" />\r\n\r\n <div\r\n class=\"pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10\"\r\n >\r\n <component\r\n :is=\"icon\"\r\n v-if=\"icon\"\r\n class=\"size-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75\"\r\n />\r\n <div\r\n v-else\r\n class=\"size-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75\"\r\n ></div>\r\n <h3 class=\"text-xl font-semibold text-neutral-700 dark:text-neutral-300\">\r\n {{ name }}\r\n </h3>\r\n <p class=\"max-w-lg text-neutral-400\">{{ description }}</p>\r\n </div>\r\n\r\n <div\r\n class=\"pointer-events-none absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100\"\r\n >\r\n <a\r\n :href=\"href\"\r\n class=\"pointer-events-auto inline-flex items-center rounded-md px-3 py-1.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800\"\r\n >\r\n {{ cta }} →\r\n </a>\r\n </div>\r\n <div\r\n class=\"pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { HTMLAttributes } from \"vue\";\r\n\r\ninterface Props {\r\n name: string;\r\n class?: HTMLAttributes[\"class\"];\r\n icon?: string;\r\n description: string;\r\n href: string;\r\n cta: string;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n</script>\r\n"
12
+ },
13
+ {
14
+ "path": "BentoGridItem.vue",
15
+ "content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'row-span-1 rounded-xl group/bento hover:shadow-xl transition duration-200 shadow-input dark:shadow-none p-4 dark:bg-black dark:border-white/[0.2] bg-white border border-transparent justify-between flex flex-col space-y-4',\r\n props.class,\r\n )\r\n \"\r\n >\r\n <slot name=\"header\" />\r\n <div class=\"transition duration-200 group-hover/bento:translate-x-2\">\r\n <slot name=\"icon\" />\r\n <div class=\"my-2 font-sans font-bold text-neutral-600 dark:text-neutral-200\">\r\n <slot name=\"title\" />\r\n </div>\r\n <div class=\"font-sans text-xs font-normal text-neutral-600 dark:text-neutral-300\">\r\n <slot name=\"description\" />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { HTMLAttributes } from \"vue\";\r\n\r\ninterface Props {\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n</script>\r\n"
16
+ },
17
+ {
18
+ "path": "index.ts",
19
+ "content": "export { default as BentoGrid } from \"./BentoGrid.vue\";\r\nexport { default as BentoGridItem } from \"./BentoGridItem.vue\";\r\nexport { default as BentoGridCard } from \"./BentoGridCard.vue\";\r\n"
20
+ }
21
+ ],
22
+ "fileCount": 4,
23
+ "contentHash": "6f8da1ef18b97ac83ab222dbc89b55563dd8c62d"
24
+ }