mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
7.9 KiB
JSON
1 line
7.9 KiB
JSON
{"name":"ChromaGrid","title":"ChromaGrid","description":"A responsive grid of grayscale tiles. Hovering the grid reaveals their colors.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { onMounted, computed, useTemplateRef, shallowRef, reactive } from 'vue';\nimport gsap from 'gsap';\n\ninterface CardItem {\n image: string;\n title: string;\n subtitle: string;\n handle?: string;\n borderColor?: string;\n gradient?: string;\n url?: string;\n location?: string;\n}\n\ninterface GridMotionProps {\n items?: CardItem[];\n className?: string;\n radius?: number;\n damping?: number;\n fadeOut?: number;\n ease?: string;\n}\n\nconst props = withDefaults(defineProps<GridMotionProps>(), {\n items: () => [],\n className: '',\n radius: 300,\n damping: 0.45,\n fadeOut: 0.6,\n ease: 'power3.out'\n});\n\nconst rootRef = useTemplateRef<HTMLElement>('rootRef');\nconst fadeRef = useTemplateRef<HTMLElement>('fadeRef');\nconst setX = shallowRef<(value: number | string) => void>();\nconst setY = shallowRef<(value: number | string) => void>();\nconst pos = reactive({ x: 0, y: 0 });\n\nconst demo: CardItem[] = [\n {\n image: 'https://i.pravatar.cc/300?img=8',\n title: 'Alex Rivera',\n subtitle: 'Full Stack Developer',\n handle: '@alexrivera',\n borderColor: '#4F46E5',\n gradient: 'linear-gradient(145deg,#4F46E5,#000)',\n url: 'https://github.com/'\n },\n {\n image: 'https://i.pravatar.cc/300?img=11',\n title: 'Jordan Chen',\n subtitle: 'DevOps Engineer',\n handle: '@jordanchen',\n borderColor: '#10B981',\n gradient: 'linear-gradient(210deg,#10B981,#000)',\n url: 'https://linkedin.com/in/'\n },\n {\n image: 'https://i.pravatar.cc/300?img=3',\n title: 'Morgan Blake',\n subtitle: 'UI/UX Designer',\n handle: '@morganblake',\n borderColor: '#F59E0B',\n gradient: 'linear-gradient(165deg,#F59E0B,#000)',\n url: 'https://dribbble.com/'\n },\n {\n image: 'https://i.pravatar.cc/300?img=16',\n title: 'Casey Park',\n subtitle: 'Data Scientist',\n handle: '@caseypark',\n borderColor: '#EF4444',\n gradient: 'linear-gradient(195deg,#EF4444,#000)',\n url: 'https://kaggle.com/'\n },\n {\n image: 'https://i.pravatar.cc/300?img=25',\n title: 'Sam Kim',\n subtitle: 'Mobile Developer',\n handle: '@thesamkim',\n borderColor: '#8B5CF6',\n gradient: 'linear-gradient(225deg,#8B5CF6,#000)',\n url: 'https://github.com/'\n },\n {\n image: 'https://i.pravatar.cc/300?img=60',\n title: 'Tyler Rodriguez',\n subtitle: 'Cloud Architect',\n handle: '@tylerrod',\n borderColor: '#06B6D4',\n gradient: 'linear-gradient(135deg,#06B6D4,#000)',\n url: 'https://aws.amazon.com/'\n }\n];\n\nconst data = computed(() => (props.items.length ? props.items : demo));\n\nonMounted(() => {\n const el = rootRef.value;\n if (!el) return;\n\n setX.value = gsap.quickSetter(el, '--x', 'px') as (value: number | string) => void;\n setY.value = gsap.quickSetter(el, '--y', 'px') as (value: number | string) => void;\n const { width, height } = el.getBoundingClientRect();\n pos.x = width / 2;\n pos.y = height / 2;\n setX.value?.(pos.x);\n setY.value?.(pos.y);\n});\n\nconst moveTo = (x: number, y: number) => {\n gsap.to(pos, {\n x,\n y,\n duration: props.damping,\n ease: props.ease,\n onUpdate: () => {\n setX.value?.(pos.x);\n setY.value?.(pos.y);\n },\n overwrite: true\n });\n};\n\nconst handleMove = (e: PointerEvent) => {\n const r = rootRef.value?.getBoundingClientRect();\n if (!r) return;\n moveTo(e.clientX - r.left, e.clientY - r.top);\n if (fadeRef.value) {\n gsap.to(fadeRef.value, { opacity: 0, duration: 0.25, overwrite: true });\n }\n};\n\nconst handleLeave = () => {\n if (fadeRef.value) {\n gsap.to(fadeRef.value, {\n opacity: 1,\n duration: props.fadeOut,\n overwrite: true\n });\n }\n};\n\nconst handleCardClick = (url?: string) => {\n if (url) window.open(url, '_blank', 'noopener,noreferrer');\n};\n\nconst handleCardMove = (e: MouseEvent) => {\n const c = e.currentTarget as HTMLElement;\n const rect = c.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n c.style.setProperty('--mouse-x', `${x}px`);\n c.style.setProperty('--mouse-y', `${y}px`);\n};\n\nconst spotlightStyle = {\n backdropFilter: 'grayscale(1) brightness(0.78)',\n WebkitBackdropFilter: 'grayscale(1) brightness(0.78)',\n background: 'rgba(0,0,0,0.001)',\n maskImage:\n 'radial-gradient(circle var(--r) at var(--x) var(--y),transparent 0%,transparent 15%,rgba(0,0,0,0.10) 30%,rgba(0,0,0,0.22)45%,rgba(0,0,0,0.35)60%,rgba(0,0,0,0.50)75%,rgba(0,0,0,0.68)88%,white 100%)',\n WebkitMaskImage:\n 'radial-gradient(circle var(--r) at var(--x) var(--y),transparent 0%,transparent 15%,rgba(0,0,0,0.10) 30%,rgba(0,0,0,0.22)45%,rgba(0,0,0,0.35)60%,rgba(0,0,0,0.50)75%,rgba(0,0,0,0.68)88%,white 100%)'\n};\n\nconst fadeStyle = {\n ...spotlightStyle,\n maskImage:\n 'radial-gradient(circle var(--r) at var(--x) var(--y),white 0%,white 15%,rgba(255,255,255,0.90)30%,rgba(255,255,255,0.78)45%,rgba(255,255,255,0.65)60%,rgba(255,255,255,0.50)75%,rgba(255,255,255,0.32)88%,transparent 100%)',\n WebkitMaskImage:\n 'radial-gradient(circle var(--r) at var(--x) var(--y),white 0%,white 15%,rgba(255,255,255,0.90)30%,rgba(255,255,255,0.78)45%,rgba(255,255,255,0.65)60%,rgba(255,255,255,0.50)75%,rgba(255,255,255,0.32)88%,transparent 100%)',\n opacity: 1\n};\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n class=\"relative w-full h-full flex flex-wrap justify-center items-start gap-3\"\n :style=\"{\n '--r': `${props.radius}px`,\n '--x': '50%',\n '--y': '50%'\n }\"\n @pointermove=\"handleMove\"\n @pointerleave=\"handleLeave\"\n >\n <article\n v-for=\"(c, i) in data\"\n :key=\"i\"\n class=\"group relative flex flex-col w-[300px] rounded-[20px] overflow-hidden border border-[#333] hover:border-[var(--card-border)] transition-all duration-300\"\n :style=\"{\n '--mouse-x': '50%',\n '--mouse-y': '50%',\n '--card-border': c.borderColor || 'transparent',\n '--spotlight-color': 'rgba(255,255,255,0.3)',\n background: c.gradient,\n cursor: c.url ? 'pointer' : 'default'\n }\"\n @mousemove=\"handleCardMove\"\n @click=\"() => handleCardClick(c.url)\"\n >\n <div\n class=\"absolute inset-0 pointer-events-none transition-opacity duration-500 z-20 opacity-0 group-hover:opacity-100\"\n :style=\"{\n background:\n 'radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--spotlight-color), transparent 70%)'\n }\"\n />\n\n <div class=\"relative z-10 flex-1 p-[10px] box-border bg-transparent transition-colors duration-300\">\n <img :src=\"c.image\" :alt=\"c.title\" loading=\"lazy\" class=\"w-full h-full object-cover rounded-[10px] block\" />\n </div>\n\n <footer class=\"relative z-10 p-3 text-white font-sans grid grid-cols-[1fr_auto] gap-x-3 gap-y-1\">\n <h3 class=\"m-0 text-[1.05rem] font-semibold\">{{ c.title }}</h3>\n <span v-if=\"c.handle\" class=\"text-[0.95rem] text-[#aaa] text-right\">{{ c.handle }}</span>\n <p class=\"m-0 text-[0.85rem] text-[#aaa]\">{{ c.subtitle }}</p>\n <span v-if=\"c.location\" class=\"text-[0.85rem] text-[#aaa] text-right\">{{ c.location }}</span>\n </footer>\n </article>\n\n <div class=\"absolute inset-0 pointer-events-none z-30\" :style=\"spotlightStyle\" />\n <div\n ref=\"fadeRef\"\n class=\"absolute inset-0 pointer-events-none transition-opacity duration-[250ms] z-40\"\n :style=\"fadeStyle\"\n />\n </div>\n</template>\n","path":"ChromaGrid/ChromaGrid.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Components"]} |