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.
- package/README.md +47 -0
- package/dist/index.js +871 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
- package/registry/.gitkeep +2 -0
- package/registry/components/animate-grid.json +18 -0
- package/registry/components/animated-beam.json +16 -0
- package/registry/components/animated-circular-progressbar.json +16 -0
- package/registry/components/animated-list.json +22 -0
- package/registry/components/animated-testimonials.json +18 -0
- package/registry/components/animated-tooltip.json +18 -0
- package/registry/components/apple-card-carousel.json +35 -0
- package/registry/components/aurora-background.json +16 -0
- package/registry/components/balance-slider.json +16 -0
- package/registry/components/bending-gallery.json +18 -0
- package/registry/components/bento-grid.json +24 -0
- package/registry/components/bg-black-hole.json +14 -0
- package/registry/components/bg-bubbles.json +18 -0
- package/registry/components/bg-falling-stars.json +16 -0
- package/registry/components/bg-neural.json +14 -0
- package/registry/components/bg-particle-whirlpool.json +18 -0
- package/registry/components/bg-silk.json +16 -0
- package/registry/components/bg-stars.json +18 -0
- package/registry/components/bg-stractium.json +16 -0
- package/registry/components/blur-reveal.json +18 -0
- package/registry/components/book.json +28 -0
- package/registry/components/border-beam.json +16 -0
- package/registry/components/box-reveal.json +18 -0
- package/registry/components/card-3d.json +24 -0
- package/registry/components/card-spotlight.json +16 -0
- package/registry/components/carousel-3d.json +15 -0
- package/registry/components/color-picker.json +26 -0
- package/registry/components/colourful-text.json +18 -0
- package/registry/components/compare.json +22 -0
- package/registry/components/confetti.json +22 -0
- package/registry/components/container-scroll.json +26 -0
- package/registry/components/container-text-flip.json +19 -0
- package/registry/components/cosmic-portal.json +18 -0
- package/registry/components/direction-aware-hover.json +16 -0
- package/registry/components/dock.json +32 -0
- package/registry/components/expandable-gallery.json +16 -0
- package/registry/components/file-tree.json +28 -0
- package/registry/components/file-upload.json +22 -0
- package/registry/components/flickering-grid.json +16 -0
- package/registry/components/flip-card.json +16 -0
- package/registry/components/flip-words.json +16 -0
- package/registry/components/fluid-cursor.json +16 -0
- package/registry/components/focus.json +16 -0
- package/registry/components/github-globe.json +23 -0
- package/registry/components/glare-card.json +18 -0
- package/registry/components/globe.json +19 -0
- package/registry/components/glow-border.json +16 -0
- package/registry/components/glowing-effect.json +18 -0
- package/registry/components/gradient-button.json +16 -0
- package/registry/components/halo-search.json +16 -0
- package/registry/components/hyper-text.json +19 -0
- package/registry/components/icon-cloud.json +16 -0
- package/registry/components/image-trail-cursor.json +22 -0
- package/registry/components/images-slider.json +18 -0
- package/registry/components/infinite-grid.json +47 -0
- package/registry/components/input.json +18 -0
- package/registry/components/interactive-grid-pattern.json +16 -0
- package/registry/components/interactive-hover-button.json +16 -0
- package/registry/components/iphone-mockup.json +16 -0
- package/registry/components/lamp-effect.json +16 -0
- package/registry/components/lens.json +18 -0
- package/registry/components/letter-pullup.json +18 -0
- package/registry/components/light-speed.json +31 -0
- package/registry/components/line-shadow-text.json +16 -0
- package/registry/components/link-preview.json +16 -0
- package/registry/components/liquid-background.json +18 -0
- package/registry/components/liquid-glass.json +16 -0
- package/registry/components/liquid-logo.json +24 -0
- package/registry/components/logo-cloud.json +24 -0
- package/registry/components/logo-origami.json +22 -0
- package/registry/components/marquee.json +20 -0
- package/registry/components/meteors.json +16 -0
- package/registry/components/morphing-tabs.json +16 -0
- package/registry/components/morphing-text.json +16 -0
- package/registry/components/multi-step-loader.json +16 -0
- package/registry/components/neon-border.json +16 -0
- package/registry/components/number-ticker.json +18 -0
- package/registry/components/orbit.json +16 -0
- package/registry/components/particle-image.json +24 -0
- package/registry/components/particles-bg.json +18 -0
- package/registry/components/pattern-background.json +18 -0
- package/registry/components/photo-gallery.json +16 -0
- package/registry/components/radiant-text.json +16 -0
- package/registry/components/rainbow-button.json +16 -0
- package/registry/components/ripple-button.json +16 -0
- package/registry/components/ripple.json +24 -0
- package/registry/components/safari-mockup.json +16 -0
- package/registry/components/scratch-to-reveal.json +18 -0
- package/registry/components/scroll-island.json +20 -0
- package/registry/components/shader-toy.json +22 -0
- package/registry/components/shimmer-button.json +16 -0
- package/registry/components/sleek-line-cursor.json +12 -0
- package/registry/components/smooth-cursor.json +23 -0
- package/registry/components/snowfall-bg.json +18 -0
- package/registry/components/sparkles-text.json +18 -0
- package/registry/components/sparkles.json +18 -0
- package/registry/components/spinning-text.json +18 -0
- package/registry/components/spline.json +23 -0
- package/registry/components/spring-calendar.json +22 -0
- package/registry/components/svg-mask.json +16 -0
- package/registry/components/tailed-cursor.json +14 -0
- package/registry/components/testimonial-slider.json +16 -0
- package/registry/components/tetris.json +19 -0
- package/registry/components/text-3d.json +16 -0
- package/registry/components/text-generate-effect.json +16 -0
- package/registry/components/text-glitch.json +12 -0
- package/registry/components/text-highlight.json +16 -0
- package/registry/components/text-hover-effect.json +16 -0
- package/registry/components/text-reveal-card.json +20 -0
- package/registry/components/text-reveal.json +18 -0
- package/registry/components/text-scroll-reveal.json +20 -0
- package/registry/components/timeline.json +18 -0
- package/registry/components/tracing-beam.json +19 -0
- package/registry/components/vanishing-input.json +18 -0
- package/registry/components/video-text.json +16 -0
- package/registry/components/vortex.json +19 -0
- package/registry/components/warp-background.json +22 -0
- package/registry/components/wavy-background.json +19 -0
- package/registry/components/world-map.json +19 -0
- package/registry/registry.json +2007 -0
- package/templates/composables/useMouseState.ts +21 -0
- package/templates/lib/utils.ts +13 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "light-speed",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"postprocessing",
|
|
5
|
+
"three"
|
|
6
|
+
],
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"path": "index.ts",
|
|
10
|
+
"content": "export { default as LightSpeed } from \"./LightSpeed.vue\";\r\nexport * from \"./LightSpeedApp\";\r\nexport * from \"./presets\";\r\nexport * from \"./shaders\";\r\n"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "LightSpeed.vue",
|
|
14
|
+
"content": "<template>\r\n <div\r\n ref=\"lightSpeedRef\"\r\n class=\"w-full h-full overflow-hidden block\"\r\n />\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { LightSpeedOptions, LightSpeedProps } from \"./LightSpeedApp\";\r\nimport { defaultOptions, distortions, LightSpeedApp } from \"./LightSpeedApp\";\r\n\r\nconst props = defineProps<LightSpeedProps>();\r\n\r\nconst containerRef = useTemplateRef(\"lightSpeedRef\");\r\n\r\nonMounted(() => {\r\n if (!containerRef.value) return;\r\n\r\n const mergedOptions: LightSpeedOptions = {\r\n ...defaultOptions,\r\n ...(props.effectOptions || {}),\r\n };\r\n\r\n if (typeof mergedOptions.distortion === \"string\") {\r\n mergedOptions.distortion = distortions[mergedOptions.distortion];\r\n }\r\n\r\n const lightSpeedApp = new LightSpeedApp(containerRef.value, mergedOptions);\r\n lightSpeedApp.loadAssets().then(lightSpeedApp.init);\r\n});\r\n</script>\r\n"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "LightSpeedApp.ts",
|
|
18
|
+
"content": "/* eslint-disable func-style */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\nimport {\r\n BloomEffect,\r\n EffectComposer,\r\n EffectPass,\r\n RenderPass,\r\n SMAAEffect,\r\n SMAAPreset,\r\n} from \"postprocessing\";\r\nimport * as THREE from \"three\";\r\nimport {\r\n carLightsFragment,\r\n carLightsVertex,\r\n distortion_vertex,\r\n islandFragment,\r\n roadFragment,\r\n roadVertex,\r\n sideSticksFragment,\r\n sideSticksVertex,\r\n} from \"./shaders\";\r\n\r\nexport interface Distortion {\r\n uniforms: Record<string, { value: any }>;\r\n getDistortion: string;\r\n getJS?: (progress: number, time: number) => THREE.Vector3;\r\n}\r\n\r\nexport interface Distortions {\r\n [key: string]: Distortion;\r\n}\r\n\r\nexport interface Colors {\r\n roadColor: number;\r\n islandColor: number;\r\n background: number;\r\n shoulderLines: number;\r\n brokenLines: number;\r\n leftCars: number[];\r\n rightCars: number[];\r\n sticks: number;\r\n}\r\n\r\nexport interface LightSpeedOptions {\r\n onSpeedUp?: (ev: MouseEvent | TouchEvent) => void;\r\n onSlowDown?: (ev: MouseEvent | TouchEvent) => void;\r\n distortion?: string | Distortion;\r\n length: number;\r\n roadWidth: number;\r\n islandWidth: number;\r\n lanesPerRoad: number;\r\n fov: number;\r\n fovSpeedUp: number;\r\n speedUp: number;\r\n carLightsFade: number;\r\n totalSideLightSticks: number;\r\n lightPairsPerRoadWay: number;\r\n shoulderLinesWidthPercentage: number;\r\n brokenLinesWidthPercentage: number;\r\n brokenLinesLengthPercentage: number;\r\n lightStickWidth: [number, number];\r\n lightStickHeight: [number, number];\r\n movingAwaySpeed: [number, number];\r\n movingCloserSpeed: [number, number];\r\n carLightsLength: [number, number];\r\n carLightsRadius: [number, number];\r\n carWidthPercentage: [number, number];\r\n carShiftX: [number, number];\r\n carFloorSeparation: [number, number];\r\n colors: Colors;\r\n isHyper?: boolean;\r\n}\r\n\r\nexport interface LightSpeedProps {\r\n effectOptions?: Partial<LightSpeedOptions>;\r\n}\r\n\r\nexport const defaultOptions: LightSpeedOptions = {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"turbulentDistortion\",\r\n length: 400,\r\n roadWidth: 10,\r\n islandWidth: 2,\r\n lanesPerRoad: 4,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 2,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 20,\r\n lightPairsPerRoadWay: 40,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.12, 0.5],\r\n lightStickHeight: [1.3, 1.7],\r\n movingAwaySpeed: [60, 80],\r\n movingCloserSpeed: [-120, -160],\r\n carLightsLength: [400 * 0.03, 400 * 0.2],\r\n carLightsRadius: [0.05, 0.14],\r\n carWidthPercentage: [0.3, 0.5],\r\n carShiftX: [-0.8, 0.8],\r\n carFloorSeparation: [0, 5],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0xffffff,\r\n brokenLines: 0xffffff,\r\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\r\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\r\n sticks: 0x03b3c3,\r\n },\r\n};\r\n\r\nfunction nsin(val: number) {\r\n return Math.sin(val) * 0.5 + 0.5;\r\n}\r\n\r\nconst mountainUniforms = {\r\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\r\n uAmp: { value: new THREE.Vector3(30, 30, 20) },\r\n};\r\n\r\nconst xyUniforms = {\r\n uFreq: { value: new THREE.Vector2(5, 2) },\r\n uAmp: { value: new THREE.Vector2(25, 15) },\r\n};\r\n\r\nconst LongRaceUniforms = {\r\n uFreq: { value: new THREE.Vector2(2, 3) },\r\n uAmp: { value: new THREE.Vector2(35, 10) },\r\n};\r\n\r\nconst turbulentUniforms = {\r\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\r\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) },\r\n};\r\n\r\nconst deepUniforms = {\r\n uFreq: { value: new THREE.Vector2(4, 8) },\r\n uAmp: { value: new THREE.Vector2(10, 20) },\r\n uPowY: { value: new THREE.Vector2(20, 2) },\r\n};\r\n\r\nexport const distortions: Distortions = {\r\n mountainDistortion: {\r\n uniforms: mountainUniforms,\r\n getDistortion: `\r\n uniform vec3 uAmp;\r\n uniform vec3 uFreq;\r\n #define PI 3.14159265358979\r\n float nsin(float val){\r\n return sin(val) * 0.5 + 0.5;\r\n }\r\n vec3 getDistortion(float progress){\r\n float movementProgressFix = 0.02;\r\n return vec3( \r\n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\r\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\r\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\r\n );\r\n }\r\n `,\r\n getJS: (progress: number, time: number) => {\r\n const movementProgressFix = 0.02;\r\n const uFreq = mountainUniforms.uFreq.value;\r\n const uAmp = mountainUniforms.uAmp.value;\r\n const distortion = new THREE.Vector3(\r\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\r\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\r\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\r\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\r\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\r\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z,\r\n );\r\n const lookAtAmp = new THREE.Vector3(2, 2, 2);\r\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\r\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\r\n },\r\n },\r\n xyDistortion: {\r\n uniforms: xyUniforms,\r\n getDistortion: `\r\n uniform vec2 uFreq;\r\n uniform vec2 uAmp;\r\n #define PI 3.14159265358979\r\n vec3 getDistortion(float progress){\r\n float movementProgressFix = 0.02;\r\n return vec3( \r\n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\r\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\r\n 0.\r\n );\r\n }\r\n `,\r\n getJS: (progress: number, time: number) => {\r\n const movementProgressFix = 0.02;\r\n const uFreq = xyUniforms.uFreq.value;\r\n const uAmp = xyUniforms.uAmp.value;\r\n const distortion = new THREE.Vector3(\r\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\r\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\r\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\r\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\r\n 0,\r\n );\r\n const lookAtAmp = new THREE.Vector3(2, 0.4, 1);\r\n const lookAtOffset = new THREE.Vector3(0, 0, -3);\r\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\r\n },\r\n },\r\n LongRaceDistortion: {\r\n uniforms: LongRaceUniforms,\r\n getDistortion: `\r\n uniform vec2 uFreq;\r\n uniform vec2 uAmp;\r\n #define PI 3.14159265358979\r\n vec3 getDistortion(float progress){\r\n float camProgress = 0.0125;\r\n return vec3( \r\n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\r\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\r\n 0.\r\n );\r\n }\r\n `,\r\n getJS: (progress: number, time: number) => {\r\n const camProgress = 0.0125;\r\n const uFreq = LongRaceUniforms.uFreq.value;\r\n const uAmp = LongRaceUniforms.uAmp.value;\r\n const distortion = new THREE.Vector3(\r\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\r\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\r\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\r\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\r\n 0,\r\n );\r\n const lookAtAmp = new THREE.Vector3(1, 1, 0);\r\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\r\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\r\n },\r\n },\r\n turbulentDistortion: {\r\n uniforms: turbulentUniforms,\r\n getDistortion: `\r\n uniform vec4 uFreq;\r\n uniform vec4 uAmp;\r\n float nsin(float val){\r\n return sin(val) * 0.5 + 0.5;\r\n }\r\n #define PI 3.14159265358979\r\n float getDistortionX(float progress){\r\n return (\r\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\r\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\r\n );\r\n }\r\n float getDistortionY(float progress){\r\n return (\r\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\r\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\r\n );\r\n }\r\n vec3 getDistortion(float progress){\r\n return vec3(\r\n getDistortionX(progress) - getDistortionX(0.0125),\r\n getDistortionY(progress) - getDistortionY(0.0125),\r\n 0.\r\n );\r\n }\r\n `,\r\n getJS: (progress: number, time: number) => {\r\n const uFreq = turbulentUniforms.uFreq.value;\r\n const uAmp = turbulentUniforms.uAmp.value;\r\n\r\n const getX = (p: number) =>\r\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\r\n Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)) ** 2 * uAmp.y;\r\n\r\n const getY = (p: number) =>\r\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\r\n nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)) ** 5 * uAmp.w;\r\n\r\n const distortion = new THREE.Vector3(\r\n getX(progress) - getX(progress + 0.007),\r\n getY(progress) - getY(progress + 0.007),\r\n 0,\r\n );\r\n const lookAtAmp = new THREE.Vector3(-2, -5, 0);\r\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\r\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\r\n },\r\n },\r\n turbulentDistortionStill: {\r\n uniforms: turbulentUniforms,\r\n getDistortion: `\r\n uniform vec4 uFreq;\r\n uniform vec4 uAmp;\r\n float nsin(float val){\r\n return sin(val) * 0.5 + 0.5;\r\n }\r\n #define PI 3.14159265358979\r\n float getDistortionX(float progress){\r\n return (\r\n cos(PI * progress * uFreq.r) * uAmp.r +\r\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\r\n );\r\n }\r\n float getDistortionY(float progress){\r\n return (\r\n -nsin(PI * progress * uFreq.b) * uAmp.b +\r\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\r\n );\r\n }\r\n vec3 getDistortion(float progress){\r\n return vec3(\r\n getDistortionX(progress) - getDistortionX(0.02),\r\n getDistortionY(progress) - getDistortionY(0.02),\r\n 0.\r\n );\r\n }\r\n `,\r\n },\r\n deepDistortionStill: {\r\n uniforms: deepUniforms,\r\n getDistortion: `\r\n uniform vec4 uFreq;\r\n uniform vec4 uAmp;\r\n uniform vec2 uPowY;\r\n float nsin(float val){\r\n return sin(val) * 0.5 + 0.5;\r\n }\r\n #define PI 3.14159265358979\r\n float getDistortionX(float progress){\r\n return (\r\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\r\n );\r\n }\r\n float getDistortionY(float progress){\r\n return (\r\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\r\n );\r\n }\r\n vec3 getDistortion(float progress){\r\n return vec3(\r\n getDistortionX(progress) - getDistortionX(0.02),\r\n getDistortionY(progress) - getDistortionY(0.05),\r\n 0.\r\n );\r\n }\r\n `,\r\n },\r\n deepDistortion: {\r\n uniforms: deepUniforms,\r\n getDistortion: `\r\n uniform vec4 uFreq;\r\n uniform vec4 uAmp;\r\n uniform vec2 uPowY;\r\n float nsin(float val){\r\n return sin(val) * 0.5 + 0.5;\r\n }\r\n #define PI 3.14159265358979\r\n float getDistortionX(float progress){\r\n return (\r\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\r\n );\r\n }\r\n float getDistortionY(float progress){\r\n return (\r\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\r\n );\r\n }\r\n vec3 getDistortion(float progress){\r\n return vec3(\r\n getDistortionX(progress) - getDistortionX(0.02),\r\n getDistortionY(progress) - getDistortionY(0.02),\r\n 0.\r\n );\r\n }\r\n `,\r\n getJS: (progress: number, time: number) => {\r\n const uFreq = deepUniforms.uFreq.value;\r\n const uAmp = deepUniforms.uAmp.value;\r\n const uPowY = deepUniforms.uPowY.value;\r\n\r\n const getX = (p: number) => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\r\n const getY = (p: number) =>\r\n (p * uPowY.x) ** uPowY.y + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\r\n\r\n const distortion = new THREE.Vector3(\r\n getX(progress) - getX(progress + 0.01),\r\n getY(progress) - getY(progress + 0.01),\r\n 0,\r\n );\r\n const lookAtAmp = new THREE.Vector3(-2, -4, 0);\r\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\r\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\r\n },\r\n },\r\n};\r\n\r\nconst distortion_uniforms = {\r\n uDistortionX: { value: new THREE.Vector2(80, 3) },\r\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) },\r\n};\r\n\r\nfunction random(base: number | [number, number]): number {\r\n if (Array.isArray(base)) {\r\n return Math.random() * (base[1] - base[0]) + base[0];\r\n }\r\n return Math.random() * base;\r\n}\r\n\r\nfunction pickRandom<T>(arr: T | T[]): T {\r\n if (Array.isArray(arr)) {\r\n return arr[Math.floor(Math.random() * arr.length)]!;\r\n }\r\n return arr;\r\n}\r\n\r\nfunction lerp(current: number, target: number, speed = 0.1, limit = 0.001): number {\r\n let change = (target - current) * speed;\r\n if (Math.abs(change) < limit) {\r\n change = target - current;\r\n }\r\n return change;\r\n}\r\n\r\nclass CarLights {\r\n webgl: LightSpeedApp;\r\n options: LightSpeedOptions;\r\n colors: number[] | THREE.Color;\r\n speed: [number, number];\r\n fade: THREE.Vector2;\r\n mesh!: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>;\r\n\r\n constructor(\r\n webgl: LightSpeedApp,\r\n options: LightSpeedOptions,\r\n colors: number[] | THREE.Color,\r\n speed: [number, number],\r\n fade: THREE.Vector2,\r\n ) {\r\n this.webgl = webgl;\r\n this.options = options;\r\n this.colors = colors;\r\n this.speed = speed;\r\n this.fade = fade;\r\n }\r\n\r\n init() {\r\n const options = this.options;\r\n const curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\r\n const geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\r\n\r\n const instanced = new THREE.InstancedBufferGeometry().copy(\r\n geometry as any,\r\n ) as THREE.InstancedBufferGeometry;\r\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\r\n\r\n const laneWidth = options.roadWidth / options.lanesPerRoad;\r\n\r\n const aOffset: number[] = [];\r\n const aMetrics: number[] = [];\r\n const aColor: number[] = [];\r\n\r\n let colorArray: THREE.Color[];\r\n if (Array.isArray(this.colors)) {\r\n colorArray = this.colors.map((c) => new THREE.Color(c));\r\n } else {\r\n colorArray = [new THREE.Color(this.colors)];\r\n }\r\n\r\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\r\n const radius = random(options.carLightsRadius);\r\n const length = random(options.carLightsLength);\r\n const spd = random(this.speed);\r\n\r\n const carLane = i % options.lanesPerRoad;\r\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\r\n\r\n const carWidth = random(options.carWidthPercentage) * laneWidth;\r\n const carShiftX = random(options.carShiftX) * laneWidth;\r\n laneX += carShiftX;\r\n\r\n const offsetY = random(options.carFloorSeparation) + radius * 1.3;\r\n const offsetZ = -random(options.length);\r\n\r\n // left side\r\n aOffset.push(laneX - carWidth / 2);\r\n aOffset.push(offsetY);\r\n aOffset.push(offsetZ);\r\n\r\n // right side\r\n aOffset.push(laneX + carWidth / 2);\r\n aOffset.push(offsetY);\r\n aOffset.push(offsetZ);\r\n\r\n aMetrics.push(radius);\r\n aMetrics.push(length);\r\n aMetrics.push(spd);\r\n\r\n aMetrics.push(radius);\r\n aMetrics.push(length);\r\n aMetrics.push(spd);\r\n\r\n const color = pickRandom<THREE.Color>(colorArray);\r\n aColor.push(color.r);\r\n aColor.push(color.g);\r\n aColor.push(color.b);\r\n\r\n aColor.push(color.r);\r\n aColor.push(color.g);\r\n aColor.push(color.b);\r\n }\r\n\r\n instanced.setAttribute(\r\n \"aOffset\",\r\n new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false),\r\n );\r\n instanced.setAttribute(\r\n \"aMetrics\",\r\n new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false),\r\n );\r\n instanced.setAttribute(\r\n \"aColor\",\r\n new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false),\r\n );\r\n\r\n const material = new THREE.ShaderMaterial({\r\n fragmentShader: carLightsFragment,\r\n vertexShader: carLightsVertex,\r\n transparent: true,\r\n uniforms: Object.assign(\r\n {\r\n uTime: { value: 0 },\r\n uTravelLength: { value: options.length },\r\n uFade: { value: this.fade },\r\n },\r\n this.webgl.fogUniforms,\r\n (typeof this.options.distortion === \"object\" ? this.options.distortion.uniforms : {}) || {},\r\n ),\r\n });\r\n\r\n material.onBeforeCompile = (shader) => {\r\n shader.vertexShader = shader.vertexShader.replace(\r\n \"#include <getDistortion_vertex>\",\r\n typeof this.options.distortion === \"object\" ? this.options.distortion.getDistortion : \"\",\r\n );\r\n };\r\n\r\n const mesh = new THREE.Mesh(instanced, material);\r\n mesh.frustumCulled = false;\r\n this.webgl.scene.add(mesh);\r\n this.mesh = mesh;\r\n }\r\n\r\n update(time: number) {\r\n if (this.mesh.material.uniforms.uTime) {\r\n this.mesh.material.uniforms.uTime.value = time;\r\n }\r\n }\r\n}\r\n\r\nclass LightsSticks {\r\n webgl: LightSpeedApp;\r\n options: LightSpeedOptions;\r\n mesh!: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>;\r\n\r\n constructor(webgl: LightSpeedApp, options: LightSpeedOptions) {\r\n this.webgl = webgl;\r\n this.options = options;\r\n }\r\n\r\n init() {\r\n const options = this.options;\r\n const geometry = new THREE.PlaneGeometry(1, 1);\r\n const instanced = new THREE.InstancedBufferGeometry().copy(\r\n geometry as any,\r\n ) as THREE.InstancedBufferGeometry;\r\n const totalSticks = options.totalSideLightSticks;\r\n instanced.instanceCount = totalSticks;\r\n\r\n const stickoffset = options.length / (totalSticks - 1);\r\n const aOffset: number[] = [];\r\n const aColor: number[] = [];\r\n const aMetrics: number[] = [];\r\n\r\n let colorArray: THREE.Color[];\r\n if (Array.isArray(options.colors.sticks)) {\r\n colorArray = options.colors.sticks.map((c) => new THREE.Color(c));\r\n } else {\r\n colorArray = [new THREE.Color(options.colors.sticks)];\r\n }\r\n\r\n for (let i = 0; i < totalSticks; i++) {\r\n const width = random(options.lightStickWidth);\r\n const height = random(options.lightStickHeight);\r\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\r\n\r\n const color = pickRandom<THREE.Color>(colorArray);\r\n aColor.push(color.r);\r\n aColor.push(color.g);\r\n aColor.push(color.b);\r\n\r\n aMetrics.push(width);\r\n aMetrics.push(height);\r\n }\r\n\r\n instanced.setAttribute(\r\n \"aOffset\",\r\n new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false),\r\n );\r\n instanced.setAttribute(\r\n \"aColor\",\r\n new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false),\r\n );\r\n instanced.setAttribute(\r\n \"aMetrics\",\r\n new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false),\r\n );\r\n\r\n const material = new THREE.ShaderMaterial({\r\n fragmentShader: sideSticksFragment,\r\n vertexShader: sideSticksVertex,\r\n side: THREE.DoubleSide,\r\n uniforms: Object.assign(\r\n {\r\n uTravelLength: { value: options.length },\r\n uTime: { value: 0 },\r\n },\r\n this.webgl.fogUniforms,\r\n (typeof options.distortion === \"object\" ? options.distortion.uniforms : {}) || {},\r\n ),\r\n });\r\n\r\n material.onBeforeCompile = (shader) => {\r\n shader.vertexShader = shader.vertexShader.replace(\r\n \"#include <getDistortion_vertex>\",\r\n typeof this.options.distortion === \"object\" ? this.options.distortion.getDistortion : \"\",\r\n );\r\n };\r\n\r\n const mesh = new THREE.Mesh(instanced, material);\r\n mesh.frustumCulled = false;\r\n this.webgl.scene.add(mesh);\r\n this.mesh = mesh;\r\n }\r\n\r\n update(time: number) {\r\n if (this.mesh.material.uniforms.uTime) {\r\n this.mesh.material.uniforms.uTime.value = time;\r\n }\r\n }\r\n}\r\n\r\nclass Road {\r\n webgl: LightSpeedApp;\r\n options: LightSpeedOptions;\r\n uTime: { value: number };\r\n leftRoadWay!: THREE.Mesh;\r\n rightRoadWay!: THREE.Mesh;\r\n island!: THREE.Mesh;\r\n\r\n constructor(webgl: LightSpeedApp, options: LightSpeedOptions) {\r\n this.webgl = webgl;\r\n this.options = options;\r\n this.uTime = { value: 0 };\r\n }\r\n\r\n createPlane(side: number, width: number, isRoad: boolean) {\r\n const options = this.options;\r\n const segments = 100;\r\n const geometry = new THREE.PlaneGeometry(\r\n isRoad ? options.roadWidth : options.islandWidth,\r\n options.length,\r\n 20,\r\n segments,\r\n );\r\n\r\n let uniforms: Record<string, { value: any }> = {\r\n uTravelLength: { value: options.length },\r\n uColor: {\r\n value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor),\r\n },\r\n uTime: this.uTime,\r\n };\r\n\r\n if (isRoad) {\r\n uniforms = Object.assign(uniforms, {\r\n uLanes: { value: options.lanesPerRoad },\r\n uBrokenLinesColor: {\r\n value: new THREE.Color(options.colors.brokenLines),\r\n },\r\n uShoulderLinesColor: {\r\n value: new THREE.Color(options.colors.shoulderLines),\r\n },\r\n uShoulderLinesWidthPercentage: {\r\n value: options.shoulderLinesWidthPercentage,\r\n },\r\n uBrokenLinesLengthPercentage: {\r\n value: options.brokenLinesLengthPercentage,\r\n },\r\n uBrokenLinesWidthPercentage: {\r\n value: options.brokenLinesWidthPercentage,\r\n },\r\n });\r\n }\r\n\r\n const material = new THREE.ShaderMaterial({\r\n fragmentShader: isRoad ? roadFragment : islandFragment,\r\n vertexShader: roadVertex,\r\n side: THREE.DoubleSide,\r\n uniforms: Object.assign(\r\n uniforms,\r\n this.webgl.fogUniforms,\r\n (typeof options.distortion === \"object\" ? options.distortion.uniforms : {}) || {},\r\n ),\r\n });\r\n\r\n material.onBeforeCompile = (shader) => {\r\n shader.vertexShader = shader.vertexShader.replace(\r\n \"#include <getDistortion_vertex>\",\r\n typeof this.options.distortion === \"object\" ? this.options.distortion.getDistortion : \"\",\r\n );\r\n };\r\n\r\n const mesh = new THREE.Mesh(geometry, material);\r\n mesh.rotation.x = -Math.PI / 2;\r\n mesh.position.z = -options.length / 2;\r\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\r\n\r\n this.webgl.scene.add(mesh);\r\n return mesh;\r\n }\r\n\r\n init() {\r\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\r\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\r\n this.island = this.createPlane(0, this.options.islandWidth, false);\r\n }\r\n\r\n update(time: number) {\r\n this.uTime.value = time;\r\n }\r\n}\r\n\r\nfunction resizeRendererToDisplaySize(\r\n renderer: THREE.WebGLRenderer,\r\n setSize: (width: number, height: number, updateStyle: boolean) => void,\r\n) {\r\n const canvas = renderer.domElement;\r\n const width = canvas.clientWidth;\r\n const height = canvas.clientHeight;\r\n const needResize = canvas.width !== width || canvas.height !== height;\r\n if (needResize) {\r\n setSize(width, height, false);\r\n }\r\n return needResize;\r\n}\r\n\r\nexport class LightSpeedApp {\r\n container: HTMLElement;\r\n options: LightSpeedOptions;\r\n renderer: THREE.WebGLRenderer;\r\n composer: EffectComposer;\r\n camera: THREE.PerspectiveCamera;\r\n scene: THREE.Scene;\r\n renderPass!: RenderPass;\r\n bloomPass!: EffectPass;\r\n clock: THREE.Clock;\r\n assets: Record<string, any>;\r\n disposed: boolean;\r\n road: Road;\r\n leftCarLights: CarLights;\r\n rightCarLights: CarLights;\r\n leftSticks: LightsSticks;\r\n fogUniforms: Record<string, { value: any }>;\r\n fovTarget: number;\r\n speedUpTarget: number;\r\n speedUp: number;\r\n timeOffset: number;\r\n\r\n constructor(container: HTMLElement, options: LightSpeedOptions) {\r\n this.options = options;\r\n if (!this.options.distortion) {\r\n this.options.distortion = {\r\n uniforms: distortion_uniforms,\r\n getDistortion: distortion_vertex,\r\n };\r\n }\r\n this.container = container;\r\n\r\n this.renderer = new THREE.WebGLRenderer({\r\n antialias: false,\r\n alpha: true,\r\n });\r\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\r\n this.renderer.setPixelRatio(window.devicePixelRatio);\r\n\r\n this.renderer.domElement.style.setProperty(\"height\", \"100%\");\r\n this.renderer.domElement.style.setProperty(\"width\", \"100%\");\r\n\r\n this.composer = new EffectComposer(this.renderer);\r\n container.appendChild(this.renderer.domElement);\r\n\r\n this.camera = new THREE.PerspectiveCamera(\r\n options.fov,\r\n container.offsetWidth / container.offsetHeight,\r\n 0.1,\r\n 10000,\r\n );\r\n this.camera.position.z = -5;\r\n this.camera.position.y = 8;\r\n this.camera.position.x = 0;\r\n\r\n this.scene = new THREE.Scene();\r\n this.scene.background = null;\r\n\r\n const fog = new THREE.Fog(\r\n options.colors.background,\r\n options.length * 0.2,\r\n options.length * 500,\r\n );\r\n this.scene.fog = fog;\r\n\r\n this.fogUniforms = {\r\n fogColor: { value: fog.color },\r\n fogNear: { value: fog.near },\r\n fogFar: { value: fog.far },\r\n };\r\n\r\n this.clock = new THREE.Clock();\r\n this.assets = {};\r\n this.disposed = false;\r\n\r\n this.road = new Road(this, options);\r\n this.leftCarLights = new CarLights(\r\n this,\r\n options,\r\n options.colors.leftCars,\r\n options.movingAwaySpeed,\r\n new THREE.Vector2(0, 1 - options.carLightsFade),\r\n );\r\n this.rightCarLights = new CarLights(\r\n this,\r\n options,\r\n options.colors.rightCars,\r\n options.movingCloserSpeed,\r\n new THREE.Vector2(1, 0 + options.carLightsFade),\r\n );\r\n this.leftSticks = new LightsSticks(this, options);\r\n\r\n this.fovTarget = options.fov;\r\n this.speedUpTarget = 0;\r\n this.speedUp = 0;\r\n this.timeOffset = 0;\r\n\r\n this.tick = this.tick.bind(this);\r\n this.init = this.init.bind(this);\r\n this.setSize = this.setSize.bind(this);\r\n this.onMouseDown = this.onMouseDown.bind(this);\r\n this.onMouseUp = this.onMouseUp.bind(this);\r\n\r\n window.addEventListener(\"resize\", this.onWindowResize.bind(this));\r\n }\r\n\r\n onWindowResize() {\r\n const width = this.container.offsetWidth;\r\n const height = this.container.offsetHeight;\r\n\r\n this.renderer.setSize(width, height);\r\n this.camera.aspect = width / height;\r\n this.camera.updateProjectionMatrix();\r\n this.composer.setSize(width, height);\r\n }\r\n\r\n initPasses() {\r\n this.renderPass = new RenderPass(this.scene, this.camera);\r\n this.bloomPass = new EffectPass(\r\n this.camera,\r\n new BloomEffect({\r\n luminanceThreshold: 0.2,\r\n luminanceSmoothing: 0,\r\n resolutionScale: 1,\r\n }),\r\n );\r\n\r\n const smaaPass = new EffectPass(\r\n this.camera,\r\n new SMAAEffect({\r\n preset: SMAAPreset.MEDIUM,\r\n }),\r\n );\r\n this.renderPass.renderToScreen = false;\r\n this.bloomPass.renderToScreen = false;\r\n smaaPass.renderToScreen = true;\r\n\r\n this.composer.addPass(this.renderPass);\r\n this.composer.addPass(this.bloomPass);\r\n this.composer.addPass(smaaPass);\r\n }\r\n\r\n loadAssets(): Promise<void> {\r\n const assets = this.assets;\r\n return new Promise((resolve) => {\r\n const manager = new THREE.LoadingManager(resolve);\r\n\r\n const searchImage = new Image();\r\n const areaImage = new Image();\r\n assets.smaa = {};\r\n\r\n searchImage.addEventListener(\"load\", function () {\r\n assets.smaa.search = this;\r\n manager.itemEnd(\"smaa-search\");\r\n });\r\n\r\n areaImage.addEventListener(\"load\", function () {\r\n assets.smaa.area = this;\r\n manager.itemEnd(\"smaa-area\");\r\n });\r\n\r\n manager.itemStart(\"smaa-search\");\r\n manager.itemStart(\"smaa-area\");\r\n\r\n searchImage.src = SMAAEffect.searchImageDataURL;\r\n areaImage.src = SMAAEffect.areaImageDataURL;\r\n });\r\n }\r\n\r\n init() {\r\n this.initPasses();\r\n const options = this.options;\r\n this.road.init();\r\n this.leftCarLights.init();\r\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\r\n\r\n this.rightCarLights.init();\r\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\r\n\r\n this.leftSticks.init();\r\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\r\n\r\n this.container.addEventListener(\"mousedown\", this.onMouseDown);\r\n this.container.addEventListener(\"mouseup\", this.onMouseUp);\r\n this.container.addEventListener(\"mouseout\", this.onMouseUp);\r\n\r\n this.container.addEventListener(\"touchstart\", this.onTouchStart);\r\n this.container.addEventListener(\"touchend\", this.onTouchEnd);\r\n\r\n this.tick();\r\n }\r\n\r\n onTouchStart(ev: TouchEvent) {\r\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\r\n this.fovTarget = this.options.fovSpeedUp;\r\n this.speedUpTarget = this.options.speedUp;\r\n }\r\n\r\n onTouchEnd(ev: TouchEvent) {\r\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\r\n this.fovTarget = this.options.fov;\r\n this.speedUpTarget = 0;\r\n }\r\n\r\n onMouseDown(ev: MouseEvent) {\r\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\r\n this.fovTarget = this.options.fovSpeedUp;\r\n this.speedUpTarget = this.options.speedUp;\r\n }\r\n\r\n onMouseUp(ev: MouseEvent) {\r\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\r\n this.fovTarget = this.options.fov;\r\n this.speedUpTarget = 0;\r\n }\r\n\r\n update(delta: number) {\r\n const lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\r\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\r\n this.timeOffset += this.speedUp * delta;\r\n const time = this.clock.elapsedTime + this.timeOffset;\r\n\r\n this.rightCarLights.update(time);\r\n this.leftCarLights.update(time);\r\n this.leftSticks.update(time);\r\n this.road.update(time);\r\n\r\n let updateCamera = false;\r\n const fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\r\n if (fovChange !== 0) {\r\n this.camera.fov += fovChange * delta * 6;\r\n updateCamera = true;\r\n }\r\n\r\n if (typeof this.options.distortion === \"object\" && this.options.distortion.getJS) {\r\n const distortion = this.options.distortion.getJS(0.025, time);\r\n this.camera.lookAt(\r\n new THREE.Vector3(\r\n this.camera.position.x + distortion.x,\r\n this.camera.position.y + distortion.y,\r\n this.camera.position.z + distortion.z,\r\n ),\r\n );\r\n updateCamera = true;\r\n }\r\n\r\n if (updateCamera) {\r\n this.camera.updateProjectionMatrix();\r\n }\r\n }\r\n\r\n render(delta: number) {\r\n this.composer.render(delta);\r\n }\r\n\r\n dispose() {\r\n this.disposed = true;\r\n }\r\n\r\n setSize(width: number, height: number, updateStyles: boolean) {\r\n this.composer.setSize(width, height, updateStyles);\r\n }\r\n\r\n tick() {\r\n if (this.disposed || !this) return;\r\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\r\n const canvas = this.renderer.domElement;\r\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\r\n this.camera.updateProjectionMatrix();\r\n }\r\n const delta = this.clock.getDelta();\r\n this.render(delta);\r\n this.update(delta);\r\n requestAnimationFrame(this.tick);\r\n }\r\n}\r\n"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "presets.ts",
|
|
22
|
+
"content": "export const lightSpeedPresets = {\r\n one: {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"deepDistortion\",\r\n length: 400,\r\n roadWidth: 18,\r\n islandWidth: 2,\r\n lanesPerRoad: 3,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 2,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 50,\r\n lightPairsPerRoadWay: 50,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.12, 0.5],\r\n lightStickHeight: [1.3, 1.7],\r\n movingAwaySpeed: [60, 80],\r\n movingCloserSpeed: [-120, -160],\r\n carLightsLength: [400 * 0.05, 400 * 0.15],\r\n carLightsRadius: [0.05, 0.14],\r\n carWidthPercentage: [0.3, 0.5],\r\n carShiftX: [-0.2, 0.2],\r\n carFloorSeparation: [0.05, 1],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0x131318,\r\n brokenLines: 0x131318,\r\n leftCars: [0xff322f, 0xa33010, 0xa81508],\r\n rightCars: [0xfdfdf0, 0xf3dea0, 0xe2bb88],\r\n sticks: 0xfdfdf0,\r\n },\r\n },\r\n two: {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"turbulentDistortion\",\r\n length: 400,\r\n roadWidth: 9,\r\n islandWidth: 2,\r\n lanesPerRoad: 3,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 2,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 50,\r\n lightPairsPerRoadWay: 50,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.12, 0.5],\r\n lightStickHeight: [1.3, 1.7],\r\n movingAwaySpeed: [60, 80],\r\n movingCloserSpeed: [-120, -160],\r\n carLightsLength: [400 * 0.05, 400 * 0.15],\r\n carLightsRadius: [0.05, 0.14],\r\n carWidthPercentage: [0.3, 0.5],\r\n carShiftX: [-0.2, 0.2],\r\n carFloorSeparation: [0.05, 1],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0x131318,\r\n brokenLines: 0x131318,\r\n /*** Only these colors can be an array ***/\r\n leftCars: [0xdc5b20, 0xdca320, 0xdc2020],\r\n rightCars: [0x334bf7, 0xe5e6ed, 0xbfc6f3],\r\n sticks: 0xc5e8eb,\r\n },\r\n },\r\n three: {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"LongRaceDistortion\",\r\n length: 400,\r\n roadWidth: 10,\r\n islandWidth: 5,\r\n lanesPerRoad: 2,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 2,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 50,\r\n lightPairsPerRoadWay: 70,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.12, 0.5],\r\n lightStickHeight: [1.3, 1.7],\r\n movingAwaySpeed: [60, 80],\r\n movingCloserSpeed: [-120, -160],\r\n carLightsLength: [400 * 0.05, 400 * 0.15],\r\n carLightsRadius: [0.05, 0.14],\r\n carWidthPercentage: [0.3, 0.5],\r\n carShiftX: [-0.2, 0.2],\r\n carFloorSeparation: [0.05, 1],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0x131318,\r\n brokenLines: 0x131318,\r\n leftCars: [0xff5f73, 0xe74d60, 0xff102a],\r\n rightCars: [0xa4e3e6, 0x80d1d4, 0x53c2c6],\r\n sticks: 0xa4e3e6,\r\n },\r\n },\r\n four: {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"xyDistortion\",\r\n length: 400,\r\n roadWidth: 9,\r\n islandWidth: 2,\r\n lanesPerRoad: 3,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 3,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 50,\r\n lightPairsPerRoadWay: 30,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.02, 0.05],\r\n lightStickHeight: [0.3, 0.7],\r\n movingAwaySpeed: [20, 50],\r\n movingCloserSpeed: [-150, -230],\r\n carLightsLength: [400 * 0.05, 400 * 0.2],\r\n carLightsRadius: [0.03, 0.08],\r\n carWidthPercentage: [0.1, 0.5],\r\n carShiftX: [-0.5, 0.5],\r\n carFloorSeparation: [0, 0.1],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0x131318,\r\n brokenLines: 0x131318,\r\n leftCars: [0x7d0d1b, 0xa90519, 0xff102a],\r\n rightCars: [0xf1eece, 0xe6e2b1, 0xdfd98a],\r\n sticks: 0xf1eece,\r\n },\r\n },\r\n five: {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"mountainDistortion\",\r\n length: 400,\r\n roadWidth: 9,\r\n islandWidth: 2,\r\n lanesPerRoad: 3,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 2,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 50,\r\n lightPairsPerRoadWay: 50,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.12, 0.5],\r\n lightStickHeight: [1.3, 1.7],\r\n\r\n movingAwaySpeed: [60, 80],\r\n movingCloserSpeed: [-120, -160],\r\n carLightsLength: [400 * 0.05, 400 * 0.15],\r\n carLightsRadius: [0.05, 0.14],\r\n carWidthPercentage: [0.3, 0.5],\r\n carShiftX: [-0.2, 0.2],\r\n carFloorSeparation: [0.05, 1],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0x131318,\r\n brokenLines: 0x131318,\r\n leftCars: [0xff102a, 0xeb383e, 0xff102a],\r\n rightCars: [0xdadafa, 0xbebae3, 0x8f97e4],\r\n sticks: 0xdadafa,\r\n },\r\n },\r\n six: {\r\n onSpeedUp: () => {},\r\n onSlowDown: () => {},\r\n distortion: \"turbulentDistortion\",\r\n length: 400,\r\n roadWidth: 10,\r\n islandWidth: 2,\r\n lanesPerRoad: 3,\r\n fov: 90,\r\n fovSpeedUp: 150,\r\n speedUp: 2,\r\n carLightsFade: 0.4,\r\n totalSideLightSticks: 20,\r\n lightPairsPerRoadWay: 40,\r\n shoulderLinesWidthPercentage: 0.05,\r\n brokenLinesWidthPercentage: 0.1,\r\n brokenLinesLengthPercentage: 0.5,\r\n lightStickWidth: [0.12, 0.5],\r\n lightStickHeight: [1.3, 1.7],\r\n movingAwaySpeed: [60, 80],\r\n movingCloserSpeed: [-120, -160],\r\n carLightsLength: [400 * 0.03, 400 * 0.2],\r\n carLightsRadius: [0.05, 0.14],\r\n carWidthPercentage: [0.3, 0.5],\r\n carShiftX: [-0.8, 0.8],\r\n carFloorSeparation: [0, 5],\r\n colors: {\r\n roadColor: 0x080808,\r\n islandColor: 0x0a0a0a,\r\n background: 0x000000,\r\n shoulderLines: 0x131318,\r\n brokenLines: 0x131318,\r\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\r\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\r\n sticks: 0x03b3c3,\r\n },\r\n },\r\n};\r\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "shaders.ts",
|
|
26
|
+
"content": "import * as THREE from \"three\";\r\n\r\nexport const roadBaseFragment = `\r\n #define USE_FOG;\r\n varying vec2 vUv; \r\n uniform vec3 uColor;\r\n uniform float uTime;\r\n #include <roadMarkings_vars>\r\n ${THREE.ShaderChunk.fog_pars_fragment}\r\n void main() {\r\n vec2 uv = vUv;\r\n vec3 color = vec3(uColor);\r\n #include <roadMarkings_fragment>\r\n gl_FragColor = vec4(color, 1.);\r\n ${THREE.ShaderChunk.fog_fragment}\r\n }\r\n`;\r\n\r\nexport const islandFragment = roadBaseFragment\r\n .replace(\"#include <roadMarkings_fragment>\", \"\")\r\n .replace(\"#include <roadMarkings_vars>\", \"\");\r\n\r\nexport const roadMarkings_vars = `\r\n uniform float uLanes;\r\n uniform vec3 uBrokenLinesColor;\r\n uniform vec3 uShoulderLinesColor;\r\n uniform float uShoulderLinesWidthPercentage;\r\n uniform float uBrokenLinesWidthPercentage;\r\n uniform float uBrokenLinesLengthPercentage;\r\n highp float random(vec2 co) {\r\n highp float a = 12.9898;\r\n highp float b = 78.233;\r\n highp float c = 43758.5453;\r\n highp float dt = dot(co.xy, vec2(a, b));\r\n highp float sn = mod(dt, 3.14);\r\n return fract(sin(sn) * c);\r\n }\r\n`;\r\n\r\nexport const roadMarkings_fragment = `\r\n uv.y = mod(uv.y + uTime * 0.05, 1.);\r\n float laneWidth = 1.0 / uLanes;\r\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\r\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\r\n\r\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\r\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\r\n\r\n brokenLines = mix(brokenLines, sideLines, uv.x);\r\n // color = mix(color, uBrokenLinesColor, brokenLines);\r\n\r\n // vec2 noiseFreq = vec2(4., 7000.);\r\n // float roadNoise = random(floor(uv * noiseFreq) / noiseFreq) * 0.02 - 0.01; \r\n // color += roadNoise;\r\n`;\r\n\r\nexport const roadFragment = roadBaseFragment\r\n .replace(\"#include <roadMarkings_fragment>\", roadMarkings_fragment)\r\n .replace(\"#include <roadMarkings_vars>\", roadMarkings_vars);\r\n\r\nexport const roadVertex = `\r\n #define USE_FOG;\r\n uniform float uTime;\r\n ${THREE.ShaderChunk.fog_pars_vertex}\r\n uniform float uTravelLength;\r\n varying vec2 vUv; \r\n #include <getDistortion_vertex>\r\n void main() {\r\n vec3 transformed = position.xyz;\r\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\r\n transformed.x += distortion.x;\r\n transformed.z += distortion.y;\r\n transformed.y += -1. * distortion.z; \r\n \r\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\r\n gl_Position = projectionMatrix * mvPosition;\r\n vUv = uv;\r\n ${THREE.ShaderChunk.fog_vertex}\r\n }\r\n`;\r\n\r\nexport const carLightsFragment = `\r\n #define USE_FOG;\r\n ${THREE.ShaderChunk.fog_pars_fragment}\r\n varying vec3 vColor;\r\n varying vec2 vUv; \r\n uniform vec2 uFade;\r\n void main() {\r\n vec3 color = vec3(vColor);\r\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\r\n gl_FragColor = vec4(color, alpha);\r\n if (gl_FragColor.a < 0.0001) discard;\r\n ${THREE.ShaderChunk.fog_fragment}\r\n }\r\n`;\r\n\r\nexport const carLightsVertex = `\r\n #define USE_FOG;\r\n ${THREE.ShaderChunk.fog_pars_vertex}\r\n attribute vec3 aOffset;\r\n attribute vec3 aMetrics;\r\n attribute vec3 aColor;\r\n uniform float uTravelLength;\r\n uniform float uTime;\r\n varying vec2 vUv; \r\n varying vec3 vColor; \r\n #include <getDistortion_vertex>\r\n void main() {\r\n vec3 transformed = position.xyz;\r\n float radius = aMetrics.r;\r\n float myLength = aMetrics.g;\r\n float speed = aMetrics.b;\r\n\r\n transformed.xy *= radius;\r\n transformed.z *= myLength;\r\n\r\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\r\n transformed.xy += aOffset.xy;\r\n\r\n float progress = abs(transformed.z / uTravelLength);\r\n transformed.xyz += getDistortion(progress);\r\n\r\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\r\n gl_Position = projectionMatrix * mvPosition;\r\n vUv = uv;\r\n vColor = aColor;\r\n ${THREE.ShaderChunk.fog_vertex}\r\n }\r\n`;\r\n\r\nexport const sideSticksVertex = `\r\n #define USE_FOG;\r\n ${THREE.ShaderChunk.fog_pars_vertex}\r\n attribute float aOffset;\r\n attribute vec3 aColor;\r\n attribute vec2 aMetrics;\r\n uniform float uTravelLength;\r\n uniform float uTime;\r\n varying vec3 vColor;\r\n mat4 rotationY( in float angle ) {\r\n return mat4(\r\n cos(angle),\t\t0,\t\tsin(angle),\t0,\r\n 0,\t\t 1.0,\t0,\t\t\t0,\r\n -sin(angle),\t 0,\t\tcos(angle),\t0,\r\n 0, \t\t 0,\t\t0,\t\t\t1\r\n );\r\n }\r\n #include <getDistortion_vertex>\r\n void main(){\r\n vec3 transformed = position.xyz;\r\n float width = aMetrics.x;\r\n float height = aMetrics.y;\r\n\r\n transformed.xy *= vec2(width, height);\r\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\r\n\r\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\r\n transformed.z += - uTravelLength + time;\r\n\r\n float progress = abs(transformed.z / uTravelLength);\r\n transformed.xyz += getDistortion(progress);\r\n\r\n transformed.y += height / 2.;\r\n transformed.x += -width / 2.;\r\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\r\n gl_Position = projectionMatrix * mvPosition;\r\n vColor = aColor;\r\n ${THREE.ShaderChunk.fog_vertex}\r\n }\r\n`;\r\n\r\nexport const sideSticksFragment = `\r\n #define USE_FOG;\r\n ${THREE.ShaderChunk.fog_pars_fragment}\r\n varying vec3 vColor;\r\n void main(){\r\n vec3 color = vec3(vColor);\r\n gl_FragColor = vec4(color,1.);\r\n ${THREE.ShaderChunk.fog_fragment}\r\n }\r\n`;\r\n\r\nexport const distortion_vertex = `\r\n #define PI 3.14159265358979\r\n uniform vec2 uDistortionX;\r\n uniform vec2 uDistortionY;\r\n float nsin(float val){\r\n return sin(val) * 0.5 + 0.5;\r\n }\r\n vec3 getDistortion(float progress){\r\n progress = clamp(progress, 0., 1.);\r\n float xAmp = uDistortionX.r;\r\n float xFreq = uDistortionX.g;\r\n float yAmp = uDistortionY.r;\r\n float yFreq = uDistortionY.g;\r\n return vec3( \r\n xAmp * nsin(progress * PI * xFreq - PI / 2.),\r\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\r\n 0.\r\n );\r\n }\r\n`;\r\n"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"fileCount": 5,
|
|
30
|
+
"contentHash": "637a99acd13f514c6fb6420d5326a124dd6a41bd"
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "line-shadow-text",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as LineShadowText } from \"./LineShadowText.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "LineShadowText.vue",
|
|
11
|
+
"content": "<template>\r\n <component\r\n :is=\"as\"\r\n class=\"shadow-color\"\r\n :class=\"\r\n cn(\r\n 'relative z-0 inline-flex',\r\n 'after:absolute after:left-[0.04em] after:top-[0.04em] after:-z-10',\r\n 'after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]',\r\n 'after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent',\r\n 'after:content-[attr(data-text)]',\r\n 'animate-line-shadow',\r\n props.class,\r\n )\r\n \"\r\n :data-text=\"content\"\r\n >\r\n <slot />\r\n </component>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useSlots, type VNode } from \"vue\";\r\n\r\ninterface LineShadowTextProps {\r\n shadowColor?: string;\r\n as?: keyof HTMLElement;\r\n class?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<LineShadowTextProps>(), {\r\n shadowColor: \"black\",\r\n as: \"span\" as keyof HTMLElement,\r\n});\r\n\r\nconst slots = useSlots() as Record<string, () => VNode[]>;\r\nconst children = slots?.default ? slots.default()[0]?.children : null;\r\n\r\nconst content = typeof children === \"string\" ? children : null;\r\n\r\nif (!content) {\r\n throw new Error(\"LineShadowText only accepts string content\");\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.shadow-color {\r\n --shadow-color: v-bind(props.shadowColor);\r\n}\r\n\r\n.animate-line-shadow::after {\r\n animation: line-shadow 15s linear infinite;\r\n}\r\n\r\n@keyframes line-shadow {\r\n 0% {\r\n background-position: 0 0;\r\n }\r\n 100% {\r\n background-position: 100% -100%;\r\n }\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "153d96053f66a1fc8fcc01f17e80e6a33f772162"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "link-preview",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as LinkPreview } from \"./LinkPreview.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "LinkPreview.vue",
|
|
11
|
+
"content": "<template>\r\n <div :class=\"cn('relative inline-block', props.class)\">\r\n <!-- Trigger -->\r\n <NuxtLink\r\n :to=\"url\"\r\n :class=\"cn('text-black dark:text-white', props.linkClass)\"\r\n @mousemove=\"handleMouseMove\"\r\n @mouseenter=\"showPreview\"\r\n @mouseleave=\"hidePreview\"\r\n >\r\n <slot />\r\n </NuxtLink>\r\n\r\n <!-- Preview -->\r\n <div\r\n v-if=\"isVisible\"\r\n ref=\"preview\"\r\n class=\"pointer-events-none absolute z-50\"\r\n :style=\"previewStyle\"\r\n >\r\n <div\r\n class=\"overflow-hidden rounded-xl shadow-xl\"\r\n :class=\"[popClass, { 'transform-gpu': !props.isStatic }]\"\r\n >\r\n <div\r\n class=\"block rounded-xl border-2 border-transparent bg-white p-1 shadow-lg dark:bg-gray-900\"\r\n >\r\n <img\r\n :src=\"previewSrc\"\r\n :width=\"width\"\r\n :height=\"height\"\r\n class=\"size-full rounded-lg object-cover\"\r\n :style=\"imageStyle\"\r\n alt=\"preview\"\r\n @load=\"handleImageLoad\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, reactive, type CSSProperties } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface BaseProps {\r\n class?: string;\r\n linkClass?: string;\r\n width?: number;\r\n height?: number;\r\n}\r\n\r\n// Props for static image mode\r\ninterface StaticImageProps extends BaseProps {\r\n isStatic?: true;\r\n imageSrc?: string;\r\n url?: string; // optional in static mode\r\n}\r\n\r\n// Props for URL preview mode\r\ninterface URLPreviewProps extends BaseProps {\r\n isStatic?: false; // optional but must be false if specified\r\n imageSrc?: string; // optional in URL mode\r\n url?: string;\r\n}\r\n\r\n// Combined type that enforces the requirements\r\ntype Props = StaticImageProps | URLPreviewProps;\r\nconst props = withDefaults(defineProps<Props>(), {\r\n isStatic: false,\r\n imageSrc: \"\",\r\n url: \"\",\r\n width: 200,\r\n height: 125,\r\n});\r\n\r\nconst isVisible = ref(false);\r\nconst isLoading = ref(true);\r\nconst preview = ref<HTMLElement | null>(null);\r\nconst hasPopped = ref(false);\r\n\r\n// Generate preview URL\r\nconst previewSrc = computed(() => {\r\n if (props.isStatic) return props.imageSrc;\r\n\r\n const params = new URLSearchParams({\r\n url: props.url,\r\n screenshot: \"true\",\r\n meta: \"false\",\r\n embed: \"screenshot.url\",\r\n colorScheme: \"light\",\r\n \"viewport.isMobile\": \"true\",\r\n \"viewport.deviceScaleFactor\": \"1\",\r\n \"viewport.width\": String(props.width * 3),\r\n \"viewport.height\": String(props.height * 3),\r\n });\r\n\r\n return `https://api.microlink.io/?${params.toString()}`;\r\n});\r\n\r\n// Position tracking\r\nconst mousePosition = reactive({\r\n x: 0,\r\n y: 0,\r\n});\r\n\r\n// Calculate preview position\r\nconst previewStyle = computed<CSSProperties>(() => {\r\n if (!preview.value) return {};\r\n\r\n const offset = 20;\r\n const previewWidth = props.width;\r\n const previewHeight = props.height;\r\n const viewportWidth = window.innerWidth;\r\n\r\n let x = mousePosition.x - previewWidth / 2;\r\n x = Math.min(Math.max(0, x), viewportWidth - previewWidth);\r\n\r\n const linkRect = preview.value.parentElement?.getBoundingClientRect();\r\n const y = linkRect ? linkRect.top - previewHeight - offset : 0;\r\n\r\n return {\r\n position: \"fixed\",\r\n left: `${x}px`,\r\n top: `${y}px`,\r\n width: `${previewWidth}px`,\r\n height: `${previewHeight}px`,\r\n };\r\n});\r\n\r\n// Image specific styling\r\nconst imageStyle = computed<CSSProperties>(() => ({\r\n width: `${props.width}px`,\r\n height: `${props.height}px`,\r\n}));\r\n\r\n// Pop animation class\r\nconst popClass = computed(() => {\r\n if (!hasPopped.value) return \"\";\r\n return \"animate-pop\";\r\n});\r\n\r\nfunction handleMouseMove(event: MouseEvent) {\r\n mousePosition.x = event.clientX;\r\n mousePosition.y = event.clientY;\r\n}\r\n\r\nfunction showPreview() {\r\n isVisible.value = true;\r\n setTimeout(() => {\r\n hasPopped.value = true;\r\n }, 50);\r\n}\r\n\r\nfunction hidePreview() {\r\n isVisible.value = false;\r\n hasPopped.value = false;\r\n}\r\n\r\nfunction handleImageLoad() {\r\n isLoading.value = false;\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.transform-gpu {\r\n transform: scale3d(0, 0, 1);\r\n transform-origin: center bottom;\r\n will-change: transform;\r\n backface-visibility: hidden;\r\n}\r\n\r\n.animate-pop {\r\n animation: pop 1000ms ease forwards;\r\n will-change: transform;\r\n}\r\n\r\n@keyframes pop {\r\n 0% {\r\n transform: scale3d(0.26, 0.26, 1);\r\n }\r\n 25% {\r\n transform: scale3d(1.1, 1.1, 1);\r\n }\r\n 65% {\r\n transform: scale3d(0.98, 0.98, 1);\r\n }\r\n 100% {\r\n transform: scale3d(1, 1, 1);\r\n }\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "ab2a4cb377af243b44750f7e29fd80fc165439ab"
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "liquid-background",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"ogl"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as LiquidBackground } from \"./LiquidBackground.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "LiquidBackground.vue",
|
|
13
|
+
"content": "<template>\r\n <div\r\n ref=\"ctnDom\"\r\n :class=\"cn('block size-full', props?.class)\"\r\n ></div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { onMounted, onUnmounted, ref, type HTMLAttributes } from \"vue\";\r\nimport { Renderer, Program, Mesh, Color, Triangle, type OGLRenderingContext } from \"ogl\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nconst props = defineProps<{ class?: HTMLAttributes[\"class\"] }>();\r\n\r\nconst ctnDom = ref<HTMLDivElement | null>(null);\r\n\r\nlet animateId: number;\r\nlet renderer: Renderer;\r\nlet gl: OGLRenderingContext;\r\nlet mesh: Mesh;\r\n\r\n// Vertex Shader\r\nconst vert = `\r\n attribute vec2 uv;\r\n attribute vec2 position;\r\n \r\n varying vec2 vUv;\r\n \r\n void main() {\r\n vUv = uv;\r\n gl_Position = vec4(position, 0, 1);\r\n }\r\n `;\r\n\r\n// Fragment Shader\r\nconst frag = `\r\n precision highp float;\r\n \r\n uniform float uTime;\r\n uniform vec3 uColor;\r\n uniform vec3 uResolution;\r\n \r\n varying vec2 vUv;\r\n \r\n void main() {\r\n float mr = min(uResolution.x, uResolution.y);\r\n vec2 uv = (vUv.xy * 2.0 - 1.0) * uResolution.xy / mr;\r\n \r\n float d = -uTime * 1.2;\r\n float a = 0.0;\r\n for (float i = 0.0; i < 8.0; ++i) {\r\n a += cos(i - d - a * uv.x);\r\n d += sin(uv.y * i + a);\r\n }\r\n d += uTime * 1.0;\r\n vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5);\r\n col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5);\r\n gl_FragColor = vec4(col, 1.0);\r\n }\r\n `;\r\n\r\nfunction resize() {\r\n if (!ctnDom.value) return;\r\n const scale = 1;\r\n renderer.setSize(ctnDom.value.offsetWidth * scale, ctnDom.value.offsetHeight * scale);\r\n if (mesh) {\r\n mesh.program.uniforms.uResolution.value = [\r\n gl.canvas.width,\r\n gl.canvas.height,\r\n gl.canvas.width / gl.canvas.height,\r\n ];\r\n }\r\n}\r\n\r\nfunction update(t: number) {\r\n animateId = requestAnimationFrame(update);\r\n if (mesh) {\r\n mesh.program.uniforms.uTime.value = t * 0.001;\r\n renderer.render({ scene: mesh });\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n if (!ctnDom.value) return;\r\n\r\n renderer = new Renderer();\r\n gl = renderer.gl;\r\n gl.clearColor(1, 1, 1, 1);\r\n\r\n window.addEventListener(\"resize\", resize, false);\r\n resize();\r\n\r\n const geometry = new Triangle(gl);\r\n\r\n const program = new Program(gl, {\r\n vertex: vert,\r\n fragment: frag,\r\n uniforms: {\r\n uTime: { value: 0 },\r\n uColor: { value: new Color(0.3, 0.2, 0.5) },\r\n uResolution: {\r\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height],\r\n },\r\n },\r\n });\r\n\r\n mesh = new Mesh(gl, { geometry, program });\r\n animateId = requestAnimationFrame(update);\r\n\r\n ctnDom.value.appendChild(gl.canvas);\r\n});\r\n\r\nonUnmounted(() => {\r\n cancelAnimationFrame(animateId);\r\n window.removeEventListener(\"resize\", resize);\r\n if (ctnDom.value && gl?.canvas) {\r\n ctnDom.value.removeChild(gl.canvas);\r\n }\r\n gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\r\n});\r\n</script>\r\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"fileCount": 2,
|
|
17
|
+
"contentHash": "bcd98ec4b41f738e43cb7ab2d0fbc0542f8edc72"
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "liquid-glass",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as LiquidGlass } from \"./LiquidGlass.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "LiquidGlass.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n ref=\"liquidGlassRoot\"\r\n class=\"effect\"\r\n :class=\"[props.containerClass]\"\r\n :style=\"baseStyle\"\r\n >\r\n <div :class=\"cn('slot-container', props.class)\">\r\n <slot />\r\n </div>\r\n\r\n <svg\r\n class=\"filter\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <defs>\r\n <filter\r\n id=\"displacementFilter\"\r\n color-interpolation-filters=\"sRGB\"\r\n >\r\n <feImage\r\n x=\"0\"\r\n y=\"0\"\r\n width=\"100%\"\r\n height=\"100%\"\r\n :href=\"displacementDataUri\"\r\n result=\"map\"\r\n />\r\n <feDisplacementMap\r\n id=\"redchannel\"\r\n in=\"SourceGraphic\"\r\n in2=\"map\"\r\n :xChannelSelector=\"xChannel\"\r\n :yChannelSelector=\"yChannel\"\r\n :scale=\"scale + rOffset\"\r\n result=\"dispRed\"\r\n />\r\n <feColorMatrix\r\n in=\"dispRed\"\r\n type=\"matrix\"\r\n values=\"1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"\r\n result=\"red\"\r\n />\r\n <feDisplacementMap\r\n id=\"greenchannel\"\r\n in=\"SourceGraphic\"\r\n in2=\"map\"\r\n :xChannelSelector=\"xChannel\"\r\n :yChannelSelector=\"yChannel\"\r\n :scale=\"scale + gOffset\"\r\n result=\"dispGreen\"\r\n />\r\n <feColorMatrix\r\n in=\"dispGreen\"\r\n type=\"matrix\"\r\n values=\"0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0\"\r\n result=\"green\"\r\n />\r\n <feDisplacementMap\r\n id=\"bluechannel\"\r\n in=\"SourceGraphic\"\r\n in2=\"map\"\r\n :xChannelSelector=\"xChannel\"\r\n :yChannelSelector=\"yChannel\"\r\n :scale=\"scale + bOffset\"\r\n result=\"dispBlue\"\r\n />\r\n <feColorMatrix\r\n in=\"dispBlue\"\r\n type=\"matrix\"\r\n values=\"0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0\"\r\n result=\"blue\"\r\n />\r\n <feBlend\r\n in=\"red\"\r\n in2=\"green\"\r\n mode=\"screen\"\r\n result=\"rg\"\r\n />\r\n <feBlend\r\n in=\"rg\"\r\n in2=\"blue\"\r\n mode=\"screen\"\r\n result=\"output\"\r\n />\r\n <feGaussianBlur :stdDeviation=\"displace\" />\r\n </filter>\r\n </defs>\r\n </svg>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from \"vue\";\r\nimport { cn } from \"~/lib/utils\";\r\n\r\ninterface Props {\r\n radius?: number;\r\n border?: number;\r\n lightness?: number;\r\n displace?: number;\r\n blend?: string;\r\n xChannel?: \"R\" | \"G\" | \"B\";\r\n yChannel?: \"R\" | \"G\" | \"B\";\r\n alpha?: number;\r\n blur?: number;\r\n rOffset?: number;\r\n gOffset?: number;\r\n bOffset?: number;\r\n scale?: number;\r\n frost?: number;\r\n class?: HTMLAttributes[\"class\"];\r\n containerClass?: HTMLAttributes[\"class\"];\r\n}\r\n\r\n// Props definition\r\nconst props = withDefaults(defineProps<Props>(), {\r\n radius: 16,\r\n border: 0.07,\r\n lightness: 50,\r\n blend: \"difference\",\r\n xChannel: \"R\",\r\n yChannel: \"B\",\r\n alpha: 0.93,\r\n blur: 11,\r\n rOffset: 0,\r\n gOffset: 10,\r\n bOffset: 20,\r\n scale: -180,\r\n frost: 0.05,\r\n});\r\n\r\n// Refs\r\nconst liquidGlassRoot = ref<HTMLElement | null>(null);\r\nconst dimensions = reactive({\r\n width: 0,\r\n height: 0,\r\n});\r\n\r\nlet observer: ResizeObserver | null = null;\r\n\r\nconst baseStyle = computed(() => {\r\n return {\r\n \"--frost\": props.frost,\r\n \"border-radius\": `${props.radius}px`,\r\n };\r\n});\r\n\r\n// Computed displacement image\r\nconst displacementImage = computed(() => {\r\n const border = Math.min(dimensions.width, dimensions.height) * (props.border * 0.5);\r\n const yBorder = Math.min(dimensions.width, dimensions.height) * (props.border * 0.5);\r\n\r\n return `\r\n <svg viewBox=\"0 0 ${dimensions.width} ${dimensions.height}\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <defs>\r\n <linearGradient id=\"red\" x1=\"100%\" y1=\"0%\" x2=\"0%\" y2=\"0%\">\r\n <stop offset=\"0%\" stop-color=\"#0000\"/>\r\n <stop offset=\"100%\" stop-color=\"red\"/>\r\n </linearGradient>\r\n <linearGradient id=\"blue\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\r\n <stop offset=\"0%\" stop-color=\"#0000\"/>\r\n <stop offset=\"100%\" stop-color=\"blue\"/>\r\n </linearGradient>\r\n </defs>\r\n <rect x=\"0\" y=\"0\" width=\"${dimensions.width}\" height=\"${dimensions.height}\" fill=\"black\"></rect>\r\n <rect x=\"0\" y=\"0\" width=\"${dimensions.width}\" height=\"${dimensions.height}\" rx=\"${props.radius}\" fill=\"url(#red)\" />\r\n <rect x=\"0\" y=\"0\" width=\"${dimensions.width}\" height=\"${dimensions.height}\" rx=\"${props.radius}\" fill=\"url(#blue)\" style=\"mix-blend-mode: ${props.blend}\" />\r\n <rect \r\n x=\"${border}\" \r\n y=\"${yBorder}\" \r\n width=\"${dimensions.width - border * 2}\" \r\n height=\"${dimensions.height - border * 2}\" \r\n rx=\"${props.radius}\" \r\n fill=\"hsl(0 0% ${props.lightness}% / ${props.alpha})\" \r\n style=\"filter:blur(${props.blur}px)\" \r\n />\r\n </svg>\r\n `;\r\n});\r\n\r\n// Data URI for SVG filter\r\nconst displacementDataUri = computed(() => {\r\n const encoded = encodeURIComponent(displacementImage.value);\r\n return `data:image/svg+xml,${encoded}`;\r\n});\r\n\r\n// Lifecycle hooks\r\nonMounted(() => {\r\n if (!liquidGlassRoot.value) return;\r\n\r\n observer = new ResizeObserver((entries) => {\r\n const entry = entries[0];\r\n if (!entry) return;\r\n\r\n let width = 0;\r\n let height = 0;\r\n\r\n if (entry.borderBoxSize && entry.borderBoxSize?.length) {\r\n width = entry.borderBoxSize[0]!.inlineSize;\r\n height = entry.borderBoxSize[0]!.blockSize;\r\n } else if (entry.contentRect) {\r\n width = entry.contentRect.width;\r\n height = entry.contentRect.height;\r\n }\r\n\r\n dimensions.width = width;\r\n dimensions.height = height;\r\n });\r\n\r\n observer.observe(liquidGlassRoot.value);\r\n});\r\n\r\nonUnmounted(() => {\r\n observer?.disconnect();\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.effect {\r\n position: fixed;\r\n display: block;\r\n opacity: 1;\r\n border-radius: inherit;\r\n backdrop-filter: url(#displacementFilter);\r\n background: light-dark(hsl(0 0% 100% / var(--frost, 0)), hsl(0 0% 0% / var(--frost, 0)));\r\n box-shadow:\r\n 0 0 2px 1px\r\n light-dark(\r\n color-mix(in oklch, canvasText, #0000 85%),\r\n color-mix(in oklch, canvasText, #0000 90%)\r\n )\r\n inset,\r\n 0 0 10px 4px\r\n light-dark(\r\n color-mix(in oklch, canvasText, #0000 90%),\r\n color-mix(in oklch, canvasText, #0000 95%)\r\n )\r\n inset,\r\n 0px 4px 16px rgba(17, 17, 26, 0.05),\r\n 0px 8px 24px rgba(17, 17, 26, 0.05),\r\n 0px 16px 56px rgba(17, 17, 26, 0.05),\r\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\r\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\r\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset;\r\n}\r\n\r\n.slot-container {\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n border-radius: inherit;\r\n}\r\n\r\n.filter {\r\n position: absolute;\r\n inset: 0;\r\n width: 100%;\r\n height: 100%;\r\n pointer-events: none;\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "651005e2f06924ba0775a627db5349d6733d18b8"
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "liquid-logo",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as LiquidLogo } from \"./LiquidLogo.vue\";\r\nexport * from \"./parseLogoImage\";\r\nexport * from \"./shader\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "LiquidLogo.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n v-if=\"processing && showProcessing\"\r\n class=\"flex size-full items-center justify-center text-2xl font-bold text-primary/50\"\r\n >\r\n <span> Processing Logo </span>\r\n </div>\r\n <canvas\r\n ref=\"liquidLogoRef\"\r\n :class=\"cn('block size-full object-contain', props.class, { hidden: processing })\"\r\n ></canvas>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { type HTMLAttributes } from \"vue\";\r\nimport { liquidFragSource, vertexShaderSource } from \"./shader\";\r\nimport { parseLogoImage } from \"./parseLogoImage\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n class?: HTMLAttributes[\"class\"];\r\n imageUrl: string;\r\n patternScale?: number;\r\n refraction?: number;\r\n edge?: number;\r\n patternBlur?: number;\r\n liquid?: number;\r\n speed?: number;\r\n showProcessing?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n refraction: 0.015,\r\n edge: 0.4,\r\n patternScale: 2,\r\n patternBlur: 0.005,\r\n liquid: 0.07,\r\n speed: 0.3,\r\n showProcessing: true,\r\n});\r\n\r\nconst imageData = ref<ImageData | null>(null);\r\nconst glRef = ref<WebGLRenderingContext | null>(null);\r\nconst uniforms = ref<Record<string, WebGLUniformLocation>>({});\r\nconst totalAnimationTime = ref(0);\r\nconst lastRenderTime = ref(0);\r\nconst liquidLogoRef = ref<HTMLCanvasElement | null>(null);\r\nconst processing = ref(false);\r\n\r\nlet renderId: number;\r\nlet cleanUpTexture: (() => void) | undefined;\r\n\r\nonMounted(async () => {\r\n processing.value = true;\r\n\r\n await processImage();\r\n\r\n initShader();\r\n updateUniforms();\r\n cleanUpTexture = await cleanTexture();\r\n\r\n processing.value = false;\r\n\r\n window.addEventListener(\"resize\", resizeCanvas);\r\n\r\n resizeCanvas();\r\n\r\n animate();\r\n});\r\n\r\nonUnmounted(() => {\r\n window.removeEventListener(\"resize\", resizeCanvas);\r\n cancelAnimationFrame(renderId);\r\n if (cleanUpTexture) {\r\n cleanUpTexture();\r\n }\r\n});\r\n\r\nfunction updateUniforms() {\r\n if (!glRef.value || !uniforms.value) return;\r\n glRef.value.uniform1f(uniforms.value.u_edge, props.edge);\r\n glRef.value.uniform1f(uniforms.value.u_patternBlur, props.patternBlur);\r\n glRef.value.uniform1f(uniforms.value.u_time, 0);\r\n glRef.value.uniform1f(uniforms.value.u_patternScale, props.patternScale);\r\n glRef.value.uniform1f(uniforms.value.u_refraction, props.refraction);\r\n glRef.value.uniform1f(uniforms.value.u_liquid, props.liquid);\r\n}\r\n\r\nfunction initShader() {\r\n const canvas = liquidLogoRef.value;\r\n const gl = canvas?.getContext(\"webgl2\", {\r\n antialias: true,\r\n alpha: true,\r\n });\r\n if (!canvas || !gl) {\r\n // \"Failed to initialize shader. Does your browser support WebGL2?\";\r\n return;\r\n }\r\n\r\n gl.enable(gl.BLEND);\r\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\r\n\r\n function createShader(gl: WebGL2RenderingContext, sourceCode: string, type: number) {\r\n const shader = gl.createShader(type);\r\n if (!shader) {\r\n // \"Failed to create shader\";\r\n return null;\r\n }\r\n\r\n gl.shaderSource(shader, sourceCode);\r\n gl.compileShader(shader);\r\n\r\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\r\n // \"An error occurred compiling the shaders: \" + gl.getShaderInfoLog(shader);\r\n gl.deleteShader(shader);\r\n return null;\r\n }\r\n\r\n return shader;\r\n }\r\n\r\n const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);\r\n const fragmentShader = createShader(gl, liquidFragSource, gl.FRAGMENT_SHADER);\r\n const program = gl.createProgram();\r\n if (!program || !vertexShader || !fragmentShader) {\r\n // \"Failed to create program or shaders\";\r\n return;\r\n }\r\n\r\n gl.attachShader(program, vertexShader);\r\n gl.attachShader(program, fragmentShader);\r\n gl.linkProgram(program);\r\n\r\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\r\n // \"Unable to initialize the shader program: \" + gl.getProgramInfoLog(program);\r\n return null;\r\n }\r\n\r\n function getUniforms(program: WebGLProgram, gl: WebGL2RenderingContext) {\r\n let uniforms: Record<string, WebGLUniformLocation> = {};\r\n let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\r\n for (let i = 0; i < uniformCount; i++) {\r\n let uniformName = gl.getActiveUniform(program, i)?.name;\r\n if (!uniformName) continue;\r\n uniforms[uniformName] = gl.getUniformLocation(program, uniformName) as WebGLUniformLocation;\r\n }\r\n return uniforms;\r\n }\r\n const unfm = getUniforms(program, gl);\r\n uniforms.value = unfm;\r\n\r\n // Vertex position\r\n const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);\r\n const vertexBuffer = gl.createBuffer();\r\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\r\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);\r\n\r\n gl.useProgram(program);\r\n\r\n const positionLocation = gl.getAttribLocation(program, \"a_position\");\r\n gl.enableVertexAttribArray(positionLocation);\r\n\r\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\r\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\r\n\r\n glRef.value = gl;\r\n}\r\n\r\nfunction render(currentTime: number) {\r\n const deltaTime = currentTime - lastRenderTime.value;\r\n lastRenderTime.value = currentTime;\r\n\r\n // Update the total animation time and time uniform\r\n totalAnimationTime.value += deltaTime * props.speed;\r\n glRef.value!.uniform1f(uniforms.value.u_time, totalAnimationTime.value);\r\n // Draw!\r\n glRef.value!.drawArrays(glRef.value!.TRIANGLE_STRIP, 0, 4);\r\n // rAF\r\n renderId = requestAnimationFrame(render);\r\n}\r\n\r\nfunction animate() {\r\n // Kick off the render loop\r\n lastRenderTime.value = performance.now();\r\n renderId = requestAnimationFrame(render);\r\n\r\n return () => {\r\n cancelAnimationFrame(renderId);\r\n };\r\n}\r\n\r\nfunction resizeCanvas() {\r\n const canvasEl = liquidLogoRef.value;\r\n const gl = glRef.value;\r\n\r\n if (!canvasEl || !gl || !uniforms.value) return;\r\n const imgRatio = imageData.value ? imageData.value.width / imageData.value.height : 1;\r\n gl.uniform1f(uniforms.value.u_img_ratio, imgRatio);\r\n\r\n const side = 1000;\r\n canvasEl.width = side * devicePixelRatio;\r\n canvasEl.height = side * devicePixelRatio;\r\n gl.viewport(0, 0, canvasEl.height, canvasEl.height);\r\n gl.uniform1f(uniforms.value.u_ratio, 1);\r\n gl.uniform1f(uniforms.value.u_img_ratio, imgRatio);\r\n}\r\n\r\nasync function processImage() {\r\n try {\r\n const { imageData: imgData } = await parseLogoImage(props.imageUrl);\r\n imageData.value = imgData;\r\n } catch (error) {\r\n // handle error\r\n }\r\n}\r\n\r\nasync function cleanTexture() {\r\n const gl = glRef.value;\r\n if (!gl || !uniforms.value || !imageData.value) return;\r\n\r\n // Delete any existing texture first\r\n const existingTexture = gl.getParameter(gl.TEXTURE_BINDING_2D);\r\n if (existingTexture) {\r\n gl.deleteTexture(existingTexture);\r\n }\r\n\r\n const imageTexture = gl.createTexture();\r\n gl.activeTexture(gl.TEXTURE0);\r\n gl.bindTexture(gl.TEXTURE_2D, imageTexture);\r\n\r\n // Set texture parameters before uploading the data\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\r\n\r\n // Ensure power-of-two dimensions or use appropriate texture parameters\r\n gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\r\n\r\n try {\r\n gl.texImage2D(\r\n gl.TEXTURE_2D,\r\n 0,\r\n gl.RGBA,\r\n imageData.value.width,\r\n imageData.value.height,\r\n 0,\r\n gl.RGBA,\r\n gl.UNSIGNED_BYTE,\r\n imageData.value.data,\r\n );\r\n\r\n gl.uniform1i(uniforms.value.u_image_texture, 0);\r\n } catch (e) {\r\n // handle error\r\n }\r\n\r\n return () => {\r\n // Cleanup texture when component unmounts or imageData changes\r\n if (imageTexture) {\r\n gl.deleteTexture(imageTexture);\r\n }\r\n };\r\n}\r\n\r\n// Update uniforms when relevant props change\r\nwatch(\r\n () => [props.edge, props.patternBlur, props.patternScale, props.refraction, props.liquid],\r\n updateUniforms,\r\n);\r\n</script>\r\n"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "parseLogoImage.ts",
|
|
15
|
+
"content": "export function parseLogoImage(imageUrl: string): Promise<{ imageData: ImageData; pngBlob: Blob }> {\r\n const canvas = document.createElement(\"canvas\");\r\n const ctx = canvas.getContext(\"2d\");\r\n\r\n return new Promise((resolve, reject) => {\r\n if (!imageUrl || !ctx) {\r\n reject(new Error(\"Invalid image URL or context\"));\r\n return;\r\n }\r\n\r\n const img = new Image();\r\n img.crossOrigin = \"anonymous\"; // Enable CORS for external images\r\n img.onload = function () {\r\n const MAX_SIZE = 1000;\r\n const MIN_SIZE = 500;\r\n let width = img.naturalWidth;\r\n let height = img.naturalHeight;\r\n\r\n if (width > MAX_SIZE || height > MAX_SIZE || width < MIN_SIZE || height < MIN_SIZE) {\r\n if (width > height) {\r\n if (width > MAX_SIZE) {\r\n height = Math.round((height * MAX_SIZE) / width);\r\n width = MAX_SIZE;\r\n } else if (width < MIN_SIZE) {\r\n height = Math.round((height * MIN_SIZE) / width);\r\n width = MIN_SIZE;\r\n }\r\n } else {\r\n if (height > MAX_SIZE) {\r\n width = Math.round((width * MAX_SIZE) / height);\r\n height = MAX_SIZE;\r\n } else if (height < MIN_SIZE) {\r\n width = Math.round((width * MIN_SIZE) / height);\r\n height = MIN_SIZE;\r\n }\r\n }\r\n }\r\n\r\n canvas.width = width;\r\n canvas.height = height;\r\n\r\n const shapeCanvas = document.createElement(\"canvas\");\r\n shapeCanvas.width = width;\r\n shapeCanvas.height = height;\r\n const shapeCtx = shapeCanvas.getContext(\"2d\")!;\r\n shapeCtx.drawImage(img, 0, 0, width, height);\r\n\r\n const shapeImageData = shapeCtx.getImageData(0, 0, width, height);\r\n const data = shapeImageData.data;\r\n const shapeMask = new Array(width * height).fill(false);\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const idx4 = (y * width + x) * 4;\r\n const r = data[idx4];\r\n const g = data[idx4 + 1];\r\n const b = data[idx4 + 2];\r\n const a = data[idx4 + 3];\r\n shapeMask[y * width + x] = !(\r\n (r === 255 && g === 255 && b === 255 && a === 255) ||\r\n a === 0\r\n );\r\n }\r\n }\r\n\r\n function inside(x: number, y: number) {\r\n if (x < 0 || x >= width || y < 0 || y >= height) return false;\r\n return shapeMask[y * width + x];\r\n }\r\n\r\n const boundaryMask = new Array(width * height).fill(false);\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const idx = y * width + x;\r\n if (!shapeMask[idx]) continue;\r\n let isBoundary = false;\r\n for (let ny = y - 1; ny <= y + 1 && !isBoundary; ny++) {\r\n for (let nx = x - 1; nx <= x + 1 && !isBoundary; nx++) {\r\n if (!inside(nx, ny)) {\r\n isBoundary = true;\r\n }\r\n }\r\n }\r\n if (isBoundary) {\r\n boundaryMask[idx] = true;\r\n }\r\n }\r\n }\r\n\r\n const u = new Float32Array(width * height).fill(0);\r\n const newU = new Float32Array(width * height).fill(0);\r\n const C = 0.01;\r\n const ITERATIONS = 300;\r\n\r\n function getU(x: number, y: number, arr: Float32Array) {\r\n if (x < 0 || x >= width || y < 0 || y >= height) return 0;\r\n if (!shapeMask[y * width + x]) return 0;\r\n return arr[y * width + x];\r\n }\r\n\r\n for (let iter = 0; iter < ITERATIONS; iter++) {\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const idx = y * width + x;\r\n if (!shapeMask[idx] || boundaryMask[idx]) {\r\n newU[idx] = 0;\r\n continue;\r\n }\r\n const sumN =\r\n getU(x + 1, y, u) + getU(x - 1, y, u) + getU(x, y + 1, u) + getU(x, y - 1, u);\r\n newU[idx] = (C + sumN) / 4;\r\n }\r\n }\r\n u.set(newU);\r\n }\r\n\r\n let maxVal = 0;\r\n for (let i = 0; i < width * height; i++) {\r\n if (u[i] > maxVal) maxVal = u[i];\r\n }\r\n const alpha = 2.0;\r\n const outImg = ctx.createImageData(width, height);\r\n\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const idx = y * width + x;\r\n const px = idx * 4;\r\n if (!shapeMask[idx]) {\r\n outImg.data[px] = 255;\r\n outImg.data[px + 1] = 255;\r\n outImg.data[px + 2] = 255;\r\n outImg.data[px + 3] = 255;\r\n } else {\r\n const raw = u[idx] / maxVal;\r\n const remapped = Math.pow(raw, alpha);\r\n const gray = 255 * (1 - remapped);\r\n outImg.data[px] = gray;\r\n outImg.data[px + 1] = gray;\r\n outImg.data[px + 2] = gray;\r\n outImg.data[px + 3] = 255;\r\n }\r\n }\r\n }\r\n\r\n ctx.putImageData(outImg, 0, 0);\r\n canvas.toBlob((blob) => {\r\n if (!blob) {\r\n reject(new Error(\"Failed to create PNG blob\"));\r\n return;\r\n }\r\n resolve({\r\n imageData: outImg,\r\n pngBlob: blob,\r\n });\r\n }, \"image/png\");\r\n };\r\n\r\n img.onerror = () => reject(new Error(\"Failed to load image\"));\r\n img.src = imageUrl;\r\n });\r\n}\r\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "shader.ts",
|
|
19
|
+
"content": "export const vertexShaderSource = /* glsl */ `#version 300 es\r\nprecision mediump float;\r\n\r\nin vec2 a_position;\r\nout vec2 vUv;\r\n\r\nvoid main() {\r\n vUv = .5 * (a_position + 1.);\r\n gl_Position = vec4(a_position, 0.0, 1.0);\r\n}`;\r\n\r\nexport const liquidFragSource = /* glsl */ `#version 300 es\r\nprecision mediump float;\r\n\r\nin vec2 vUv;\r\nout vec4 fragColor;\r\n\r\nuniform sampler2D u_image_texture;\r\nuniform float u_time;\r\nuniform float u_ratio;\r\nuniform float u_img_ratio;\r\nuniform float u_patternScale;\r\nuniform float u_refraction;\r\nuniform float u_edge;\r\nuniform float u_patternBlur;\r\nuniform float u_liquid;\r\n\r\n\r\n#define TWO_PI 6.28318530718\r\n#define PI 3.14159265358979323846\r\n\r\n\r\nvec3 mod289(vec3 x) { return x - floor(x * (1. / 289.)) * 289.; }\r\nvec2 mod289(vec2 x) { return x - floor(x * (1. / 289.)) * 289.; }\r\nvec3 permute(vec3 x) { return mod289(((x*34.)+1.)*x); }\r\nfloat snoise(vec2 v) {\r\n const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);\r\n vec2 i = floor(v + dot(v, C.yy));\r\n vec2 x0 = v - i + dot(i, C.xx);\r\n vec2 i1;\r\n i1 = (x0.x > x0.y) ? vec2(1., 0.) : vec2(0., 1.);\r\n vec4 x12 = x0.xyxy + C.xxzz;\r\n x12.xy -= i1;\r\n i = mod289(i);\r\n vec3 p = permute(permute(i.y + vec3(0., i1.y, 1.)) + i.x + vec3(0., i1.x, 1.));\r\n vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.);\r\n m = m*m;\r\n m = m*m;\r\n vec3 x = 2. * fract(p * C.www) - 1.;\r\n vec3 h = abs(x) - 0.5;\r\n vec3 ox = floor(x + 0.5);\r\n vec3 a0 = x - ox;\r\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\r\n vec3 g;\r\n g.x = a0.x * x0.x + h.x * x0.y;\r\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\r\n return 130. * dot(m, g);\r\n}\r\n\r\nvec2 get_img_uv() {\r\n vec2 img_uv = vUv;\r\n img_uv -= .5;\r\n if (u_ratio > u_img_ratio) {\r\n img_uv.x = img_uv.x * u_ratio / u_img_ratio;\r\n } else {\r\n img_uv.y = img_uv.y * u_img_ratio / u_ratio;\r\n }\r\n float scale_factor = 1.;\r\n img_uv *= scale_factor;\r\n img_uv += .5;\r\n\r\n img_uv.y = 1. - img_uv.y;\r\n\r\n return img_uv;\r\n}\r\nvec2 rotate(vec2 uv, float th) {\r\n return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;\r\n}\r\nfloat get_color_channel(float c1, float c2, float stripe_p, vec3 w, float extra_blur, float b) {\r\n float ch = c2;\r\n float border = 0.;\r\n float blur = u_patternBlur + extra_blur;\r\n\r\n ch = mix(ch, c1, smoothstep(.0, blur, stripe_p));\r\n\r\n border = w[0];\r\n ch = mix(ch, c2, smoothstep(border - blur, border + blur, stripe_p));\r\n\r\n b = smoothstep(.2, .8, b);\r\n border = w[0] + .4 * (1. - b) * w[1];\r\n ch = mix(ch, c1, smoothstep(border - blur, border + blur, stripe_p));\r\n\r\n border = w[0] + .5 * (1. - b) * w[1];\r\n ch = mix(ch, c2, smoothstep(border - blur, border + blur, stripe_p));\r\n\r\n border = w[0] + w[1];\r\n ch = mix(ch, c1, smoothstep(border - blur, border + blur, stripe_p));\r\n\r\n float gradient_t = (stripe_p - w[0] - w[1]) / w[2];\r\n float gradient = mix(c1, c2, smoothstep(0., 1., gradient_t));\r\n ch = mix(ch, gradient, smoothstep(border - blur, border + blur, stripe_p));\r\n\r\n return ch;\r\n}\r\n\r\nfloat get_img_frame_alpha(vec2 uv, float img_frame_width) {\r\n float img_frame_alpha = smoothstep(0., img_frame_width, uv.x) * smoothstep(1., 1. - img_frame_width, uv.x);\r\n img_frame_alpha *= smoothstep(0., img_frame_width, uv.y) * smoothstep(1., 1. - img_frame_width, uv.y);\r\n return img_frame_alpha;\r\n}\r\n\r\nvoid main() {\r\n vec2 uv = vUv;\r\n uv.y = 1. - uv.y;\r\n uv.x *= u_ratio;\r\n\r\n float diagonal = uv.x - uv.y;\r\n\r\n float t = .001 * u_time;\r\n\r\n vec2 img_uv = get_img_uv();\r\n vec4 img = texture(u_image_texture, img_uv);\r\n\r\n vec3 color = vec3(0.);\r\n float opacity = 1.;\r\n\r\n vec3 color1 = vec3(.98, 0.98, 1.);\r\n vec3 color2 = vec3(.1, .1, .1 + .1 * smoothstep(.7, 1.3, uv.x + uv.y));\r\n\r\n float edge = img.r;\r\n\r\n\r\n vec2 grad_uv = uv;\r\n grad_uv -= .5;\r\n\r\n float dist = length(grad_uv + vec2(0., .2 * diagonal));\r\n\r\n grad_uv = rotate(grad_uv, (.25 - .2 * diagonal) * PI);\r\n\r\n float bulge = pow(1.8 * dist, 1.2);\r\n bulge = 1. - bulge;\r\n bulge *= pow(uv.y, .3);\r\n\r\n\r\n float cycle_width = u_patternScale;\r\n float thin_strip_1_ratio = .12 / cycle_width * (1. - .4 * bulge);\r\n float thin_strip_2_ratio = .07 / cycle_width * (1. + .4 * bulge);\r\n float wide_strip_ratio = (1. - thin_strip_1_ratio - thin_strip_2_ratio);\r\n\r\n float thin_strip_1_width = cycle_width * thin_strip_1_ratio;\r\n float thin_strip_2_width = cycle_width * thin_strip_2_ratio;\r\n\r\n opacity = 1. - smoothstep(.9 - .5 * u_edge, 1. - .5 * u_edge, edge);\r\n opacity *= get_img_frame_alpha(img_uv, 0.01);\r\n\r\n\r\n float noise = snoise(uv - t);\r\n\r\n edge += (1. - edge) * u_liquid * noise;\r\n\r\n float refr = 0.;\r\n refr += (1. - bulge);\r\n refr = clamp(refr, 0., 1.);\r\n\r\n float dir = grad_uv.x;\r\n\r\n\r\n dir += diagonal;\r\n\r\n dir -= 2. * noise * diagonal * (smoothstep(0., 1., edge) * smoothstep(1., 0., edge));\r\n\r\n bulge *= clamp(pow(uv.y, .1), .3, 1.);\r\n dir *= (.1 + (1.1 - edge) * bulge);\r\n\r\n dir *= smoothstep(1., .7, edge);\r\n\r\n dir += .18 * (smoothstep(.1, .2, uv.y) * smoothstep(.4, .2, uv.y));\r\n dir += .03 * (smoothstep(.1, .2, 1. - uv.y) * smoothstep(.4, .2, 1. - uv.y));\r\n\r\n dir *= (.5 + .5 * pow(uv.y, 2.));\r\n\r\n dir *= cycle_width;\r\n\r\n dir -= t;\r\n\r\n float refr_r = refr;\r\n refr_r += .03 * bulge * noise;\r\n float refr_b = 1.3 * refr;\r\n\r\n refr_r += 5. * (smoothstep(-.1, .2, uv.y) * smoothstep(.5, .1, uv.y)) * (smoothstep(.4, .6, bulge) * smoothstep(1., .4, bulge));\r\n refr_r -= diagonal;\r\n\r\n refr_b += (smoothstep(0., .4, uv.y) * smoothstep(.8, .1, uv.y)) * (smoothstep(.4, .6, bulge) * smoothstep(.8, .4, bulge));\r\n refr_b -= .2 * edge;\r\n\r\n refr_r *= u_refraction;\r\n refr_b *= u_refraction;\r\n\r\n vec3 w = vec3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio);\r\n w[1] -= .02 * smoothstep(.0, 1., edge + bulge);\r\n float stripe_r = mod(dir + refr_r, 1.);\r\n float r = get_color_channel(color1.r, color2.r, stripe_r, w, 0.02 + .03 * u_refraction * bulge, bulge);\r\n float stripe_g = mod(dir, 1.);\r\n float g = get_color_channel(color1.g, color2.g, stripe_g, w, 0.01 / (1. - diagonal), bulge);\r\n float stripe_b = mod(dir - refr_b, 1.);\r\n float b = get_color_channel(color1.b, color2.b, stripe_b, w, .01, bulge);\r\n\r\n color = vec3(r, g, b);\r\n\r\n color *= opacity;\r\n\r\n fragColor = vec4(color, opacity);\r\n}\r\n`;\r\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"fileCount": 4,
|
|
23
|
+
"contentHash": "df97f397e93c6e4b4972a6572b2afa098d59a887"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logo-cloud",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "AnimatedLogoCloud.vue",
|
|
7
|
+
"content": "<template>\r\n <div class=\"w-full py-12\">\r\n <div class=\"mx-auto w-full px-4 md:px-8\">\r\n <div\r\n v-if=\"props.title\"\r\n class=\"text-center font-medium text-muted-foreground\"\r\n >\r\n {{ props.title }}\r\n </div>\r\n <div\r\n :class=\"\r\n cn('mask-animation group relative mt-6 flex gap-6 overflow-hidden p-2', props.class)\r\n \"\r\n >\r\n <div\r\n v-for=\"index in Array(5).fill(null)\"\r\n :key=\"index\"\r\n class=\"animate-logo-cloud flex shrink-0 flex-row justify-around gap-6\"\r\n >\r\n <img\r\n v-for=\"(logo, key) in props.logos\"\r\n :key=\"key\"\r\n :src=\"logo.path\"\r\n :alt=\"logo.name\"\r\n class=\"h-10 w-28 px-2 brightness-0 dark:invert\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { AnimateLogoCloudProps } from \"./index\";\r\n\r\nconst props = defineProps<AnimateLogoCloudProps>();\r\n</script>\r\n\r\n<style scoped>\r\n.mask-animation {\r\n mask-image: linear-gradient(to left, transparent 0%, black 20%, black 80%, transparent 95%);\r\n}\r\n\r\n.animate-logo-cloud {\r\n animation: logo-cloud 30s linear infinite;\r\n}\r\n\r\n@keyframes logo-cloud {\r\n 0% {\r\n transform: translateX(0);\r\n }\r\n 100% {\r\n transform: translateX(calc(-100% - 4rem));\r\n }\r\n}\r\n</style>\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "IconLogoCloud.vue",
|
|
11
|
+
"content": "<template>\r\n <div class=\"w-full py-12\">\r\n <div class=\"flex w-full flex-col items-center justify-center gap-6 px-4 md:px-8\">\r\n <div\r\n v-if=\"props.title\"\r\n class=\"font-medium text-muted-foreground\"\r\n >\r\n {{ props.title }}\r\n </div>\r\n <div :class=\"cn('grid grid-cols-3 lg:grid-cols-8 md:grid-cols-8', props.class)\">\r\n <img\r\n v-for=\"(logo, key) in props.logos\"\r\n :key=\"key\"\r\n :src=\"logo.path\"\r\n :alt=\"logo.name\"\r\n class=\"h-7 w-12 px-2 brightness-0 dark:invert\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { AnimateLogoCloudProps } from \"./index\";\r\n\r\nconst props = defineProps<AnimateLogoCloudProps>();\r\n</script>\r\n"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "index.ts",
|
|
15
|
+
"content": "export { default as AnimatedLogoCloud } from \"./AnimatedLogoCloud.vue\";\r\nexport { default as StaticLogoCloud } from \"./StaticLogoCloud.vue\";\r\nexport { default as IconLogoCloud } from \"./IconLogoCloud.vue\";\r\n\r\ninterface Logo {\r\n name: string;\r\n path: string;\r\n}\r\nexport interface AnimateLogoCloudProps {\r\n class?: string;\r\n title?: string;\r\n logos?: Logo[];\r\n}\r\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "StaticLogoCloud.vue",
|
|
19
|
+
"content": "<template>\r\n <div class=\"w-full py-12\">\r\n <div class=\"flex w-full flex-col items-center justify-center gap-4 px-4 md:px-8\">\r\n <div\r\n v-if=\"props.title\"\r\n class=\"font-medium uppercase text-muted-foreground\"\r\n >\r\n {{ props.title }}\r\n </div>\r\n <div :class=\"cn('grid grid-cols-3 gap-x-4 lg:grid-cols-8 md:grid-cols-5', props.class)\">\r\n <img\r\n v-for=\"(logo, key) in props.logos\"\r\n :key=\"key\"\r\n :src=\"logo.path\"\r\n :alt=\"logo.name\"\r\n class=\"h-10 w-28 px-2 brightness-0 dark:invert\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { AnimateLogoCloudProps } from \"./index\";\r\n\r\nconst props = defineProps<AnimateLogoCloudProps>();\r\n</script>\r\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"fileCount": 4,
|
|
23
|
+
"contentHash": "c757cbc1412340dfedd0a00b1733810fb7f05ba4"
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logo-origami",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"motion-v"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as LogoOrigami } from \"./LogoOrigami.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "LogoOrigami.vue",
|
|
13
|
+
"content": "<template>\r\n <div\r\n :style=\"{\r\n transform: 'rotateY(-20deg)',\r\n transformStyle: 'preserve-3d',\r\n }\"\r\n :class=\"\r\n cn(\r\n 'relative z-0 h-44 w-60 shrink-0 rounded-xl border border-background/75 bg-background',\r\n $props.class,\r\n )\r\n \"\r\n >\r\n <!-- Static Upper part -->\r\n <div\r\n :style=\"{\r\n clipPath: 'polygon(0 0, 100% 0, 100% 50%, 0 50%)',\r\n zIndex: -999,\r\n backfaceVisibility: 'hidden',\r\n }\"\r\n class=\"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2\"\r\n >\r\n <component :is=\"children[(activeIndex + 1) % children.length]\" />\r\n </div>\r\n\r\n <!-- Static Lower part -->\r\n <div\r\n :style=\"{\r\n clipPath: 'polygon(0 50%, 100% 50%, 100% 100%, 0 100%)',\r\n zIndex: -999,\r\n backfaceVisibility: 'hidden',\r\n }\"\r\n class=\"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2\"\r\n >\r\n <component :is=\"children[activeIndex % children.length]\" />\r\n </div>\r\n\r\n <!-- Upper part of the flip -->\r\n <Motion\r\n :key=\"activeIndex\"\r\n as=\"div\"\r\n :style=\"{\r\n clipPath: 'polygon(0 0, 100% 0, 100% 50%, 0 50%)',\r\n zIndex: -activeIndex,\r\n backfaceVisibility: 'hidden',\r\n }\"\r\n :initial=\"{\r\n rotateX: '0deg',\r\n y: '-50%',\r\n x: '-50%',\r\n }\"\r\n :animate=\"{\r\n rotateX: '-180deg',\r\n }\"\r\n :exit=\"{\r\n rotateX: '-180deg',\r\n }\"\r\n :transition=\"{\r\n duration: duration,\r\n ease: 'easeInOut',\r\n }\"\r\n class=\"absolute left-1/2 top-1/2\"\r\n >\r\n <component :is=\"children[activeIndex % children.length]\" />\r\n </Motion>\r\n\r\n <!-- Lower part of the flip -->\r\n <Motion\r\n :key=\"(activeIndex + 1) * 2\"\r\n as=\"div\"\r\n :style=\"{\r\n clipPath: 'polygon(0 50%, 100% 50%, 100% 100%, 0 100%)',\r\n zIndex: activeIndex,\r\n backfaceVisibility: 'hidden',\r\n }\"\r\n :initial=\"{\r\n rotateX: '180deg',\r\n y: '-50%',\r\n x: '-50%',\r\n }\"\r\n :animate=\"{\r\n rotateX: '0deg',\r\n }\"\r\n :exit=\"{\r\n rotateX: '0deg',\r\n }\"\r\n :transition=\"{\r\n duration: duration,\r\n ease: 'easeInOut',\r\n }\"\r\n class=\"absolute left-1/2 top-1/2\"\r\n >\r\n <component :is=\"children[(activeIndex + 1) % children.length]\" />\r\n </Motion>\r\n\r\n <!-- Center divider line -->\r\n <hr\r\n style=\"transform: translateZ(1px)\"\r\n class=\"absolute left-4 right-[15px] top-1/2 z-[999999999] -translate-y-1/2 border-t-2 border-neutral-800\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Motion } from \"motion-v\";\r\nimport { ref, onMounted, onBeforeUnmount, watchEffect, useSlots } from \"vue\";\r\n\r\ntype LogoOrigamiProps = {\r\n duration?: number;\r\n delay?: number;\r\n class?: string;\r\n};\r\n\r\nconst props = withDefaults(defineProps<LogoOrigamiProps>(), {\r\n delay: 2.5,\r\n duration: 1.5,\r\n});\r\n\r\nconst slots = useSlots();\r\n\r\nconst interval = ref();\r\nconst activeIndex = ref(0);\r\nconst children = ref<unknown[]>([]);\r\n\r\nonMounted(() => {\r\n watchEffect(() => {\r\n children.value = slots.default ? slots.default() : [];\r\n });\r\n\r\n interval.value = setInterval(() => {\r\n activeIndex.value = (activeIndex.value + 1) % children.value.length;\r\n }, props.delay * 1000);\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n clearInterval(interval.value);\r\n});\r\n</script>\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "LogoOrigamiItem.vue",
|
|
17
|
+
"content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'grid h-36 w-52 place-content-center rounded-lg bg-neutral-700 text-6xl text-neutral-50',\r\n $props.class,\r\n )\r\n \"\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"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"fileCount": 3,
|
|
21
|
+
"contentHash": "914fbf744b340507b4c969c8fa0eb41bb1cbbcba"
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "marquee",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as Marquee } from \"./Marquee.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "Marquee.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]',\r\n vertical ? 'flex-col' : 'flex-row',\r\n $props.class,\r\n )\r\n \"\r\n >\r\n <div\r\n v-for=\"index in repeat\"\r\n :key=\"index\"\r\n :class=\"\r\n cn(\r\n 'flex shrink-0 justify-around [gap:var(--gap)]',\r\n vertical ? 'animate-marquee-vertical flex-col' : 'animate-marquee flex-row',\r\n pauseOnHover ? 'group-hover:[animation-play-state:paused]' : '',\r\n )\r\n \"\r\n :style=\"{\r\n animationDirection: reverse ? 'reverse' : 'normal',\r\n }\"\r\n >\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nwithDefaults(\r\n defineProps<{\r\n class?: string;\r\n reverse?: boolean;\r\n pauseOnHover?: boolean;\r\n vertical?: boolean;\r\n repeat?: number;\r\n }>(),\r\n {\r\n pauseOnHover: false,\r\n vertical: false,\r\n repeat: 4,\r\n },\r\n);\r\n</script>\r\n\r\n<style scoped>\r\n.animate-marquee {\r\n animation: marquee var(--duration) linear infinite;\r\n animation-direction: reverse;\r\n}\r\n\r\n.animate-marquee-vertical {\r\n animation: marquee-vertical var(--duration) linear infinite;\r\n}\r\n\r\n@keyframes marquee {\r\n from {\r\n transform: translateX(0);\r\n }\r\n to {\r\n transform: translateX(calc(-100% - var(--gap)));\r\n }\r\n}\r\n\r\n@keyframes marquee-vertical {\r\n from {\r\n transform: translateY(0);\r\n }\r\n to {\r\n transform: translateY(calc(-100% - var(--gap)));\r\n }\r\n}\r\n</style>\r\n"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "ReviewCard.vue",
|
|
15
|
+
"content": "<template>\r\n <figure\r\n class=\"relative w-64 cursor-pointer overflow-hidden rounded-xl border border-gray-950/[.1] bg-gray-950/[.01] p-4 hover:bg-gray-950/[.05] dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]\"\r\n >\r\n <div class=\"flex flex-row items-center gap-2\">\r\n <img\r\n :src=\"img\"\r\n class=\"rounded-full\"\r\n width=\"32\"\r\n height=\"32\"\r\n alt=\"\"\r\n />\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-sm font-medium dark:text-white\">\r\n {{ name }}\r\n </span>\r\n <p class=\"text-xs font-medium dark:text-white/40\">{{ username }}</p>\r\n </div>\r\n </div>\r\n <blockquote class=\"mt-2 text-sm\">{{ body }}</blockquote>\r\n </figure>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\ninterface Props {\r\n img: string;\r\n name: string;\r\n username: string;\r\n body: string;\r\n}\r\n\r\ndefineProps<Props>();\r\n</script>\r\n"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"fileCount": 3,
|
|
19
|
+
"contentHash": "2f060aebeadcaa344129b12ae2fd50dea8d27b11"
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "meteors",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as Meteors } from \"./Meteors.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "Meteors.vue",
|
|
11
|
+
"content": "<template>\r\n <span\r\n v-for=\"index in count\"\r\n :key=\"'meteor ' + index\"\r\n :class=\"\r\n cn(\r\n `animate-meteor-effect absolute h-0.5 w-0.5 rotate-[45deg] rounded-[9999px] bg-slate-500 shadow-[0_0_0_1px_#ffffff10]`,\r\n `before:absolute before:top-1/2 before:h-[1px] before:w-[50px] before:-translate-y-[50%] before:transform before:bg-gradient-to-r before:from-[#64748b] before:to-transparent before:content-['']`,\r\n $props.class,\r\n )\r\n \"\r\n :style=\"{\r\n top: 0,\r\n left: Math.floor(Math.random() * (400 - -400) + -400) + 'px',\r\n animationDelay: Math.random() * (0.8 - 0.2) + 0.2 + 's',\r\n animationDuration: Math.floor(Math.random() * (10 - 2) + 2) + 's',\r\n }\"\r\n ></span>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ndefineProps({\r\n count: {\r\n type: Number,\r\n default: 20,\r\n },\r\n class: String,\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n@keyframes meteor {\r\n 0% {\r\n transform: rotate(215deg) translateX(0);\r\n opacity: 1;\r\n }\r\n 70% {\r\n opacity: 1;\r\n }\r\n 100% {\r\n transform: rotate(215deg) translateX(-500px);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.animate-meteor {\r\n animation: meteor 5s linear infinite;\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "6405024c3753032c791a52602cd310583d45bbee"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "morphing-tabs",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as MorphingTabs } from \"./MorphingTabs.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "MorphingTabs.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n v-if=\"props.tabs.length\"\r\n :class=\"cn('relative', props.class)\"\r\n style=\"filter: url("#exclusionTabsGoo")\"\r\n >\r\n <button\r\n v-for=\"tab in props.tabs\"\r\n :key=\"tab\"\r\n :class=\"cn('px-4 py-2 bg-primary text-background transition-all duration-500')\"\r\n :style=\"{\r\n margin: `0 ${activeTab === tab ? props.margin : 0}px`,\r\n }\"\r\n @click=\"emit('update:activeTab', tab)\"\r\n >\r\n {{ tab }}\r\n </button>\r\n\r\n <div class=\"absolute w-full\">\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n version=\"1.1\"\r\n >\r\n <defs>\r\n <filter\r\n id=\"exclusionTabsGoo\"\r\n x=\"-50%\"\r\n y=\"-50%\"\r\n width=\"200%\"\r\n height=\"200%\"\r\n color-interpolation-filters=\"sRGB\"\r\n >\r\n <feGaussianBlur\r\n in=\"SourceGraphic\"\r\n :stdDeviation=\"blurStdDeviation\"\r\n result=\"blur\"\r\n ></feGaussianBlur>\r\n <feColorMatrix\r\n in=\"blur\"\r\n type=\"matrix\"\r\n values=\"\r\n 1 0 0 0 0 \r\n 0 1 0 0 0 \r\n 0 0 1 0 0 \r\n 0 0 0 36 -12\"\r\n result=\"goo\"\r\n ></feColorMatrix>\r\n <feComposite\r\n in=\"SourceGraphic\"\r\n in2=\"goo\"\r\n operator=\"atop\"\r\n ></feComposite>\r\n </filter>\r\n </defs>\r\n </svg>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n tabs: string[];\r\n activeTab: string;\r\n margin?: number;\r\n class?: string;\r\n blurStdDeviation?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n margin: 20,\r\n blurStdDeviation: 6,\r\n});\r\nconst emit = defineEmits<{\r\n (e: \"update:activeTab\", tab: string): void;\r\n}>();\r\n</script>\r\n\r\n<style></style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "43155d1ca7a7c256ca8a6cd1ad66dded7771e214"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "morphing-text",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as MorphingText } from \"./MorphingText.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "MorphingText.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'relative mx-auto h-16 w-full max-w-screen-md text-center font-sans text-[40pt] font-bold leading-none [filter:url(#threshold)_blur(0.6px)] md:h-24 lg:text-[6rem]',\r\n props.class,\r\n )\r\n \"\r\n >\r\n <span\r\n ref=\"text1Ref\"\r\n :class=\"cn(TEXT_CLASSES)\"\r\n />\r\n <span\r\n ref=\"text2Ref\"\r\n :class=\"cn(TEXT_CLASSES)\"\r\n />\r\n\r\n <svg\r\n id=\"filters\"\r\n class=\"fixed size-0\"\r\n preserveAspectRatio=\"xMidYMid slice\"\r\n >\r\n <defs>\r\n <filter id=\"threshold\">\r\n <feColorMatrix\r\n in=\"SourceGraphic\"\r\n type=\"matrix\"\r\n values=\"1 0 0 0 0\r\n 0 1 0 0 0\r\n 0 0 1 0 0\r\n 0 0 0 255 -140\"\r\n />\r\n </filter>\r\n </defs>\r\n </svg>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport { ref, onMounted, onUnmounted } from \"vue\";\r\n\r\nconst TEXT_CLASSES = \"absolute inset-x-0 top-0 m-auto inline-block w-full\";\r\n\r\ninterface Props {\r\n class?: string;\r\n texts: string[];\r\n morphTime?: number;\r\n coolDownTime?: number;\r\n}\r\nconst props = withDefaults(defineProps<Props>(), {\r\n morphTime: 1.5,\r\n coolDownTime: 0.5,\r\n});\r\n\r\nconst textIndex = ref(0);\r\nconst morph = ref(0);\r\nconst coolDown = ref(0);\r\nconst time = ref(new Date());\r\n\r\nconst text1Ref = ref<HTMLSpanElement>();\r\nconst text2Ref = ref<HTMLSpanElement>();\r\n\r\nfunction setStyles(fraction: number) {\r\n if (!text1Ref.value || !text2Ref.value) return;\r\n\r\n text2Ref.value.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;\r\n text2Ref.value.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;\r\n\r\n const invertedFraction = 1 - fraction;\r\n text1Ref.value.style.filter = `blur(${Math.min(8 / invertedFraction - 8, 100)}px)`;\r\n text1Ref.value.style.opacity = `${Math.pow(invertedFraction, 0.4) * 100}%`;\r\n\r\n text1Ref.value.textContent = props.texts[textIndex.value % props.texts.length];\r\n text2Ref.value.textContent = props.texts[(textIndex.value + 1) % props.texts.length];\r\n}\r\n\r\nfunction doMorph() {\r\n morph.value -= coolDown.value;\r\n coolDown.value = 0;\r\n\r\n let fraction = morph.value / props.morphTime;\r\n\r\n if (fraction > 1) {\r\n coolDown.value = props.coolDownTime;\r\n fraction = 1;\r\n }\r\n\r\n setStyles(fraction);\r\n\r\n if (fraction === 1) {\r\n textIndex.value++;\r\n }\r\n}\r\n\r\nfunction doCoolDown() {\r\n morph.value = 0;\r\n\r\n if (text1Ref.value && text2Ref.value) {\r\n text2Ref.value.style.filter = \"none\";\r\n text2Ref.value.style.opacity = \"100%\";\r\n text1Ref.value.style.filter = \"none\";\r\n text1Ref.value.style.opacity = \"0%\";\r\n }\r\n}\r\n\r\nlet animationFrameId: number = 0;\r\nfunction animate() {\r\n animationFrameId = requestAnimationFrame(animate);\r\n\r\n const newTime = new Date();\r\n const dt = (newTime.getTime() - time.value.getTime()) / 1000;\r\n time.value = newTime;\r\n\r\n coolDown.value -= dt;\r\n\r\n if (coolDown.value <= 0) {\r\n doMorph();\r\n } else {\r\n doCoolDown();\r\n }\r\n}\r\n\r\nonMounted(() => {\r\n animate();\r\n});\r\n\r\nonUnmounted(() => {\r\n cancelAnimationFrame(animationFrameId);\r\n});\r\n</script>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "4a11b913fed652c80e6aa69274bef0a3a2419d1f"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "multi-step-loader",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as MultiStepLoader } from \"./MultiStepLoader.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "MultiStepLoader.vue",
|
|
11
|
+
"content": "<template>\r\n <Transition\r\n enter-active-class=\"transition-opacity duration-300\"\r\n enter-from-class=\"opacity-0\"\r\n enter-to-class=\"opacity-100\"\r\n leave-active-class=\"transition-opacity duration-300\"\r\n leave-from-class=\"opacity-100\"\r\n leave-to-class=\"opacity-0\"\r\n >\r\n <div\r\n v-if=\"loading && steps.length > 0\"\r\n class=\"fixed inset-0 z-[100] flex size-full items-center justify-center backdrop-blur-2xl\"\r\n >\r\n <!-- Closing Button -->\r\n <button\r\n v-show=\"!preventClose\"\r\n class=\"absolute right-4 top-4 z-[101] inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\"\r\n size=\"sm\"\r\n @click=\"close\"\r\n >\r\n <!-- x-mark-heroicons -->\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke-width=\"1.5\"\r\n stroke=\"currentColor\"\r\n class=\"size-6\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M6 18 18 6M6 6l12 12\"\r\n />\r\n </svg>\r\n </button>\r\n <div class=\"relative h-96\">\r\n <div class=\"relative mx-auto mt-40 flex max-w-xl flex-col justify-start\">\r\n <div\r\n v-for=\"(step, index) in steps\"\r\n :key=\"index\"\r\n >\r\n <div\r\n v-if=\"step\"\r\n class=\"mb-4 flex items-center gap-2 text-left transition-all duration-300 ease-in-out\"\r\n :style=\"{\r\n opacity:\r\n index === currentState\r\n ? 1\r\n : Math.max(1 - Math.abs(index - currentState) * 0.2, 0),\r\n transform: `translateY(${\r\n index === currentState ? -(currentState * 40) : -(currentState * 40)\r\n }px)`,\r\n }\"\r\n >\r\n <!-- check-circle-solid-heroicons -->\r\n <svg\r\n v-if=\"\r\n index < currentState ||\r\n (index === steps.length - 1 && index === currentState && isLastStepComplete)\r\n \"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n class=\"size-6 text-primary\"\r\n >\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <!-- arrow-path-heroicons -->\r\n <svg\r\n v-else-if=\"\r\n index === currentState && (!isLastStepComplete || index !== steps.length - 1)\r\n \"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n class=\"size-6 animate-spin text-primary\"\r\n >\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M4.755 10.059a7.5 7.5 0 0 1 12.548-3.364l1.903 1.903h-3.183a.75.75 0 1 0 0 1.5h4.992a.75.75 0 0 0 .75-.75V4.356a.75.75 0 0 0-1.5 0v3.18l-1.9-1.9A9 9 0 0 0 3.306 9.67a.75.75 0 1 0 1.45.388Zm15.408 3.352a.75.75 0 0 0-.919.53 7.5 7.5 0 0 1-12.548 3.364l-1.902-1.903h3.183a.75.75 0 0 0 0-1.5H2.984a.75.75 0 0 0-.75.75v4.992a.75.75 0 0 0 1.5 0v-3.18l1.9 1.9a9 9 0 0 0 15.059-4.035.75.75 0 0 0-.53-.918Z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <!-- check-circle-outline-heroicons -->\r\n <svg\r\n v-else\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n fill=\"none\"\r\n viewBox=\"0 0 24 24\"\r\n stroke-width=\"1.5\"\r\n stroke=\"currentColor\"\r\n class=\"size-6 text-black opacity-50 dark:text-white\"\r\n >\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n d=\"M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z\"\r\n />\r\n </svg>\r\n <div class=\"flex flex-col\">\r\n <span\r\n :class=\"[\r\n 'text-lg text-black dark:text-white',\r\n index > currentState && 'opacity-50',\r\n ]\"\r\n >\r\n {{ step.text }}\r\n </span>\r\n <Transition\r\n enter-active-class=\"transition-all duration-300\"\r\n enter-from-class=\"opacity-0 -translate-y-1\"\r\n enter-to-class=\"opacity-100 translate-y-0\"\r\n >\r\n <span\r\n v-if=\"\r\n step.afterText &&\r\n (index < currentState ||\r\n (index === steps.length - 1 &&\r\n index === currentState &&\r\n isLastStepComplete))\r\n \"\r\n class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\"\r\n >\r\n {{ step.afterText }}\r\n </span>\r\n </Transition>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n class=\"absolute inset-x-0 bottom-0 z-[-1] h-full bg-white bg-gradient-to-t [mask-image:radial-gradient(900px_at_center,white_30%,transparent)] dark:bg-black\"\r\n ></div>\r\n </div>\r\n </Transition>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, watch, onUnmounted } from \"vue\";\r\n\r\ninterface Step {\r\n text: string; // Display text for the step\r\n afterText?: string; // Text to show after step completion\r\n async?: boolean; // If true, waits for external trigger to proceed\r\n duration?: number; // Duration in ms before proceeding (default: 2000)\r\n action?: () => void; // Function to execute when step is active\r\n}\r\ninterface Props {\r\n steps: Step[];\r\n loading?: boolean;\r\n defaultDuration?: number;\r\n preventClose?: boolean;\r\n}\r\nconst props = withDefaults(defineProps<Props>(), {\r\n loading: false,\r\n defaultDuration: 1500,\r\n preventClose: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n \"state-change\": [number];\r\n complete: [];\r\n close: [];\r\n}>();\r\n\r\nconst currentState = ref(0);\r\nconst stepStartTime = ref(Date.now());\r\nconst isLastStepComplete = ref(false);\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nlet currentTimer: any = null;\r\n\r\nasync function executeStepAction(step: Step) {\r\n if (typeof step.action === \"function\") {\r\n await step.action();\r\n }\r\n}\r\n\r\nasync function proceedToNextStep() {\r\n const currentStep = props.steps[currentState.value];\r\n if (!currentStep) return;\r\n\r\n // Execute the current step's action\r\n await executeStepAction(currentStep);\r\n\r\n if (currentState.value < props.steps.length - 1) {\r\n currentState.value++;\r\n stepStartTime.value = Date.now();\r\n emit(\"state-change\", currentState.value);\r\n processCurrentStep();\r\n } else {\r\n isLastStepComplete.value = true;\r\n emit(\"complete\");\r\n }\r\n}\r\n\r\nasync function processCurrentStep() {\r\n if (currentTimer) {\r\n clearTimeout(currentTimer);\r\n }\r\n\r\n const currentStep = props.steps[currentState.value];\r\n if (!currentStep) return;\r\n\r\n const duration = currentStep.duration || props.defaultDuration;\r\n\r\n if (!currentStep.async) {\r\n currentTimer = setTimeout(() => {\r\n proceedToNextStep();\r\n }, duration);\r\n }\r\n}\r\n\r\nfunction close() {\r\n emit(\"close\");\r\n}\r\n\r\n// Watch for changes in the async property\r\nwatch(\r\n () => props.steps[currentState.value]?.async,\r\n async (isAsync, oldIsAsync) => {\r\n // Only proceed if changing from async to non-async\r\n if (isAsync === false && oldIsAsync === true) {\r\n const currentStep = props.steps[currentState.value];\r\n if (!currentStep) return;\r\n\r\n const duration = currentStep.duration || props.defaultDuration;\r\n currentTimer = setTimeout(() => {\r\n proceedToNextStep();\r\n }, duration);\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.loading,\r\n (newLoading) => {\r\n if (newLoading) {\r\n currentState.value = 0;\r\n stepStartTime.value = Date.now();\r\n isLastStepComplete.value = false;\r\n processCurrentStep();\r\n } else if (currentTimer) {\r\n clearTimeout(currentTimer);\r\n }\r\n },\r\n);\r\n\r\nonUnmounted(() => {\r\n if (currentTimer) {\r\n clearTimeout(currentTimer);\r\n }\r\n});\r\n</script>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "d7d4cf72d24749df7f176f6acd0fe152f4ed3800"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neon-border",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as NeonBorder } from \"./NeonBorder.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "NeonBorder.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n 'relative inline-block h-10 w-full max-w-sm overflow-hidden rounded-lg p-px z-10',\r\n props.class,\r\n )\r\n \"\r\n :style=\"{\r\n '--neon-border-duration': durationInSeconds,\r\n }\"\r\n >\r\n <div\r\n :class=\"\r\n cn('neon-border-one rounded-lg', animationType != 'none' ? 'animate-neon-border' : '')\r\n \"\r\n ></div>\r\n <div\r\n :class=\"\r\n cn('neon-border-two rounded-lg', animationType != 'none' ? 'animate-neon-border' : '')\r\n \"\r\n ></div>\r\n <slot> </slot>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { HTMLAttributes } from \"vue\";\r\n\r\ninterface Props {\r\n color1?: string;\r\n color2?: string;\r\n animationType?: \"none\" | \"half\" | \"full\";\r\n duration?: number;\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n color1: \"#0496ff\",\r\n color2: \"#ff0a54\",\r\n duration: 6,\r\n animationType: \"half\",\r\n});\r\n\r\nconst durationInSeconds = computed(() => `${props.duration}s`);\r\nconst animWidth = computed(() => `${getWidth(props.animationType)}%`);\r\nconst colorType1 = computed(() => props.color1);\r\nconst colorType2 = computed(() => props.color2);\r\n\r\nfunction getWidth(animationType: \"none\" | \"half\" | \"full\") {\r\n switch (animationType) {\r\n case \"none\":\r\n return 12;\r\n case \"half\":\r\n return 50;\r\n case \"full\":\r\n return 100;\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.neon-border-container {\r\n position: relative;\r\n display: inline-block;\r\n padding: 2rem;\r\n}\r\n\r\n.neon-border-one {\r\n position: absolute;\r\n overflow: hidden;\r\n width: 100%;\r\n height: 100%;\r\n filter: blur(1px) drop-shadow(0 0 12px v-bind(colorType1));\r\n z-index: -1;\r\n inset: 0;\r\n}\r\n\r\n.neon-border-one::before {\r\n content: \"\";\r\n position: absolute;\r\n overflow: hidden;\r\n top: 0;\r\n left: 0;\r\n width: v-bind(animWidth);\r\n height: 100%;\r\n background: linear-gradient(\r\n 135deg,\r\n v-bind(colorType1),\r\n v-bind(colorType1),\r\n transparent,\r\n transparent\r\n );\r\n}\r\n\r\n.neon-border-two {\r\n position: absolute;\r\n overflow: hidden;\r\n width: 100%;\r\n height: 100%;\r\n filter: blur(1px) drop-shadow(0 0 12px v-bind(colorType2));\r\n z-index: -1;\r\n inset: 0;\r\n}\r\n.neon-border-two::before {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0%;\r\n right: 0%;\r\n overflow: hidden;\r\n width: v-bind(animWidth);\r\n height: 100%;\r\n background: linear-gradient(\r\n 135deg,\r\n transparent,\r\n transparent,\r\n v-bind(colorType2),\r\n v-bind(colorType2)\r\n );\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "6df301eefef2410eac03a73a42919afb4c3249b3"
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "number-ticker",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"@vueuse/core"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as NumberTicker } from \"./NumberTicker.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "NumberTicker.vue",
|
|
13
|
+
"content": "<template>\r\n <span\r\n ref=\"spanRef\"\r\n :class=\"cn('inline-block tabular-nums text-black dark:text-white tracking-wider', props.class)\"\r\n >\r\n {{ output }}\r\n </span>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { TransitionPresets, useElementVisibility, useTransition } from \"@vueuse/core\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { ref, watch, computed } from \"vue\";\r\n\r\ntype TransitionsPresetsKeys = keyof typeof TransitionPresets;\r\n\r\ninterface NumberTickerProps {\r\n value?: number;\r\n direction?: \"up\" | \"down\";\r\n duration?: number;\r\n delay?: number;\r\n decimalPlaces?: number;\r\n class?: string;\r\n transition?: TransitionsPresetsKeys;\r\n}\r\n\r\nconst spanRef = ref<HTMLSpanElement>();\r\n\r\nconst props = withDefaults(defineProps<NumberTickerProps>(), {\r\n value: 0,\r\n direction: \"up\",\r\n delay: 0,\r\n duration: 1000,\r\n decimalPlaces: 2,\r\n transition: \"easeOutCubic\",\r\n});\r\n\r\nconst transitionValue = ref(props.direction === \"down\" ? props.value : 0);\r\n\r\nconst transitionOutput = useTransition(transitionValue, {\r\n delay: props.delay,\r\n duration: props.duration,\r\n transition: TransitionPresets[props.transition],\r\n});\r\n\r\nconst output = computed(() => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: props.decimalPlaces,\r\n maximumFractionDigits: props.decimalPlaces,\r\n }).format(Number(transitionOutput.value.toFixed(props.decimalPlaces)));\r\n});\r\n\r\nconst isInView = useElementVisibility(spanRef, {\r\n threshold: 0,\r\n});\r\n\r\nconst hasBeenInView = ref(false);\r\n\r\nconst stopIsInViewWatcher = watch(\r\n isInView,\r\n (isVisible) => {\r\n if (isVisible && !hasBeenInView.value) {\r\n hasBeenInView.value = true;\r\n transitionValue.value = props.direction === \"down\" ? 0 : props.value;\r\n stopIsInViewWatcher();\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.value,\r\n (newVal) => {\r\n if (hasBeenInView.value) {\r\n transitionValue.value = props.direction === \"down\" ? 0 : newVal;\r\n }\r\n },\r\n);\r\n</script>\r\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"fileCount": 2,
|
|
17
|
+
"contentHash": "5067dfb34d73e6f1457cea0e5ea2f3f3d0ad3fb2"
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "orbit",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "type ObjectValues<T> = T[keyof T];\r\n\r\nexport const ORBIT_DIRECTION = {\r\n Clockwise: \"normal\",\r\n CounterClockwise: \"reverse\",\r\n} as const;\r\n\r\nexport type OrbitDirection = ObjectValues<typeof ORBIT_DIRECTION>;\r\n\r\nexport interface Props {\r\n class?: string;\r\n direction?: OrbitDirection;\r\n duration?: number;\r\n delay?: number;\r\n radius?: number;\r\n path?: boolean;\r\n}\r\n\r\nexport { default as Orbit } from \"./Orbit.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "Orbit.vue",
|
|
11
|
+
"content": "<template>\r\n <svg\r\n v-if=\"path\"\r\n class=\"pointer-events-none absolute inset-0 size-full\"\r\n >\r\n <circle\r\n class=\"stroke-foreground/20 stroke-1\"\r\n cx=\"50%\"\r\n cy=\"50%\"\r\n :r=\"radius\"\r\n fill=\"none\"\r\n />\r\n </svg>\r\n <div :class=\"cn('absolute flex size-full transform-gpu animate-orbit', props.class)\">\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport { ORBIT_DIRECTION, type Props } from \".\";\r\nimport { computed } from \"vue\";\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n direction: () => ORBIT_DIRECTION.Clockwise,\r\n duration: 20,\r\n delay: 10,\r\n radius: 50,\r\n path: false,\r\n});\r\n\r\nconst negativeDelay = computed(() => -props.delay);\r\n</script>\r\n\r\n<style scoped>\r\n@keyframes orbit {\r\n 0% {\r\n transform: rotate(0deg) translateY(calc(v-bind(radius) * 1px)) rotate(0deg);\r\n }\r\n 100% {\r\n transform: rotate(360deg) translateY(calc(v-bind(radius) * 1px)) rotate(-360deg);\r\n }\r\n}\r\n\r\n.animate-orbit {\r\n animation: orbit calc(v-bind(duration) * 1s) linear infinite;\r\n animation-delay: calc(v-bind(negativeDelay) * 1s);\r\n animation-direction: v-bind(direction);\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "34f2079ac111418862aadfc3c6aa5964f558264f"
|
|
16
|
+
}
|