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,14 @@
1
+ {
2
+ "name": "bg-black-hole",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "BlackHoleBackground.vue",
9
+ "content": "<template>\r\n <div\r\n data-slot=\"black-hole-background\"\r\n :class=\"[\r\n 'relative size-full overflow-hidden',\r\n `before:absolute before:left-1/2 before:top-1/2 before:block before:size-[140%] before:content-[''] before:[background:radial-gradient(ellipse_at_50%_55%,transparent_10%,white_50%)] before:[transform:translate3d(-50%,-50%,0)] dark:before:[background:radial-gradient(ellipse_at_50%_55%,transparent_10%,black_50%)]`,\r\n `after:absolute after:left-1/2 after:top-1/2 after:z-[5] after:block after:size-full after:mix-blend-overlay after:content-[''] after:[background:radial-gradient(ellipse_at_50%_75%,#a900ff_20%,transparent_75%)] after:[transform:translate3d(-50%,-50%,0)]`,\r\n ]\"\r\n v-bind=\"props\"\r\n >\r\n <slot></slot>\r\n <canvas\r\n ref=\"canvasRef\"\r\n class=\"absolute inset-0 block size-full opacity-10 dark:opacity-20\"\r\n />\r\n <motion.div\r\n :class=\"[\r\n 'absolute left-1/2 top-[-71.5%] z-[3] h-[140%] w-[30%] rounded-b-full opacity-75 mix-blend-plus-darker blur-3xl [background-position:0%_100%] [background-size:100%_200%] [transform:translate3d(-50%,0,0)] dark:mix-blend-plus-lighter',\r\n '[background:linear-gradient(20deg,#00f8f1,#ffbd1e40_16.5%,#fe848f_33%,#fe848f40_49.5%,#00f8f1_66%,#00f8f180_85.5%,#ffbd1e_100%)_0_100%_/_100%_200%] dark:[background:linear-gradient(20deg,#00f8f1,#ffbd1e20_16.5%,#fe848f_33%,#fe848f20_49.5%,#00f8f1_66%,#00f8f160_85.5%,#ffbd1e_100%)_0_100%_/_100%_200%]',\r\n ]\"\r\n :animate=\"{ backgroundPosition: '0% 300%' }\"\r\n :transition=\"{ duration: 5, ease: 'linear', repeat: Infinity }\"\r\n />\r\n <div\r\n class=\"absolute left-0 top-0 z-[7] size-full opacity-50 mix-blend-overlay dark:[background:repeating-linear-gradient(transparent,transparent_1px,white_1px,white_2px)]\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { motion } from \"motion-v\";\r\n\r\ninterface Disc {\r\n p: number;\r\n x: number;\r\n y: number;\r\n w: number;\r\n h: number;\r\n}\r\n\r\ninterface Point {\r\n x: number;\r\n y: number;\r\n}\r\n\r\ninterface Particle {\r\n x: number;\r\n sx: number;\r\n dx: number;\r\n y: number;\r\n vy: number;\r\n p: number;\r\n r: number;\r\n c: string;\r\n}\r\n\r\ninterface Clip {\r\n disc?: Disc;\r\n i?: number;\r\n path?: Path2D;\r\n}\r\n\r\ninterface State {\r\n discs: Disc[];\r\n lines: Point[][];\r\n particles: Particle[];\r\n clip: Clip;\r\n startDisc: Disc;\r\n endDisc: Disc;\r\n rect: { width: number; height: number };\r\n render: { width: number; height: number; dpi: number };\r\n particleArea: {\r\n sw?: number;\r\n ew?: number;\r\n h?: number;\r\n sx?: number;\r\n ex?: number;\r\n };\r\n linesCanvas?: HTMLCanvasElement;\r\n}\r\ninterface Props {\r\n strokeColor?: string;\r\n numberOfLines?: number;\r\n numberOfDiscs?: number;\r\n particleRGBColor?: [number, number, number];\r\n class?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n strokeColor: \"#737373\",\r\n numberOfLines: 50,\r\n numberOfDiscs: 50,\r\n particleRGBColor: () => [255, 255, 255],\r\n});\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\nconst animationFrameIdRef = ref<number>(0);\r\nconst stateRef = ref<State>({\r\n discs: [],\r\n lines: [],\r\n particles: [],\r\n clip: {},\r\n startDisc: { p: 0, x: 0, y: 0, w: 0, h: 0 },\r\n endDisc: { p: 0, x: 0, y: 0, w: 0, h: 0 },\r\n rect: { width: 0, height: 0 },\r\n render: { width: 0, height: 0, dpi: 1 },\r\n particleArea: {},\r\n});\r\n\r\nfunction linear(p: number) {\r\n return p;\r\n}\r\n\r\nfunction easeInExpo(p: number) {\r\n return p === 0 ? 0 : Math.pow(2, 10 * (p - 1));\r\n}\r\n\r\nfunction tweenValue(start: number, end: number, p: number, ease: \"inExpo\" | null = null) {\r\n const delta = end - start;\r\n const easeFn = ease === \"inExpo\" ? easeInExpo : linear;\r\n return start + delta * easeFn(p);\r\n}\r\n\r\nfunction tweenDisc(disc: Disc) {\r\n const { startDisc, endDisc } = stateRef.value;\r\n disc.x = tweenValue(startDisc.x, endDisc.x, disc.p);\r\n disc.y = tweenValue(startDisc.y, endDisc.y, disc.p, \"inExpo\");\r\n disc.w = tweenValue(startDisc.w, endDisc.w, disc.p);\r\n disc.h = tweenValue(startDisc.h, endDisc.h, disc.p);\r\n}\r\n\r\nfunction setSize() {\r\n const canvas = canvasRef.value;\r\n if (!canvas) return;\r\n const rect = canvas.getBoundingClientRect();\r\n stateRef.value.rect = { width: rect.width, height: rect.height };\r\n stateRef.value.render = {\r\n width: rect.width,\r\n height: rect.height,\r\n dpi: window.devicePixelRatio || 1,\r\n };\r\n canvas.width = stateRef.value.render.width * stateRef.value.render.dpi;\r\n canvas.height = stateRef.value.render.height * stateRef.value.render.dpi;\r\n}\r\n\r\nfunction setDiscs() {\r\n const { width, height } = stateRef.value.rect;\r\n if (width <= 0 || height <= 0) return;\r\n\r\n stateRef.value.discs = [];\r\n stateRef.value.startDisc = {\r\n p: 0,\r\n x: width * 0.5,\r\n y: height * 0.45,\r\n w: width * 0.75,\r\n h: height * 0.7,\r\n };\r\n stateRef.value.endDisc = {\r\n p: 0,\r\n x: width * 0.5,\r\n y: height * 0.95,\r\n w: 0,\r\n h: 0,\r\n };\r\n\r\n let prevBottom = height;\r\n stateRef.value.clip = {};\r\n\r\n for (let i = 0; i < props.numberOfDiscs; i++) {\r\n const p = i / props.numberOfDiscs;\r\n const disc = { p, x: 0, y: 0, w: 0, h: 0 };\r\n tweenDisc(disc);\r\n const bottom = disc.y + disc.h;\r\n if (bottom <= prevBottom) {\r\n stateRef.value.clip = { disc: { ...disc }, i };\r\n }\r\n prevBottom = bottom;\r\n stateRef.value.discs.push(disc);\r\n }\r\n\r\n if (stateRef.value.clip.disc) {\r\n const clipPath = new Path2D();\r\n const disc = stateRef.value.clip.disc;\r\n clipPath.ellipse(disc.x, disc.y, disc.w, disc.h, 0, 0, Math.PI * 2);\r\n clipPath.rect(disc.x - disc.w, 0, disc.w * 2, disc.y);\r\n stateRef.value.clip.path = clipPath;\r\n }\r\n}\r\n\r\nfunction setLines() {\r\n const { width, height } = stateRef.value.rect;\r\n // Ensure we have valid dimensions\r\n if (width <= 0 || height <= 0) return;\r\n\r\n stateRef.value.lines = [];\r\n const linesAngle = (Math.PI * 2) / props.numberOfLines;\r\n for (let i = 0; i < props.numberOfLines; i++) {\r\n stateRef.value.lines.push([]);\r\n }\r\n\r\n stateRef.value.discs.forEach((disc: Disc) => {\r\n for (let i = 0; i < props.numberOfLines; i++) {\r\n const angle = i * linesAngle;\r\n const p = {\r\n x: disc.x + Math.cos(angle) * disc.w,\r\n y: disc.y + Math.sin(angle) * disc.h,\r\n };\r\n stateRef.value.lines[i].push(p);\r\n }\r\n });\r\n\r\n const offCanvas = document.createElement(\"canvas\");\r\n // Ensure we set dimensions before getting context\r\n offCanvas.width = Math.max(1, width); // Ensure at least 1px\r\n offCanvas.height = Math.max(1, height); // Ensure at least 1px\r\n\r\n const ctx = offCanvas.getContext(\"2d\");\r\n if (!ctx || !stateRef.value.clip.path) {\r\n stateRef.value.linesCanvas = undefined;\r\n return;\r\n }\r\n\r\n // Clear the canvas first\r\n ctx.clearRect(0, 0, offCanvas.width, offCanvas.height);\r\n\r\n stateRef.value.lines.forEach((line: Point[]) => {\r\n ctx.save();\r\n let lineIsIn = false;\r\n line.forEach((p1: Point, j: number) => {\r\n if (j === 0) return;\r\n const p0 = line[j - 1];\r\n if (\r\n !lineIsIn &&\r\n (ctx.isPointInPath(stateRef.value.clip.path!, p1.x, p1.y) ||\r\n ctx.isPointInStroke(stateRef.value.clip.path!, p1.x, p1.y))\r\n ) {\r\n lineIsIn = true;\r\n } else if (lineIsIn) {\r\n ctx.clip(stateRef.value.clip.path!);\r\n }\r\n ctx.beginPath();\r\n ctx.moveTo(p0.x, p0.y);\r\n ctx.lineTo(p1.x, p1.y);\r\n ctx.strokeStyle = props.strokeColor;\r\n ctx.lineWidth = 2;\r\n ctx.stroke();\r\n ctx.closePath();\r\n });\r\n ctx.restore();\r\n });\r\n stateRef.value.linesCanvas = offCanvas;\r\n}\r\n\r\nfunction initParticle(start: boolean = false): Particle {\r\n const sx =\r\n (stateRef.value.particleArea.sx || 0) + (stateRef.value.particleArea.sw || 0) * Math.random();\r\n const ex =\r\n (stateRef.value.particleArea.ex || 0) + (stateRef.value.particleArea.ew || 0) * Math.random();\r\n const dx = ex - sx;\r\n const y = start\r\n ? (stateRef.value.particleArea.h || 0) * Math.random()\r\n : stateRef.value.particleArea.h || 0;\r\n const r = 0.5 + Math.random() * 4;\r\n const vy = 0.5 + Math.random();\r\n return {\r\n x: sx,\r\n sx,\r\n dx,\r\n y,\r\n vy,\r\n p: 0,\r\n r,\r\n c: `rgba(${props.particleRGBColor[0]}, ${props.particleRGBColor[1]}, ${props.particleRGBColor[2]}, ${Math.random()})`,\r\n };\r\n}\r\n\r\nfunction setParticles() {\r\n const { width, height } = stateRef.value.rect;\r\n stateRef.value.particles = [];\r\n const disc = stateRef.value.clip.disc;\r\n if (!disc) return;\r\n stateRef.value.particleArea = {\r\n sw: disc.w * 0.5,\r\n ew: disc.w * 2,\r\n h: height * 0.85,\r\n };\r\n stateRef.value.particleArea.sx = (width - (stateRef.value.particleArea.sw || 0)) / 2;\r\n stateRef.value.particleArea.ex = (width - (stateRef.value.particleArea.ew || 0)) / 2;\r\n const totalParticles = 100;\r\n for (let i = 0; i < totalParticles; i++) {\r\n stateRef.value.particles.push(initParticle(true));\r\n }\r\n}\r\n\r\nfunction drawDiscs(ctx: CanvasRenderingContext2D) {\r\n ctx.strokeStyle = props.strokeColor;\r\n ctx.lineWidth = 2;\r\n const outerDisc = stateRef.value.startDisc;\r\n ctx.beginPath();\r\n ctx.ellipse(outerDisc.x, outerDisc.y, outerDisc.w, outerDisc.h, 0, 0, Math.PI * 2);\r\n ctx.stroke();\r\n ctx.closePath();\r\n stateRef.value.discs.forEach((disc: Disc, i: number) => {\r\n if (i % 5 !== 0) return;\r\n if (disc.w < (stateRef.value.clip.disc?.w || 0) - 5) {\r\n ctx.save();\r\n ctx.clip(stateRef.value.clip.path!);\r\n }\r\n ctx.beginPath();\r\n ctx.ellipse(disc.x, disc.y, disc.w, disc.h, 0, 0, Math.PI * 2);\r\n ctx.stroke();\r\n ctx.closePath();\r\n if (disc.w < (stateRef.value.clip.disc?.w || 0) - 5) {\r\n ctx.restore();\r\n }\r\n });\r\n}\r\n\r\nfunction drawLines(ctx: CanvasRenderingContext2D) {\r\n if (\r\n stateRef.value.linesCanvas &&\r\n stateRef.value.linesCanvas.width > 0 &&\r\n stateRef.value.linesCanvas.height > 0\r\n ) {\r\n ctx.drawImage(stateRef.value.linesCanvas, 0, 0);\r\n }\r\n}\r\n\r\nfunction drawParticles(ctx: CanvasRenderingContext2D) {\r\n ctx.save();\r\n ctx.clip(stateRef.value.clip.path!);\r\n stateRef.value.particles.forEach((particle: Particle) => {\r\n ctx.fillStyle = particle.c;\r\n ctx.beginPath();\r\n ctx.rect(particle.x, particle.y, particle.r, particle.r);\r\n ctx.closePath();\r\n ctx.fill();\r\n });\r\n ctx.restore();\r\n}\r\n\r\nfunction moveDiscs() {\r\n stateRef.value.discs.forEach((disc: Disc) => {\r\n disc.p = (disc.p + 0.001) % 1;\r\n tweenDisc(disc);\r\n });\r\n}\r\n\r\nfunction moveParticles() {\r\n stateRef.value.particles.forEach((particle: Particle, idx: number) => {\r\n particle.p = 1 - particle.y / (stateRef.value.particleArea.h || 1);\r\n particle.x = particle.sx + particle.dx * particle.p;\r\n particle.y -= particle.vy;\r\n if (particle.y < 0) {\r\n stateRef.value.particles[idx] = initParticle();\r\n }\r\n });\r\n}\r\n\r\nfunction tick() {\r\n const canvas = canvasRef.value;\r\n if (!canvas) return;\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return;\r\n ctx.clearRect(0, 0, canvas.width, canvas.height);\r\n ctx.save();\r\n ctx.scale(stateRef.value.render.dpi, stateRef.value.render.dpi);\r\n moveDiscs();\r\n moveParticles();\r\n drawDiscs(ctx);\r\n drawLines(ctx);\r\n drawParticles(ctx);\r\n ctx.restore();\r\n animationFrameIdRef.value = requestAnimationFrame(tick);\r\n}\r\n\r\nfunction init() {\r\n setSize();\r\n setDiscs();\r\n setLines();\r\n setParticles();\r\n}\r\n\r\nfunction handleResize() {\r\n setSize();\r\n setDiscs();\r\n setLines();\r\n setParticles();\r\n}\r\n\r\nonMounted(() => {\r\n nextTick(() => {\r\n setSize();\r\n init();\r\n tick();\r\n window.addEventListener(\"resize\", handleResize);\r\n });\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n window.removeEventListener(\"resize\", handleResize);\r\n cancelAnimationFrame(animationFrameIdRef.value);\r\n});\r\n</script>\r\n"
10
+ }
11
+ ],
12
+ "fileCount": 1,
13
+ "contentHash": "4b007140cf451f87e7478adca2e53e76b260b353"
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "bg-bubbles",
3
+ "dependencies": [
4
+ "three"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "BubblesBg.vue",
9
+ "content": "<template>\r\n <div\r\n ref=\"bubbleParentContainer\"\r\n class=\"relative h-72 w-full overflow-hidden\"\r\n >\r\n <div ref=\"bubbleCanvasContainer\"></div>\r\n <div\r\n :style=\"{\r\n '--bubbles-blur': `${blur}px`,\r\n }\"\r\n class=\"absolute inset-0 z-[2] size-full backdrop-blur-[--bubbles-blur]\"\r\n >\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n ShaderMaterial,\r\n SphereGeometry,\r\n Vector3,\r\n Color,\r\n MathUtils,\r\n Mesh,\r\n Clock,\r\n WebGLRenderer,\r\n Scene,\r\n PerspectiveCamera,\r\n} from \"three\";\r\nimport { ref, onMounted, onBeforeUnmount } from \"vue\";\r\n\r\ndefineProps({\r\n blur: {\r\n type: Number,\r\n default: 0,\r\n },\r\n});\r\n\r\nconst bubbleParentContainer = ref<HTMLElement | null>(null);\r\nconst bubbleCanvasContainer = ref<HTMLElement | null>(null);\r\nlet renderer: WebGLRenderer;\r\nlet scene: Scene;\r\nlet camera: PerspectiveCamera;\r\nlet clock: Clock;\r\nconst spheres: Mesh[] = [];\r\n\r\nconst BG_COLOR_BOTTOM_BLUISH = rgb(170, 215, 217);\r\nconst BG_COLOR_TOP_BLUISH = rgb(57, 167, 255);\r\nconst BG_COLOR_BOTTOM_ORANGISH = rgb(255, 160, 75);\r\nconst BG_COLOR_TOP_ORANGISH = rgb(239, 172, 53);\r\n\r\nconst SPHERE_COLOR_BOTTOM_BLUISH = rgb(120, 235, 124);\r\nconst SPHERE_COLOR_TOP_BLUISH = rgb(0, 167, 255);\r\nconst SPHERE_COLOR_BOTTOM_ORANGISH = rgb(235, 170, 0);\r\nconst SPHERE_COLOR_TOP_ORANGISH = rgb(255, 120, 0);\r\n\r\nconst SPHERE_COUNT = 250;\r\nconst SPHERE_SCALE_COEFF = 3;\r\nconst ORBIT_MIN = SPHERE_SCALE_COEFF + 2;\r\nconst ORBIT_MAX = ORBIT_MIN + 10;\r\nconst RAND_SEED = 898211544;\r\n\r\nconst rand = seededRandom(RAND_SEED);\r\n\r\nconst { PI, cos, sin } = Math;\r\nconst PI2 = PI * 2;\r\nconst sizes = new Array(SPHERE_COUNT).fill(0).map(() => randRange(1) * Math.pow(randRange(), 3));\r\nconst orbitRadii = new Array(SPHERE_COUNT)\r\n .fill(0)\r\n .map(() => MathUtils.lerp(ORBIT_MIN, ORBIT_MAX, randRange()));\r\nconst thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));\r\nconst phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));\r\nconst positions: [number, number, number][] = orbitRadii.map((rad, i) => [\r\n rad * cos(thetas[i]) * sin(phis[i]),\r\n rad * sin(thetas[i]) * sin(phis[i]),\r\n rad * cos(phis[i]),\r\n]);\r\n\r\nconst sphereGeometry = new SphereGeometry(SPHERE_SCALE_COEFF);\r\nconst sphereMaterial = getGradientMaterial(\r\n SPHERE_COLOR_BOTTOM_BLUISH,\r\n SPHERE_COLOR_TOP_BLUISH,\r\n SPHERE_COLOR_BOTTOM_ORANGISH,\r\n SPHERE_COLOR_TOP_ORANGISH,\r\n);\r\n\r\nconst bgGeometry = new SphereGeometry();\r\nbgGeometry.scale(-1, 1, 1);\r\nconst bgMaterial = getGradientMaterial(\r\n BG_COLOR_BOTTOM_BLUISH,\r\n BG_COLOR_TOP_BLUISH,\r\n BG_COLOR_BOTTOM_ORANGISH,\r\n BG_COLOR_TOP_ORANGISH,\r\n);\r\nbgMaterial.uniforms.uTemperatureVariancePeriod.value = new Vector3(0, 0, 0.1);\r\n\r\nfunction seededRandom(a: number) {\r\n return function () {\r\n a |= 0;\r\n a = (a + 0x9e3779b9) | 0;\r\n var t = a ^ (a >>> 16);\r\n t = Math.imul(t, 0x21f0aaad);\r\n t = t ^ (t >>> 15);\r\n t = Math.imul(t, 0x735a2d97);\r\n return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;\r\n };\r\n}\r\n\r\nfunction randRange(n = 1) {\r\n return rand() * n;\r\n}\r\n\r\nfunction rgb(r: number, g: number, b: number) {\r\n return new Color(r / 255, g / 255, b / 255);\r\n}\r\n\r\nfunction getGradientMaterial(\r\n colorBottomWarm: Color,\r\n colorTopWarm: Color,\r\n colorBottomCool: Color,\r\n colorTopCool: Color,\r\n) {\r\n return new ShaderMaterial({\r\n uniforms: {\r\n colorBottomWarm: {\r\n value: new Color().copy(colorBottomWarm),\r\n },\r\n colorTopWarm: {\r\n value: new Color().copy(colorTopWarm),\r\n },\r\n colorBottomCool: {\r\n value: new Color().copy(colorBottomCool),\r\n },\r\n colorTopCool: {\r\n value: new Color().copy(colorTopCool),\r\n },\r\n uTemperature: {\r\n value: 0.0,\r\n },\r\n uTemperatureVariancePeriod: {\r\n value: new Vector3(0.08, 0.1, 0.2),\r\n },\r\n uElapsedTime: {\r\n value: 0,\r\n },\r\n },\r\n vertexShader: `\r\n uniform vec4 uTemperatureVariancePeriod;\r\n uniform float uTemperature;\r\n uniform float uElapsedTime;\r\n varying float topBottomMix;\r\n varying float warmCoolMix;\r\n\r\n void main() {\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);\r\n topBottomMix = normal.y;\r\n warmCoolMix = 0.6 * uTemperature +\r\n 0.4 * (sin(\r\n (uElapsedTime + gl_Position.x) * uTemperatureVariancePeriod.x +\r\n (uElapsedTime + gl_Position.y) * uTemperatureVariancePeriod.y +\r\n (uElapsedTime + gl_Position.z) * uTemperatureVariancePeriod.z) * 0.5 + 0.5);\r\n }\r\n `,\r\n fragmentShader: `\r\n uniform vec3 colorBottomWarm;\r\n uniform vec3 colorTopWarm;\r\n uniform vec3 colorBottomCool;\r\n uniform vec3 colorTopCool;\r\n\r\n varying float topBottomMix;\r\n varying float warmCoolMix;\r\n\r\n void main() {\r\n gl_FragColor = vec4(mix(\r\n mix(colorTopCool, colorTopWarm, warmCoolMix),\r\n mix(colorBottomCool, colorBottomWarm, warmCoolMix),\r\n topBottomMix), 1.0);\r\n }\r\n `,\r\n });\r\n}\r\n\r\nfunction createScene() {\r\n const width = bubbleCanvasContainer.value?.clientWidth || 1;\r\n const height = bubbleCanvasContainer.value?.clientHeight || 1;\r\n // Set up the scene, camera, and renderer\r\n scene = new Scene();\r\n camera = new PerspectiveCamera(50, width / height, 1, 2000);\r\n camera.position.x = 0;\r\n camera.position.y = 0;\r\n camera.position.z = 23;\r\n\r\n renderer = new WebGLRenderer({ antialias: true });\r\n renderer.setSize(width, height);\r\n renderer.setClearColor(BG_COLOR_BOTTOM_BLUISH);\r\n\r\n // Add these properties to allow overlap\r\n sphereMaterial.depthWrite = false;\r\n sphereMaterial.depthTest = true; // Keep this true for depth sorting\r\n\r\n if (bubbleCanvasContainer.value) {\r\n bubbleCanvasContainer.value.appendChild(renderer.domElement);\r\n }\r\n\r\n // Create the background mesh\r\n const bgMesh = new Mesh(bgGeometry, bgMaterial);\r\n // Position the background far behind everything\r\n bgMesh.position.set(0, 0, -1); // Move the background far back\r\n\r\n // Disable depth testing for the background to ensure it's always behind other objects\r\n bgMesh.material.depthTest = false;\r\n bgMesh.renderOrder = -1; // Ensure the background is rendered first\r\n\r\n // Calculate the scale to ensure the background covers the full canvas\r\n const distance = camera.position.z; // Distance from the camera\r\n const aspect = camera.aspect;\r\n const frustumHeight = 2 * distance * Math.tan(MathUtils.degToRad(camera.fov) / 2);\r\n const frustumWidth = frustumHeight * aspect;\r\n\r\n // Scale the background geometry to match the camera's frustum size\r\n bgMesh.scale.set(\r\n frustumWidth / bgGeometry.parameters.radius,\r\n frustumHeight / bgGeometry.parameters.radius,\r\n 1,\r\n );\r\n\r\n scene.add(bgMesh); // Add the backgrou\r\n\r\n // Create sphere meshes\r\n const orbitRadii = new Array(SPHERE_COUNT)\r\n .fill(0)\r\n .map(() => MathUtils.lerp(ORBIT_MIN, ORBIT_MAX, randRange()));\r\n const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));\r\n const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));\r\n const positions = orbitRadii.map((rad, i) => [\r\n rad * cos(thetas[i]) * sin(phis[i]),\r\n rad * sin(thetas[i]) * sin(phis[i]),\r\n rad * cos(phis[i]),\r\n ]);\r\n\r\n for (let i = 0; i < SPHERE_COUNT; i++) {\r\n const sphere = new Mesh(sphereGeometry, sphereMaterial);\r\n const [x, y, z] = positions[i];\r\n const scaleVector = sizes[i];\r\n sphere.scale.set(scaleVector, scaleVector, scaleVector);\r\n sphere.position.set(x, y, z);\r\n spheres.push(sphere);\r\n scene.add(sphere);\r\n }\r\n\r\n clock = new Clock();\r\n}\r\n\r\nfunction animate() {\r\n requestAnimationFrame(animate);\r\n\r\n const elapsed = clock.getElapsedTime();\r\n const temperature = sin(elapsed * 0.5) * 0.5 + 0.5;\r\n\r\n bgMaterial.uniforms.uTemperature.value = temperature;\r\n bgMaterial.uniforms.uElapsedTime.value = elapsed;\r\n\r\n sphereMaterial.uniforms.uTemperature.value = temperature;\r\n sphereMaterial.uniforms.uElapsedTime.value = elapsed;\r\n\r\n // Floating effect for spheres\r\n spheres.forEach((sphere, index) => {\r\n const basePosition = positions[index];\r\n const floatFactor = 2; // Adjust this value to control float intensity\r\n const speed = 0.3; // Adjust this value to control float speed\r\n const floatY = sin(elapsed * speed + index) * floatFactor;\r\n sphere.position.y = basePosition[1] + floatY;\r\n });\r\n\r\n renderer.render(scene, camera);\r\n}\r\n\r\nfunction updateRendererSize() {\r\n const width = bubbleParentContainer.value?.clientWidth || 1;\r\n const height = bubbleParentContainer.value?.clientHeight || 1;\r\n\r\n // Update renderer size and aspect ratio\r\n renderer.setSize(width, height);\r\n camera.aspect = width / height;\r\n camera.updateProjectionMatrix();\r\n\r\n // Recalculate background mesh scale\r\n const distance = camera.position.z;\r\n const frustumHeight = 2 * distance * Math.tan(MathUtils.degToRad(camera.fov) / 2);\r\n const frustumWidth = frustumHeight * camera.aspect;\r\n\r\n // Get the background mesh and update its scale\r\n const bgMesh = scene.children.find(\r\n (obj) => obj instanceof Mesh && obj.geometry === bgGeometry,\r\n ) as Mesh;\r\n if (bgMesh) {\r\n bgMesh.scale.set(\r\n frustumWidth / bgGeometry.parameters.radius,\r\n frustumHeight / bgGeometry.parameters.radius,\r\n 1,\r\n );\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n createScene();\r\n updateRendererSize();\r\n window.addEventListener(\"resize\", updateRendererSize);\r\n animate();\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n window.removeEventListener(\"resize\", updateRendererSize); // Cleanup on component unmount\r\n});\r\n</script>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as BubblesBg } from \"./BubblesBg.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "ce453fd14dd289a54428cb21eb511bb55c4ad78c"
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "bg-falling-stars",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "FallingStarsBg.vue",
7
+ "content": "<template>\r\n <canvas\r\n ref=\"starsCanvas\"\r\n :class=\"cn('absolute inset-0 w-full h-full', $props.class)\"\r\n ></canvas>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { onMounted, ref } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Star {\r\n x: number;\r\n y: number;\r\n z: number;\r\n speed: number;\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n color?: string;\r\n count?: number;\r\n class?: string;\r\n }>(),\r\n {\r\n color: \"#FFF\",\r\n count: 200,\r\n },\r\n);\r\n\r\nconst starsCanvas = ref<HTMLCanvasElement | null>(null);\r\nlet perspective: number = 0;\r\nlet stars: Star[] = [];\r\nlet ctx: CanvasRenderingContext2D | null = null;\r\n\r\nonMounted(() => {\r\n const canvas = starsCanvas.value;\r\n if (!canvas) return;\r\n\r\n window.addEventListener(\"resize\", resizeCanvas);\r\n resizeCanvas(); // Call it initially to set correct size\r\n\r\n perspective = canvas.width / 2;\r\n stars = [];\r\n\r\n // Initialize stars\r\n for (let i = 0; i < props.count; i++) {\r\n stars.push({\r\n x: (Math.random() - 0.5) * 2 * canvas.width,\r\n y: (Math.random() - 0.5) * 2 * canvas.height,\r\n z: Math.random() * canvas.width,\r\n speed: Math.random() * 5 + 2, // Speed for falling effect\r\n });\r\n }\r\n\r\n animate(); // Start animation\r\n});\r\n\r\nfunction hexToRgb() {\r\n let hex = props.color.replace(/^#/, \"\");\r\n\r\n // If the hex code is 3 characters, expand it to 6 characters\r\n if (hex.length === 3) {\r\n hex = hex\r\n .split(\"\")\r\n .map((char) => char + char)\r\n .join(\"\");\r\n }\r\n\r\n // Parse the r, g, b values from the hex string\r\n const bigint = parseInt(hex, 16);\r\n const r = (bigint >> 16) & 255; // Extract the red component\r\n const g = (bigint >> 8) & 255; // Extract the green component\r\n const b = bigint & 255; // Extract the blue component\r\n\r\n // Return the RGB values as a string separated by spaces\r\n return {\r\n r,\r\n g,\r\n b,\r\n };\r\n}\r\n\r\n// Function to draw a star with a sharp line and blurred trail\r\nfunction drawStar(star: Star) {\r\n const canvas = starsCanvas.value;\r\n if (!canvas) return;\r\n\r\n ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return;\r\n\r\n const scale = perspective / (perspective + star.z); // 3D perspective scale\r\n const x2d = canvas.width / 2 + star.x * scale;\r\n const y2d = canvas.height / 2 + star.y * scale;\r\n const size = Math.max(scale * 3, 0.5); // Size based on perspective\r\n\r\n // Previous position for a trail effect\r\n const prevScale = perspective / (perspective + star.z + star.speed * 15); // Longer trail distance\r\n const xPrev = canvas.width / 2 + star.x * prevScale;\r\n const yPrev = canvas.height / 2 + star.y * prevScale;\r\n\r\n const rgb = hexToRgb();\r\n\r\n // Draw blurred trail (longer, with low opacity)\r\n ctx.save(); // Save current context state for restoring later\r\n ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.2)`;\r\n ctx.lineWidth = size * 2.5; // Thicker trail for a blur effect\r\n ctx.shadowBlur = 35; // Add blur to the trail\r\n ctx.shadowColor = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.8)`;\r\n ctx.beginPath();\r\n ctx.moveTo(x2d, y2d);\r\n ctx.lineTo(xPrev, yPrev); // Longer trail\r\n ctx.stroke();\r\n ctx.restore(); // Restore context state to remove blur from the main line\r\n\r\n // Draw sharp line (no blur)\r\n ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.6)`;\r\n ctx.lineWidth = size; // The line width is the same as the star's size\r\n ctx.beginPath();\r\n ctx.moveTo(x2d, y2d);\r\n ctx.lineTo(xPrev, yPrev); // Sharp trail\r\n ctx.stroke();\r\n\r\n // Draw the actual star (dot)\r\n ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`;\r\n ctx.beginPath();\r\n ctx.arc(x2d, y2d, size / 4, 0, Math.PI * 2); // Dot with size matching the width\r\n ctx.fill();\r\n}\r\n\r\n// Function to animate the stars\r\nfunction animate() {\r\n const canvas = starsCanvas.value;\r\n if (!canvas) return;\r\n\r\n ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return;\r\n\r\n ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas for each frame\r\n\r\n stars.forEach((star) => {\r\n drawStar(star);\r\n\r\n // Move star towards the screen (decrease z)\r\n star.z -= star.speed;\r\n\r\n // Reset star when it reaches the viewer (z = 0)\r\n if (star.z <= 0) {\r\n star.z = canvas.width;\r\n star.x = (Math.random() - 0.5) * 2 * canvas.width;\r\n star.y = (Math.random() - 0.5) * 2 * canvas.height;\r\n }\r\n });\r\n\r\n requestAnimationFrame(animate); // Continue animation\r\n}\r\n\r\n// Set canvas to full screen\r\nfunction resizeCanvas() {\r\n const canvas = starsCanvas.value;\r\n if (!canvas) return;\r\n\r\n canvas.width = canvas.clientWidth;\r\n canvas.height = canvas.clientHeight;\r\n}\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as FallingStarsBg } from \"./FallingStarsBg.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "7ea35cdcc4e88173d2659eb2914f0455153a5dd4"
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "bg-neural",
3
+ "dependencies": [
4
+ "ogl"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "NeuralBg.vue",
9
+ "content": "<template>\r\n <canvas\r\n ref=\"canvasRef\"\r\n :class=\"cn('absolute inset-0 size-full pointer-events-none opacity-95', props.class)\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, onMounted, onUnmounted, watch, type HTMLAttributes } from \"vue\";\r\nimport { Renderer, Camera, Transform, Program, Mesh, Plane } from \"ogl\";\r\nimport { cn } from \"~/lib/utils\";\r\n\r\ninterface Props {\r\n hue?: number;\r\n saturation?: number;\r\n chroma?: number;\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n hue: 200, // Blue hue by default\r\n saturation: 0.8,\r\n chroma: 0.6,\r\n});\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\nconst animationRef = ref<number | null>(null);\r\nconst rendererRef = ref<Renderer | null>(null);\r\nconst sceneRef = ref<Transform | null>(null);\r\nconst meshRef = ref<Mesh | null>(null);\r\nconst cameraRef = ref<Camera | null>(null);\r\n\r\nconst pointerRef = ref({\r\n x: 0,\r\n y: 0,\r\n tX: 0,\r\n tY: 0,\r\n});\r\n\r\nconst vertexShader = `\r\n precision mediump float;\r\n\r\n attribute vec2 position;\r\n attribute vec2 uv;\r\n\r\n varying vec2 vUv;\r\n\r\n void main() {\r\n vUv = uv;\r\n gl_Position = vec4(position, 0.0, 1.0);\r\n }\r\n`;\r\n\r\nconst fragmentShader = `\r\n precision mediump float;\r\n\r\n varying vec2 vUv;\r\n uniform float u_time;\r\n uniform float u_ratio;\r\n uniform vec2 u_pointer_position;\r\n uniform float u_scroll_progress;\r\n uniform float u_hue;\r\n uniform float u_saturation;\r\n uniform float u_chroma;\r\n\r\n vec2 rotate(vec2 uv, float th) {\r\n return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;\r\n }\r\n\r\n float neuro_shape(vec2 uv, float t, float p) {\r\n vec2 sine_acc = vec2(0.);\r\n vec2 res = vec2(0.);\r\n float scale = 8.;\r\n\r\n for (int j = 0; j < 15; j++) {\r\n uv = rotate(uv, 1.);\r\n sine_acc = rotate(sine_acc, 1.);\r\n vec2 layer = uv * scale + float(j) + sine_acc - t;\r\n sine_acc += sin(layer) + 2.4 * p;\r\n res += (.5 + .5 * cos(layer)) / scale;\r\n scale *= (1.2);\r\n }\r\n return res.x + res.y;\r\n }\r\n\r\n // HSL to RGB conversion\r\n vec3 hsl2rgb(vec3 c) {\r\n vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0);\r\n return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));\r\n }\r\n\r\n void main() {\r\n vec2 uv = .5 * vUv;\r\n uv.x *= u_ratio;\r\n\r\n vec2 pointer = vUv - u_pointer_position;\r\n pointer.x *= u_ratio;\r\n float p = clamp(length(pointer), 0., 1.);\r\n p = .5 * pow(1. - p, 2.);\r\n\r\n float t = .001 * u_time;\r\n vec3 color = vec3(0.);\r\n\r\n float noise = neuro_shape(uv, t, p);\r\n\r\n noise = 1.2 * pow(noise, 3.);\r\n noise += pow(noise, 10.);\r\n noise = max(.0, noise - .5);\r\n noise *= (1. - length(vUv - .5));\r\n\r\n // Convert hue from degrees to 0-1 range\r\n float normalizedHue = u_hue / 360.0;\r\n \r\n // Create HSL color with animation\r\n vec3 hsl = vec3(\r\n normalizedHue + 0.1 * sin(3.0 * u_scroll_progress + 1.5),\r\n u_saturation,\r\n u_chroma * 0.5 + 0.2 * sin(2.0 * u_scroll_progress)\r\n );\r\n\r\n // Convert to RGB\r\n color = hsl2rgb(hsl);\r\n color = color * noise;\r\n\r\n gl_FragColor = vec4(color, noise);\r\n }\r\n`;\r\n\r\nfunction initOGL() {\r\n const canvas = canvasRef.value;\r\n if (!canvas) return false;\r\n\r\n try {\r\n const renderer = new Renderer({\r\n canvas,\r\n width: canvas.clientWidth,\r\n height: canvas.clientHeight,\r\n dpr: Math.min(window.devicePixelRatio, 2),\r\n });\r\n\r\n const camera = new Camera(renderer.gl);\r\n const scene = new Transform();\r\n\r\n const geometry = new Plane(renderer.gl, {\r\n width: 2,\r\n height: 2,\r\n });\r\n\r\n const program = new Program(renderer.gl, {\r\n vertex: vertexShader,\r\n fragment: fragmentShader,\r\n uniforms: {\r\n u_time: { value: 0 },\r\n u_ratio: { value: window.innerWidth / window.innerHeight },\r\n u_pointer_position: { value: [0, 0] },\r\n u_scroll_progress: { value: 0 },\r\n u_hue: { value: props.hue },\r\n u_saturation: { value: props.saturation },\r\n u_chroma: { value: props.chroma },\r\n },\r\n });\r\n\r\n const mesh = new Mesh(renderer.gl, {\r\n geometry,\r\n program,\r\n });\r\n\r\n mesh.setParent(scene);\r\n\r\n rendererRef.value = renderer;\r\n cameraRef.value = camera;\r\n sceneRef.value = scene;\r\n meshRef.value = mesh;\r\n\r\n return true;\r\n } catch (error) {\r\n console.error(\"Error initializing OGL:\", error);\r\n return false;\r\n }\r\n}\r\n\r\nfunction resizeCanvas() {\r\n const renderer = rendererRef.value;\r\n const mesh = meshRef.value;\r\n const canvas = canvasRef.value;\r\n\r\n if (!canvas) return;\r\n\r\n if (!renderer || !mesh) return;\r\n\r\n const width = canvas.clientWidth;\r\n const height = canvas.clientHeight;\r\n\r\n renderer.setSize(width, height);\r\n\r\n // Update ratio uniform\r\n if (mesh.program && mesh.program.uniforms.u_ratio) {\r\n mesh.program.uniforms.u_ratio.value = width / height;\r\n }\r\n}\r\n\r\nfunction render() {\r\n const renderer = rendererRef.value;\r\n const scene = sceneRef.value;\r\n const camera = cameraRef.value;\r\n const mesh = meshRef.value;\r\n const pointer = pointerRef.value;\r\n\r\n if (!renderer || !scene || !camera || !mesh) return;\r\n\r\n const currentTime = performance.now();\r\n\r\n // Smooth pointer interpolation\r\n pointer.x += (pointer.tX - pointer.x) * 0.2;\r\n pointer.y += (pointer.tY - pointer.y) * 0.2;\r\n\r\n // Update uniforms\r\n if (mesh.program && mesh.program.uniforms) {\r\n const uniforms = mesh.program.uniforms;\r\n\r\n if (uniforms.u_time) uniforms.u_time.value = currentTime;\r\n if (uniforms.u_pointer_position) {\r\n uniforms.u_pointer_position.value = [\r\n pointer.x / window.innerWidth,\r\n 1 - pointer.y / window.innerHeight,\r\n ];\r\n }\r\n if (uniforms.u_scroll_progress) {\r\n uniforms.u_scroll_progress.value = window.pageYOffset / (2 * window.innerHeight);\r\n }\r\n }\r\n\r\n renderer.render({ scene, camera });\r\n animationRef.value = requestAnimationFrame(render);\r\n}\r\n\r\nfunction updateMousePosition(x: number, y: number) {\r\n pointerRef.value.tX = x;\r\n pointerRef.value.tY = y;\r\n}\r\n\r\nfunction handlePointerMove(e: PointerEvent) {\r\n updateMousePosition(e.clientX, e.clientY);\r\n}\r\n\r\nfunction handleTouchMove(e: TouchEvent) {\r\n updateMousePosition(e.touches[0].clientX, e.touches[0].clientY);\r\n}\r\n\r\nfunction handleClick(e: MouseEvent) {\r\n updateMousePosition(e.clientX, e.clientY);\r\n}\r\n\r\n// Watch for prop changes and update uniforms\r\nwatch(\r\n () => props.hue,\r\n (newHue) => {\r\n const mesh = meshRef.value;\r\n if (mesh && mesh.program && mesh.program.uniforms.u_hue) {\r\n mesh.program.uniforms.u_hue.value = newHue;\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.saturation,\r\n (newSaturation) => {\r\n const mesh = meshRef.value;\r\n if (mesh && mesh.program && mesh.program.uniforms.u_saturation) {\r\n mesh.program.uniforms.u_saturation.value = newSaturation;\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.chroma,\r\n (newChroma) => {\r\n const mesh = meshRef.value;\r\n if (mesh && mesh.program && mesh.program.uniforms.u_chroma) {\r\n mesh.program.uniforms.u_chroma.value = newChroma;\r\n }\r\n },\r\n);\r\n\r\nonMounted(() => {\r\n if (initOGL()) {\r\n resizeCanvas();\r\n render();\r\n\r\n window.addEventListener(\"resize\", resizeCanvas);\r\n window.addEventListener(\"pointermove\", handlePointerMove);\r\n window.addEventListener(\"touchmove\", handleTouchMove);\r\n window.addEventListener(\"click\", handleClick);\r\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n if (animationRef.value) {\r\n cancelAnimationFrame(animationRef.value);\r\n }\r\n\r\n window.removeEventListener(\"resize\", resizeCanvas);\r\n window.removeEventListener(\"pointermove\", handlePointerMove);\r\n window.removeEventListener(\"touchmove\", handleTouchMove);\r\n window.removeEventListener(\"click\", handleClick);\r\n\r\n // Clean up OGL resources\r\n if (rendererRef.value) {\r\n rendererRef.value = null;\r\n }\r\n});\r\n</script>\r\n"
10
+ }
11
+ ],
12
+ "fileCount": 1,
13
+ "contentHash": "b0e2528da5f056c841d0ebdcb97d6c99abe2454e"
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "bg-particle-whirlpool",
3
+ "dependencies": [
4
+ "three"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "index.ts",
9
+ "content": "export { default as ParticleWhirlpoolBg } from \"./ParticleWhirlpoolBg.vue\";\r\n"
10
+ },
11
+ {
12
+ "path": "ParticleWhirlpoolBg.vue",
13
+ "content": "<template>\r\n <div\r\n ref=\"whirlpoolCanvasContainerRef\"\r\n :class=\"cn('relative w-full h-full', $props.class)\"\r\n >\r\n <canvas\r\n ref=\"whirlpoolCanvasRef\"\r\n class=\"size-full\"\r\n ></canvas>\r\n <div\r\n :style=\"{\r\n '--bubbles-blur': `${blur}px`,\r\n }\"\r\n class=\"absolute inset-0 backdrop-blur-[--bubbles-blur]\"\r\n ></div>\r\n\r\n <div class=\"absolute inset-0\">\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport {\r\n Vector3,\r\n MathUtils,\r\n AmbientLight,\r\n BoxGeometry,\r\n InstancedMesh,\r\n PointLight,\r\n WebGLRenderer,\r\n Scene,\r\n Object3D,\r\n Vector2,\r\n PerspectiveCamera,\r\n Raycaster,\r\n Plane,\r\n MeshBasicMaterial,\r\n InstancedBufferAttribute,\r\n} from \"three\";\r\nimport { onMounted, onUnmounted, ref } from \"vue\";\r\n\r\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\r\nimport { RenderPass } from \"three/addons/postprocessing/RenderPass.js\";\r\nimport { UnrealBloomPass } from \"three/addons/postprocessing/UnrealBloomPass.js\";\r\nimport { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nconst { randFloat: rnd, randFloatSpread: rndFS } = MathUtils;\r\n\r\ntype Instance = {\r\n position: Vector3;\r\n scale: number;\r\n scaleZ: number;\r\n velocity: Vector3;\r\n attraction: number;\r\n vLimit: number;\r\n};\r\n\r\nconst props = defineProps({\r\n particleCount: {\r\n type: Number,\r\n default: 2000,\r\n },\r\n class: String,\r\n blur: {\r\n type: Number,\r\n default: 0,\r\n },\r\n});\r\n\r\nconst instances: Instance[] = [];\r\nconst target = new Vector3();\r\nconst dummyO = new Object3D();\r\nconst dummyV = new Vector3();\r\nconst pointer = new Vector2();\r\n\r\nconst light = new PointLight(0x0060ff, 0.5);\r\nconst raycaster = new Raycaster();\r\n\r\nlet renderer: WebGLRenderer;\r\nlet scene: Scene;\r\nlet camera: PerspectiveCamera;\r\nlet imesh: InstancedMesh;\r\nlet controls: OrbitControls;\r\nlet effectComposer: EffectComposer;\r\n\r\nconst whirlpoolCanvasContainerRef = ref<HTMLCanvasElement>();\r\nconst whirlpoolCanvasRef = ref<HTMLCanvasElement>();\r\n\r\nloadParticleInstances();\r\n\r\nfunction loadParticleInstances() {\r\n for (let i = 0; i < props.particleCount; i++) {\r\n instances.push({\r\n position: new Vector3(rndFS(200), rndFS(200), rndFS(200)),\r\n scale: rnd(0.2, 1),\r\n scaleZ: rnd(0.1, 1),\r\n velocity: new Vector3(rndFS(2), rndFS(2), rndFS(2)),\r\n attraction: 0.03 + rnd(-0.01, 0.01),\r\n vLimit: 1.2 + rnd(-0.1, 0.1),\r\n });\r\n }\r\n}\r\n\r\nfunction setupScene() {\r\n if (!whirlpoolCanvasRef.value) {\r\n throw new Error(\"Canvas not initialized\");\r\n }\r\n\r\n const width = whirlpoolCanvasRef.value.clientWidth;\r\n const height = whirlpoolCanvasRef.value.clientHeight;\r\n\r\n // Set Renderer\r\n renderer = new WebGLRenderer({\r\n canvas: whirlpoolCanvasRef.value,\r\n antialias: true,\r\n });\r\n renderer.setSize(width, height);\r\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\r\n renderer.autoClear = false;\r\n\r\n // Camera\r\n camera = new PerspectiveCamera();\r\n camera.aspect = width / height;\r\n camera.position.set(0, 0, 200);\r\n camera.updateProjectionMatrix();\r\n\r\n const ambientLight = new AmbientLight(0x808080);\r\n const pointLight1 = new PointLight(0xff6000);\r\n\r\n const pointLight2 = new PointLight(0xff6000, 0.5);\r\n pointLight2.position.set(100, 0, 0);\r\n\r\n const pointLight3 = new PointLight(0x0000ff, 0.5);\r\n pointLight3.position.set(-100, 0, 0);\r\n\r\n const boxGeometry = new BoxGeometry(2, 2, 10);\r\n const standardMaterial = new MeshBasicMaterial({\r\n transparent: true,\r\n opacity: 0.9,\r\n });\r\n\r\n imesh = new InstancedMesh(boxGeometry, standardMaterial, props.particleCount);\r\n\r\n scene = new Scene();\r\n scene.add(ambientLight);\r\n scene.add(pointLight1);\r\n scene.add(light);\r\n scene.add(pointLight2);\r\n scene.add(pointLight3);\r\n\r\n scene.add(imesh);\r\n\r\n controls = new OrbitControls(camera, renderer.domElement);\r\n\r\n effectComposer = new EffectComposer(renderer);\r\n effectComposer.setSize(width, height);\r\n effectComposer.addPass(new RenderPass(scene, camera));\r\n\r\n const unrealBloomPass = new UnrealBloomPass(new Vector2(width, height), 1, 0, 0);\r\n\r\n effectComposer.addPass(unrealBloomPass);\r\n}\r\n\r\nfunction init() {\r\n for (let i = 0; i < props.particleCount; i++) {\r\n const { position, scale, scaleZ } = instances[i];\r\n dummyO.position.copy(position);\r\n dummyO.scale.set(scale, scale, scaleZ);\r\n dummyO.updateMatrix();\r\n imesh.setMatrixAt(i, dummyO.matrix);\r\n }\r\n\r\n const colors = new Float32Array(props.particleCount * 3); // Each color is an RGB triplet\r\n\r\n for (let i = 0; i < props.particleCount; i++) {\r\n // Assign random colors\r\n colors[i * 3] = rnd(0, 1); // Red component\r\n colors[i * 3 + 1] = rnd(0, 1); // Green component\r\n colors[i * 3 + 2] = rnd(0, 1); // Blue component\r\n }\r\n\r\n imesh.instanceColor = new InstancedBufferAttribute(colors, 3); // Add the colors as an attribute\r\n imesh.instanceMatrix.needsUpdate = true;\r\n}\r\n\r\nfunction animate() {\r\n requestAnimationFrame(animate);\r\n controls.update();\r\n\r\n light.position.copy(target);\r\n\r\n for (let i = 0; i < props.particleCount; i++) {\r\n const { position, scale, scaleZ, velocity, attraction, vLimit } = instances[i];\r\n\r\n dummyV.copy(target).sub(position).normalize().multiplyScalar(attraction);\r\n\r\n velocity.add(dummyV).clampScalar(-vLimit, vLimit);\r\n position.add(velocity);\r\n\r\n dummyO.position.copy(position);\r\n dummyO.scale.set(scale, scale, scaleZ);\r\n dummyO.lookAt(dummyV.copy(position).add(velocity));\r\n dummyO.updateMatrix();\r\n imesh.setMatrixAt(i, dummyO.matrix);\r\n }\r\n imesh.instanceMatrix.needsUpdate = true;\r\n\r\n effectComposer.render();\r\n}\r\n\r\nonMounted(() => {\r\n setupScene();\r\n init();\r\n animate();\r\n\r\n whirlpoolCanvasContainerRef.value?.addEventListener(\"mousemove\", onPointerMove);\r\n whirlpoolCanvasContainerRef.value?.addEventListener(\"touchmove\", onPointerMove);\r\n window.addEventListener(\"resize\", onWindowResize);\r\n});\r\n\r\nfunction onPointerMove(event: MouseEvent | TouchEvent) {\r\n if (!renderer || !camera) return;\r\n\r\n let clientX: number;\r\n let clientY: number;\r\n\r\n // Check if it's a touch event\r\n if (event instanceof TouchEvent) {\r\n clientX = event.touches[0].clientX;\r\n clientY = event.touches[0].clientY;\r\n } else {\r\n clientX = (event as MouseEvent).clientX;\r\n clientY = (event as MouseEvent).clientY;\r\n }\r\n\r\n const rect = whirlpoolCanvasContainerRef.value!.getBoundingClientRect();\r\n const x = clientX - rect.left;\r\n const y = clientY - rect.top;\r\n\r\n // Check if the pointer is within the bounds of the canvas\r\n if (x >= 0 && x <= rect.width && y >= 0 && y <= rect.height) {\r\n // Pointer is within canvas bounds\r\n pointer.x = (x / rect.width) * 2 - 1;\r\n pointer.y = -(y / rect.height) * 2 + 1;\r\n\r\n // Update the target position in 3D space\r\n raycaster.setFromCamera(pointer, camera);\r\n const planeZ = new Plane(new Vector3(0, 0, 1), 0);\r\n const point = new Vector3();\r\n raycaster.ray.intersectPlane(planeZ, point);\r\n target.copy(point);\r\n } else {\r\n // Pointer is outside canvas bounds\r\n // Set target to center of canvas in 3D space\r\n target.set(0, 0, 0); // Or any default position you prefer\r\n }\r\n}\r\n\r\nfunction onWindowResize() {\r\n if (!whirlpoolCanvasRef.value || !renderer || !camera || !effectComposer) return;\r\n\r\n const width = whirlpoolCanvasContainerRef.value!.clientWidth;\r\n const height = whirlpoolCanvasContainerRef.value!.clientHeight;\r\n\r\n // Update camera\r\n camera.aspect = width / height;\r\n camera.updateProjectionMatrix();\r\n\r\n // Update renderer size\r\n renderer.setSize(width, height);\r\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\r\n\r\n // Update effect composer size\r\n effectComposer.setSize(width, height);\r\n}\r\n\r\nonUnmounted(() => {\r\n window.removeEventListener(\"mousemove\", onPointerMove);\r\n window.removeEventListener(\"resize\", onWindowResize);\r\n});\r\n</script>\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "e3bfe90fe5c7346eed342cb46b509f7cfc72d594"
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "bg-silk",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "index.ts",
7
+ "content": "export { default as SilkBackground } from \"./SilkBackground.vue\";\r\n"
8
+ },
9
+ {
10
+ "path": "SilkBackground.vue",
11
+ "content": "<template>\r\n <div :class=\"cn('absolute inset-0', props.class)\">\r\n <ShaderToy\r\n :shader-code=\"shaderCode\"\r\n :hue=\"props.hue\"\r\n :saturation=\"props.saturation\"\r\n :brightness=\"props.brightness\"\r\n :speed=\"props.speed\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"~/lib/utils\";\r\n\r\ninterface Props {\r\n class?: HTMLAttributes[\"class\"];\r\n hue?: number;\r\n saturation?: number;\r\n brightness?: number;\r\n speed?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n hue: 300,\r\n saturation: 0.5,\r\n brightness: 1,\r\n speed: 1,\r\n});\r\n\r\nconst shaderCode = `\r\n// ShaderToy URL: https://www.shadertoy.com/view/X3yXRd\r\n// The MIT License\r\n// Copyright © 2024 Giorgi Azmaipharashvili\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\n#define INVERT 1\r\n\r\nfloat noise(vec2 p) {\r\n return smoothstep(-0.5, 0.9, sin((p.x - p.y) * 555.0) * sin(p.y * 1444.0)) - 0.4;\r\n}\r\n\r\nfloat fabric(vec2 p) {\r\n const mat2 m = mat2(1.6, 1.2, -1.2, 1.6);\r\n float f = 0.4 * noise(p);\r\n f += 0.3 * noise(p = m * p);\r\n f += 0.2 * noise(p = m * p);\r\n return f + 0.1 * noise(m * p);\r\n}\r\n\r\nfloat silk(vec2 uv, float t) {\r\n float s = sin(5.0 * (uv.x + uv.y + cos(2.0 * uv.x + 5.0 * uv.y)) + sin(12.0 * (uv.x + uv.y)) - t);\r\n s = 0.7 + 0.3 * (s * s * 0.5 + s);\r\n s *= 0.9 + 0.6 * fabric(uv * min(iResolution.x, iResolution.y) * 0.0006);\r\n return s * 0.9 + 0.1;\r\n}\r\n\r\nfloat silkd(vec2 uv, float t) {\r\n float xy = uv.x + uv.y;\r\n float d = (5.0 * (1.0 - 2.0 * sin(2.0 * uv.x + 5.0 * uv.y)) + 12.0 * cos(12.0 * xy)) * cos(5.0 * (cos(2.0 * uv.x + 5.0 * uv.y) + xy) + sin(12.0 * xy) - t);\r\n return 0.005 * d * (sign(d) + 3.0);\r\n}\r\n\r\nvoid mainImage(out vec4 fragColor, vec2 fragCoord) {\r\n float mr = min(iResolution.x, iResolution.y);\r\n vec2 uv = fragCoord / mr;\r\n\r\n float t = iTime;\r\n uv.y += 0.03 * sin(8.0 * uv.x - t);\r\n\r\n if (iMouse.z > 1.0)\r\n uv += smoothstep(0.5, 0.0, distance(iMouse.xy / mr, uv)) * 0.08;\r\n // uv -= normalize(iMouse.xy / iResolution.xy * 2.0 - 1.0) * t * 0.05; // Wind, requires state\r\n\r\n float s = sqrt(silk(uv, t));\r\n float d = silkd(uv, t);\r\n\r\n vec3 c = vec3(s);\r\n c += 0.7 * vec3(1, 0.83, 0.6) * d;\r\n c *= 1.0 - max(0.0, 0.8 * d);\r\n#if INVERT\r\n c = pow(c, 0.3 / vec3(0.52, 0.5, 0.4));\r\n c = 1.0 - c;\r\n#else\r\n c = pow(c, vec3(0.52, 0.5, 0.4));\r\n#endif\r\n\r\n fragColor = vec4(c, 1);\r\n}\r\n`;\r\n</script>\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "aa191ffa5aa3533ac262da57ecb21f19a71ab860"
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "bg-stars",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "index.ts",
9
+ "content": "export { default as StarsBackground } from \"./StarsBackground.vue\";\r\n"
10
+ },
11
+ {
12
+ "path": "StarsBackground.vue",
13
+ "content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'relative size-full overflow-hidden bg-[radial-gradient(ellipse_at_bottom,_#262626_0%,_#000_100%)]',\r\n props.class,\r\n )\r\n \"\r\n @mousemove=\"handleMouseMove\"\r\n >\r\n <motion.div :style=\"{ x: springX, y: springY }\">\r\n <!-- Star Layer 1 -->\r\n <motion.div\r\n class=\"absolute top-0 left-0 w-full h-[2000px]\"\r\n :animate=\"{ y: [0, -2000] }\"\r\n :transition=\"starLayer1Transition\"\r\n >\r\n <div\r\n class=\"absolute bg-transparent rounded-full\"\r\n :style=\"{\r\n width: '1px',\r\n height: '1px',\r\n boxShadow: boxShadow1,\r\n }\"\r\n />\r\n <div\r\n class=\"absolute bg-transparent rounded-full top-[2000px]\"\r\n :style=\"{\r\n width: '1px',\r\n height: '1px',\r\n boxShadow: boxShadow1,\r\n }\"\r\n />\r\n </motion.div>\r\n\r\n <!-- Star Layer 2 -->\r\n <motion.div\r\n class=\"absolute top-0 left-0 w-full h-[2000px]\"\r\n :animate=\"{ y: [0, -2000] }\"\r\n :transition=\"starLayer2Transition\"\r\n >\r\n <div\r\n class=\"absolute bg-transparent rounded-full\"\r\n :style=\"{\r\n width: '2px',\r\n height: '2px',\r\n boxShadow: boxShadow2,\r\n }\"\r\n />\r\n <div\r\n class=\"absolute bg-transparent rounded-full top-[2000px]\"\r\n :style=\"{\r\n width: '2px',\r\n height: '2px',\r\n boxShadow: boxShadow2,\r\n }\"\r\n />\r\n </motion.div>\r\n\r\n <!-- Star Layer 3 -->\r\n <motion.div\r\n class=\"absolute top-0 left-0 w-full h-[2000px]\"\r\n :animate=\"{ y: [0, -2000] }\"\r\n :transition=\"starLayer3Transition\"\r\n >\r\n <div\r\n class=\"absolute bg-transparent rounded-full\"\r\n :style=\"{\r\n width: '3px',\r\n height: '3px',\r\n boxShadow: boxShadow3,\r\n }\"\r\n />\r\n <div\r\n class=\"absolute bg-transparent rounded-full top-[2000px]\"\r\n :style=\"{\r\n width: '3px',\r\n height: '3px',\r\n boxShadow: boxShadow3,\r\n }\"\r\n />\r\n </motion.div>\r\n </motion.div>\r\n\r\n <!-- Slot for child content -->\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { SpringOptions } from \"motion-v\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { motion, useMotionValue, useSpring } from \"motion-v\";\r\nimport { computed, onMounted, ref, watch } from \"vue\";\r\n\r\ninterface StarsBackgroundProps {\r\n factor?: number;\r\n speed?: number;\r\n transition?: SpringOptions;\r\n starColor?: string;\r\n class?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<StarsBackgroundProps>(), {\r\n factor: 0.05,\r\n speed: 50,\r\n transition: () => ({ stiffness: 50, damping: 20 }),\r\n starColor: \"#fff\",\r\n});\r\n\r\n// For slot content\r\ndefineSlots();\r\n\r\nfunction generateStars(count: number, starColor: string) {\r\n const shadows: string[] = [];\r\n for (let i = 0; i < count; i++) {\r\n const x = Math.floor(Math.random() * 4000) - 2000;\r\n const y = Math.floor(Math.random() * 4000) - 2000;\r\n shadows.push(`${x}px ${y}px ${starColor}`);\r\n }\r\n return shadows.join(\", \");\r\n}\r\n\r\nconst offsetX = useMotionValue(1);\r\nconst offsetY = useMotionValue(1);\r\n\r\nconst springX = useSpring(offsetX, props.transition);\r\nconst springY = useSpring(offsetY, props.transition);\r\n\r\nfunction handleMouseMove(e: MouseEvent) {\r\n const centerX = window.innerWidth / 2;\r\n const centerY = window.innerHeight / 2;\r\n const newOffsetX = -(e.clientX - centerX) * props.factor;\r\n const newOffsetY = -(e.clientY - centerY) * props.factor;\r\n offsetX.set(newOffsetX);\r\n offsetY.set(newOffsetY);\r\n}\r\n\r\nconst boxShadow1 = ref(\"\");\r\nconst boxShadow2 = ref(\"\");\r\nconst boxShadow3 = ref(\"\");\r\n\r\nonMounted(() => {\r\n boxShadow1.value = generateStars(1000, props.starColor);\r\n boxShadow2.value = generateStars(400, props.starColor);\r\n boxShadow3.value = generateStars(200, props.starColor);\r\n});\r\n\r\n// Watch for starColor changes\r\nwatch(\r\n () => props.starColor,\r\n (newColor) => {\r\n boxShadow1.value = generateStars(1000, newColor);\r\n boxShadow2.value = generateStars(400, newColor);\r\n boxShadow3.value = generateStars(200, newColor);\r\n },\r\n);\r\n\r\nconst starLayer1Transition = computed(() => ({\r\n repeat: Infinity,\r\n duration: props.speed,\r\n ease: \"linear\",\r\n}));\r\n\r\nconst starLayer2Transition = computed(() => ({\r\n repeat: Infinity,\r\n duration: props.speed * 2,\r\n ease: \"linear\",\r\n}));\r\n\r\nconst starLayer3Transition = computed(() => ({\r\n repeat: Infinity,\r\n duration: props.speed * 3,\r\n ease: \"linear\",\r\n}));\r\n</script>\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "fc8592823a9e8c5a9ebbdfe710d4771af99e0b7a"
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "bg-stractium",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "index.ts",
7
+ "content": "export { default as StractiumBackground } from \"./StractiumBackground.vue\";\r\n"
8
+ },
9
+ {
10
+ "path": "StractiumBackground.vue",
11
+ "content": "<template>\r\n <div :class=\"cn('absolute inset-0', props.class)\">\r\n <ShaderToy\r\n :shader-code=\"shader\"\r\n :hue=\"props.hue\"\r\n :saturation=\"props.saturation\"\r\n :brightness=\"props.brightness\"\r\n :speed=\"props.speed\"\r\n :mouse-sensitivity=\"props.mouseSensitivity\"\r\n :damping=\"props.damping\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"~/lib/utils\";\r\n\r\ninterface Props {\r\n class?: HTMLAttributes[\"class\"];\r\n hue?: number;\r\n saturation?: number;\r\n brightness?: number;\r\n speed?: number;\r\n mouseSensitivity?: number;\r\n damping?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n hue: 0,\r\n saturation: 1,\r\n brightness: 1,\r\n speed: 1,\r\n mouseSensitivity: 0.5,\r\n damping: 1,\r\n});\r\n\r\nconst shader = `\r\n// ShaderToy URL: https://www.shadertoy.com/view/Mlf3R4\r\n\r\n// Set this to change detail level. [1 - 10] is a good range.\r\nconst int NUM_SIN_REPS = 9;\r\nconst int MAX_MARCH_REPS = 250;\r\nconst float MARCH_DISTANCE_MULTIPLIER = 0.1;\r\n\r\nfloat localTime = 0.0;\r\n\r\n// some noise functions\r\nfloat Hash(float f)\r\n{\r\n return fract(cos(f)*7561.0);\r\n}\r\nfloat Hash2d(vec2 uv)\r\n{\r\n float f = uv.x + uv.y * 521.0;\t// repeats after this value\r\n float rand = fract(cos(f)*104729.0);\r\n return rand;\r\n}\r\nvec2 Hash2(vec2 v)\r\n{\r\n return fract(cos(v*3.333)*vec2(100003.9, 37049.7));\r\n}\r\nfloat Hash3d(vec3 uv)\r\n{\r\n float f = uv.x + uv.y * 37.0 + uv.z * 521.0;\r\n return fract(sin(f)*110003.9);\r\n}\r\n\r\nfloat mixS(float f0, float f1, float a)\r\n{\r\n if (a < 0.5) return f0;\r\n return f1;\r\n}\r\n\r\nfloat mixC(float f0, float f1, float a)\r\n{\r\n return mix(f1, f0, cos(a*3.1415926) *0.5+0.5);\r\n}\r\n\r\nfloat mixP(float f0, float f1, float a)\r\n{\r\n return mix(f0, f1, a*a*(3.0-2.0*a));\r\n}\r\nvec2 mixP2(vec2 v0, vec2 v1, float a)\r\n{\r\n return mix(v0, v1, a*a*(3.0-2.0*a));\r\n}\r\n\r\nfloat mixSS(float f0, float f1, float a)\r\n{\r\n return mix(f0, f1, smoothstep(0.0, 1.0, a));\r\n}\r\n\r\nconst vec2 zeroOne = vec2(0.0, 1.0);\r\nfloat noise2dVec(vec2 uv)\r\n{\r\n vec2 fr = fract(uv);\r\n vec2 fl = floor(uv);\r\n vec2 h0 = vec2(Hash2d(fl), Hash2d(fl + zeroOne));\r\n vec2 h1 = vec2(Hash2d(fl + zeroOne.yx), Hash2d(fl + zeroOne.yy));\r\n vec2 xMix = mixP2(h0, h1, fr.x);\r\n return mixC(xMix.x, xMix.y, fr.y);\r\n}\r\nfloat noise2d(vec2 uv)\r\n{\r\n vec2 fr = fract(uv);\r\n vec2 fl = floor(uv);\r\n float h00 = Hash2d(fl);\r\n float h10 = Hash2d(fl + zeroOne.yx);\r\n float h01 = Hash2d(fl + zeroOne);\r\n float h11 = Hash2d(fl + zeroOne.yy);\r\n return mixP(mixP(h00, h10, fr.x), mixP(h01, h11, fr.x), fr.y);\r\n}\r\nfloat noise(vec3 uv)\r\n{\r\n vec3 fr = fract(uv.xyz);\r\n vec3 fl = floor(uv.xyz);\r\n float h000 = Hash3d(fl);\r\n float h100 = Hash3d(fl + zeroOne.yxx);\r\n float h010 = Hash3d(fl + zeroOne.xyx);\r\n float h110 = Hash3d(fl + zeroOne.yyx);\r\n float h001 = Hash3d(fl + zeroOne.xxy);\r\n float h101 = Hash3d(fl + zeroOne.yxy);\r\n float h011 = Hash3d(fl + zeroOne.xyy);\r\n float h111 = Hash3d(fl + zeroOne.yyy);\r\n return mixP(\r\n mixP(mixP(h000, h100, fr.x),\r\n mixP(h010, h110, fr.x), fr.y),\r\n mixP(mixP(h001, h101, fr.x),\r\n mixP(h011, h111, fr.x), fr.y)\r\n , fr.z);\r\n}\r\n\r\nfloat PI=3.14159265;\r\n\r\nvec3 saturate(vec3 a) { return clamp(a, 0.0, 1.0); }\r\nvec2 saturate(vec2 a) { return clamp(a, 0.0, 1.0); }\r\nfloat saturate(float a) { return clamp(a, 0.0, 1.0); }\r\n\r\nvec3 RotateX(vec3 v, float rad)\r\n{\r\n float cos = cos(rad);\r\n float sin = sin(rad);\r\n //if (RIGHT_HANDED_COORD)\r\n return vec3(v.x, cos * v.y + sin * v.z, -sin * v.y + cos * v.z);\r\n //else return new float3(x, cos * y - sin * z, sin * y + cos * z);\r\n}\r\nvec3 RotateY(vec3 v, float rad)\r\n{\r\n float cos = cos(rad);\r\n float sin = sin(rad);\r\n //if (RIGHT_HANDED_COORD)\r\n return vec3(cos * v.x - sin * v.z, v.y, sin * v.x + cos * v.z);\r\n //else return new float3(cos * x + sin * z, y, -sin * x + cos * z);\r\n}\r\nvec3 RotateZ(vec3 v, float rad)\r\n{\r\n float cos = cos(rad);\r\n float sin = sin(rad);\r\n //if (RIGHT_HANDED_COORD)\r\n return vec3(cos * v.x + sin * v.y, -sin * v.x + cos * v.y, v.z);\r\n}\r\n\r\n\r\n// This function basically is a procedural environment map that makes the sun\r\nvec3 sunCol = vec3(258.0, 228.0, 170.0) / 3555.0;//unfortunately, i seem to have 2 different sun colors. :(\r\nvec3 GetSunColorReflection(vec3 rayDir, vec3 sunDir)\r\n{\r\n\tvec3 localRay = normalize(rayDir);\r\n\tfloat dist = 1.0 - (dot(localRay, sunDir) * 0.5 + 0.5);\r\n\tfloat sunIntensity = 0.015 / dist;\r\n\tsunIntensity = pow(sunIntensity, 0.3)*100.0;\r\n\r\n sunIntensity += exp(-dist*12.0)*300.0;\r\n\tsunIntensity = min(sunIntensity, 40000.0);\r\n //vec3 skyColor = mix(vec3(1.0, 0.95, 0.85), vec3(0.2,0.3,0.95), pow(saturate(rayDir.y), 0.7))*skyMultiplier*0.95;\r\n\treturn sunCol * sunIntensity*0.0425;\r\n}\r\nvec3 GetSunColorSmall(vec3 rayDir, vec3 sunDir)\r\n{\r\n\tvec3 localRay = normalize(rayDir);\r\n\tfloat dist = 1.0 - (dot(localRay, sunDir) * 0.5 + 0.5);\r\n\tfloat sunIntensity = 0.05 / dist;\r\n sunIntensity += exp(-dist*12.0)*300.0;\r\n\tsunIntensity = min(sunIntensity, 40000.0);\r\n\treturn sunCol * sunIntensity*0.025;\r\n}\r\n\r\nvec4 cXX = vec4(0.0, 3.0, 0.0, 0.0);\r\n\r\nvec3 camPos = vec3(0.0), camFacing;\r\nvec3 camLookat=vec3(0,0.0,0);\r\n\r\nfloat SinRep(float a)\r\n{\r\n float h = 0.0;\r\n float mult = 1.0;\r\n for (int i = 0; i < NUM_SIN_REPS; i++)\r\n {\r\n h += (cos(a*mult)/(mult));\r\n mult *= 2.0;\r\n }\r\n return h;\r\n}\r\n\r\nvec2 DistanceToObject(vec3 p)\r\n{\r\n float material = 0.0;\r\n float h = 0.0;\r\n p = RotateY(p, p.y*0.4 - cos(localTime)*0.4);\r\n h += SinRep(RotateY(p, p.z*3.14*0.25).x);\r\n h += SinRep(RotateZ(p, p.x*3.14*0.25).y);\r\n h += SinRep(RotateX(p, p.y*3.14*0.25).z);\r\n material = h;\r\n //h += SinRep(RotateX(p, p.y).z);\r\n //h += SinRep(RotateZ(p, sin(h)).y);\r\n //h += SinRep(RotateY(p, h*1.0).x);\r\n //h += SinRep(p.x+h)*0.5;\r\n //h += SinRep(p.y+h)*0.5;\r\n float final = (length(p)-4.0 - h*(0.25 + sin(localTime)*0.35));\r\n return vec2(final, material);\r\n}\r\n\r\nfloat distFromSphere;\r\nfloat IntersectSphereAndRay(vec3 pos, float radius, vec3 posA, vec3 posB, out vec3 intersectA2, out vec3 intersectB2)\r\n{\r\n\t// Use dot product along line to find closest point on line\r\n\tvec3 eyeVec2 = normalize(posB-posA);\r\n\tfloat dp = dot(eyeVec2, pos - posA);\r\n\tvec3 pointOnLine = eyeVec2 * dp + posA;\r\n\t// Clamp that point to line end points if outside\r\n\t//if ((dp - radius) < 0) pointOnLine = posA;\r\n\t//if ((dp + radius) > (posB-posA).Length()) pointOnLine = posB;\r\n\t// Distance formula from that point to sphere center, compare with radius.\r\n\tfloat distance = length(pointOnLine - pos);\r\n\tfloat ac = radius*radius - distance*distance;\r\n\tfloat rightLen = 0.0;\r\n\tif (ac >= 0.0) rightLen = sqrt(ac);\r\n\tintersectA2 = pointOnLine - eyeVec2 * rightLen;\r\n\tintersectB2 = pointOnLine + eyeVec2 * rightLen;\r\n\tdistFromSphere = distance - radius;\r\n\tif (distance <= radius) return 1.0;\r\n\treturn 0.0;\r\n}\r\n\r\n\r\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\r\n{\r\n localTime = iTime - 1.6;\r\n\t// ---------------- First, set up the camera rays for ray marching ----------------\r\n\tvec2 uv = fragCoord.xy/iResolution.xy * 2.0 - 1.0;\r\n\r\n\t// Camera up vector.\r\n\tvec3 camUp=vec3(0,1,0); // vuv\r\n\r\n\t// Camera lookat.\r\n\tcamLookat=vec3(0,0.0,0);\t// vrp\r\n\r\n // debugging camera\r\n float mx=iMouse.x/iResolution.x*PI*2.0-0.7 + localTime * 0.123;\r\n\tfloat my=-iMouse.y/iResolution.y*10.0 - sin(localTime * 0.31)*0.5;//*PI/2.01;\r\n\tcamPos += vec3(cos(my)*cos(mx),sin(my),cos(my)*sin(mx))*(9.2); \t// prp\r\n\r\n\r\n // add randomness to camera for depth-of-field look close up.\r\n //camPos += vec3(Hash2d(uv)*0.91, Hash2d(uv+37.0), Hash2d(uv+47.0))*0.01;\r\n\r\n\t// Camera setup.\r\n\tvec3 camVec=normalize(camLookat - camPos);//vpn\r\n\tvec3 sideNorm=normalize(cross(camUp, camVec));\t// u\r\n\tvec3 upNorm=cross(camVec, sideNorm);//v\r\n\tvec3 worldFacing=(camPos + camVec);//vcv\r\n\tvec3 worldPix = worldFacing + uv.x * sideNorm * (iResolution.x/iResolution.y) + uv.y * upNorm;//scrCoord\r\n\tvec3 relVec = normalize(worldPix - camPos);//scp\r\n\r\n\t// --------------------------------------------------------------------------------\r\n\t// I put a bounding sphere around the whole object. If the ray is outside\r\n\t// of the bounding sphere, I don't bother ray marching. It's just an optimization.\r\n\tvec3 iA, iB;\r\n\tfloat hit = IntersectSphereAndRay(vec3(0,0,0), 7.6, camPos, camPos+relVec, iA, iB);\r\n\r\n\t// --------------------------------------------------------------------------------\r\n\tvec2 distAndMat = vec2(0.05, 0.0);\r\n\tfloat t = 0.0;\r\n\tfloat inc = 0.02;\r\n\tfloat maxDepth = 110.0;\r\n\tvec3 pos = vec3(0,0,0);\r\n // start and end the camera ray at the sphere intersections.\r\n camPos = iA;\r\n maxDepth = distance(iA, iB);\r\n\t// ray marching time\r\n\tif (hit > 0.5)\t// check if inside bounding sphere before wasting time ray marching.\r\n\t{\r\n for (int i = 0; i < MAX_MARCH_REPS; i++)\t// This is the count of the max times the ray actually marches.\r\n {\r\n if ((t > maxDepth) || (abs(distAndMat.x) < 0.0075)) break;\r\n pos = camPos + relVec * t;\r\n // *******************************************************\r\n // This is _the_ function that defines the \"distance field\".\r\n // It's really what makes the scene geometry.\r\n // *******************************************************\r\n distAndMat = DistanceToObject(pos);\r\n // adjust by constant because deformations mess up distance function.\r\n t += distAndMat.x * MARCH_DISTANCE_MULTIPLIER;\r\n }\r\n }\r\n else\r\n {\r\n\t\tt = maxDepth + 1.0;\r\n distAndMat.x = 1.0;\r\n }\r\n\t// --------------------------------------------------------------------------------\r\n\t// Now that we have done our ray marching, let's put some color on this geometry.\r\n\r\n\tvec3 sunDir = normalize(vec3(0.93, 1.0, -1.5));\r\n\tvec3 finalColor = vec3(0.0);\r\n\r\n\t// If a ray actually hit the object, let's light it.\r\n\tif (abs(distAndMat.x) < 0.75)\r\n //if (t <= maxDepth)\r\n\t{\r\n // calculate the normal from the distance field. The distance field is a volume, so if you\r\n // sample the current point and neighboring points, you can use the difference to get\r\n // the normal.\r\n vec3 smallVec = vec3(0.005, 0, 0);\r\n vec3 normalU = vec3(distAndMat.x - DistanceToObject(pos - smallVec.xyy).x,\r\n distAndMat.x - DistanceToObject(pos - smallVec.yxy).x,\r\n distAndMat.x - DistanceToObject(pos - smallVec.yyx).x);\r\n\r\n vec3 normal = normalize(normalU);\r\n\r\n // calculate 2 ambient occlusion values. One for global stuff and one\r\n // for local stuff - so the green sphere light source can also have ambient.\r\n float ambientS = 1.0;\r\n ambientS *= saturate(DistanceToObject(pos + normal * 0.1).x*10.0);\r\n ambientS *= saturate(DistanceToObject(pos + normal * 0.2).x*5.0);\r\n ambientS *= saturate(DistanceToObject(pos + normal * 0.4).x*2.5);\r\n ambientS *= saturate(DistanceToObject(pos + normal * 0.8).x*1.25);\r\n float ambient = ambientS * saturate(DistanceToObject(pos + normal * 1.6).x*1.25*0.5);\r\n ambient *= saturate(DistanceToObject(pos + normal * 3.2).x*1.25*0.25);\r\n ambient *= saturate(DistanceToObject(pos + normal * 6.4).x*1.25*0.125);\r\n ambient = max(0.15, pow(ambient, 0.3));\t// tone down ambient with a pow and min clamp it.\r\n ambient = saturate(ambient);\r\n\r\n // Trace a ray toward the sun for sun shadows\r\n float sunShadow = 1.0;\r\n float iter = 0.2;\r\n\t\tfor (int i = 0; i < 10; i++)\r\n {\r\n float tempDist = DistanceToObject(pos + sunDir * iter).x;\r\n\t sunShadow *= saturate(tempDist*10.0);\r\n if (tempDist <= 0.0) break;\r\n iter *= 1.5;\t// constant is more reliable than distance-based\r\n //iter += max(0.2, tempDist)*1.2;\r\n }\r\n sunShadow = saturate(sunShadow);\r\n\r\n // calculate the reflection vector for highlights\r\n vec3 ref = reflect(relVec, normal);\r\n\r\n // ------ Calculate texture color of the rock ------\r\n // base texture can be swirled noise.\r\n\t\tvec3 rp = RotateY(pos, pos.y*0.4 - cos(localTime)*0.4);\r\n float n = noise(rp*4.0) + noise(rp*8.0) + noise(rp*16.0) + noise(rp*32.0);\r\n n = saturate(n*0.25 * 0.95 + 0.05);\r\n vec3 texColor = vec3(0.2,0.3,0.3)*n;\r\n\r\n // fade to reddish texture on outside\r\n texColor += vec3(0.99, 0.21, 0.213) * clamp(length(pos)-4.0, 0.0, 0.4);\r\n // give it green-blue texture that matches the shape using normal length\r\n texColor += vec3(1.0, 21.0, 26.0)*0.6 * saturate(length(normalU)-0.01);\r\n // Give it a reddish-rust color in the middle\r\n texColor -= vec3(0.0, 0.3, 0.5)*saturate(-distAndMat.y*(0.9+sin(localTime+0.5)*0.9));\r\n // make sure it's not too saturated so it looks realistic\r\n texColor = max(vec3(0.02),texColor);\r\n\r\n // ------ Calculate lighting color ------\r\n // Start with sun color, standard lighting equation, and shadow\r\n vec3 lightColor = sunCol * saturate(dot(sunDir, normal)) * sunShadow*14.0;\r\n // sky color, hemisphere light equation approximation, anbient occlusion\r\n lightColor += vec3(0.1,0.35,0.95) * (normal.y * 0.5 + 0.5) * ambient * 0.25;\r\n // ground color - another hemisphere light\r\n lightColor += vec3(1.0) * ((-normal.y) * 0.5 + 0.5) * ambient * 0.2;\r\n\r\n // finally, apply the light to the texture.\r\n finalColor = texColor * lightColor;\r\n\r\n // specular highlights - just a little\r\n vec3 refColor = GetSunColorReflection(ref, sunDir)*0.68;\r\n finalColor += refColor * sunCol * sunShadow * 9.0 * texColor.g;\r\n\r\n // fog that fades to sun color so that fog is brightest towards sun\r\n finalColor = mix(vec3(0.98, 0.981, 0.981) + min(vec3(0.25),GetSunColorSmall(relVec, sunDir))*2.0, finalColor, exp(-t*0.007));\r\n //finalColor = vec3(1.0, 21.0, 26.0) * saturate(length(normalU)-0.01);\r\n\t}\r\n else\r\n {\r\n // Our ray trace hit nothing, so draw sky.\r\n // fade the sky color, multiply sunset dimming\r\n finalColor = mix(vec3(1.0, 0.95, 0.85), vec3(0.2,0.5,0.95), pow(saturate(relVec.y), 0.7))*0.95;\r\n // add the sun\r\n finalColor += GetSunColorSmall(relVec, sunDir);// + vec3(0.1, 0.1, 0.1);\r\n }\r\n\r\n // vignette?\r\n finalColor *= vec3(1.0) * saturate(1.0 - length(uv/2.5));\r\n finalColor *= 1.95;\r\n\r\n\t// output the final color with sqrt for \"gamma correction\"\r\n\tfragColor = vec4(sqrt(clamp(finalColor, 0.0, 1.0)),1.0);\r\n}\r\n`;\r\n</script>\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "3a1e32dc5b78788edc4208198e2282b2f50d66c8"
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "blur-reveal",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "BlurReveal.vue",
9
+ "content": "<template>\r\n <div\r\n ref=\"container\"\r\n :class=\"props.class\"\r\n >\r\n <Motion\r\n v-for=\"(child, index) in children\"\r\n :key=\"index\"\r\n ref=\"childElements\"\r\n as=\"div\"\r\n :initial=\"getInitial()\"\r\n :while-in-view=\"getAnimate()\"\r\n :transition=\"{\r\n duration: props.duration,\r\n easing: 'easeInOut',\r\n delay: props.delay * index,\r\n }\"\r\n >\r\n <component :is=\"child\" />\r\n </Motion>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { Motion } from \"motion-v\";\r\nimport { ref, onMounted, watchEffect, useSlots } from \"vue\";\r\n\r\ninterface Props {\r\n duration?: number;\r\n delay?: number;\r\n blur?: string;\r\n yOffset?: number;\r\n class?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n duration: 1,\r\n delay: 2,\r\n blur: \"20px\",\r\n yOffset: 20,\r\n});\r\n\r\nconst container = ref(null);\r\nconst childElements = ref([]);\r\nconst slots = useSlots();\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nconst children = ref<any>([]);\r\n\r\nonMounted(() => {\r\n // This will reactively capture all content provided in the default slot\r\n watchEffect(() => {\r\n children.value = slots.default ? slots.default() : [];\r\n });\r\n});\r\n\r\nfunction getInitial() {\r\n return {\r\n opacity: 0,\r\n filter: `blur(${props.blur})`,\r\n y: props.yOffset,\r\n };\r\n}\r\n\r\nfunction getAnimate() {\r\n return {\r\n opacity: 1,\r\n filter: `blur(0px)`,\r\n y: 0,\r\n };\r\n}\r\n</script>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as BlurReveal } from \"./BlurReveal.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "f2a258bf3b9ab1b0d53aa6a25cae43e44e9392e3"
18
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "book",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "Book.vue",
7
+ "content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'z-10 group [perspective:800px] w-min [--shadowColor:#bbb] dark:[--shadowColor:#111]',\r\n $props.class,\r\n )\r\n \"\r\n >\r\n <div\r\n :style=\"{ width: sizeMap[size].width, transition: `transform ${props.duration}ms ease` }\"\r\n :class=\"[\r\n 'relative aspect-[3/4] [transform-style:preserve-3d]',\r\n isStatic\r\n ? '[transform:rotateY(-30deg)]'\r\n : '[transform:rotateY(0deg)] group-hover:[transform:rotateY(-30deg)]',\r\n radiusMap[radius],\r\n ]\"\r\n >\r\n <div\r\n :class=\"`\r\n absolute inset-y-0 overflow-hidden size-full left-0\r\n text-white flex flex-col justify-end p-6\r\n bg-gradient-to-tr\r\n ${computedGradient.from}\r\n ${computedGradient.to}\r\n ${radiusMap[radius]}\r\n `\"\r\n :style=\"{\r\n transform: 'translateZ(25px)',\r\n boxShadow: '5px 5px 20px var(--shadowColor)',\r\n }\"\r\n >\r\n <div\r\n class=\"absolute left-0 top-0 h-full\"\r\n :style=\"{\r\n minWidth: '8.2%',\r\n background:\r\n 'linear-gradient(90deg, hsla(0, 0%, 100%, 0), hsla(0, 0%, 100%, 0) 12%, hsla(0, 0%, 100%, .25) 29.25%, hsla(0, 0%, 100%, 0) 50.5%, hsla(0, 0%, 100%, 0) 75.25%, hsla(0, 0%, 100%, .25) 91%, hsla(0, 0%, 100%, 0)), linear-gradient(90deg, rgba(0, 0, 0, .03), rgba(0, 0, 0, .1) 12%, transparent 30%, rgba(0, 0, 0, .02) 50%, rgba(0, 0, 0, .2) 73.5%, rgba(0, 0, 0, .5) 75.25%, rgba(0, 0, 0, .15) 85.25%, transparent)',\r\n opacity: '0.2',\r\n }\"\r\n ></div>\r\n <div class=\"pl-1\">\r\n <slot />\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"absolute left-0 bg-white\"\r\n :style=\"{\r\n top: '3px',\r\n bottom: '3px',\r\n width: '48px',\r\n transform: 'translateX(' + sizeMap[size].spineTranslation + ') rotateY(90deg)',\r\n background: 'linear-gradient(90deg, rgba(255,255,255,1) 50%, rgba(249,249,249,1) 50%)',\r\n }\"\r\n ></div>\r\n\r\n <div\r\n :class=\"`\r\n absolute inset-y-0 overflow-hidden size-full left-0\r\n text-white flex flex-col justify-end p-6\r\n bg-gradient-to-tr\r\n ${computedGradient.from}\r\n ${computedGradient.to}\r\n ${radiusMap[radius]}\r\n `\"\r\n :style=\"{\r\n transform: 'translateZ(-25px)',\r\n boxShadow: shadowSizeMap[shadowSize],\r\n }\"\r\n ></div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { computed } from \"vue\";\r\nimport {\r\n BOOK_RADIUS_MAP as radiusMap,\r\n BOOK_SIZE_MAP as sizeMap,\r\n BOOK_COLOR_MAP as colorMap,\r\n BOOK_SHADOW_SIZE_MAP as shadowSizeMap,\r\n type BookRadius,\r\n type BookSize,\r\n type BookColor,\r\n type BookShadowSize,\r\n} from \"./index\";\r\n\r\ninterface BookProps {\r\n class?: HTMLAttributes[\"class\"];\r\n duration?: number;\r\n color?: BookColor;\r\n isStatic?: boolean;\r\n size?: BookSize;\r\n radius?: BookRadius;\r\n shadowSize?: BookShadowSize;\r\n}\r\n\r\nconst props = withDefaults(defineProps<BookProps>(), {\r\n duration: 1000,\r\n color: \"zinc\",\r\n isStatic: false,\r\n size: \"md\",\r\n radius: \"md\",\r\n shadowSize: \"lg\",\r\n});\r\n\r\nconst computedGradient = computed(() => {\r\n return colorMap[props.color] || colorMap.zinc;\r\n});\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "BookDescription.vue",
11
+ "content": "<template>\r\n <p :class=\"cn('select-none text-xs/relaxed', $props.class)\">\r\n <slot />\r\n </p>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface BookDescriptionProps {\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\ndefineProps<BookDescriptionProps>();\r\n</script>\r\n"
12
+ },
13
+ {
14
+ "path": "BookHeader.vue",
15
+ "content": "<template>\r\n <div :class=\"cn('flex gap-2 flex-wrap', $props.class)\">\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface BookHeaderProps {\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\ndefineProps<BookHeaderProps>();\r\n</script>\r\n"
16
+ },
17
+ {
18
+ "path": "BookTitle.vue",
19
+ "content": "<template>\r\n <h1 :class=\"cn('font-bold select-none mt-3 mb-1 text-balance', $props.class)\">\r\n <slot />\r\n </h1>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface BookTitleProps {\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\ndefineProps<BookTitleProps>();\r\n</script>\r\n"
20
+ },
21
+ {
22
+ "path": "index.ts",
23
+ "content": "export const BOOK_RADIUS_MAP = {\r\n sm: \"rounded-sm\",\r\n md: \"rounded-md\",\r\n lg: \"rounded-lg\",\r\n xl: \"rounded-xl\",\r\n} as const;\r\n\r\nexport const BOOK_SIZE_MAP = {\r\n sm: { width: \"180px\", spineTranslation: \"152px\" },\r\n md: { width: \"220px\", spineTranslation: \"192px\" },\r\n lg: { width: \"260px\", spineTranslation: \"232px\" },\r\n xl: { width: \"300px\", spineTranslation: \"272px\" },\r\n} as const;\r\n\r\nexport const BOOK_SHADOW_SIZE_MAP = {\r\n sm: \"-5px 0 15px 5px var(--shadowColor)\",\r\n md: \"-7px 0 25px 7px var(--shadowColor)\",\r\n lg: \"-10px 0 35px 10px var(--shadowColor)\",\r\n xl: \"-12px 0 45px 12px var(--shadowColor)\",\r\n} as const;\r\n\r\nexport const BOOK_COLOR_MAP = {\r\n slate: { from: \"from-slate-900\", to: \"to-slate-700\" },\r\n gray: { from: \"from-gray-900\", to: \"to-gray-700\" },\r\n zinc: { from: \"from-zinc-900\", to: \"to-zinc-700\" },\r\n neutral: { from: \"from-neutral-900\", to: \"to-neutral-700\" },\r\n stone: { from: \"from-stone-900\", to: \"to-stone-700\" },\r\n red: { from: \"from-red-900\", to: \"to-red-700\" },\r\n orange: { from: \"from-orange-900\", to: \"to-orange-700\" },\r\n amber: { from: \"from-amber-900\", to: \"to-amber-700\" },\r\n yellow: { from: \"from-yellow-900\", to: \"to-yellow-700\" },\r\n lime: { from: \"from-lime-900\", to: \"to-lime-700\" },\r\n green: { from: \"from-green-900\", to: \"to-green-700\" },\r\n emerald: { from: \"from-emerald-900\", to: \"to-emerald-700\" },\r\n teal: { from: \"from-teal-900\", to: \"to-teal-700\" },\r\n cyan: { from: \"from-cyan-900\", to: \"to-cyan-700\" },\r\n sky: { from: \"from-sky-900\", to: \"to-sky-700\" },\r\n blue: { from: \"from-blue-900\", to: \"to-blue-700\" },\r\n indigo: { from: \"from-indigo-900\", to: \"to-indigo-700\" },\r\n violet: { from: \"from-violet-900\", to: \"to-violet-700\" },\r\n purple: { from: \"from-purple-900\", to: \"to-purple-700\" },\r\n fuchsia: { from: \"from-fuchsia-900\", to: \"to-fuchsia-700\" },\r\n pink: { from: \"from-pink-900\", to: \"to-pink-700\" },\r\n rose: { from: \"from-rose-900\", to: \"to-rose-700\" },\r\n} as const;\r\n\r\nexport type BookColor = keyof typeof BOOK_COLOR_MAP;\r\nexport type BookSize = keyof typeof BOOK_SIZE_MAP;\r\nexport type BookRadius = keyof typeof BOOK_RADIUS_MAP;\r\nexport type BookShadowSize = keyof typeof BOOK_SHADOW_SIZE_MAP;\r\n\r\nexport { default as Book } from \"./Book.vue\";\r\nexport { default as BookHeader } from \"./BookHeader.vue\";\r\nexport { default as BookTitle } from \"./BookTitle.vue\";\r\nexport { default as BookDescription } from \"./BookDescription.vue\";\r\n"
24
+ }
25
+ ],
26
+ "fileCount": 5,
27
+ "contentHash": "291891332a734040b4abfaa3e72fa037c7a82ce1"
28
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "border-beam",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "BorderBeam.vue",
7
+ "content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'border-beam',\r\n 'pointer-events-none absolute inset-0 rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent]',\r\n '![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]',\r\n 'after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] animate-border-beam after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]',\r\n props.class,\r\n )\r\n \"\r\n ></div>\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 BorderBeamProps {\r\n class?: string;\r\n size?: number;\r\n duration?: number;\r\n borderWidth?: number;\r\n anchor?: number;\r\n colorFrom?: string;\r\n colorTo?: string;\r\n delay?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<BorderBeamProps>(), {\r\n size: 200,\r\n duration: 15000,\r\n anchor: 90,\r\n borderWidth: 1.5,\r\n colorFrom: \"#ffaa40\",\r\n colorTo: \"#9c40ff\",\r\n delay: 0,\r\n});\r\n\r\nconst durationInSeconds = computed(() => `${props.duration}s`);\r\nconst delayInSeconds = computed(() => `${props.delay}s`);\r\n</script>\r\n\r\n<style scoped>\r\n.border-beam {\r\n --size: v-bind(size);\r\n --duration: v-bind(durationInSeconds);\r\n --anchor: v-bind(anchor);\r\n --border-width: v-bind(borderWidth);\r\n --color-from: v-bind(colorFrom);\r\n --color-to: v-bind(colorTo);\r\n --delay: v-bind(delayInSeconds);\r\n}\r\n\r\n.animate-border-beam::after {\r\n animation: border-beam-anim var(--duration) infinite linear;\r\n}\r\n\r\n@keyframes border-beam-anim {\r\n to {\r\n offset-distance: 100%;\r\n }\r\n}\r\n</style>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as BorderBeam } from \"./BorderBeam.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "c9595c8335acee7d96a69587379e6d2d89325de4"
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "box-reveal",
3
+ "dependencies": [
4
+ "motion-v"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "BoxReveal.vue",
9
+ "content": "<template>\r\n <div :class=\"cn('relative', $props.class)\">\r\n <Motion\r\n :initial=\"initialMainVariants\"\r\n :while-in-view=\"visibleMainVariants\"\r\n :transition=\"{\r\n duration: props.duration,\r\n delay: props.delay * 2,\r\n }\"\r\n >\r\n <slot />\r\n </Motion>\r\n <Motion\r\n class=\"box-background absolute inset-0 z-20\"\r\n :initial=\"initialSlideVariants\"\r\n :while-in-view=\"visibleSlideVariants\"\r\n :transition=\"{\r\n duration: props.duration,\r\n ease: 'easeIn',\r\n delay: props.delay,\r\n }\"\r\n ></Motion>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { Motion } from \"motion-v\";\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface BoxRevealProps {\r\n color?: string;\r\n duration?: number;\r\n class?: HTMLAttributes[\"class\"];\r\n delay?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<BoxRevealProps>(), {\r\n color: \"#5046e6\",\r\n duration: 0.5,\r\n delay: 0.25,\r\n});\r\n\r\n// Motion variants\r\nconst initialMainVariants = { opacity: 0, y: 25 };\r\nconst visibleMainVariants = {\r\n opacity: 1,\r\n y: 0,\r\n};\r\n\r\nconst initialSlideVariants = { left: \"0%\" };\r\nconst visibleSlideVariants = {\r\n left: \"100%\",\r\n};\r\n</script>\r\n\r\n<style scoped>\r\n.box-background {\r\n background: v-bind(color);\r\n}\r\n</style>\r\n"
10
+ },
11
+ {
12
+ "path": "index.ts",
13
+ "content": "export { default as BoxReveal } from \"./BoxReveal.vue\";\r\n"
14
+ }
15
+ ],
16
+ "fileCount": 2,
17
+ "contentHash": "6c09c0979307f59dc6e780a415e1158d1fde5ae5"
18
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "card-3d",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "CardBody.vue",
7
+ "content": "<template>\r\n <div\r\n :class=\"cn('h-96 w-96', $props.class)\"\r\n style=\"transform-style: preserve-3d\"\r\n >\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ndefineProps({\r\n class: String,\r\n});\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "CardContainer.vue",
11
+ "content": "<template>\r\n <div\r\n :class=\"['flex items-center justify-center p-2', containerClass]\"\r\n style=\"perspective: 1000px\"\r\n >\r\n <div\r\n ref=\"containerRef\"\r\n :class=\"[\r\n 'relative flex items-center justify-center transition-all duration-200 ease-linear',\r\n $props.class,\r\n ]\"\r\n style=\"transform-style: preserve-3d\"\r\n @mouseenter=\"handleMouseEnter\"\r\n @mousemove=\"handleMouseMove\"\r\n @mouseleave=\"handleMouseLeave\"\r\n >\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { useMouseState } from \"@/composables/useMouseState\";\r\nimport { provide, ref } from \"vue\";\r\n\r\ndefineProps({\r\n class: String,\r\n containerClass: String,\r\n});\r\n\r\nconst containerRef = ref<HTMLElement | null>(null);\r\n\r\nconst mouseState = useMouseState(); // Use the composable\r\nprovide(\"use3DCardMouseState\", mouseState);\r\n\r\nfunction handleMouseMove(e: MouseEvent) {\r\n if (!containerRef.value) return;\r\n const { left, top, width, height } = containerRef.value.getBoundingClientRect();\r\n const x = (e.clientX - left - width / 2) / 25;\r\n const y = (e.clientY - top - height / 2) / 25;\r\n containerRef.value.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`;\r\n}\r\n\r\nfunction handleMouseEnter() {\r\n mouseState.setMouseEntered(true);\r\n}\r\n\r\nfunction handleMouseLeave() {\r\n if (!containerRef.value) return;\r\n\r\n mouseState.setMouseEntered(false);\r\n containerRef.value.style.transform = `rotateY(0deg) rotateX(0deg)`;\r\n}\r\n</script>\r\n"
12
+ },
13
+ {
14
+ "path": "CardItem.vue",
15
+ "content": "<template>\r\n <component\r\n :is=\"as\"\r\n ref=\"refElement\"\r\n :class=\"cn('w-fit transition duration-500 ease-in-out', $props.class)\"\r\n >\r\n <slot />\r\n </component>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { useMouseState } from \"@/composables/useMouseState\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { inject, ref, watch, type Ref } from \"vue\";\r\n\r\nconst props = defineProps({\r\n as: { type: String, default: \"div\" },\r\n class: String,\r\n translateX: { type: [Number, String], default: 0 },\r\n translateY: { type: [Number, String], default: 0 },\r\n translateZ: { type: [Number, String], default: 0 },\r\n rotateX: { type: [Number, String], default: 0 },\r\n rotateY: { type: [Number, String], default: 0 },\r\n rotateZ: { type: [Number, String], default: 0 },\r\n});\r\n\r\nconst refElement = ref<HTMLElement | null>(null);\r\n\r\nconst mouseState = inject(\"use3DCardMouseState\") as ReturnType<typeof useMouseState>;\r\n\r\nfunction handleAnimation(isMouseEntered: Readonly<Ref<boolean, boolean>>) {\r\n if (!refElement.value) return;\r\n\r\n if (isMouseEntered) {\r\n refElement.value.style.transform = `translateX(${props.translateX}px) translateY(${props.translateY}px) translateZ(${props.translateZ}px) rotateX(${props.rotateX}deg) rotateY(${props.rotateY}deg) rotateZ(${props.rotateZ}deg)`;\r\n } else {\r\n refElement.value.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`;\r\n }\r\n}\r\n\r\nwatch(mouseState.isMouseEntered, handleAnimation, { immediate: true });\r\n</script>\r\n"
16
+ },
17
+ {
18
+ "path": "index.ts",
19
+ "content": "export { default as CardContainer } from \"./CardContainer.vue\";\r\nexport { default as CardBody } from \"./CardBody.vue\";\r\nexport { default as CardItem } from \"./CardItem.vue\";\r\n"
20
+ }
21
+ ],
22
+ "fileCount": 4,
23
+ "contentHash": "bb5ac373a9fea8165151bea0f2919f216d1133a8"
24
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "card-spotlight",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "CardSpotlight.vue",
7
+ "content": "<template>\r\n <div\r\n :class=\"[\r\n 'group relative flex size-full overflow-hidden rounded-xl border bg-neutral-100 text-black dark:bg-neutral-900 dark:text-white',\r\n $props.class,\r\n ]\"\r\n @mousemove=\"handleMouseMove\"\r\n @mouseleave=\"handleMouseLeave\"\r\n >\r\n <div :class=\"cn('relative z-10', props.slotClass)\">\r\n <slot></slot>\r\n </div>\r\n <div\r\n class=\"pointer-events-none absolute inset-0 rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100\"\r\n :style=\"{\r\n background: backgroundStyle,\r\n opacity: gradientOpacity,\r\n }\"\r\n ></div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, type HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n class?: HTMLAttributes[\"class\"];\r\n slotClass?: HTMLAttributes[\"class\"];\r\n gradientSize?: number;\r\n gradientColor?: string;\r\n gradientOpacity?: number;\r\n }>(),\r\n {\r\n class: \"\",\r\n slotClass: \"\",\r\n gradientSize: 200,\r\n gradientColor: \"#262626\",\r\n gradientOpacity: 0.8,\r\n },\r\n);\r\n\r\nconst mouseX = ref(-props.gradientSize * 10);\r\nconst mouseY = ref(-props.gradientSize * 10);\r\n\r\nfunction handleMouseMove(e: MouseEvent) {\r\n const target = e.currentTarget as HTMLElement;\r\n const rect = target.getBoundingClientRect();\r\n mouseX.value = e.clientX - rect.left;\r\n mouseY.value = e.clientY - rect.top;\r\n}\r\n\r\nfunction handleMouseLeave() {\r\n mouseX.value = -props.gradientSize * 10;\r\n mouseY.value = -props.gradientSize * 10;\r\n}\r\n\r\nonMounted(() => {\r\n mouseX.value = -props.gradientSize * 10;\r\n mouseY.value = -props.gradientSize * 10;\r\n});\r\n\r\nconst backgroundStyle = computed(() => {\r\n return `radial-gradient(\r\n circle at ${mouseX.value}px ${mouseY.value}px,\r\n ${props.gradientColor} 0%,\r\n rgba(0, 0, 0, 0) 70%\r\n )`;\r\n});\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as CardSpotlight } from \"./CardSpotlight.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "e9950d3bb933da8766f84a56869ae33662ad02e1"
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "carousel-3d",
3
+ "dependencies": [
4
+ "motion-v",
5
+ "three"
6
+ ],
7
+ "files": [
8
+ {
9
+ "path": "Carousel3D.vue",
10
+ "content": "<template>\r\n <div\r\n ref=\"carouselContainer\"\r\n :class=\"cn('w-full h-[60vh] relative', props.containerClass)\"\r\n >\r\n <div\r\n :class=\"\r\n cn('absolute top-[40%] translate-y-[-50%] left-0 w-full h-[80%] z-[100]', props.class)\r\n \"\r\n @mousedown=\"onDragStart\"\r\n @mouseup=\"onDragEnd\"\r\n @mouseleave=\"onDragEnd\"\r\n @mousemove=\"onMouseMove\"\r\n @touchstart=\"onTouchStart\"\r\n @touchend=\"onDragEnd\"\r\n @touchmove=\"onTouchMove\"\r\n ></div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport * as THREE from \"three\";\r\nimport { CSS3DRenderer, CSS3DObject } from \"three/addons/renderers/CSS3DRenderer.js\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { animate, type AnimationPlaybackControls } from \"motion-v\";\r\n\r\ninterface Props {\r\n items?: unknown[];\r\n class?: HTMLAttributes[\"class\"];\r\n containerClass?: HTMLAttributes[\"class\"];\r\n width?: number;\r\n height?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n class: \"\",\r\n containerClass: \"\",\r\n width: 450,\r\n height: 600,\r\n items: () => [],\r\n});\r\n\r\nconst carouselContainer = ref<HTMLDivElement>();\r\nconst isDragging = ref(false);\r\nconst startX = ref(0);\r\nconst currentX = ref(0);\r\nconst sensitivity = 0.0025; // Adjusted rotation sensitivity for handleDrag\r\n\r\nconst scene = new THREE.Scene();\r\nconst camera = new THREE.PerspectiveCamera(50, 1, 1, 5000);\r\nlet renderer: CSS3DRenderer;\r\n\r\nconst radius = ref(750);\r\nlet carousel: THREE.Object3D;\r\nlet continuousAnimation = ref<AnimationPlaybackControls | null>(null);\r\n\r\nonMounted(() => {\r\n if (!carouselContainer.value) return;\r\n\r\n // Setup renderer\r\n renderer = new CSS3DRenderer();\r\n const width = carouselContainer.value.clientWidth;\r\n const height = carouselContainer.value.clientHeight;\r\n\r\n renderer.setSize(width, height);\r\n carouselContainer.value.appendChild(renderer.domElement);\r\n\r\n // Camera setup\r\n camera.position.z = 550;\r\n camera.position.y = 70;\r\n\r\n // Create carousel\r\n carousel = new THREE.Object3D();\r\n scene.add(carousel);\r\n\r\n // Add items\r\n props.items.forEach((image, index) => {\r\n const element = document.createElement(\"div\");\r\n element.style.width = `${props.width}px`;\r\n element.style.height = `${props.height}px`;\r\n element.classList.add(\"rounded-lg\");\r\n element.style.backgroundImage = `url(${image})`;\r\n element.style.backgroundSize = \"cover\";\r\n\r\n const object = new CSS3DObject(element);\r\n const angle = (index / props.items.length) * Math.PI * 2;\r\n object.position.setFromSphericalCoords(radius.value, Math.PI / 2, angle);\r\n object.lookAt(carousel.position);\r\n\r\n carousel.add(object);\r\n });\r\n\r\n // Initial rotation\r\n carousel.rotation.x = THREE.MathUtils.degToRad(20);\r\n startContinuousRotation();\r\n\r\n window.addEventListener(\"resize\", onWindowResize);\r\n});\r\n\r\nfunction startContinuousRotation() {\r\n if (continuousAnimation.value) {\r\n continuousAnimation.value.stop();\r\n }\r\n\r\n const fromVal = carousel.rotation.y;\r\n const toVal = fromVal + Math.PI * 2;\r\n\r\n continuousAnimation.value = animate(fromVal, toVal, {\r\n duration: 20,\r\n ease: \"linear\",\r\n repeat: Infinity,\r\n repeatType: \"loop\",\r\n onUpdate: (latest) => {\r\n carousel.rotation.y = latest;\r\n renderer.render(scene, camera);\r\n },\r\n });\r\n}\r\n\r\nfunction onDragStart(event: MouseEvent | TouchEvent) {\r\n isDragging.value = true;\r\n startX.value = \"touches\" in event ? event.touches[0].clientX : event.clientX;\r\n currentX.value = startX.value;\r\n\r\n if (continuousAnimation.value) {\r\n continuousAnimation.value.stop();\r\n continuousAnimation.value = null;\r\n }\r\n}\r\n\r\nfunction onDragEnd() {\r\n if (!isDragging.value) return;\r\n isDragging.value = false;\r\n startContinuousRotation();\r\n}\r\n\r\nfunction onMouseMove(event: MouseEvent) {\r\n if (!isDragging.value) return;\r\n handleDrag(event.clientX);\r\n}\r\n\r\nfunction onTouchStart(event: TouchEvent) {\r\n onDragStart(event);\r\n}\r\n\r\nfunction onTouchMove(event: TouchEvent) {\r\n if (!isDragging.value) return;\r\n event.preventDefault();\r\n handleDrag(event.touches[0].clientX);\r\n}\r\n\r\nfunction handleDrag(clientX: number) {\r\n const deltaX = clientX - currentX.value;\r\n currentX.value = clientX;\r\n\r\n carousel.rotation.y += -deltaX * sensitivity;\r\n renderer.render(scene, camera);\r\n}\r\n\r\nfunction onWindowResize() {\r\n if (!carouselContainer.value) return;\r\n\r\n const width = carouselContainer.value.clientWidth;\r\n const height = carouselContainer.value.clientHeight;\r\n\r\n radius.value = width / 3;\r\n camera.aspect = width / height;\r\n camera.updateProjectionMatrix();\r\n renderer.setSize(width, height);\r\n}\r\n\r\nonBeforeUnmount(() => {\r\n if (carouselContainer.value && renderer) {\r\n carouselContainer.value.removeChild(renderer.domElement);\r\n }\r\n window.removeEventListener(\"resize\", onWindowResize);\r\n\r\n if (continuousAnimation.value) {\r\n continuousAnimation.value.stop();\r\n }\r\n});\r\n</script>\r\n"
11
+ }
12
+ ],
13
+ "fileCount": 1,
14
+ "contentHash": "3fc5222de1047177e1c9f54321f989cda0072cae"
15
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "color-picker",
3
+ "dependencies": [
4
+ "@uiw/color-convert"
5
+ ],
6
+ "files": [
7
+ {
8
+ "path": "ColorPicker.vue",
9
+ "content": "<template>\r\n <div class=\"relative\">\r\n <div @click=\"isOpen = !isOpen\">\r\n <slot />\r\n </div>\r\n\r\n <div\r\n v-if=\"isOpen\"\r\n class=\"absolute top-full left-1/2 transform -translate-x-1/2 mt-2 w-80 p-4 bg-popover border rounded-md shadow-md z-50\"\r\n >\r\n <div class=\"space-y-4\">\r\n <div\r\n ref=\"saturationRef\"\r\n class=\"relative w-full aspect-[4/2] rounded border cursor-crosshair\"\r\n :style=\"{\r\n background: `linear-gradient(to right, #fff, hsl(${colorHsv.h}, 100%, 50%)), linear-gradient(to top, #000, transparent)`,\r\n }\"\r\n @mousedown=\"startSaturationDrag\"\r\n @touchstart=\"startSaturationDrag\"\r\n >\r\n <div\r\n class=\"absolute w-4 h-4 border-2 border-white rounded-full pointer-events-none\"\r\n :style=\"{\r\n left: `${colorHsv.s}%`,\r\n top: `${100 - colorHsv.v}%`,\r\n transform: 'translate(-50%, -50%)',\r\n }\"\r\n />\r\n </div>\r\n\r\n <div\r\n ref=\"hueRef\"\r\n class=\"relative w-full h-4 rounded cursor-pointer\"\r\n :style=\"{\r\n background:\r\n 'linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)',\r\n }\"\r\n @mousedown=\"startHueDrag\"\r\n @touchstart=\"startHueDrag\"\r\n @click=\"handleHueClick\"\r\n >\r\n <div\r\n class=\"absolute w-4 h-4 border-2 border-foreground rounded-full pointer-events-none bg-white\"\r\n :style=\"{\r\n left: `${(colorHsv.h / 360) * 100}%`,\r\n transform: 'translate(-50%, -50%)',\r\n top: '50%',\r\n }\"\r\n />\r\n </div>\r\n\r\n <div class=\"flex items-center gap-2\">\r\n <div class=\"relative\">\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex h-8 w-20 gap-2 items-center justify-between rounded-md border border-input bg-background px-2 py-1 text-xs font-medium uppercase\"\r\n @click=\"toggleDropdown\"\r\n >\r\n <span>{{ colorType }}</span>\r\n <Icon\r\n name=\"lucide:chevron-down\"\r\n :size=\"14\"\r\n />\r\n </button>\r\n\r\n <div\r\n v-if=\"isDropdownOpen\"\r\n class=\"absolute top-full left-0 z-50 mt-1 w-full rounded-md border border-input bg-popover shadow-md\"\r\n >\r\n <div class=\"p-1\">\r\n <button\r\n v-for=\"option in colorOptions\"\r\n :key=\"option.value\"\r\n type=\"button\"\r\n class=\"relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-xs font-medium uppercase hover:bg-accent hover:text-accent-foreground\"\r\n @click=\"selectColorType(option.value)\"\r\n >\r\n {{ option.label }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex grow\">\r\n <input\r\n v-if=\"colorType === 'hex'\"\r\n :value=\"hexValue\"\r\n type=\"text\"\r\n class=\"flex h-8 w-full rounded-md border border-input bg-background px-3 py-1 text-sm placeholder:text-muted-foreground focus:outline-none\"\r\n placeholder=\"#000000\"\r\n @input=\"updateFromHex(($event.target as HTMLInputElement).value)\"\r\n />\r\n\r\n <ObjectColorInput\r\n v-else\r\n :value=\"colorType === 'hsl' || colorType === 'hsla' ? hslValue : rgbValue\"\r\n :label=\"colorType\"\r\n @value-change=\"updateFromObject\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n v-if=\"colorType === 'rgba' || colorType === 'hsla'\"\r\n class=\"space-y-2\"\r\n >\r\n <div\r\n ref=\"alphaRef\"\r\n class=\"relative w-full h-4 rounded cursor-pointer\"\r\n :style=\"{\r\n background: `linear-gradient(to right, rgba(${rgbValue.r}, ${rgbValue.g}, ${rgbValue.b}, 0) 0%, rgba(${rgbValue.r}, ${rgbValue.g}, ${rgbValue.b}, 1) 100%), repeating-conic-gradient(#ccc 0% 25%, transparent 0% 50%) 50% / 8px 8px`,\r\n }\"\r\n @mousedown=\"startAlphaDrag\"\r\n @touchstart=\"startAlphaDrag\"\r\n >\r\n <div\r\n class=\"absolute w-4 h-4 border-2 border-foreground rounded-full pointer-events-none bg-white\"\r\n :style=\"{\r\n left: `${colorHsv.a * 100}%`,\r\n transform: 'translate(-50%, -50%)',\r\n top: '50%',\r\n }\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"swatches.length > 0 || !hideDefaultSwatches\">\r\n <div class=\"h-px bg-border\" />\r\n <div\r\n v-if=\"!hideDefaultSwatches\"\r\n class=\"flex flex-wrap justify-start gap-2 pt-2\"\r\n >\r\n <button\r\n v-for=\"color in sortedSwatches\"\r\n :key=\"`${color}-swatch`\"\r\n type=\"button\"\r\n class=\"size-5 cursor-pointer rounded ring-2 ring-transparent ring-offset-1 ring-offset-background transition-all duration-100 hover:ring-current\"\r\n :style=\"{ backgroundColor: color }\"\r\n :aria-label=\"`Set color to ${color}`\"\r\n @click=\"setColorFromHex(color)\"\r\n @keydown.enter=\"setColorFromHex(color)\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"!hideContrastRatio\">\r\n <div class=\"h-px bg-border\" />\r\n <ContrastRatio :color=\"colorHsv\" />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div\r\n v-if=\"isOpen\"\r\n class=\"fixed inset-0 z-40\"\r\n @click=\"isOpen = false\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, watch } from \"vue\";\r\nimport {\r\n type HexColor,\r\n hexToHsva,\r\n type HslaColor,\r\n hslaToHsva,\r\n type HsvaColor,\r\n hsvaToHex,\r\n hsvaToHsla,\r\n hsvaToRgba,\r\n type RgbaColor,\r\n rgbaToHsva,\r\n} from \"@uiw/color-convert\";\r\nimport ObjectColorInput from \"./ObjectColorInput.vue\";\r\nimport ContrastRatio from \"./ContrastRatio.vue\";\r\n\r\nexport interface ColorPickerValue {\r\n hex: string;\r\n hsl: HslaColor;\r\n hsla: HslaColor;\r\n rgb: RgbaColor;\r\n rgba: RgbaColor;\r\n}\r\n\r\nexport interface ColorPickerProps {\r\n value?: `#${string}` | HsvaColor | HslaColor | RgbaColor;\r\n type?: \"hsl\" | \"hsla\" | \"rgb\" | \"rgba\" | \"hex\";\r\n swatches?: HexColor[];\r\n hideContrastRatio?: boolean;\r\n hideDefaultSwatches?: boolean;\r\n class?: string;\r\n open?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<ColorPickerProps>(), {\r\n type: \"hsl\",\r\n swatches: () => [],\r\n hideContrastRatio: false,\r\n hideDefaultSwatches: false,\r\n open: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"value-change\": [value: ColorPickerValue];\r\n \"update:open\": [value: boolean];\r\n}>();\r\n\r\nconst colorType = ref(props.type);\r\nconst isDropdownOpen = ref(false);\r\nconst isOpen = ref(props.open);\r\n\r\nconst colorOptions = [\r\n { value: \"hex\", label: \"HEX\" },\r\n { value: \"hsl\", label: \"HSL\" },\r\n { value: \"hsla\", label: \"HSLA\" },\r\n { value: \"rgb\", label: \"RGB\" },\r\n { value: \"rgba\", label: \"RGBA\" },\r\n];\r\n\r\nfunction getColorAsHsva(color: ColorPickerProps[\"value\"]): HsvaColor {\r\n if (!color) return { h: 0, s: 0, v: 0, a: 1 };\r\n\r\n if (typeof color === \"string\") {\r\n return hexToHsva(color);\r\n } else if (\"h\" in color && \"s\" in color && \"v\" in color) {\r\n return color;\r\n } else if (\"r\" in color) {\r\n return rgbaToHsva(color);\r\n } else {\r\n return hslaToHsva(color);\r\n }\r\n}\r\n\r\nconst colorHsv = ref<HsvaColor>(getColorAsHsva(props.value));\r\n\r\nwatch(\r\n () => props.value,\r\n (newValue) => {\r\n if (newValue) {\r\n colorHsv.value = getColorAsHsva(newValue);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nwatch(\r\n () => props.open,\r\n (newValue) => {\r\n isOpen.value = newValue;\r\n },\r\n);\r\n\r\nwatch(isOpen, (newValue) => {\r\n emit(\"update:open\", newValue);\r\n});\r\n\r\nconst hexValue = computed(() => hsvaToHex(colorHsv.value));\r\nconst hslValue = computed(() => hsvaToHsla(colorHsv.value));\r\nconst rgbValue = computed(() => hsvaToRgba(colorHsv.value));\r\n\r\nconst defaultSwatches = [\"#F8371A\", \"#F97C1B\", \"#FAC81C\", \"#3FD0B6\", \"#2CADF6\", \"#6462FC\"];\r\nconst sortedSwatches = computed(() => {\r\n const allSwatches = [...defaultSwatches, ...props.swatches];\r\n return allSwatches.sort((a, b) => hexToHsva(a).h - hexToHsva(b).h);\r\n});\r\n\r\nconst isDraggingSaturation = ref(false);\r\nconst isDraggingHue = ref(false);\r\nconst isDraggingAlpha = ref(false);\r\nconst saturationRef = ref<HTMLElement>();\r\nconst hueRef = ref<HTMLElement>();\r\nconst alphaRef = ref<HTMLElement>();\r\n\r\nfunction handleValueChange(color: HsvaColor) {\r\n colorHsv.value = color;\r\n\r\n const hslColor = hsvaToHsla(color);\r\n const rgbColor = hsvaToRgba(color);\r\n\r\n emit(\"value-change\", {\r\n hex: hsvaToHex(color),\r\n hsl: {\r\n ...hslColor,\r\n a: Math.round(hslColor.a * 100) / 100,\r\n },\r\n hsla: {\r\n ...hslColor,\r\n a: Math.round(hslColor.a * 100) / 100,\r\n },\r\n rgb: {\r\n ...rgbColor,\r\n a: Math.round(rgbColor.a * 100) / 100,\r\n },\r\n rgba: {\r\n ...rgbColor,\r\n a: Math.round(rgbColor.a * 100) / 100,\r\n },\r\n });\r\n}\r\n\r\nfunction updateFromHex(value: string | number) {\r\n try {\r\n const newColor = hexToHsva(String(value));\r\n handleValueChange(newColor);\r\n } catch (error) {\r\n // Invalid hex color, ignore\r\n }\r\n}\r\n\r\nfunction updateFromObject(value: HslaColor | RgbaColor) {\r\n try {\r\n const newColor = \"r\" in value ? rgbaToHsva(value) : hslaToHsva(value);\r\n handleValueChange(newColor);\r\n } catch (error) {\r\n // Invalid color object, ignore\r\n }\r\n}\r\n\r\nfunction setColorFromHex(hex: string) {\r\n handleValueChange(hexToHsva(hex));\r\n}\r\n\r\nfunction toggleDropdown() {\r\n isDropdownOpen.value = !isDropdownOpen.value;\r\n}\r\n\r\nfunction selectColorType(value: string) {\r\n colorType.value = value as \"hsl\" | \"hsla\" | \"rgb\" | \"rgba\" | \"hex\";\r\n isDropdownOpen.value = false;\r\n}\r\n\r\nfunction startSaturationDrag(event: MouseEvent | TouchEvent) {\r\n event.preventDefault();\r\n isDraggingSaturation.value = true;\r\n updateSaturation(event);\r\n\r\n function handleMove(e: MouseEvent | TouchEvent) {\r\n if (isDraggingSaturation.value) {\r\n updateSaturation(e);\r\n }\r\n }\r\n\r\n function handleEnd() {\r\n isDraggingSaturation.value = false;\r\n document.removeEventListener(\"mousemove\", handleMove);\r\n document.removeEventListener(\"mouseup\", handleEnd);\r\n document.removeEventListener(\"touchmove\", handleMove);\r\n document.removeEventListener(\"touchend\", handleEnd);\r\n }\r\n\r\n document.addEventListener(\"mousemove\", handleMove);\r\n document.addEventListener(\"mouseup\", handleEnd);\r\n document.addEventListener(\"touchmove\", handleMove);\r\n document.addEventListener(\"touchend\", handleEnd);\r\n}\r\n\r\nfunction updateSaturation(event: MouseEvent | TouchEvent) {\r\n if (!saturationRef.value) return;\r\n\r\n const rect = saturationRef.value.getBoundingClientRect();\r\n const clientX = \"touches\" in event ? event.touches[0].clientX : event.clientX;\r\n const clientY = \"touches\" in event ? event.touches[0].clientY : event.clientY;\r\n\r\n const relativeX = (clientX - rect.left) / rect.width;\r\n const relativeY = (clientY - rect.top) / rect.height;\r\n\r\n const x = Math.max(0.01, Math.min(0.99, relativeX));\r\n const y = Math.max(0.01, Math.min(0.99, relativeY));\r\n\r\n handleValueChange({\r\n ...colorHsv.value,\r\n s: x * 100,\r\n v: (1 - y) * 100,\r\n });\r\n}\r\n\r\nfunction handleHueClick(event: MouseEvent) {\r\n updateHue(event);\r\n}\r\n\r\nfunction startHueDrag(event: MouseEvent | TouchEvent) {\r\n event.preventDefault();\r\n isDraggingHue.value = true;\r\n updateHue(event);\r\n\r\n function handleMove(e: MouseEvent | TouchEvent) {\r\n if (isDraggingHue.value) {\r\n updateHue(e);\r\n }\r\n }\r\n\r\n function handleEnd() {\r\n isDraggingHue.value = false;\r\n document.removeEventListener(\"mousemove\", handleMove);\r\n document.removeEventListener(\"mouseup\", handleEnd);\r\n document.removeEventListener(\"touchmove\", handleMove);\r\n document.removeEventListener(\"touchend\", handleEnd);\r\n }\r\n\r\n document.addEventListener(\"mousemove\", handleMove);\r\n document.addEventListener(\"mouseup\", handleEnd);\r\n document.addEventListener(\"touchmove\", handleMove);\r\n document.addEventListener(\"touchend\", handleEnd);\r\n}\r\n\r\nfunction updateHue(event: MouseEvent | TouchEvent) {\r\n if (!hueRef.value) return;\r\n\r\n const rect = hueRef.value.getBoundingClientRect();\r\n const clientX = \"touches\" in event ? event.touches[0].clientX : event.clientX;\r\n\r\n const x = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\r\n\r\n handleValueChange({\r\n ...colorHsv.value,\r\n h: x * 360,\r\n });\r\n}\r\n\r\nfunction startAlphaDrag(event: MouseEvent | TouchEvent) {\r\n event.preventDefault();\r\n isDraggingAlpha.value = true;\r\n updateAlpha(event);\r\n\r\n function handleMove(e: MouseEvent | TouchEvent) {\r\n if (isDraggingAlpha.value) {\r\n updateAlpha(e);\r\n }\r\n }\r\n\r\n function handleEnd() {\r\n isDraggingAlpha.value = false;\r\n document.removeEventListener(\"mousemove\", handleMove);\r\n document.removeEventListener(\"mouseup\", handleEnd);\r\n document.removeEventListener(\"touchmove\", handleMove);\r\n document.removeEventListener(\"touchend\", handleEnd);\r\n }\r\n\r\n document.addEventListener(\"mousemove\", handleMove);\r\n document.addEventListener(\"mouseup\", handleEnd);\r\n document.addEventListener(\"touchmove\", handleMove);\r\n document.addEventListener(\"touchend\", handleEnd);\r\n}\r\n\r\nfunction updateAlpha(event: MouseEvent | TouchEvent) {\r\n if (!alphaRef.value) return;\r\n\r\n const rect = alphaRef.value.getBoundingClientRect();\r\n const clientX = \"touches\" in event ? event.touches[0].clientX : event.clientX;\r\n\r\n const x = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\r\n\r\n handleValueChange({\r\n ...colorHsv.value,\r\n a: x,\r\n });\r\n}\r\n</script>\r\n"
10
+ },
11
+ {
12
+ "path": "ContrastRatio.vue",
13
+ "content": "<template>\r\n <div class=\"flex items-center justify-between gap-4 pt-2 select-none\">\r\n <div class=\"flex items-center gap-4\">\r\n <div\r\n class=\"flex size-10 items-center justify-center rounded border\"\r\n :style=\"{\r\n backgroundColor: `rgba(${rgbaColor.r}, ${rgbaColor.g}, ${rgbaColor.b}, ${rgbaColor.a})`,\r\n backgroundSize: '8px 8px',\r\n }\"\r\n >\r\n <span\r\n class=\"font-medium\"\r\n :style=\"{\r\n color: shouldUseWhiteText ? 'white' : 'black',\r\n }\"\r\n >A</span\r\n >\r\n </div>\r\n <div class=\"flex flex-col justify-between\">\r\n <span class=\"whitespace-nowrap text-nowrap text-xs text-muted-foreground\">\r\n Contrast Ratio\r\n </span>\r\n <span class=\"text-sm\">{{ currentContrastRatio }}</span>\r\n </div>\r\n </div>\r\n <div class=\"flex items-center justify-end gap-1 *:select-none\">\r\n <UiBadge\r\n :variant=\"isAccessible.aa ? 'default' : 'secondary'\"\r\n class=\"gap-1\"\r\n >\r\n <Icon\r\n :name=\"isAccessible.aa ? 'lucide:check' : 'lucide:x'\"\r\n :size=\"12\"\r\n />\r\n AA\r\n </UiBadge>\r\n <UiBadge\r\n :variant=\"isAccessible.aaa ? 'default' : 'secondary'\"\r\n class=\"gap-1\"\r\n >\r\n <Icon\r\n :name=\"isAccessible.aaa ? 'lucide:check' : 'lucide:x'\"\r\n :size=\"12\"\r\n />\r\n AAA\r\n </UiBadge>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { watch, ref, computed } from \"vue\";\r\nimport type { HsvaColor } from \"@uiw/color-convert\";\r\nimport { hsvaToRgba } from \"@uiw/color-convert\";\r\n\r\nexport interface ContrastRatioProps {\r\n color: HsvaColor;\r\n}\r\n\r\nconst props = defineProps<ContrastRatioProps>();\r\n\r\nconst darkModeContrastRatio = ref(0);\r\nconst lightModeContrastValue = ref(0);\r\n\r\nconst rgbaColor = computed(() => hsvaToRgba(props.color));\r\n\r\nconst shouldUseWhiteText = computed(() => {\r\n const rgb = rgbaColor.value;\r\n // Calculate luminance of the color background\r\n const r = rgb.r / 255;\r\n const g = rgb.g / 255;\r\n const b = rgb.b / 255;\r\n\r\n const rLinear = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);\r\n const gLinear = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);\r\n const bLinear = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);\r\n\r\n const luminance = 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;\r\n\r\n // Use white text if the background is dark (luminance < 0.5)\r\n return luminance < 0.5;\r\n});\r\n\r\nconst currentContrastRatio = computed(() => {\r\n // For accessibility, use the better of the two contrast ratios (dark or light mode)\r\n // This gives the most accurate assessment of accessibility across different contexts\r\n return Math.max(darkModeContrastRatio.value, lightModeContrastValue.value);\r\n});\r\n\r\nconst isAccessible = computed(() => {\r\n return {\r\n aa: currentContrastRatio.value >= 4.5,\r\n aaa: currentContrastRatio.value >= 7,\r\n };\r\n});\r\n\r\nfunction calculateContrastRatios(color: HsvaColor) {\r\n const rgb = hsvaToRgba(color);\r\n\r\n function toSRGB(c: number) {\r\n const channel = c / 255;\r\n return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);\r\n }\r\n\r\n function alphaBlend(foreground: number, background: number, alpha: number) {\r\n return foreground * alpha + background * (1 - alpha);\r\n }\r\n\r\n // Apply alpha blending for light background (white: 255)\r\n const lightR = alphaBlend(rgb.r, 255, rgb.a);\r\n const lightG = alphaBlend(rgb.g, 255, rgb.a);\r\n const lightB = alphaBlend(rgb.b, 255, rgb.a);\r\n\r\n // Apply alpha blending for dark background (dark gray: 32)\r\n const darkR = alphaBlend(rgb.r, 32, rgb.a);\r\n const darkG = alphaBlend(rgb.g, 32, rgb.a);\r\n const darkB = alphaBlend(rgb.b, 32, rgb.a);\r\n\r\n // Calculate luminance for light background composition\r\n const lightRSRGB = toSRGB(lightR);\r\n const lightGSRGB = toSRGB(lightG);\r\n const lightBSRGB = toSRGB(lightB);\r\n const lightLuminance = 0.2126 * lightRSRGB + 0.7152 * lightGSRGB + 0.0722 * lightBSRGB;\r\n\r\n // Calculate luminance for dark background composition\r\n const darkRSRGB = toSRGB(darkR);\r\n const darkGSRGB = toSRGB(darkG);\r\n const darkBSRGB = toSRGB(darkB);\r\n const darkLuminance = 0.2126 * darkRSRGB + 0.7152 * darkGSRGB + 0.0722 * darkBSRGB;\r\n\r\n // Text color luminances (what we're measuring contrast against)\r\n const whiteTextLuminance = 1.0;\r\n const blackTextLuminance = 0.0;\r\n\r\n // Calculate contrast ratios: text against the color background (alpha-blended)\r\n // For dark mode: white text against the dark-composited color\r\n const darkModeRatio =\r\n (Math.max(whiteTextLuminance, darkLuminance) + 0.05) /\r\n (Math.min(whiteTextLuminance, darkLuminance) + 0.05);\r\n // For light mode: black text against the light-composited color\r\n const lightModeRatio =\r\n (Math.max(blackTextLuminance, lightLuminance) + 0.05) /\r\n (Math.min(blackTextLuminance, lightLuminance) + 0.05);\r\n\r\n darkModeContrastRatio.value = Number(darkModeRatio.toFixed(2));\r\n lightModeContrastValue.value = Number(lightModeRatio.toFixed(2));\r\n}\r\n\r\nwatch(() => props.color, calculateContrastRatios, { immediate: true, deep: true });\r\n</script>\r\n"
14
+ },
15
+ {
16
+ "path": "index.ts",
17
+ "content": "export { default as ColorPicker } from \"./ColorPicker.vue\";\r\nexport { default as ContrastRatio } from \"./ContrastRatio.vue\";\r\nexport { default as ObjectColorInput } from \"./ObjectColorInput.vue\";\r\n\r\nexport type { ColorPickerValue, ColorPickerProps } from \"./ColorPicker.vue\";\r\n"
18
+ },
19
+ {
20
+ "path": "ObjectColorInput.vue",
21
+ "content": "<template>\r\n <div class=\"flex w-full min-w-0\">\r\n <input\r\n v-for=\"(input, idx) in inputs\"\r\n :key=\"input.key\"\r\n :value=\"inputValues[idx]\"\r\n :min=\"input.min\"\r\n :max=\"input.max\"\r\n :class=\"input.class\"\r\n step=\"1\"\r\n @input=\"handleChange(idx, $event)\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport type { HslaColor, RgbaColor } from \"@uiw/color-convert\";\r\n\r\ninterface ObjectColorInputProps {\r\n label: \"hsl\" | \"hsla\" | \"rgb\" | \"rgba\";\r\n value: HslaColor | RgbaColor;\r\n}\r\n\r\nconst props = defineProps<ObjectColorInputProps>();\r\nconst emit = defineEmits<{ \"value-change\": [value: HslaColor | RgbaColor] }>();\r\n\r\nconst inputValues = computed(() => {\r\n return getInputValues(props.value);\r\n});\r\n\r\nfunction getInputValues(value: HslaColor | RgbaColor): number[] {\r\n const isHsl = props.label === \"hsl\" || props.label === \"hsla\";\r\n const hasAlpha = props.label === \"hsla\" || props.label === \"rgba\";\r\n\r\n if (isHsl) {\r\n const hsl = value as HslaColor;\r\n const values = [Math.round(hsl.h), Math.round(hsl.s), Math.round(hsl.l)];\r\n if (hasAlpha) {\r\n values.push(Math.round(hsl.a * 100));\r\n }\r\n return values;\r\n } else {\r\n const rgb = value as RgbaColor;\r\n const values = [Math.round(rgb.r), Math.round(rgb.g), Math.round(rgb.b)];\r\n if (hasAlpha) {\r\n values.push(Math.round(rgb.a * 100));\r\n }\r\n return values;\r\n }\r\n}\r\n\r\nfunction getInputClass(index: number, totalInputs: number): string {\r\n const baseClass =\r\n \"flex-1 w-0 h-8 px-2 py-1 text-xs border border-input bg-background text-center overflow-hidden focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0\";\r\n\r\n const isFirst = index === 0;\r\n const isLast = index === totalInputs - 1;\r\n const isMiddle = !isFirst && !isLast;\r\n\r\n let positionClasses = \"\";\r\n if (isFirst) {\r\n positionClasses = \"rounded-l-md -mr-px\";\r\n } else if (isLast) {\r\n positionClasses = \"rounded-r-md\";\r\n } else if (isMiddle) {\r\n positionClasses = \"rounded-none -mr-px\";\r\n }\r\n\r\n return `${baseClass} ${positionClasses}`;\r\n}\r\n\r\nconst inputs = computed(() => {\r\n const isHsl = props.label === \"hsl\" || props.label === \"hsla\";\r\n const hasAlpha = props.label === \"hsla\" || props.label === \"rgba\";\r\n\r\n let inputsData;\r\n if (isHsl) {\r\n const hsl = props.value as HslaColor;\r\n inputsData = [\r\n { key: \"h\", value: Math.round(hsl.h), min: 0, max: 360, prop: \"h\" },\r\n { key: \"s\", value: Math.round(hsl.s), min: 0, max: 100, prop: \"s\" },\r\n { key: \"l\", value: Math.round(hsl.l), min: 0, max: 100, prop: \"l\" },\r\n ];\r\n if (hasAlpha) {\r\n inputsData.push({ key: \"a\", value: Math.round(hsl.a * 100), min: 0, max: 100, prop: \"a\" });\r\n }\r\n } else {\r\n const rgb = props.value as RgbaColor;\r\n inputsData = [\r\n { key: \"r\", value: Math.round(rgb.r), min: 0, max: 255, prop: \"r\" },\r\n { key: \"g\", value: Math.round(rgb.g), min: 0, max: 255, prop: \"g\" },\r\n { key: \"b\", value: Math.round(rgb.b), min: 0, max: 255, prop: \"b\" },\r\n ];\r\n if (hasAlpha) {\r\n inputsData.push({ key: \"a\", value: Math.round(rgb.a * 100), min: 0, max: 100, prop: \"a\" });\r\n }\r\n }\r\n\r\n return inputsData.map((input, index) => ({\r\n ...input,\r\n class: getInputClass(index, inputsData.length),\r\n }));\r\n});\r\n\r\nfunction handleChange(idx: number, event: Event) {\r\n const target = event.target as HTMLInputElement;\r\n const value = Number(target.value);\r\n if (Number.isNaN(value)) return;\r\n\r\n const input = inputs.value[idx];\r\n const clampedValue = Math.max(input.min, Math.min(input.max, value));\r\n const finalValue = input.prop === \"a\" ? clampedValue / 100 : clampedValue;\r\n\r\n emit(\"value-change\", { ...props.value, [input.prop]: finalValue } as HslaColor | RgbaColor);\r\n}\r\n</script>\r\n"
22
+ }
23
+ ],
24
+ "fileCount": 4,
25
+ "contentHash": "5a4c224ec56a8c2bc98ac3ca1ff727671ad10e64"
26
+ }