mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
jsrepo v3
This commit is contained in:
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://unpkg.com/jsrepo@1.30.1/schemas/registry-config.json",
|
|
||||||
"meta": {
|
|
||||||
"authors": ["David Haz"],
|
|
||||||
"description": "An open source collection of animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.",
|
|
||||||
"bugs": "https://github.com/DavidHDev/vue-bits/issues",
|
|
||||||
"homepage": "https://vue-bits.dev",
|
|
||||||
"repository": "https://github.com/DavidHDev/vue-bits",
|
|
||||||
"tags": [
|
|
||||||
"vue",
|
|
||||||
"javascript",
|
|
||||||
"components",
|
|
||||||
"web",
|
|
||||||
"vuejs",
|
|
||||||
"css-animations",
|
|
||||||
"component-library",
|
|
||||||
"ui-components",
|
|
||||||
"3d",
|
|
||||||
"ui-library",
|
|
||||||
"tailwind",
|
|
||||||
"tailwindcss",
|
|
||||||
"components",
|
|
||||||
"components-library"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dirs": [],
|
|
||||||
"doNotListBlocks": [],
|
|
||||||
"doNotListCategories": [],
|
|
||||||
"listBlocks": [],
|
|
||||||
"listCategories": [],
|
|
||||||
"excludeDeps": ["vue"],
|
|
||||||
"includeBlocks": [],
|
|
||||||
"includeCategories": [],
|
|
||||||
"excludeBlocks": [],
|
|
||||||
"excludeCategories": [],
|
|
||||||
"preview": true
|
|
||||||
}
|
|
||||||
75
jsrepo.config.ts
Normal file
75
jsrepo.config.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { defineConfig, type RegistryItem } from 'jsrepo';
|
||||||
|
import { distributed } from 'jsrepo/outputs';
|
||||||
|
import { componentMetadata } from './src/constants/Information';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
registry: {
|
||||||
|
name: '@vue-bits',
|
||||||
|
description:
|
||||||
|
'An open source collection of animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.',
|
||||||
|
homepage: 'https://vue-bits.dev',
|
||||||
|
authors: ['David Haz'],
|
||||||
|
bugs: 'https://github.com/DavidHDev/vue-bits/issues',
|
||||||
|
repository: 'https://github.com/DavidHDev/vue-bits',
|
||||||
|
tags: [
|
||||||
|
'vue',
|
||||||
|
'javascript',
|
||||||
|
'components',
|
||||||
|
'web',
|
||||||
|
'vuejs',
|
||||||
|
'css-animations',
|
||||||
|
'component-library',
|
||||||
|
'ui-components',
|
||||||
|
'3d',
|
||||||
|
'ui-library',
|
||||||
|
'tailwind',
|
||||||
|
'tailwindcss',
|
||||||
|
'components',
|
||||||
|
'components-library'
|
||||||
|
],
|
||||||
|
excludeDeps: ['vue'],
|
||||||
|
outputs: [distributed({ dir: 'public/r' })],
|
||||||
|
items: [
|
||||||
|
...Object.values(componentMetadata).map(component =>
|
||||||
|
defineComponent({
|
||||||
|
title: component.name,
|
||||||
|
description: component.description,
|
||||||
|
category: component.category
|
||||||
|
})
|
||||||
|
)
|
||||||
|
].flat()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a component to be exposed from the registry. Creates a single variant of the component.
|
||||||
|
*
|
||||||
|
* @param title The title of the component.
|
||||||
|
* @param description The description of the component.
|
||||||
|
* @param category The category of the component.
|
||||||
|
* @returns An array with a single RegistryItem object.
|
||||||
|
*/
|
||||||
|
function defineComponent({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
category
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
category: string;
|
||||||
|
}): RegistryItem[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: title,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
type: 'registry:component',
|
||||||
|
categories: [category],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
path: `src/content/${category}/${title}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
2303
package-lock.json
generated
2303
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "npm run build:registry && run-p type-check \"build-only {@}\" --",
|
"build": "npm run build:registry && run-p type-check \"build-only {@}\" --",
|
||||||
"build:registry": "jsrepo build --dirs ./src/content --output-dir ./public/ui",
|
"build:registry": "jsrepo build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-plugin-vue": "~10.2.0",
|
"eslint-plugin-vue": "~10.2.0",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"jsrepo": "^1.30.1",
|
"jsrepo": "^3.0.8",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
|||||||
1
public/r/ASCIIText.json
Normal file
1
public/r/ASCIIText.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/AnimatedContent.json
Normal file
1
public/r/AnimatedContent.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"AnimatedContent","title":"AnimatedContent","description":"Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n className?: string;\n}\n\nconst props = withDefaults(defineProps<AnimatedContentProps>(), {\n distance: 100,\n direction: 'vertical',\n reverse: false,\n duration: 0.8,\n ease: 'power3.out',\n initialOpacity: 0,\n animateOpacity: true,\n scale: 1,\n threshold: 0.1,\n delay: 0,\n className: ''\n});\n\nconst emit = defineEmits<{\n complete: [];\n}>();\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n\nonMounted(() => {\n const el = containerRef.value;\n if (!el) return;\n\n const axis = props.direction === 'horizontal' ? 'x' : 'y';\n const offset = props.reverse ? -props.distance : props.distance;\n const startPct = (1 - props.threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale: props.scale,\n opacity: props.animateOpacity ? props.initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration: props.duration,\n ease: props.ease,\n delay: props.delay,\n onComplete: () => emit('complete'),\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n});\n\nwatch(\n () => [\n props.distance,\n props.direction,\n props.reverse,\n props.duration,\n props.ease,\n props.initialOpacity,\n props.animateOpacity,\n props.scale,\n props.threshold,\n props.delay\n ],\n () => {\n const el = containerRef.value;\n if (!el) return;\n\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n\n const axis = props.direction === 'horizontal' ? 'x' : 'y';\n const offset = props.reverse ? -props.distance : props.distance;\n const startPct = (1 - props.threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale: props.scale,\n opacity: props.animateOpacity ? props.initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration: props.duration,\n ease: props.ease,\n delay: props.delay,\n onComplete: () => emit('complete'),\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n },\n { deep: true }\n);\n\nonUnmounted(() => {\n const el = containerRef.value;\n if (el) {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n }\n});\n</script>\n\n<template>\n <div ref=\"containerRef\" :class=\"`animated-content ${props.className}`\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n/* GSAP will handle all transforms and opacity */\n</style>\n","path":"AnimatedContent/AnimatedContent.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/AnimatedList.json
Normal file
1
public/r/AnimatedList.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Aurora.json
Normal file
1
public/r/Aurora.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Balatro.json
Normal file
1
public/r/Balatro.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Ballpit.json
Normal file
1
public/r/Ballpit.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Beams.json
Normal file
1
public/r/Beams.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/BlobCursor.json
Normal file
1
public/r/BlobCursor.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"BlobCursor","title":"BlobCursor","description":"Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport gsap from 'gsap';\nimport { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';\n\ninterface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nconst props = withDefaults(defineProps<BlobCursorProps>(), {\n blobType: 'circle',\n fillColor: '#27FF64',\n trailCount: 3,\n sizes: () => [60, 125, 75],\n innerSizes: () => [20, 35, 25],\n innerColor: 'rgba(255,255,255,0.8)',\n opacities: () => [0.6, 0.6, 0.6],\n shadowColor: 'rgba(0,0,0,0.75)',\n shadowBlur: 5,\n shadowOffsetX: 10,\n shadowOffsetY: 10,\n filterId: 'blob',\n filterStdDeviation: 30,\n filterColorMatrixValues: '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter: true,\n fastDuration: 0.1,\n slowDuration: 0.5,\n fastEase: 'power3.out',\n slowEase: 'power1.out',\n zIndex: 100\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst blobsRef = ref<(HTMLElement | null)[]>([]);\n\nconst updateOffset = () => {\n if (!containerRef.value) return { left: 0, top: 0 };\n const rect = containerRef.value.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n};\n\nconst handleMove = (e: MouseEvent | TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.value.forEach((el, i) => {\n if (!el) return;\n\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? props.fastDuration : props.slowDuration,\n ease: isLead ? props.fastEase : props.slowEase\n });\n });\n};\n\nonMounted(() => {\n if (!updateOffset) return;\n window.addEventListener('resize', updateOffset);\n});\n\nonUnmounted(() => {\n window.removeEventListener('resize', updateOffset);\n});\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n @mousemove=\"handleMove\"\n @touchmove=\"handleMove\"\n class=\"top-0 left-0 relative w-full h-full\"\n :style=\"{ zIndex: props.zIndex }\"\n >\n <svg v-if=\"props.useFilter\" class=\"absolute w-0 h-0\">\n <filter :id=\"props.filterId\">\n <feGaussianBlur in=\"SourceGraphic\" result=\"blur\" :stdDeviation=\"props.filterStdDeviation\" />\n <feColorMatrix in=\"blur\" :values=\"props.filterColorMatrixValues\" />\n </filter>\n </svg>\n\n <div\n class=\"absolute inset-0 overflow-hidden cursor-default pointer-events-none select-none\"\n :style=\"{\n filter: props.useFilter ? `url(#${props.filterId})` : undefined\n }\"\n >\n <div\n v-for=\"(_, i) in props.trailCount\"\n :key=\"i\"\n :ref=\"\n el => {\n blobsRef[i] = el as HTMLElement | null;\n }\n \"\n class=\"absolute -translate-x-1/2 -translate-y-1/2 will-change-transform transform\"\n :style=\"{\n width: `${props.sizes[i]}px`,\n height: `${props.sizes[i]}px`,\n borderRadius: props.blobType === 'circle' ? '50%' : '0',\n backgroundColor: props.fillColor,\n opacity: props.opacities[i],\n boxShadow: `${props.shadowOffsetX}px ${props.shadowOffsetY}px ${props.shadowBlur}px 0 ${props.shadowColor}`\n }\"\n >\n <div\n class=\"absolute\"\n :style=\"{\n width: `${props.innerSizes[i]}px`,\n height: `${props.innerSizes[i]}px`,\n top: `${(props.sizes[i] - props.innerSizes[i]) / 2}px`,\n left: `${(props.sizes[i] - props.innerSizes[i]) / 2}px`,\n backgroundColor: props.innerColor,\n borderRadius: props.blobType === 'circle' ? '50%' : '0'\n }\"\n />\n </div>\n </div>\n </div>\n</template>\n","path":"BlobCursor/BlobCursor.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/BlurText.json
Normal file
1
public/r/BlurText.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/BounceCards.json
Normal file
1
public/r/BounceCards.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/BubbleMenu.json
Normal file
1
public/r/BubbleMenu.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/CardNav.json
Normal file
1
public/r/CardNav.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/CardSwap.json
Normal file
1
public/r/CardSwap.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Carousel.json
Normal file
1
public/r/Carousel.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ChromaGrid.json
Normal file
1
public/r/ChromaGrid.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/CircularGallery.json
Normal file
1
public/r/CircularGallery.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/CircularText.json
Normal file
1
public/r/CircularText.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"CircularText","title":"CircularText","description":"Layouts characters around a circle with optional rotation animation.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { computed, ref, watchEffect, onUnmounted } from 'vue';\nimport { Motion } from 'motion-v';\n\ninterface CircularTextProps {\n text: string;\n spinDuration?: number;\n onHover?: 'slowDown' | 'speedUp' | 'pause' | 'goBonkers';\n className?: string;\n}\n\nconst props = withDefaults(defineProps<CircularTextProps>(), {\n text: '',\n spinDuration: 20,\n onHover: 'speedUp',\n className: ''\n});\n\nconst letters = computed(() => Array.from(props.text));\nconst isHovered = ref(false);\n\nconst currentRotation = ref(0);\nconst animationId = ref<number | null>(null);\nconst lastTime = ref<number>(Date.now());\nconst rotationSpeed = ref<number>(0);\n\nconst getCurrentSpeed = () => {\n if (isHovered.value && props.onHover === 'pause') return 0;\n\n const baseDuration = props.spinDuration;\n const baseSpeed = 360 / baseDuration;\n\n if (!isHovered.value) return baseSpeed;\n\n switch (props.onHover) {\n case 'slowDown':\n return baseSpeed / 2;\n case 'speedUp':\n return baseSpeed * 4;\n case 'goBonkers':\n return baseSpeed * 20;\n default:\n return baseSpeed;\n }\n};\n\nconst getCurrentScale = () => {\n return isHovered.value && props.onHover === 'goBonkers' ? 0.8 : 1;\n};\n\nconst animate = () => {\n const now = Date.now();\n const deltaTime = (now - lastTime.value) / 1000;\n lastTime.value = now;\n\n const targetSpeed = getCurrentSpeed();\n\n const speedDiff = targetSpeed - rotationSpeed.value;\n const smoothingFactor = Math.min(1, deltaTime * 5);\n rotationSpeed.value += speedDiff * smoothingFactor;\n\n currentRotation.value = (currentRotation.value + rotationSpeed.value * deltaTime) % 360;\n\n animationId.value = requestAnimationFrame(animate);\n};\n\nconst startAnimation = () => {\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n }\n lastTime.value = Date.now();\n rotationSpeed.value = getCurrentSpeed();\n animate();\n};\n\nwatchEffect(() => {\n startAnimation();\n});\n\nstartAnimation();\n\nonUnmounted(() => {\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n }\n});\n\nconst handleHoverStart = () => {\n isHovered.value = true;\n};\n\nconst handleHoverEnd = () => {\n isHovered.value = false;\n};\n\nconst getLetterTransform = (index: number) => {\n const rotationDeg = (360 / letters.value.length) * index;\n const factor = Math.PI / letters.value.length;\n const x = factor * index;\n const y = factor * index;\n return `rotateZ(${rotationDeg}deg) translate3d(${x}px, ${y}px, 0)`;\n};\n</script>\n\n<template>\n <Motion\n :animate=\"{\n rotate: currentRotation,\n scale: getCurrentScale()\n }\"\n :transition=\"{\n rotate: {\n duration: 0\n },\n scale: {\n type: 'spring',\n damping: 20,\n stiffness: 300\n }\n }\"\n :class=\"`m-0 mx-auto rounded-full w-[200px] h-[200px] relative font-black text-white text-center cursor-pointer origin-center ${props.className}`\"\n @mouseenter=\"handleHoverStart\"\n @mouseleave=\"handleHoverEnd\"\n >\n <span\n v-for=\"(letter, i) in letters\"\n :key=\"i\"\n class=\"absolute inline-block inset-0 text-2xl transition-all duration-500 ease-[cubic-bezier(0,0,0,1)]\"\n :style=\"{\n transform: getLetterTransform(i),\n WebkitTransform: getLetterTransform(i)\n }\"\n >\n {{ letter }}\n </span>\n </Motion>\n</template>\n","path":"CircularText/CircularText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion-v","version":"^1.5.0"}],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/ClickSpark.json
Normal file
1
public/r/ClickSpark.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"ClickSpark","title":"ClickSpark","description":"Creates particle spark bursts at click position.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div ref=\"containerRef\" class=\"relative w-full h-full\" @click=\"handleClick\">\n <canvas ref=\"canvasRef\" class=\"absolute inset-0 pointer-events-none\" />\n\n <slot />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, computed, watch, useTemplateRef } from 'vue';\n\ninterface Spark {\n x: number;\n y: number;\n angle: number;\n startTime: number;\n}\n\ninterface Props {\n sparkColor?: string;\n sparkSize?: number;\n sparkRadius?: number;\n sparkCount?: number;\n duration?: number;\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n extraScale?: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n sparkColor: '#fff',\n sparkSize: 10,\n sparkRadius: 15,\n sparkCount: 8,\n duration: 400,\n easing: 'ease-out',\n extraScale: 1.0\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\nconst sparks = ref<Spark[]>([]);\nconst startTimeRef = ref<number | null>(null);\nconst animationId = ref<number | null>(null);\n\nconst easeFunc = computed(() => {\n return (t: number) => {\n switch (props.easing) {\n case 'linear':\n return t;\n case 'ease-in':\n return t * t;\n case 'ease-in-out':\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n default:\n return t * (2 - t);\n }\n };\n});\n\nconst handleClick = (e: MouseEvent) => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n const rect = canvas.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const now = performance.now();\n const newSparks: Spark[] = Array.from({ length: props.sparkCount }, (_, i) => ({\n x,\n y,\n angle: (2 * Math.PI * i) / props.sparkCount,\n startTime: now\n }));\n\n sparks.value.push(...newSparks);\n};\n\nconst draw = (timestamp: number) => {\n if (!startTimeRef.value) {\n startTimeRef.value = timestamp;\n }\n\n const canvas = canvasRef.value;\n const ctx = canvas?.getContext('2d');\n if (!ctx || !canvas) return;\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n sparks.value = sparks.value.filter((spark: Spark) => {\n const elapsed = timestamp - spark.startTime;\n if (elapsed >= props.duration) {\n return false;\n }\n\n const progress = elapsed / props.duration;\n const eased = easeFunc.value(progress);\n\n const distance = eased * props.sparkRadius * props.extraScale;\n const lineLength = props.sparkSize * (1 - eased);\n\n const x1 = spark.x + distance * Math.cos(spark.angle);\n const y1 = spark.y + distance * Math.sin(spark.angle);\n const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);\n const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);\n\n ctx.strokeStyle = props.sparkColor;\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n\n return true;\n });\n\n animationId.value = requestAnimationFrame(draw);\n};\n\nconst resizeCanvas = () => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n const parent = canvas.parentElement;\n if (!parent) return;\n\n const { width, height } = parent.getBoundingClientRect();\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width;\n canvas.height = height;\n }\n};\n\nlet resizeTimeout: number;\n\nconst handleResize = () => {\n clearTimeout(resizeTimeout);\n resizeTimeout = setTimeout(resizeCanvas, 100);\n};\n\nlet resizeObserver: ResizeObserver | null = null;\n\nonMounted(() => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n const parent = canvas.parentElement;\n if (!parent) return;\n\n resizeObserver = new ResizeObserver(handleResize);\n resizeObserver.observe(parent);\n\n resizeCanvas();\n\n animationId.value = requestAnimationFrame(draw);\n});\n\nonUnmounted(() => {\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n clearTimeout(resizeTimeout);\n\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n }\n});\n\nwatch(\n [\n () => props.sparkColor,\n () => props.sparkSize,\n () => props.sparkRadius,\n () => props.sparkCount,\n () => props.duration,\n easeFunc,\n () => props.extraScale\n ],\n () => {\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n }\n animationId.value = requestAnimationFrame(draw);\n }\n);\n</script>\n","path":"ClickSpark/ClickSpark.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/ColorBends.json
Normal file
1
public/r/ColorBends.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/CountUp.json
Normal file
1
public/r/CountUp.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"CountUp","title":"CountUp","description":"Animated number counter supporting formatting and decimals.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <span ref=\"elementRef\" :class=\"className\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, computed, useTemplateRef } from 'vue';\n\ninterface Props {\n to: number;\n from?: number;\n direction?: 'up' | 'down';\n delay?: number;\n duration?: number;\n className?: string;\n startWhen?: boolean;\n separator?: string;\n onStart?: () => void;\n onEnd?: () => void;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n from: 0,\n direction: 'up',\n delay: 0,\n duration: 2,\n className: '',\n startWhen: true,\n separator: ''\n});\n\nconst elementRef = useTemplateRef<HTMLSpanElement>('elementRef');\nconst currentValue = ref(props.direction === 'down' ? props.to : props.from);\nconst isInView = ref(false);\nconst animationId = ref<number | null>(null);\nconst hasStarted = ref(false);\n\nlet intersectionObserver: IntersectionObserver | null = null;\n\nconst damping = computed(() => 20 + 40 * (1 / props.duration));\nconst stiffness = computed(() => 100 * (1 / props.duration));\n\nlet velocity = 0;\nlet startTime = 0;\n\nconst formatNumber = (value: number) => {\n const options = {\n useGrouping: !!props.separator,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n };\n\n const formattedNumber = Intl.NumberFormat('en-US', options).format(Number(value.toFixed(0)));\n\n return props.separator ? formattedNumber.replace(/,/g, props.separator) : formattedNumber;\n};\n\nconst updateDisplay = () => {\n if (elementRef.value) {\n elementRef.value.textContent = formatNumber(currentValue.value);\n }\n};\n\nconst springAnimation = (timestamp: number) => {\n if (!startTime) startTime = timestamp;\n\n const target = props.direction === 'down' ? props.from : props.to;\n const current = currentValue.value;\n\n const displacement = target - current;\n const springForce = displacement * stiffness.value;\n const dampingForce = velocity * damping.value;\n const acceleration = springForce - dampingForce;\n\n velocity += acceleration * 0.016; // Assuming 60fps\n currentValue.value += velocity * 0.016;\n\n updateDisplay();\n\n if (Math.abs(displacement) > 0.01 || Math.abs(velocity) > 0.01) {\n animationId.value = requestAnimationFrame(springAnimation);\n } else {\n currentValue.value = target;\n updateDisplay();\n animationId.value = null;\n\n if (props.onEnd) {\n props.onEnd();\n }\n }\n};\n\nconst startAnimation = () => {\n if (hasStarted.value || !isInView.value || !props.startWhen) return;\n\n hasStarted.value = true;\n\n if (props.onStart) {\n props.onStart();\n }\n\n setTimeout(() => {\n startTime = 0;\n velocity = 0;\n animationId.value = requestAnimationFrame(springAnimation);\n }, props.delay * 1000);\n};\n\nconst setupIntersectionObserver = () => {\n if (!elementRef.value) return;\n\n intersectionObserver = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && !isInView.value) {\n isInView.value = true;\n startAnimation();\n }\n },\n {\n threshold: 0,\n rootMargin: '0px'\n }\n );\n\n intersectionObserver.observe(elementRef.value);\n};\n\nconst cleanup = () => {\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n animationId.value = null;\n }\n\n if (intersectionObserver) {\n intersectionObserver.disconnect();\n intersectionObserver = null;\n }\n};\n\nwatch(\n [() => props.from, () => props.to, () => props.direction],\n () => {\n currentValue.value = props.direction === 'down' ? props.to : props.from;\n updateDisplay();\n hasStarted.value = false;\n },\n { immediate: true }\n);\n\nwatch(\n () => props.startWhen,\n () => {\n if (props.startWhen && isInView.value && !hasStarted.value) {\n startAnimation();\n }\n }\n);\n\nonMounted(() => {\n updateDisplay();\n setupIntersectionObserver();\n});\n\nonUnmounted(() => {\n cleanup();\n});\n</script>\n","path":"CountUp/CountUp.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/Counter.json
Normal file
1
public/r/Counter.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"Counter","title":"Counter","description":"Flexible animated counter supporting increments + easing.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div class=\"relative inline-block\" :style=\"containerStyle\">\n <div class=\"flex overflow-hidden\" :style=\"counterStyles\">\n <div v-for=\"place in places\" :key=\"place\" class=\"relative w-[1ch] tabular-nums\" :style=\"digitStyles\">\n <Motion\n v-for=\"digit in 10\"\n :key=\"digit - 1\"\n tag=\"span\"\n class=\"absolute top-0 left-0 w-full h-full flex items-center justify-center\"\n :animate=\"{ y: getDigitPosition(place, digit - 1) }\"\n >\n {{ digit - 1 }}\n </Motion>\n </div>\n </div>\n <div class=\"pointer-events-none absolute inset-0\">\n <div class=\"absolute top-0 w-full\" :style=\"topGradientStyle ?? topGradientStyles\" />\n <div class=\"absolute bottom-0 w-full\" :style=\"bottomGradientStyle ?? bottomGradientStyles\" />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue';\nimport { Motion } from 'motion-v';\nimport type { CSSProperties } from 'vue';\n\ninterface CounterProps {\n value: number;\n fontSize?: number;\n padding?: number;\n places?: number[];\n gap?: number;\n borderRadius?: number;\n horizontalPadding?: number;\n textColor?: string;\n fontWeight?: string | number;\n containerStyle?: CSSProperties;\n counterStyle?: CSSProperties;\n digitStyle?: CSSProperties;\n gradientHeight?: number;\n gradientFrom?: string;\n gradientTo?: string;\n topGradientStyle?: CSSProperties;\n bottomGradientStyle?: CSSProperties;\n}\n\nconst props = withDefaults(defineProps<CounterProps>(), {\n fontSize: 100,\n padding: 0,\n places: () => [100, 10, 1],\n gap: 8,\n borderRadius: 4,\n horizontalPadding: 8,\n textColor: 'white',\n fontWeight: 'bold',\n containerStyle: () => ({}),\n counterStyle: () => ({}),\n digitStyle: () => ({}),\n gradientHeight: 16,\n gradientFrom: 'black',\n gradientTo: 'transparent',\n topGradientStyle: undefined,\n bottomGradientStyle: undefined\n});\n\nconst digitHeight = computed(() => props.fontSize + props.padding);\n\nconst counterStyles = computed(() => ({\n fontSize: `${props.fontSize}px`,\n gap: `${props.gap}px`,\n borderRadius: `${props.borderRadius}px`,\n paddingLeft: `${props.horizontalPadding}px`,\n paddingRight: `${props.horizontalPadding}px`,\n color: props.textColor,\n fontWeight: props.fontWeight,\n ...props.counterStyle\n}));\n\nconst digitStyles = computed(() => ({\n height: `${digitHeight.value}px`,\n ...props.digitStyle\n}));\n\nconst topGradientStyles = computed(\n (): CSSProperties => ({\n height: `${props.gradientHeight}px`,\n background: `linear-gradient(to bottom, ${props.gradientFrom}, ${props.gradientTo})`\n })\n);\n\nconst bottomGradientStyles = computed(\n (): CSSProperties => ({\n height: `${props.gradientHeight}px`,\n background: `linear-gradient(to top, ${props.gradientFrom}, ${props.gradientTo})`\n })\n);\n\nconst springValues = ref<Record<number, number>>({});\n\nconst initializeSpringValues = () => {\n props.places.forEach(place => {\n springValues.value[place] = Math.floor(props.value / place);\n });\n};\n\ninitializeSpringValues();\n\nwatch(\n () => props.value,\n (newValue, oldValue) => {\n if (newValue === oldValue) return;\n\n props.places.forEach(place => {\n const newRoundedValue = Math.floor(newValue / place);\n const oldRoundedValue = springValues.value[place];\n\n if (newRoundedValue !== oldRoundedValue) {\n springValues.value[place] = newRoundedValue;\n }\n });\n },\n { immediate: true }\n);\n\nwatch(\n () => digitHeight.value,\n () => {\n positionCache.clear();\n }\n);\n\nconst positionCache = new Map<string, number>();\n\nconst getDigitPosition = (place: number, digit: number): number => {\n const springValue = springValues.value[place] || 0;\n const cacheKey = `${place}-${digit}-${springValue}`;\n\n if (positionCache.has(cacheKey)) {\n return positionCache.get(cacheKey)!;\n }\n\n const placeValue = springValue % 10;\n const offset = (10 + digit - placeValue) % 10;\n let position = offset * digitHeight.value;\n\n if (offset > 5) {\n position -= 10 * digitHeight.value;\n }\n\n if (positionCache.size > 200) {\n const firstKey = positionCache.keys().next().value;\n if (typeof firstKey === 'string') {\n positionCache.delete(firstKey);\n }\n }\n\n positionCache.set(cacheKey, position);\n return position;\n};\n</script>\n","path":"Counter/Counter.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"motion-v","version":"^1.5.0"}],"devDependencies":[],"categories":["Components"]}
|
||||||
1
public/r/Crosshair.json
Normal file
1
public/r/Crosshair.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Cubes.json
Normal file
1
public/r/Cubes.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/CurvedLoop.json
Normal file
1
public/r/CurvedLoop.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/DarkVeil.json
Normal file
1
public/r/DarkVeil.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/DecayCard.json
Normal file
1
public/r/DecayCard.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/DecryptedText.json
Normal file
1
public/r/DecryptedText.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Dither.json
Normal file
1
public/r/Dither.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Dock.json
Normal file
1
public/r/Dock.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/DomeGallery.json
Normal file
1
public/r/DomeGallery.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/DotGrid.json
Normal file
1
public/r/DotGrid.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ElasticSlider.json
Normal file
1
public/r/ElasticSlider.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ElectricBorder.json
Normal file
1
public/r/ElectricBorder.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/FadeContent.json
Normal file
1
public/r/FadeContent.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"FadeContent","title":"FadeContent","description":"Simple directional fade / slide entrance wrapper with threshold-based activation.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div\n ref=\"elementRef\"\n :class=\"className\"\n :style=\"{\n opacity: inView ? 1 : initialOpacity,\n transition: `opacity ${duration}ms ${easing}, filter ${duration}ms ${easing}`,\n filter: blur ? (inView ? 'blur(0px)' : 'blur(10px)') : 'none'\n }\"\n >\n <slot />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, useTemplateRef } from 'vue';\n\ninterface Props {\n blur?: boolean;\n duration?: number;\n easing?: string;\n delay?: number;\n threshold?: number;\n initialOpacity?: number;\n className?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n blur: false,\n duration: 1000,\n easing: 'ease-out',\n delay: 0,\n threshold: 0.1,\n initialOpacity: 0,\n className: ''\n});\n\nconst inView = ref(false);\nconst elementRef = useTemplateRef<HTMLDivElement>('elementRef');\nlet observer: IntersectionObserver | null = null;\n\nonMounted(() => {\n const element = elementRef.value;\n if (!element) return;\n\n observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n observer?.unobserve(element);\n setTimeout(() => {\n inView.value = true;\n }, props.delay);\n }\n },\n { threshold: props.threshold }\n );\n\n observer.observe(element);\n});\n\nonUnmounted(() => {\n if (observer) {\n observer.disconnect();\n }\n});\n</script>\n","path":"FadeContent/FadeContent.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/FallingText.json
Normal file
1
public/r/FallingText.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/FaultyTerminal.json
Normal file
1
public/r/FaultyTerminal.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/FloatingLines.json
Normal file
1
public/r/FloatingLines.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/FlowingMenu.json
Normal file
1
public/r/FlowingMenu.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"FlowingMenu","title":"FlowingMenu","description":"Liquid flowing active indicator glides between menu items.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div class=\"w-full h-full overflow-hidden\">\n <nav class=\"flex flex-col h-full m-0 p-0\">\n <div\n v-for=\"(item, idx) in items\"\n :key=\"idx\"\n class=\"flex-1 relative overflow-hidden text-center shadow-[0_-1px_0_0_#fff]\"\n :ref=\"el => setItemRef(el as HTMLDivElement, idx)\"\n >\n <a\n class=\"flex items-center justify-center h-full relative cursor-pointer uppercase no-underline font-semibold text-white text-[4vh] hover:text-[#0b0b0b] focus:text-white focus-visible:text-[#0b0b0b]\"\n :href=\"item.link\"\n @mouseenter=\"ev => handleMouseEnter(ev, idx)\"\n @mouseleave=\"ev => handleMouseLeave(ev, idx)\"\n >\n {{ item.text }}\n </a>\n\n <div\n class=\"absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none bg-white translate-y-[101%]\"\n :ref=\"el => (marqueeRefs[idx] = el as HTMLDivElement)\"\n >\n <div class=\"h-full w-[200%] flex\" :ref=\"el => (marqueeInnerRefs[idx] = el as HTMLDivElement)\">\n <div class=\"flex items-center relative h-full w-[200%] will-change-transform animate-marquee\">\n <template v-for=\"i in 4\" :key=\"`${idx}-${i}`\">\n <span class=\"text-[#0b0b0b] uppercase font-normal text-[4vh] leading-[1.2] p-[1vh_1vw_0]\">\n {{ item.text }}\n </span>\n\n <div\n class=\"w-[200px] h-[7vh] my-[2em] mx-[2vw] p-[1em_0] rounded-[50px] bg-cover bg-center\"\n :style=\"{ backgroundImage: `url(${item.image})` }\"\n />\n </template>\n </div>\n </div>\n </div>\n </div>\n </nav>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { gsap } from 'gsap';\n\ninterface MenuItemProps {\n link: string;\n text: string;\n image: string;\n}\n\ninterface Props {\n items?: MenuItemProps[];\n}\n\nwithDefaults(defineProps<Props>(), {\n items: () => []\n});\n\nconst itemRefs = ref<(HTMLDivElement | null)[]>([]);\nconst marqueeRefs = ref<(HTMLDivElement | null)[]>([]);\nconst marqueeInnerRefs = ref<(HTMLDivElement | null)[]>([]);\n\nconst animationDefaults = { duration: 0.6, ease: 'expo' };\n\nconst setItemRef = (el: HTMLDivElement | null, idx: number) => {\n if (el) {\n itemRefs.value[idx] = el;\n }\n};\n\nconst findClosestEdge = (mouseX: number, mouseY: number, width: number, height: number): 'top' | 'bottom' => {\n const topEdgeDist = Math.pow(mouseX - width / 2, 2) + Math.pow(mouseY, 2);\n const bottomEdgeDist = Math.pow(mouseX - width / 2, 2) + Math.pow(mouseY - height, 2);\n return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom';\n};\n\nconst handleMouseEnter = (ev: MouseEvent, idx: number) => {\n const itemRef = itemRefs.value[idx];\n const marqueeRef = marqueeRefs.value[idx];\n const marqueeInnerRef = marqueeInnerRefs.value[idx];\n\n if (!itemRef || !marqueeRef || !marqueeInnerRef) return;\n\n const rect = itemRef.getBoundingClientRect();\n const edge = findClosestEdge(ev.clientX - rect.left, ev.clientY - rect.top, rect.width, rect.height);\n\n const tl = gsap.timeline({ defaults: animationDefaults });\n tl.set(marqueeRef, { y: edge === 'top' ? '-101%' : '101%' })\n .set(marqueeInnerRef, { y: edge === 'top' ? '101%' : '-101%' })\n .to([marqueeRef, marqueeInnerRef], { y: '0%' });\n};\n\nconst handleMouseLeave = (ev: MouseEvent, idx: number) => {\n const itemRef = itemRefs.value[idx];\n const marqueeRef = marqueeRefs.value[idx];\n const marqueeInnerRef = marqueeInnerRefs.value[idx];\n\n if (!itemRef || !marqueeRef || !marqueeInnerRef) return;\n\n const rect = itemRef.getBoundingClientRect();\n const edge = findClosestEdge(ev.clientX - rect.left, ev.clientY - rect.top, rect.width, rect.height);\n\n const tl = gsap.timeline({ defaults: animationDefaults });\n tl.to(marqueeRef, { y: edge === 'top' ? '-101%' : '101%' }).to(marqueeInnerRef, {\n y: edge === 'top' ? '101%' : '-101%'\n });\n};\n</script>\n\n<style scoped>\n@keyframes marquee {\n from {\n transform: translateX(0%);\n }\n\n to {\n transform: translateX(-50%);\n }\n}\n\n.animate-marquee {\n animation: marquee 15s linear infinite;\n}\n</style>\n","path":"FlowingMenu/FlowingMenu.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Components"]}
|
||||||
1
public/r/FlyingPosters.json
Normal file
1
public/r/FlyingPosters.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Folder.json
Normal file
1
public/r/Folder.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/FuzzyText.json
Normal file
1
public/r/FuzzyText.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Galaxy.json
Normal file
1
public/r/Galaxy.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GhostCursor.json
Normal file
1
public/r/GhostCursor.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GlareHover.json
Normal file
1
public/r/GlareHover.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"GlareHover","title":"GlareHover","description":"Adds a realistic moving glare highlight on hover over any element.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { computed, useTemplateRef } from 'vue';\n\ninterface GlareHoverProps {\n width?: string;\n height?: string;\n background?: string;\n borderRadius?: string;\n borderColor?: string;\n glareColor?: string;\n glareOpacity?: number;\n glareAngle?: number;\n glareSize?: number;\n transitionDuration?: number;\n playOnce?: boolean;\n className?: string;\n style?: Record<string, string | number>;\n}\n\nconst props = withDefaults(defineProps<GlareHoverProps>(), {\n width: '500px',\n height: '500px',\n background: '#000',\n borderRadius: '10px',\n borderColor: '#333',\n glareColor: '#ffffff',\n glareOpacity: 0.5,\n glareAngle: -45,\n glareSize: 250,\n transitionDuration: 650,\n playOnce: false,\n className: '',\n style: () => ({})\n});\n\nconst overlayRef = useTemplateRef<HTMLDivElement>('overlayRef');\n\nconst rgba = computed(() => {\n const hex = props.glareColor.replace('#', '');\n let result = props.glareColor;\n\n if (/^[\\dA-Fa-f]{6}$/.test(hex)) {\n const r = parseInt(hex.slice(0, 2), 16);\n const g = parseInt(hex.slice(2, 4), 16);\n const b = parseInt(hex.slice(4, 6), 16);\n result = `rgba(${r}, ${g}, ${b}, ${props.glareOpacity})`;\n } else if (/^[\\dA-Fa-f]{3}$/.test(hex)) {\n const r = parseInt(hex[0] + hex[0], 16);\n const g = parseInt(hex[1] + hex[1], 16);\n const b = parseInt(hex[2] + hex[2], 16);\n result = `rgba(${r}, ${g}, ${b}, ${props.glareOpacity})`;\n }\n\n return result;\n});\n\nconst overlayStyle = computed(() => ({\n position: 'absolute' as const,\n inset: '0',\n background: `linear-gradient(${props.glareAngle}deg,\n hsla(0,0%,0%,0) 60%,\n ${rgba.value} 70%,\n hsla(0,0%,0%,0) 100%)`,\n backgroundSize: `${props.glareSize}% ${props.glareSize}%, 100% 100%`,\n backgroundRepeat: 'no-repeat',\n backgroundPosition: '-100% -100%, 0 0',\n pointerEvents: 'none' as const\n}));\n\nconst animateIn = () => {\n const el = overlayRef.value;\n if (!el) return;\n\n el.style.transition = 'none';\n el.style.backgroundPosition = '-100% -100%, 0 0';\n void el.offsetHeight;\n el.style.transition = `${props.transitionDuration}ms ease`;\n el.style.backgroundPosition = '100% 100%, 0 0';\n};\n\nconst animateOut = () => {\n const el = overlayRef.value;\n if (!el) return;\n\n if (props.playOnce) {\n el.style.transition = 'none';\n el.style.backgroundPosition = '-100% -100%, 0 0';\n } else {\n el.style.transition = `${props.transitionDuration}ms ease`;\n el.style.backgroundPosition = '-100% -100%, 0 0';\n }\n};\n</script>\n\n<template>\n <div\n :class=\"`relative grid place-items-center overflow-hidden border cursor-pointer ${props.className}`\"\n :style=\"{\n width: props.width,\n height: props.height,\n background: props.background,\n borderRadius: props.borderRadius,\n borderColor: props.borderColor,\n ...props.style\n }\"\n @mouseenter=\"animateIn\"\n @mouseleave=\"animateOut\"\n >\n <div ref=\"overlayRef\" :style=\"overlayStyle\" />\n\n <slot />\n </div>\n</template>\n","path":"GlareHover/GlareHover.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/GlassIcons.json
Normal file
1
public/r/GlassIcons.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"GlassIcons","title":"GlassIcons","description":"Icon set styled with frosted glass blur.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div :class=\"['grid gap-[5em] grid-cols-2 md:grid-cols-3 mx-auto py-[3em] overflow-visible', className]\">\n <button\n v-for=\"(item, index) in items\"\n :key=\"index\"\n type=\"button\"\n :aria-label=\"item.label\"\n :class=\"[\n 'relative bg-transparent outline-none w-[4.5em] h-[4.5em] [perspective:24em] [transform-style:preserve-3d] [-webkit-tap-highlight-color:transparent] group',\n item.customClass\n ]\"\n >\n <span\n class=\"absolute top-0 left-0 w-full h-full rounded-[1.25em] block transition-[opacity,transform] duration-300 ease-[cubic-bezier(0.83,0,0.17,1)] origin-[100%_100%] rotate-[15deg] group-hover:[transform:rotate(25deg)_translate3d(-0.5em,-0.5em,0.5em)]\"\n :style=\"{\n ...getBackgroundStyle(item.color),\n boxShadow: '0.5em -0.5em 0.75em hsla(223, 10%, 10%, 0.15)'\n }\"\n ></span>\n\n <span\n class=\"absolute top-0 left-0 w-full h-full rounded-[1.25em] bg-[hsla(0,0%,100%,0.15)] transition-[opacity,transform] duration-300 ease-[cubic-bezier(0.83,0,0.17,1)] origin-[80%_50%] flex backdrop-blur-[0.75em] [-webkit-backdrop-filter:blur(0.75em)] transform group-hover:[transform:translateZ(2em)]\"\n :style=\"{\n boxShadow: '0 0 0 0.1em hsla(0, 0%, 100%, 0.3) inset'\n }\"\n >\n <span class=\"m-auto w-[1.5em] h-[1.5em] flex items-center justify-center\" aria-hidden=\"true\">\n <i :class=\"item.icon\" class=\"text-xl\"></i>\n </span>\n </span>\n\n <span\n class=\"absolute top-full left-0 right-0 text-center whitespace-nowrap leading-[2] text-base opacity-0 transition-[opacity,transform] duration-300 ease-[cubic-bezier(0.83,0,0.17,1)] translate-y-0 group-hover:opacity-100 group-hover:[transform:translateY(20%)]\"\n >\n {{ item.label }}\n </span>\n </button>\n </div>\n</template>\n\n<script setup lang=\"ts\">\ninterface GlassIconsItem {\n icon: string;\n color: string;\n label: string;\n customClass?: string;\n}\n\ninterface Props {\n items: GlassIconsItem[];\n className?: string;\n}\n\nwithDefaults(defineProps<Props>(), {\n items: () => [],\n className: ''\n});\n\nconst gradientMapping: Record<string, string> = {\n blue: 'linear-gradient(hsl(223, 90%, 50%), hsl(208, 90%, 50%))',\n purple: 'linear-gradient(hsl(283, 90%, 50%), hsl(268, 90%, 50%))',\n red: 'linear-gradient(hsl(3, 90%, 50%), hsl(348, 90%, 50%))',\n indigo: 'linear-gradient(hsl(253, 90%, 50%), hsl(238, 90%, 50%))',\n orange: 'linear-gradient(hsl(43, 90%, 50%), hsl(28, 90%, 50%))',\n green: 'linear-gradient(hsl(123, 90%, 40%), hsl(108, 90%, 40%))'\n};\n\nconst getBackgroundStyle = (color: string): Record<string, string> => {\n if (gradientMapping[color]) {\n return { background: gradientMapping[color] };\n }\n return { background: color };\n};\n</script>\n","path":"GlassIcons/GlassIcons.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Components"]}
|
||||||
1
public/r/GlassSurface.json
Normal file
1
public/r/GlassSurface.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GlitchText.json
Normal file
1
public/r/GlitchText.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"GlitchText","title":"GlitchText","description":"RGB split and distortion glitch effect with jitter effects.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div :class=\"computedClasses\" :style=\"inlineStyles\" :data-text=\"children\">\n {{ children }}\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport type { CSSProperties } from 'vue';\n\ninterface GlitchTextProps {\n children: string;\n speed?: number;\n enableShadows?: boolean;\n enableOnHover?: boolean;\n className?: string;\n}\n\ninterface CustomCSSProperties extends CSSProperties {\n '--after-duration': string;\n '--before-duration': string;\n '--after-shadow': string;\n '--before-shadow': string;\n}\n\nconst props = withDefaults(defineProps<GlitchTextProps>(), {\n speed: 0.5,\n enableShadows: true,\n enableOnHover: false,\n className: ''\n});\n\nconst inlineStyles = computed(\n (): CustomCSSProperties => ({\n '--after-duration': `${props.speed * 3}s`,\n '--before-duration': `${props.speed * 2}s`,\n '--after-shadow': props.enableShadows ? '-5px 0 red' : 'none',\n '--before-shadow': props.enableShadows ? '5px 0 cyan' : 'none'\n })\n);\n\nconst baseClasses = [\n 'text-white',\n 'font-black',\n 'whitespace-nowrap',\n 'relative',\n 'mx-auto',\n 'select-none',\n 'cursor-pointer',\n 'text-[clamp(2rem,10vw,8rem)]',\n\n 'before:content-[attr(data-text)]',\n 'before:absolute',\n 'before:top-0',\n 'before:text-white',\n 'before:bg-[#0b0b0b]',\n 'before:overflow-hidden',\n 'before:[clip-path:inset(0_0_0_0)]',\n\n 'after:content-[attr(data-text)]',\n 'after:absolute',\n 'after:top-0',\n 'after:text-white',\n 'after:bg-[#0b0b0b]',\n 'after:overflow-hidden',\n 'after:[clip-path:inset(0_0_0_0)]'\n];\n\nconst normalGlitchClasses = [\n 'after:left-[10px]',\n 'after:[text-shadow:var(--after-shadow,-10px_0_red)]',\n 'after:[animation:animate-glitch_var(--after-duration,3s)_infinite_linear_alternate-reverse]',\n\n 'before:left-[-10px]',\n 'before:[text-shadow:var(--before-shadow,10px_0_cyan)]',\n 'before:[animation:animate-glitch_var(--before-duration,2s)_infinite_linear_alternate-reverse]'\n];\n\nconst hoverOnlyClasses = [\n 'before:content-[\"\"]',\n 'before:opacity-0',\n 'before:[animation:none]',\n 'after:content-[\"\"]',\n 'after:opacity-0',\n 'after:[animation:none]',\n\n 'hover:before:content-[attr(data-text)]',\n 'hover:before:opacity-100',\n 'hover:before:left-[-10px]',\n 'hover:before:[text-shadow:var(--before-shadow,10px_0_cyan)]',\n 'hover:before:[animation:animate-glitch_var(--before-duration,2s)_infinite_linear_alternate-reverse]',\n\n 'hover:after:content-[attr(data-text)]',\n 'hover:after:opacity-100',\n 'hover:after:left-[10px]',\n 'hover:after:[text-shadow:var(--after-shadow,-10px_0_red)]',\n 'hover:after:[animation:animate-glitch_var(--after-duration,3s)_infinite_linear_alternate-reverse]'\n];\n\nconst computedClasses = computed(() => {\n const classes = [...baseClasses];\n\n if (props.enableOnHover) {\n classes.push(...hoverOnlyClasses);\n } else {\n classes.push(...normalGlitchClasses);\n }\n\n if (props.className) {\n classes.push(props.className);\n }\n\n return classes.join(' ');\n});\n</script>\n\n<style>\n@keyframes animate-glitch {\n 0% {\n clip-path: inset(20% 0 50% 0);\n }\n 5% {\n clip-path: inset(10% 0 60% 0);\n }\n 10% {\n clip-path: inset(15% 0 55% 0);\n }\n 15% {\n clip-path: inset(25% 0 35% 0);\n }\n 20% {\n clip-path: inset(30% 0 40% 0);\n }\n 25% {\n clip-path: inset(40% 0 20% 0);\n }\n 30% {\n clip-path: inset(10% 0 60% 0);\n }\n 35% {\n clip-path: inset(15% 0 55% 0);\n }\n 40% {\n clip-path: inset(25% 0 35% 0);\n }\n 45% {\n clip-path: inset(30% 0 40% 0);\n }\n 50% {\n clip-path: inset(20% 0 50% 0);\n }\n 55% {\n clip-path: inset(10% 0 60% 0);\n }\n 60% {\n clip-path: inset(15% 0 55% 0);\n }\n 65% {\n clip-path: inset(25% 0 35% 0);\n }\n 70% {\n clip-path: inset(30% 0 40% 0);\n }\n 75% {\n clip-path: inset(40% 0 20% 0);\n }\n 80% {\n clip-path: inset(20% 0 50% 0);\n }\n 85% {\n clip-path: inset(10% 0 60% 0);\n }\n 90% {\n clip-path: inset(15% 0 55% 0);\n }\n 95% {\n clip-path: inset(25% 0 35% 0);\n }\n 100% {\n clip-path: inset(30% 0 40% 0);\n }\n}\n</style>\n","path":"GlitchText/GlitchText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/GooeyNav.json
Normal file
1
public/r/GooeyNav.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GradientBlinds.json
Normal file
1
public/r/GradientBlinds.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GradientText.json
Normal file
1
public/r/GradientText.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"GradientText","title":"GradientText","description":"Animated gradient sweep across live text with speed and color control.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\ninterface GradientTextProps {\n text: string;\n className?: string;\n colors?: string[];\n animationSpeed?: number;\n showBorder?: boolean;\n}\n\nconst props = withDefaults(defineProps<GradientTextProps>(), {\n text: '',\n className: '',\n colors: () => ['#ffaa40', '#9c40ff', '#ffaa40'],\n animationSpeed: 8,\n showBorder: false\n});\n\nconst gradientStyle = computed(() => ({\n backgroundImage: `linear-gradient(to right, ${props.colors.join(', ')})`,\n animationDuration: `${props.animationSpeed}s`,\n backgroundSize: '300% 100%',\n '--animation-duration': `${props.animationSpeed}s`\n}));\n\nconst borderStyle = computed(() => ({\n ...gradientStyle.value\n}));\n\nconst textStyle = computed(() => ({\n ...gradientStyle.value,\n backgroundClip: 'text',\n WebkitBackgroundClip: 'text'\n}));\n</script>\n\n<template>\n <div\n :class=\"`relative mx-auto flex max-w-fit flex-row items-center justify-center rounded-[1.25rem] font-medium backdrop-blur transition-shadow duration-500 overflow-hidden cursor-pointer ${className}`\"\n >\n <div\n v-if=\"showBorder\"\n class=\"absolute inset-0 bg-cover z-0 pointer-events-none animate-gradient\"\n :style=\"borderStyle\"\n >\n <div\n class=\"absolute inset-0 bg-black rounded-[1.25rem] z-[-1]\"\n style=\"width: calc(100% - 2px); height: calc(100% - 2px); left: 50%; top: 50%; transform: translate(-50%, -50%)\"\n />\n </div>\n\n <div class=\"inline-block relative z-2 text-transparent bg-cover animate-gradient\" :style=\"textStyle\">\n {{ text }}\n </div>\n </div>\n</template>\n\n<style scoped>\n@keyframes gradient {\n 0% {\n background-position: 0% 50%;\n }\n 50% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0% 50%;\n }\n}\n\n.animate-gradient {\n animation: gradient var(--animation-duration, 8s) linear infinite;\n}\n</style>\n","path":"GradientText/GradientText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/GradualBlur.json
Normal file
1
public/r/GradualBlur.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GridDistortion.json
Normal file
1
public/r/GridDistortion.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/GridMotion.json
Normal file
1
public/r/GridMotion.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"GridMotion","title":"GridMotion","description":"Perspective moving grid lines based on cusror position.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, computed, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\n\ninterface GridMotionProps {\n items?: string[];\n gradientColor?: string;\n}\n\nconst props = withDefaults(defineProps<GridMotionProps>(), {\n items: () => [],\n gradientColor: '#222222'\n});\n\nconst gridRef = useTemplateRef<HTMLElement>('gridRef');\nconst rowRefs = ref<HTMLElement[]>([]);\nconst mouseX = ref(window.innerWidth / 2);\n\nconst totalItems = 150;\nconst combinedItems = computed(() => {\n if (props.items.length === 0) {\n return [];\n }\n\n const repeatedItems = [];\n for (let i = 0; i < totalItems; i++) {\n repeatedItems.push(props.items[i % props.items.length]);\n }\n return repeatedItems;\n});\n\nfunction isImage(item: string) {\n return typeof item === 'string' && item.startsWith('http');\n}\n\nfunction isTag(item: string) {\n return typeof item === 'string' && item.startsWith('<') && item.endsWith('>');\n}\n\nonMounted(() => {\n gsap.ticker.lagSmoothing(0);\n\n const handleMouseMove = (e: MouseEvent) => {\n mouseX.value = e.clientX;\n };\n\n const updateMotion = () => {\n const maxMoveAmount = 300;\n const baseDuration = 0.8;\n const inertiaFactors = [0.6, 0.4, 0.3, 0.2];\n\n rowRefs.value.forEach((row, index) => {\n const direction = index % 2 === 0 ? 1 : -1;\n const moveAmount = ((mouseX.value / window.innerWidth) * maxMoveAmount - maxMoveAmount / 2) * direction;\n\n gsap.to(row, {\n x: moveAmount,\n duration: baseDuration + inertiaFactors[index % inertiaFactors.length],\n ease: 'power3.out',\n overwrite: 'auto'\n });\n });\n };\n\n const removeAnimation = gsap.ticker.add(updateMotion);\n window.addEventListener('mousemove', handleMouseMove);\n\n onBeforeUnmount(() => {\n window.removeEventListener('mousemove', handleMouseMove);\n removeAnimation();\n });\n});\n</script>\n\n<template>\n <div ref=\"gridRef\" class=\"w-full h-full overflow-hidden\">\n <section\n class=\"relative flex justify-center items-center w-full h-screen overflow-hidden\"\n :style=\"{\n background: `radial-gradient(circle, ${gradientColor} 0%, transparent 100%)`,\n backgroundPosition: 'center'\n }\"\n >\n <div class=\"z-[4] absolute inset-0 bg-[length:250px] pointer-events-none\"></div>\n\n <div class=\"z-[2] relative flex-none gap-4 grid grid-cols-1 w-[150vw] h-[150vh] rotate-[-15deg] origin-center\">\n <div\n v-for=\"rowIndex in 10\"\n :key=\"rowIndex\"\n class=\"gap-4 flex\"\n :style=\"{ willChange: 'transform, filter' }\"\n ref=\"rowRefs\"\n >\n <div\n v-for=\"itemIndex in 15\"\n :key=\"itemIndex\"\n class=\"relative h-[250px] min-w-[300px] flex-shrink-0\"\n v-show=\"combinedItems[(rowIndex - 1) * 15 + (itemIndex - 1)]\"\n >\n <div\n class=\"relative flex justify-center items-center bg-[#111] rounded-[10px] w-full h-full overflow-hidden text-[1.5rem] text-white\"\n >\n <div\n v-if=\"isImage(combinedItems[(rowIndex - 1) * 15 + (itemIndex - 1)])\"\n class=\"top-0 left-0 absolute bg-cover bg-center w-full h-full\"\n :style=\"{\n backgroundImage: `url(${combinedItems[(rowIndex - 1) * 15 + (itemIndex - 1)]})`\n }\"\n ></div>\n\n <div\n v-else-if=\"isTag(combinedItems[(rowIndex - 1) * 15 + (itemIndex - 1)])\"\n class=\"z-[2] p-4 text-center\"\n v-html=\"combinedItems[(rowIndex - 1) * 15 + (itemIndex - 1)]\"\n ></div>\n\n <div v-else class=\"z-[1] p-4 text-center\">\n {{ combinedItems[(rowIndex - 1) * 15 + (itemIndex - 1)] }}\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"top-0 left-0 relative w-full h-full pointer-events-none\"></div>\n </section>\n </div>\n</template>\n","path":"GridMotion/GridMotion.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Backgrounds"]}
|
||||||
1
public/r/Hyperspeed.json
Normal file
1
public/r/Hyperspeed.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ImageTrail.json
Normal file
1
public/r/ImageTrail.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/InfiniteMenu.json
Normal file
1
public/r/InfiniteMenu.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/InfiniteScroll.json
Normal file
1
public/r/InfiniteScroll.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Iridescence.json
Normal file
1
public/r/Iridescence.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LaserFlow.json
Normal file
1
public/r/LaserFlow.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LetterGlitch.json
Normal file
1
public/r/LetterGlitch.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LightPillar.json
Normal file
1
public/r/LightPillar.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LightRays.json
Normal file
1
public/r/LightRays.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Lightning.json
Normal file
1
public/r/Lightning.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LiquidChrome.json
Normal file
1
public/r/LiquidChrome.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LiquidEther.json
Normal file
1
public/r/LiquidEther.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/LogoLoop.json
Normal file
1
public/r/LogoLoop.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/MagicBento.json
Normal file
1
public/r/MagicBento.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Magnet.json
Normal file
1
public/r/Magnet.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"Magnet","title":"Magnet","description":"Elements magnetically ease toward the cursor then settle back with spring physics.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div\n ref=\"magnetRef\"\n :class=\"wrapperClassName\"\n :style=\"{ position: 'relative', display: 'inline-block' }\"\n v-bind=\"$attrs\"\n >\n <div\n :class=\"innerClassName\"\n :style=\"{\n transform: `translate3d(${position.x}px, ${position.y}px, 0)`,\n transition: transitionStyle,\n willChange: 'transform'\n }\"\n >\n <slot />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\n\ninterface Props {\n padding?: number;\n disabled?: boolean;\n magnetStrength?: number;\n activeTransition?: string;\n inactiveTransition?: string;\n wrapperClassName?: string;\n innerClassName?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n padding: 100,\n disabled: false,\n magnetStrength: 2,\n activeTransition: 'transform 0.3s ease-out',\n inactiveTransition: 'transform 0.5s ease-in-out',\n wrapperClassName: '',\n innerClassName: ''\n});\n\ndefineOptions({\n inheritAttrs: false\n});\n\nconst magnetRef = useTemplateRef<HTMLDivElement>('magnetRef');\nconst isActive = ref(false);\nconst position = ref({ x: 0, y: 0 });\n\nconst transitionStyle = computed(() => (isActive.value ? props.activeTransition : props.inactiveTransition));\n\nconst handleMouseMove = (e: MouseEvent) => {\n if (!magnetRef.value || props.disabled) return;\n\n const { left, top, width, height } = magnetRef.value.getBoundingClientRect();\n const centerX = left + width / 2;\n const centerY = top + height / 2;\n\n const distX = Math.abs(centerX - e.clientX);\n const distY = Math.abs(centerY - e.clientY);\n\n if (distX < width / 2 + props.padding && distY < height / 2 + props.padding) {\n isActive.value = true;\n const offsetX = (e.clientX - centerX) / props.magnetStrength;\n const offsetY = (e.clientY - centerY) / props.magnetStrength;\n position.value = { x: offsetX, y: offsetY };\n } else {\n isActive.value = false;\n position.value = { x: 0, y: 0 };\n }\n};\n\nonMounted(() => {\n window.addEventListener('mousemove', handleMouseMove);\n});\n\nonUnmounted(() => {\n window.removeEventListener('mousemove', handleMouseMove);\n});\n\nwatch(\n () => props.disabled,\n newDisabled => {\n if (newDisabled) {\n position.value = { x: 0, y: 0 };\n isActive.value = false;\n }\n }\n);\n</script>\n","path":"Magnet/Magnet.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/MagnetLines.json
Normal file
1
public/r/MagnetLines.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"MagnetLines","title":"MagnetLines","description":"Animated field lines bend toward the cursor.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, computed, useTemplateRef } from 'vue';\n\ninterface MagnetLinesProps {\n rows?: number;\n columns?: number;\n containerSize?: string;\n lineColor?: string;\n lineWidth?: string;\n lineHeight?: string;\n baseAngle?: number;\n className?: string;\n style?: Record<string, string | number>;\n}\n\nconst props = withDefaults(defineProps<MagnetLinesProps>(), {\n rows: 9,\n columns: 9,\n containerSize: '80vmin',\n lineColor: '#efefef',\n lineWidth: '1vmin',\n lineHeight: '6vmin',\n baseAngle: -10,\n className: '',\n style: () => ({})\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n\nconst total = computed(() => props.rows * props.columns);\n\nconst onPointerMove = (pointer: { x: number; y: number }) => {\n const container = containerRef.value;\n if (!container) return;\n\n const items = container.querySelectorAll<HTMLSpanElement>('span');\n\n items.forEach(item => {\n const rect = item.getBoundingClientRect();\n const centerX = rect.x + rect.width / 2;\n const centerY = rect.y + rect.height / 2;\n\n const b = pointer.x - centerX;\n const a = pointer.y - centerY;\n const c = Math.sqrt(a * a + b * b) || 1;\n const r = ((Math.acos(b / c) * 180) / Math.PI) * (pointer.y > centerY ? 1 : -1);\n\n item.style.setProperty('--rotate', `${r}deg`);\n });\n};\n\nconst handlePointerMove = (e: PointerEvent) => {\n onPointerMove({ x: e.x, y: e.y });\n};\n\nonMounted(() => {\n const container = containerRef.value;\n if (!container) return;\n\n window.addEventListener('pointermove', handlePointerMove);\n\n const items = container.querySelectorAll<HTMLSpanElement>('span');\n if (items.length) {\n const middleIndex = Math.floor(items.length / 2);\n const rect = items[middleIndex].getBoundingClientRect();\n onPointerMove({ x: rect.x, y: rect.y });\n }\n});\n\nonUnmounted(() => {\n window.removeEventListener('pointermove', handlePointerMove);\n});\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n :class=\"`grid place-items-center ${props.className}`\"\n :style=\"{\n gridTemplateColumns: `repeat(${props.columns}, 1fr)`,\n gridTemplateRows: `repeat(${props.rows}, 1fr)`,\n width: props.containerSize,\n height: props.containerSize,\n ...props.style\n }\"\n >\n <span\n v-for=\"i in total\"\n :key=\"i\"\n class=\"block origin-center\"\n :style=\"{\n backgroundColor: props.lineColor,\n width: props.lineWidth,\n height: props.lineHeight,\n '--rotate': `${props.baseAngle}deg`,\n transform: 'rotate(var(--rotate))',\n willChange: 'transform'\n }\"\n />\n </div>\n</template>\n","path":"MagnetLines/MagnetLines.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/Masonry.json
Normal file
1
public/r/Masonry.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/MetaBalls.json
Normal file
1
public/r/MetaBalls.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/MetallicPaint.json
Normal file
1
public/r/MetallicPaint.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Noise.json
Normal file
1
public/r/Noise.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"Noise","title":"Noise","description":"Animated film grain / noise overlay adding subtle texture and motion.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <canvas\n ref=\"grainRef\"\n class=\"pointer-events-none absolute top-0 left-0 h-screen w-screen\"\n :style=\"`image-rendering: pixelated; mix-blend-mode: ${props.mixBlendMode}`\"\n ></canvas>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, useTemplateRef } from 'vue';\n\ninterface NoiseProps {\n patternRefreshInterval?: number;\n patternAlpha?: number;\n mixBlendMode?: string;\n}\n\nconst props = withDefaults(defineProps<NoiseProps>(), {\n patternRefreshInterval: 2,\n patternAlpha: 10,\n mixBlendMode: 'normal'\n});\n\nconst grainRef = useTemplateRef<HTMLCanvasElement>('grainRef');\n\nlet animationId = 0;\nlet frame = 0;\nconst canvasSize = 1024;\n\nconst resize = () => {\n const canvas = grainRef.value;\n if (!canvas) return;\n canvas.width = canvasSize;\n canvas.height = canvasSize;\n canvas.style.width = '100vw';\n canvas.style.height = '100vh';\n};\n\nlet noiseData: ImageData;\nlet noise32: Uint32Array;\n\nconst initImageData = (ctx: CanvasRenderingContext2D) => {\n noiseData = ctx.createImageData(canvasSize, canvasSize);\n noise32 = new Uint32Array(noiseData.data.buffer);\n};\n\nconst drawGrain = () => {\n const a = props.patternAlpha << 24;\n for (let i = 0; i < noise32.length; i++) {\n const v = (Math.random() * 255) | 0;\n noise32[i] = a | (v << 16) | (v << 8) | v;\n }\n};\n\nconst loop = (ctx: CanvasRenderingContext2D) => {\n if (frame % Math.max(1, Math.round(props.patternRefreshInterval)) === 0) {\n drawGrain();\n ctx.putImageData(noiseData, 0, 0);\n }\n frame++;\n animationId = requestAnimationFrame(() => loop(ctx));\n};\n\nonMounted(() => {\n const canvas = grainRef.value;\n if (!canvas) return;\n const ctx = canvas.getContext('2d', { alpha: true });\n if (!ctx) return;\n\n resize();\n initImageData(ctx);\n drawGrain();\n ctx.putImageData(noiseData, 0, 0);\n loop(ctx);\n\n window.addEventListener('resize', resize);\n});\n\nonBeforeUnmount(() => {\n window.removeEventListener('resize', resize);\n cancelAnimationFrame(animationId);\n});\n</script>\n","path":"Noise/Noise.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/Orb.json
Normal file
1
public/r/Orb.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Particles.json
Normal file
1
public/r/Particles.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/PillNav.json
Normal file
1
public/r/PillNav.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/PixelBlast.json
Normal file
1
public/r/PixelBlast.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/PixelCard.json
Normal file
1
public/r/PixelCard.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/PixelTransition.json
Normal file
1
public/r/PixelTransition.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"PixelTransition","title":"PixelTransition","description":"Pixel dissolve transition for content reveal on hover.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { ref, onMounted, watch, onUnmounted, nextTick, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\n\ninterface PixelTransitionProps {\n gridSize?: number;\n pixelColor?: string;\n animationStepDuration?: number;\n className?: string;\n style?: Record<string, string | number>;\n aspectRatio?: string;\n}\n\nconst props = withDefaults(defineProps<PixelTransitionProps>(), {\n gridSize: 7,\n pixelColor: 'currentColor',\n animationStepDuration: 0.3,\n className: '',\n style: () => ({}),\n aspectRatio: '100%'\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst pixelGridRef = useTemplateRef<HTMLDivElement>('pixelGridRef');\nconst activeRef = useTemplateRef<HTMLDivElement>('activeRef');\nconst isActive = ref(false);\nlet delayedCall: gsap.core.Tween | null = null;\n\nconst isTouchDevice =\n typeof window !== 'undefined' &&\n ('ontouchstart' in window ||\n (navigator && navigator.maxTouchPoints > 0) ||\n (window.matchMedia && window.matchMedia('(pointer: coarse)').matches));\n\nfunction buildPixelGrid() {\n const pixelGridEl = pixelGridRef.value;\n if (!pixelGridEl) return;\n pixelGridEl.innerHTML = '';\n for (let row = 0; row < props.gridSize; row++) {\n for (let col = 0; col < props.gridSize; col++) {\n const pixel = document.createElement('div');\n pixel.classList.add('pixelated-image-card__pixel', 'absolute', 'hidden');\n pixel.style.backgroundColor = props.pixelColor;\n const size = 100 / props.gridSize;\n pixel.style.width = `${size}%`;\n pixel.style.height = `${size}%`;\n pixel.style.left = `${col * size}%`;\n pixel.style.top = `${row * size}%`;\n pixelGridEl.appendChild(pixel);\n }\n }\n}\n\nasync function animatePixels(activate: boolean) {\n isActive.value = activate;\n await nextTick();\n const pixelGridEl = pixelGridRef.value;\n const activeEl = activeRef.value;\n if (!pixelGridEl || !activeEl) return;\n const pixels = pixelGridEl.querySelectorAll<HTMLDivElement>('.pixelated-image-card__pixel');\n if (!pixels.length) return;\n gsap.killTweensOf(pixels);\n if (delayedCall) delayedCall.kill();\n gsap.set(pixels, { display: 'none' });\n const totalPixels = pixels.length;\n const staggerDuration = props.animationStepDuration / totalPixels;\n gsap.to(pixels, {\n display: 'block',\n duration: 0,\n stagger: {\n each: staggerDuration,\n from: 'random'\n }\n });\n delayedCall = gsap.delayedCall(props.animationStepDuration, () => {\n activeEl.style.display = activate ? 'block' : 'none';\n activeEl.style.pointerEvents = activate ? 'none' : '';\n });\n gsap.to(pixels, {\n display: 'none',\n duration: 0,\n delay: props.animationStepDuration,\n stagger: {\n each: staggerDuration,\n from: 'random'\n }\n });\n}\n\nfunction handleMouseEnter() {\n if (isTouchDevice) return;\n if (!isActive.value) animatePixels(true);\n}\nfunction handleMouseLeave() {\n if (isTouchDevice) return;\n if (isActive.value) animatePixels(false);\n}\nfunction handleClick() {\n if (!isTouchDevice) return;\n animatePixels(!isActive.value);\n}\n\nonMounted(async () => {\n await nextTick();\n buildPixelGrid();\n});\n\nwatch(\n () => [props.gridSize, props.pixelColor],\n () => {\n buildPixelGrid();\n }\n);\n\nonUnmounted(() => {\n if (delayedCall) delayedCall.kill();\n});\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n :class=\"[\n props.className,\n 'bg-[#222] text-white rounded-[15px] border-2 border-white w-[300px] max-w-full relative overflow-hidden'\n ]\"\n :style=\"props.style\"\n @mouseenter=\"handleMouseEnter\"\n @mouseleave=\"handleMouseLeave\"\n @click=\"handleClick\"\n >\n <div :style=\"{ paddingTop: props.aspectRatio }\" />\n\n <div class=\"absolute inset-0 w-full h-full\">\n <slot name=\"firstContent\" />\n </div>\n\n <div ref=\"activeRef\" class=\"absolute inset-0 w-full h-full z-[2]\" style=\"display: none\">\n <slot name=\"secondContent\" />\n </div>\n\n <div ref=\"pixelGridRef\" class=\"absolute inset-0 w-full h-full pointer-events-none z-[3]\" />\n </div>\n</template>\n\n<style scoped>\n.pixelated-image-card__pixel {\n transition: none;\n}\n</style>\n","path":"PixelTransition/PixelTransition.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Animations"]}
|
||||||
1
public/r/Plasma.json
Normal file
1
public/r/Plasma.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Prism.json
Normal file
1
public/r/Prism.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/PrismaticBurst.json
Normal file
1
public/r/PrismaticBurst.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ProfileCard.json
Normal file
1
public/r/ProfileCard.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/Ribbons.json
Normal file
1
public/r/Ribbons.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/RippleGrid.json
Normal file
1
public/r/RippleGrid.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/RotatingText.json
Normal file
1
public/r/RotatingText.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ScrambleText.json
Normal file
1
public/r/ScrambleText.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"ScrambleText","title":"ScrambleText","description":"Detects cursor position and applies a distortion effect to text.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\nimport { SplitText } from 'gsap/SplitText';\nimport { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin';\n\ngsap.registerPlugin(SplitText, ScrambleTextPlugin);\n\ninterface ScrambleTextProps {\n radius?: number;\n duration?: number;\n speed?: number;\n scrambleChars?: string;\n className?: string;\n style?: Record<string, string | number>;\n}\n\nconst props = withDefaults(defineProps<ScrambleTextProps>(), {\n radius: 100,\n duration: 1.2,\n speed: 0.5,\n scrambleChars: '.:',\n className: '',\n style: () => ({})\n});\n\nconst rootRef = useTemplateRef<HTMLDivElement>('rootRef');\n\nlet splitText: SplitText | null = null;\nlet handleMove: ((e: PointerEvent) => void) | null = null;\n\nconst initializeScrambleText = () => {\n if (!rootRef.value) return;\n\n const pElement = rootRef.value.querySelector('p');\n if (!pElement) return;\n\n splitText = new SplitText(pElement, {\n type: 'chars',\n charsClass: 'inline-block will-change-transform'\n });\n\n splitText.chars.forEach(el => {\n const c = el as HTMLElement;\n gsap.set(c, { attr: { 'data-content': c.innerHTML } });\n });\n\n handleMove = (e: PointerEvent) => {\n if (!splitText) return;\n\n splitText.chars.forEach(el => {\n const c = el as HTMLElement;\n const { left, top, width, height } = c.getBoundingClientRect();\n const dx = e.clientX - (left + width / 2);\n const dy = e.clientY - (top + height / 2);\n const dist = Math.hypot(dx, dy);\n\n if (dist < props.radius) {\n gsap.to(c, {\n overwrite: true,\n duration: props.duration * (1 - dist / props.radius),\n scrambleText: {\n text: c.dataset.content || '',\n chars: props.scrambleChars,\n speed: props.speed\n },\n ease: 'none'\n });\n }\n });\n };\n\n rootRef.value.addEventListener('pointermove', handleMove);\n};\n\nconst cleanup = () => {\n if (rootRef.value && handleMove) {\n rootRef.value.removeEventListener('pointermove', handleMove);\n }\n if (splitText) {\n splitText.revert();\n splitText = null;\n }\n handleMove = null;\n};\n\nonMounted(() => {\n initializeScrambleText();\n});\n\nonUnmounted(() => {\n cleanup();\n});\n\nwatch([() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars], () => {\n cleanup();\n initializeScrambleText();\n});\n</script>\n\n<template>\n <div ref=\"rootRef\" :class=\"`scramble-text ${className}`\" :style=\"style\">\n <p>\n <slot></slot>\n </p>\n </div>\n</template>\n","path":"ScrambleText/ScrambleText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/ScrollFloat.json
Normal file
1
public/r/ScrollFloat.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"ScrollFloat","title":"ScrollFloat","description":"Text gently floats / parallax shifts on scroll.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <h2 ref=\"containerRef\" :class=\"`overflow-hidden ${containerClassName}`\">\n <span\n :class=\"`inline-block text-center leading-relaxed font-black ${textClassName}`\"\n style=\"font-size: clamp(1.6rem, 8vw, 10rem)\"\n >\n <span v-for=\"(char, index) in splitText\" :key=\"index\" class=\"inline-block char\">\n {{ char === ' ' ? '\\u00A0' : char }}\n </span>\n </span>\n </h2>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface Props {\n children: string;\n scrollContainerRef?: { current: HTMLElement | null };\n containerClassName?: string;\n textClassName?: string;\n animationDuration?: number;\n ease?: string;\n scrollStart?: string;\n scrollEnd?: string;\n stagger?: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n containerClassName: '',\n textClassName: '',\n animationDuration: 1,\n ease: 'back.inOut(2)',\n scrollStart: 'center bottom+=50%',\n scrollEnd: 'bottom bottom-=40%',\n stagger: 0.03\n});\n\nconst containerRef = useTemplateRef<HTMLElement>('containerRef');\nlet scrollTriggerInstance: ScrollTrigger | null = null;\n\nconst splitText = computed(() => {\n const text = typeof props.children === 'string' ? props.children : '';\n return text.split('');\n});\n\nconst initializeAnimation = () => {\n const el = containerRef.value;\n if (!el) return;\n\n const scroller =\n props.scrollContainerRef && props.scrollContainerRef.current ? props.scrollContainerRef.current : window;\n\n const charElements = el.querySelectorAll('.char');\n\n if (scrollTriggerInstance) {\n scrollTriggerInstance.kill();\n }\n\n const tl = gsap.fromTo(\n charElements,\n {\n willChange: 'opacity, transform',\n opacity: 0,\n yPercent: 120,\n scaleY: 2.3,\n scaleX: 0.7,\n transformOrigin: '50% 0%'\n },\n {\n duration: props.animationDuration,\n ease: props.ease,\n opacity: 1,\n yPercent: 0,\n scaleY: 1,\n scaleX: 1,\n stagger: props.stagger,\n scrollTrigger: {\n trigger: el,\n scroller,\n start: props.scrollStart,\n end: props.scrollEnd,\n scrub: true\n }\n }\n );\n\n scrollTriggerInstance = tl.scrollTrigger || null;\n};\n\nonMounted(() => {\n initializeAnimation();\n});\n\nonUnmounted(() => {\n if (scrollTriggerInstance) {\n scrollTriggerInstance.kill();\n }\n});\n\nwatch(\n [\n () => props.children,\n () => props.scrollContainerRef,\n () => props.animationDuration,\n () => props.ease,\n () => props.scrollStart,\n () => props.scrollEnd,\n () => props.stagger\n ],\n () => {\n initializeAnimation();\n },\n { deep: true }\n);\n</script>\n","path":"ScrollFloat/ScrollFloat.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/ScrollReveal.json
Normal file
1
public/r/ScrollReveal.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"ScrollReveal","title":"ScrollReveal","description":"Text gently unblurs and reveals on scroll.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <h2 ref=\"containerRef\" :class=\"`my-5 ${containerClassName}`\">\n <p :class=\"`leading-relaxed font-semibold ${textClassName}`\" style=\"font-size: clamp(1.6rem, 4vw, 3rem)\">\n <span v-for=\"(word, index) in splitText\" :key=\"index\" :class=\"word.isWhitespace ? '' : 'inline-block word'\">\n {{ word.text }}\n </span>\n </p>\n </h2>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface Props {\n children: string;\n scrollContainerRef?: { current: HTMLElement | null };\n enableBlur?: boolean;\n baseOpacity?: number;\n baseRotation?: number;\n blurStrength?: number;\n containerClassName?: string;\n textClassName?: string;\n rotationEnd?: string;\n wordAnimationEnd?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n enableBlur: true,\n baseOpacity: 0.1,\n baseRotation: 3,\n blurStrength: 4,\n containerClassName: '',\n textClassName: '',\n rotationEnd: 'bottom bottom',\n wordAnimationEnd: 'bottom bottom'\n});\n\nconst containerRef = useTemplateRef<HTMLElement>('containerRef');\nlet scrollTriggerInstances: ScrollTrigger[] = [];\n\nconst splitText = computed(() => {\n const text = typeof props.children === 'string' ? props.children : '';\n return text.split(/(\\s+)/).map((word, index) => ({\n text: word,\n isWhitespace: word.match(/^\\s+$/) !== null,\n key: index\n }));\n});\n\nconst initializeAnimation = () => {\n const el = containerRef.value;\n if (!el) return;\n\n scrollTriggerInstances.forEach(trigger => trigger.kill());\n scrollTriggerInstances = [];\n\n const scroller =\n props.scrollContainerRef && props.scrollContainerRef.current ? props.scrollContainerRef.current : window;\n\n const rotationTl = gsap.fromTo(\n el,\n { transformOrigin: '0% 50%', rotate: props.baseRotation },\n {\n ease: 'none',\n rotate: 0,\n scrollTrigger: {\n trigger: el,\n scroller,\n start: 'top bottom',\n end: props.rotationEnd,\n scrub: true\n }\n }\n );\n\n if (rotationTl.scrollTrigger) {\n scrollTriggerInstances.push(rotationTl.scrollTrigger);\n }\n\n const wordElements = el.querySelectorAll('.word');\n\n const opacityTl = gsap.fromTo(\n wordElements,\n { opacity: props.baseOpacity, willChange: 'opacity' },\n {\n ease: 'none',\n opacity: 1,\n stagger: 0.05,\n scrollTrigger: {\n trigger: el,\n scroller,\n start: 'top bottom-=20%',\n end: props.wordAnimationEnd,\n scrub: true\n }\n }\n );\n\n if (opacityTl.scrollTrigger) {\n scrollTriggerInstances.push(opacityTl.scrollTrigger);\n }\n\n if (props.enableBlur) {\n const blurTl = gsap.fromTo(\n wordElements,\n { filter: `blur(${props.blurStrength}px)` },\n {\n ease: 'none',\n filter: 'blur(0px)',\n stagger: 0.05,\n scrollTrigger: {\n trigger: el,\n scroller,\n start: 'top bottom-=20%',\n end: props.wordAnimationEnd,\n scrub: true\n }\n }\n );\n\n if (blurTl.scrollTrigger) {\n scrollTriggerInstances.push(blurTl.scrollTrigger);\n }\n }\n};\n\nonMounted(() => {\n initializeAnimation();\n});\n\nonUnmounted(() => {\n scrollTriggerInstances.forEach(trigger => trigger.kill());\n});\n\nwatch(\n [\n () => props.children,\n () => props.scrollContainerRef,\n () => props.enableBlur,\n () => props.baseRotation,\n () => props.baseOpacity,\n () => props.rotationEnd,\n () => props.wordAnimationEnd,\n () => props.blurStrength\n ],\n () => {\n initializeAnimation();\n },\n { deep: true }\n);\n</script>\n","path":"ScrollReveal/ScrollReveal.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/ScrollStack.json
Normal file
1
public/r/ScrollStack.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ScrollVelocity.json
Normal file
1
public/r/ScrollVelocity.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ShapeBlur.json
Normal file
1
public/r/ShapeBlur.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/ShinyText.json
Normal file
1
public/r/ShinyText.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"ShinyText","title":"ShinyText","description":"Metallic sheen sweeps across text producing a reflective highlight.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\ninterface ShinyTextProps {\n text: string;\n disabled?: boolean;\n speed?: number;\n className?: string;\n}\n\nconst props = withDefaults(defineProps<ShinyTextProps>(), {\n text: '',\n disabled: false,\n speed: 5,\n className: ''\n});\n\nconst animationDuration = computed(() => `${props.speed}s`);\n</script>\n\n<template>\n <div\n :class=\"`text-[#b5b5b5a4] bg-clip-text inline-block ${!props.disabled ? 'animate-shine' : ''} ${props.className}`\"\n :style=\"{\n backgroundImage:\n 'linear-gradient(120deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 60%)',\n backgroundSize: '200% 100%',\n WebkitBackgroundClip: 'text',\n animationDuration: animationDuration\n }\"\n >\n {{ props.text }}\n </div>\n</template>\n\n<style scoped>\n@keyframes shine {\n 0% {\n background-position: 100%;\n }\n 100% {\n background-position: -100%;\n }\n}\n\n.animate-shine {\n animation: shine 5s linear infinite;\n}\n</style>\n","path":"ShinyText/ShinyText.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["TextAnimations"]}
|
||||||
1
public/r/Silk.json
Normal file
1
public/r/Silk.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/SplashCursor.json
Normal file
1
public/r/SplashCursor.json
Normal file
File diff suppressed because one or more lines are too long
1
public/r/SplitText.json
Normal file
1
public/r/SplitText.json
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user