mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
26 KiB
JSON
1 line
26 KiB
JSON
{"name":"MagicBento","title":"MagicBento","description":"Interactive bento grid tiles expand + animate with various options.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { gsap } from 'gsap';\nimport { computed, defineComponent, nextTick, onMounted, onUnmounted, ref, watch, type PropType } from 'vue';\n\ninterface BentoCardProps {\n color?: string;\n title?: string;\n description?: string;\n label?: string;\n textAutoHide?: boolean;\n disableAnimations?: boolean;\n}\n\ninterface BentoProps {\n textAutoHide?: boolean;\n enableStars?: boolean;\n enableSpotlight?: boolean;\n enableBorderGlow?: boolean;\n disableAnimations?: boolean;\n spotlightRadius?: number;\n particleCount?: number;\n enableTilt?: boolean;\n glowColor?: string;\n clickEffect?: boolean;\n enableMagnetism?: boolean;\n}\n\nconst DEFAULT_PARTICLE_COUNT = 12;\nconst DEFAULT_SPOTLIGHT_RADIUS = 300;\nconst DEFAULT_GLOW_COLOR = '0, 200, 83';\nconst MOBILE_BREAKPOINT = 768;\n\nconst cardData: BentoCardProps[] = [\n {\n color: '#000C00',\n title: 'Analytics',\n description: 'Track user behavior',\n label: 'Insights'\n },\n {\n color: '#000C00',\n title: 'Dashboard',\n description: 'Centralized data view',\n label: 'Overview'\n },\n {\n color: '#000C00',\n title: 'Collaboration',\n description: 'Work together seamlessly',\n label: 'Teamwork'\n },\n {\n color: '#000C00',\n title: 'Automation',\n description: 'Streamline workflows',\n label: 'Efficiency'\n },\n {\n color: '#000C00',\n title: 'Integration',\n description: 'Connect favorite tools',\n label: 'Connectivity'\n },\n {\n color: '#000C00',\n title: 'Security',\n description: 'Enterprise-grade protection',\n label: 'Protection'\n }\n];\n\nconst createParticleElement = (x: number, y: number, color: string = DEFAULT_GLOW_COLOR): HTMLDivElement => {\n const el = document.createElement('div');\n el.className = 'particle';\n el.style.cssText = `\n position: absolute;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: rgba(${color}, 1);\n box-shadow: 0 0 6px rgba(${color}, 0.6);\n pointer-events: none;\n z-index: 100;\n left: ${x}px;\n top: ${y}px;\n `;\n return el;\n};\n\nconst calculateSpotlightValues = (radius: number) => ({\n proximity: radius * 0.5,\n fadeDistance: radius * 0.75\n});\n\nconst updateCardGlowProperties = (card: HTMLElement, mouseX: number, mouseY: number, glow: number, radius: number) => {\n const rect = card.getBoundingClientRect();\n const relativeX = ((mouseX - rect.left) / rect.width) * 100;\n const relativeY = ((mouseY - rect.top) / rect.height) * 100;\n\n card.style.setProperty('--glow-x', `${relativeX}%`);\n card.style.setProperty('--glow-y', `${relativeY}%`);\n card.style.setProperty('--glow-intensity', glow.toString());\n card.style.setProperty('--glow-radius', `${radius}px`);\n};\n\nconst ParticleCard = defineComponent({\n name: 'ParticleCard',\n props: {\n disableAnimations: { type: Boolean, default: false },\n particleCount: { type: Number, default: DEFAULT_PARTICLE_COUNT },\n glowColor: { type: String, default: DEFAULT_GLOW_COLOR },\n enableTilt: { type: Boolean, default: true },\n clickEffect: { type: Boolean, default: false },\n enableMagnetism: { type: Boolean, default: false }\n },\n setup(props) {\n const cardRef = ref<HTMLDivElement | null>(null);\n const particlesRef = ref<HTMLDivElement[]>([]);\n const timeoutsRef = ref<ReturnType<typeof setTimeout>[]>([]);\n const isHoveredRef = ref(false);\n const memoizedParticles = ref<HTMLDivElement[]>([]);\n const particlesInitialized = ref(false);\n const magnetismAnimationRef = ref<gsap.core.Tween | null>(null);\n\n const initializeParticles = () => {\n if (particlesInitialized.value || !cardRef.value) return;\n\n const { width, height } = cardRef.value.getBoundingClientRect();\n memoizedParticles.value = Array.from({ length: props.particleCount }, () =>\n createParticleElement(Math.random() * width, Math.random() * height, props.glowColor)\n );\n particlesInitialized.value = true;\n };\n\n const clearAllParticles = () => {\n timeoutsRef.value.forEach(clearTimeout);\n timeoutsRef.value = [];\n magnetismAnimationRef.value?.kill();\n\n particlesRef.value.forEach(particle => {\n gsap.to(particle, {\n scale: 0,\n opacity: 0,\n duration: 0.3,\n ease: 'back.in(1.7)',\n onComplete: () => {\n particle.parentNode?.removeChild(particle);\n }\n });\n });\n particlesRef.value = [];\n };\n\n const animateParticles = () => {\n if (!cardRef.value || !isHoveredRef.value) return;\n\n if (!particlesInitialized.value) {\n initializeParticles();\n }\n\n memoizedParticles.value.forEach((particle, index) => {\n const timeoutId = setTimeout(() => {\n if (!isHoveredRef.value || !cardRef.value) return;\n\n const clone = particle.cloneNode(true) as HTMLDivElement;\n cardRef.value.appendChild(clone);\n particlesRef.value.push(clone);\n\n gsap.fromTo(clone, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)' });\n\n gsap.to(clone, {\n x: (Math.random() - 0.5) * 100,\n y: (Math.random() - 0.5) * 100,\n rotation: Math.random() * 360,\n duration: 2 + Math.random() * 2,\n ease: 'none',\n repeat: -1,\n yoyo: true\n });\n\n gsap.to(clone, {\n opacity: 0.3,\n duration: 1.5,\n ease: 'power2.inOut',\n repeat: -1,\n yoyo: true\n });\n }, index * 100);\n\n timeoutsRef.value.push(timeoutId);\n });\n };\n\n let cleanupEventListeners: (() => void) | null = null;\n const setupEventListeners = () => {\n if (props.disableAnimations || !cardRef.value) return;\n\n const element = cardRef.value;\n\n const handleMouseEnter = () => {\n isHoveredRef.value = true;\n animateParticles();\n\n gsap.to(element, { y: -2, duration: 0.3, ease: 'power2.out' });\n\n if (props.enableTilt) {\n gsap.to(element, {\n rotateX: 5,\n rotateY: 5,\n duration: 0.3,\n ease: 'power2.out',\n transformPerspective: 1000\n });\n }\n };\n\n const handleMouseLeave = () => {\n isHoveredRef.value = false;\n clearAllParticles();\n\n if (props.enableTilt) {\n gsap.to(element, {\n rotateX: 0,\n rotateY: 0,\n y: 0,\n duration: 0.3,\n ease: 'power2.out'\n });\n }\n\n if (props.enableMagnetism) {\n gsap.to(element, {\n x: 0,\n y: 0,\n duration: 0.3,\n ease: 'power2.out'\n });\n }\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!props.enableTilt && !props.enableMagnetism) return;\n\n const rect = element.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n\n if (props.enableTilt) {\n const rotateX = ((y - centerY) / centerY) * -10;\n const rotateY = ((x - centerX) / centerX) * 10;\n\n gsap.to(element, {\n rotateX,\n rotateY,\n duration: 0.1,\n ease: 'power2.out',\n transformPerspective: 1000\n });\n }\n\n if (props.enableMagnetism) {\n const magnetX = (x - centerX) * 0.05;\n const magnetY = (y - centerY) * 0.05;\n\n magnetismAnimationRef.value = gsap.to(element, {\n x: magnetX,\n y: magnetY,\n duration: 0.3,\n ease: 'power2.out'\n });\n }\n };\n\n const handleClick = (e: MouseEvent) => {\n if (!props.clickEffect) return;\n\n const rect = element.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const maxDistance = Math.max(\n Math.hypot(x, y),\n Math.hypot(x - rect.width, y),\n Math.hypot(x, y - rect.height),\n Math.hypot(x - rect.width, y - rect.height)\n );\n\n const ripple = document.createElement('div');\n ripple.style.cssText = `\n position: absolute;\n width: ${maxDistance * 2}px;\n height: ${maxDistance * 2}px;\n border-radius: 50%;\n background: radial-gradient(circle, rgba(${props.glowColor}, 0.4) 0%, rgba(${props.glowColor}, 0.2) 30%, transparent 70%);\n left: ${x - maxDistance}px;\n top: ${y - maxDistance}px;\n pointer-events: none;\n z-index: 1000;\n `;\n\n element.appendChild(ripple);\n\n gsap.fromTo(\n ripple,\n {\n scale: 0,\n opacity: 1\n },\n {\n scale: 1,\n opacity: 0,\n duration: 0.8,\n ease: 'power2.out',\n onComplete: () => ripple.remove()\n }\n );\n };\n\n element.addEventListener('mouseenter', handleMouseEnter);\n element.addEventListener('mouseleave', handleMouseLeave);\n element.addEventListener('mousemove', handleMouseMove);\n element.addEventListener('click', handleClick);\n\n cleanupEventListeners = () => {\n isHoveredRef.value = false;\n element.removeEventListener('mouseenter', handleMouseEnter);\n element.removeEventListener('mouseleave', handleMouseLeave);\n element.removeEventListener('mousemove', handleMouseMove);\n element.removeEventListener('click', handleClick);\n clearAllParticles();\n };\n };\n\n onMounted(() => {\n nextTick(() => {\n setupEventListeners();\n });\n });\n\n onUnmounted(() => {\n cleanupEventListeners?.();\n });\n\n watch(\n () => [\n animateParticles,\n clearAllParticles,\n props.disableAnimations,\n props.enableTilt,\n props.enableMagnetism,\n props.clickEffect,\n props.glowColor\n ],\n () => {\n cleanupEventListeners?.();\n setupEventListeners();\n },\n { deep: true }\n );\n\n return {\n cardRef\n };\n },\n template: `\n <div\n ref=\"cardRef\"\n class=\"relative overflow-hidden\"\n :style=\"{ position: 'relative', overflow: 'hidden' }\"\n >\n <slot />\n </div>\n `\n});\n\nconst GlobalSpotlight = defineComponent({\n name: 'GlobalSpotlight',\n props: {\n gridRef: { type: [Object, null] as PropType<HTMLDivElement | null>, required: true },\n disableAnimations: { type: Boolean, default: false },\n enabled: { type: Boolean, default: true },\n spotlightRadius: { type: Number, default: DEFAULT_SPOTLIGHT_RADIUS },\n glowColor: { type: String, default: DEFAULT_GLOW_COLOR }\n },\n setup(props) {\n const spotlightRef = ref<HTMLDivElement | null>(null);\n const isInsideSection = ref(false);\n\n let cleanupEventListeners: (() => void) | null = null;\n const setupEventListeners = () => {\n if (props.disableAnimations || !props.gridRef || !props.enabled) return;\n\n const spotlight = document.createElement('div');\n spotlight.className = 'global-spotlight';\n spotlight.style.cssText = `\n position: fixed;\n width: 800px;\n height: 800px;\n border-radius: 50%;\n pointer-events: none;\n background: radial-gradient(circle,\n rgba(${props.glowColor}, 0.15) 0%,\n rgba(${props.glowColor}, 0.08) 15%,\n rgba(${props.glowColor}, 0.04) 25%,\n rgba(${props.glowColor}, 0.02) 40%,\n rgba(${props.glowColor}, 0.01) 65%,\n transparent 70%\n );\n z-index: 200;\n opacity: 0;\n transform: translate(-50%, -50%);\n mix-blend-mode: screen;\n `;\n document.body.appendChild(spotlight);\n spotlightRef.value = spotlight;\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!spotlightRef.value || !props.gridRef) return;\n\n const section = props.gridRef.closest('.bento-section');\n const rect = section?.getBoundingClientRect();\n const mouseInside =\n rect &&\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom;\n\n isInsideSection.value = mouseInside || false;\n const cards = props.gridRef.querySelectorAll('.card') as NodeListOf<HTMLDivElement>;\n\n if (!mouseInside) {\n gsap.to(spotlightRef.value, {\n opacity: 0,\n duration: 0.3,\n ease: 'power2.out'\n });\n cards.forEach(card => {\n card.style.setProperty('--glow-intensity', '0');\n });\n return;\n }\n\n const { proximity, fadeDistance } = calculateSpotlightValues(props.spotlightRadius);\n let minDistance = Infinity;\n\n cards.forEach(card => {\n const cardRect = card.getBoundingClientRect();\n const centerX = cardRect.left + cardRect.width / 2;\n const centerY = cardRect.top + cardRect.height / 2;\n const distance =\n Math.hypot(e.clientX - centerX, e.clientY - centerY) - Math.max(cardRect.width, cardRect.height) / 2;\n const effectiveDistance = Math.max(0, distance);\n\n minDistance = Math.min(minDistance, effectiveDistance);\n\n let glowIntensity = 0;\n if (effectiveDistance <= proximity) {\n glowIntensity = 1;\n } else if (effectiveDistance <= fadeDistance) {\n glowIntensity = (fadeDistance - effectiveDistance) / (fadeDistance - proximity);\n }\n\n updateCardGlowProperties(card, e.clientX, e.clientY, glowIntensity, props.spotlightRadius);\n });\n\n gsap.to(spotlightRef.value, {\n left: e.clientX,\n top: e.clientY,\n duration: 0.1,\n ease: 'power2.out'\n });\n\n const targetOpacity =\n minDistance <= proximity\n ? 0.8\n : minDistance <= fadeDistance\n ? ((fadeDistance - minDistance) / (fadeDistance - proximity)) * 0.8\n : 0;\n\n gsap.to(spotlightRef.value, {\n opacity: targetOpacity,\n duration: targetOpacity > 0 ? 0.2 : 0.5,\n ease: 'power2.out'\n });\n };\n\n const handleMouseLeave = () => {\n isInsideSection.value = false;\n (props.gridRef?.querySelectorAll('.card') as NodeListOf<HTMLDivElement>).forEach(card => {\n card.style.setProperty('--glow-intensity', '0');\n });\n if (spotlightRef.value) {\n gsap.to(spotlightRef.value, {\n opacity: 0,\n duration: 0.3,\n ease: 'power2.out'\n });\n }\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseleave', handleMouseLeave);\n\n cleanupEventListeners = () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseleave', handleMouseLeave);\n spotlightRef.value?.parentNode?.removeChild(spotlightRef.value);\n };\n };\n\n onMounted(() => {\n nextTick(() => {\n setupEventListeners();\n });\n });\n\n onUnmounted(() => {\n cleanupEventListeners?.();\n cleanupEventListeners = null;\n });\n\n watch(\n () => props,\n () => {\n if (props.gridRef) {\n cleanupEventListeners?.();\n setupEventListeners();\n }\n },\n { deep: true }\n );\n },\n template: `<div></div>`\n});\n\nconst BentoCardGrid = defineComponent({\n name: 'BentoCardGrid',\n props: {\n gridRef: {\n type: Function as PropType<(el: HTMLDivElement | null) => void>,\n required: true\n }\n },\n template: `\n <div\n class=\"relative gap-2 grid p-3 select-none bento-section\"\n :style=\"{ fontSize: 'clamp(1rem, 0.9rem + 0.5vw, 1.5rem)' }\"\n :ref=\"gridRef\"\n >\n <slot />\n </div>\n `\n});\n\nconst useMobileDetection = () => {\n const isMobile = ref(false);\n\n const checkMobile = () => {\n isMobile.value = window.innerWidth <= MOBILE_BREAKPOINT;\n };\n\n onMounted(() => {\n checkMobile();\n window.addEventListener('resize', checkMobile);\n\n onUnmounted(() => {\n window.removeEventListener('resize', checkMobile);\n });\n });\n\n return isMobile;\n};\n\nconst props = withDefaults(defineProps<BentoProps>(), {\n textAutoHide: true,\n enableStars: true,\n enableSpotlight: true,\n enableBorderGlow: true,\n disableAnimations: false,\n spotlightRadius: DEFAULT_SPOTLIGHT_RADIUS,\n particleCount: DEFAULT_PARTICLE_COUNT,\n enableTilt: false,\n glowColor: DEFAULT_GLOW_COLOR,\n clickEffect: true,\n enableMagnetism: true\n});\n\nconst gridRef = ref<HTMLDivElement | null>(null);\nconst isMobile = useMobileDetection();\nconst cardElements = ref<HTMLDivElement[]>([]);\n\nconst shouldDisableAnimations = computed(() => props.disableAnimations || isMobile.value);\nconst baseClassName = computed(() => {\n return `card flex flex-col justify-between relative aspect-[4/3] min-h-[200px] w-full max-w-full p-5 rounded-[20px] border border-solid font-light overflow-hidden transition-[box-shadow] duration-300 ease-in-out hover:shadow-[0_8px_25px_rgba(0,0,0,0.15)] ${\n props.enableBorderGlow ? 'card--border-glow' : ''\n }`;\n});\nconst getCardStyle = (card: BentoCardProps) => ({\n backgroundColor: card.color || 'var(--background-dark)',\n borderColor: 'var(--border-color)',\n color: 'var(--white)',\n '--glow-x': '50%',\n '--glow-y': '50%',\n '--glow-intensity': '0',\n '--glow-radius': '200px'\n});\n\nconst setupCardRef = (el: HTMLDivElement | null, index: number) => {\n if (!el) return;\n cardElements.value[index] = el;\n\n const handleMouseEnter = () => {\n if (shouldDisableAnimations.value) return;\n gsap.to(el, { y: -2, duration: 0.3, ease: 'power2.out' });\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n if (shouldDisableAnimations.value) return;\n\n const rect = el.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n\n if (props.enableTilt) {\n const rotateX = ((y - centerY) / centerY) * -10;\n const rotateY = ((x - centerX) / centerX) * 10;\n\n gsap.to(el, {\n rotateX,\n rotateY,\n duration: 0.1,\n ease: 'power2.out',\n transformPerspective: 1000\n });\n }\n\n if (props.enableMagnetism) {\n const magnetX = (x - centerX) * 0.05;\n const magnetY = (y - centerY) * 0.05;\n\n gsap.to(el, {\n x: magnetX,\n y: magnetY,\n duration: 0.3,\n ease: 'power2.out'\n });\n }\n };\n\n const handleMouseLeave = () => {\n if (shouldDisableAnimations.value) return;\n\n if (props.enableTilt) {\n gsap.to(el, {\n rotateX: 0,\n rotateY: 0,\n duration: 0.3,\n ease: 'power2.out'\n });\n }\n\n if (props.enableMagnetism) {\n gsap.to(el, {\n x: 0,\n y: 0,\n duration: 0.3,\n ease: 'power2.out'\n });\n } else {\n gsap.to(el, { y: 0, duration: 0.3, ease: 'power2.out' });\n }\n };\n\n const handleClick = (e: MouseEvent) => {\n if (!props.clickEffect || shouldDisableAnimations.value) return;\n\n const rect = el.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const maxDistance = Math.max(\n Math.hypot(x, y),\n Math.hypot(x - rect.width, y),\n Math.hypot(x, y - rect.height),\n Math.hypot(x - rect.width, y - rect.height)\n );\n\n const ripple = document.createElement('div');\n ripple.style.cssText = `\n position: absolute;\n width: ${maxDistance * 2}px;\n height: ${maxDistance * 2}px;\n border-radius: 50%;\n background: radial-gradient(circle, rgba(${props.glowColor}, 0.4) 0%, rgba(${props.glowColor}, 0.2) 30%, transparent 70%);\n left: ${x - maxDistance}px;\n top: ${y - maxDistance}px;\n pointer-events: none;\n z-index: 1000;\n `;\n\n el.appendChild(ripple);\n\n gsap.fromTo(\n ripple,\n {\n scale: 0,\n opacity: 1\n },\n {\n scale: 1,\n opacity: 0,\n duration: 0.8,\n ease: 'power2.out',\n onComplete: () => ripple.remove()\n }\n );\n };\n\n el.addEventListener('mouseenter', handleMouseEnter);\n el.addEventListener('mousemove', handleMouseMove);\n el.addEventListener('mouseleave', handleMouseLeave);\n el.addEventListener('click', handleClick);\n};\n</script>\n\n<template>\n <GlobalSpotlight\n v-if=\"enableSpotlight\"\n :grid-ref=\"gridRef\"\n :disable-animations=\"shouldDisableAnimations\"\n :enabled=\"enableSpotlight\"\n :spotlight-radius=\"spotlightRadius\"\n :glow-color=\"glowColor\"\n />\n\n <BentoCardGrid\n :grid-ref=\"\n (el: HTMLDivElement | null) => {\n gridRef = el;\n }\n \"\n >\n <div class=\"gap-2 grid card-responsive\">\n <template v-for=\"(card, index) in cardData\" :key=\"index\">\n <ParticleCard\n v-if=\"enableStars\"\n :class=\"baseClassName\"\n :style=\"getCardStyle(card)\"\n :disable-animations=\"shouldDisableAnimations\"\n :particle-count=\"particleCount\"\n :glow-color=\"glowColor\"\n :enable-tilt=\"enableTilt\"\n :click-effect=\"clickEffect\"\n :enable-magnetism=\"enableMagnetism\"\n >\n <div class=\"relative flex justify-between gap-3 text-white card__header\">\n <span class=\"text-base card__label\">{{ card.label }}</span>\n </div>\n <div class=\"relative flex flex-col text-white card__content\">\n <h3 :class=\"`card__title font-normal text-base m-0 mb-1 ${textAutoHide ? 'text-clamp-1' : ''}`\">\n {{ card.title }}\n </h3>\n <p :class=\"`card__description text-xs leading-5 opacity-90 ${textAutoHide ? 'text-clamp-2' : ''}`\">\n {{ card.description }}\n </p>\n </div>\n </ParticleCard>\n\n <div\n v-else\n :class=\"baseClassName\"\n :style=\"getCardStyle(card)\"\n :ref=\"el => setupCardRef(el as HTMLDivElement, index)\"\n >\n <div class=\"relative flex justify-between gap-3 text-white card__header\">\n <span class=\"text-base card__label\">{{ card.label }}</span>\n </div>\n <div class=\"relative flex flex-col text-white card__content\">\n <h3 :class=\"`card__title font-normal text-base m-0 mb-1 ${textAutoHide ? 'text-clamp-1' : ''}`\">\n {{ card.title }}\n </h3>\n <p :class=\"`card__description text-xs leading-5 opacity-90 ${textAutoHide ? 'text-clamp-2' : ''}`\">\n {{ card.description }}\n </p>\n </div>\n </div>\n </template>\n </div>\n </BentoCardGrid>\n</template>\n\n<style>\n.bento-section {\n --glow-x: 50%;\n --glow-y: 50%;\n --glow-intensity: 0;\n --glow-radius: 200px;\n --glow-color: v-bind(glowColor);\n --border-color: #333;\n --background-dark: #060010;\n --white: hsl(0, 0%, 100%);\n}\n\n.card-responsive {\n grid-template-columns: 1fr;\n width: 90%;\n margin: 0 auto;\n padding: 0.5rem;\n}\n\n@media (min-width: 600px) {\n .card-responsive {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (min-width: 1024px) {\n .card-responsive {\n grid-template-columns: repeat(4, 1fr);\n }\n\n .card-responsive .card:nth-child(3) {\n grid-column: span 2;\n grid-row: span 2;\n }\n\n .card-responsive .card:nth-child(4) {\n grid-column: 1 / span 2;\n grid-row: 2 / span 2;\n }\n\n .card-responsive .card:nth-child(6) {\n grid-column: 4;\n grid-row: 3;\n }\n}\n\n.card--border-glow::after {\n content: '';\n position: absolute;\n inset: 0;\n padding: 6px;\n background: radial-gradient(\n var(--glow-radius) circle at var(--glow-x) var(--glow-y),\n rgba(v-bind(glowColor), calc(var(--glow-intensity) * 0.8)) 0%,\n rgba(v-bind(glowColor), calc(var(--glow-intensity) * 0.4)) 30%,\n transparent 60%\n );\n border-radius: inherit;\n mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n mask-composite: subtract;\n -webkit-mask:\n linear-gradient(#fff 0 0) content-box,\n linear-gradient(#fff 0 0);\n -webkit-mask-composite: xor;\n pointer-events: none;\n transition: opacity 0.3s ease;\n z-index: 1;\n}\n\n.card--border-glow:hover::after {\n opacity: 1;\n}\n\n.card--border-glow:hover {\n box-shadow:\n 0 4px 20px rgba(46, 24, 78, 0.4),\n 0 0 30px rgba(v-bind(glowColor), 0.2);\n}\n\n.particle::before {\n content: '';\n position: absolute;\n top: -2px;\n left: -2px;\n right: -2px;\n bottom: -2px;\n background: rgba(v-bind(glowColor), 0.2);\n border-radius: 50%;\n z-index: -1;\n}\n\n.particle-container:hover {\n box-shadow:\n 0 4px 20px rgba(46, 24, 78, 0.2),\n 0 0 30px rgba(v-bind(glowColor), 0.2);\n}\n\n.text-clamp-1 {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 1;\n line-clamp: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.text-clamp-2 {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n@media (max-width: 599px) {\n .card-responsive {\n grid-template-columns: 1fr;\n width: 90%;\n margin: 0 auto;\n padding: 0.5rem;\n }\n\n .card-responsive .card {\n width: 100%;\n min-height: 180px;\n }\n}\n</style>\n","path":"MagicBento/MagicBento.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Components"]} |