@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<template> |
||||
<p |
||||
:style="styleVar" |
||||
:class=" |
||||
cn( |
||||
'radiant-animation bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--radiant-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]', |
||||
'bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent ', |
||||
$props.class, |
||||
) |
||||
" |
||||
> |
||||
<slot /> |
||||
</p> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { cn } from '@/lib/utils' |
||||
import { computed } from 'vue' |
||||
|
||||
const props = defineProps({ |
||||
duration: { |
||||
type: Number, |
||||
default: 1, |
||||
}, |
||||
radiantWidth: { |
||||
type: Number, |
||||
default: 500, |
||||
}, |
||||
class: String, |
||||
}) |
||||
|
||||
const styleVar = computed(() => { |
||||
return { |
||||
'--radiant-anim-duration': `${props.duration}s`, |
||||
'--radiant-width': `${props.radiantWidth}px`, |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
@keyframes radiant { |
||||
0%, |
||||
90%, |
||||
100% { |
||||
background-position: calc(-100% - var(--radiant-width)) 0; |
||||
} |
||||
30%, |
||||
60% { |
||||
background-position: calc(100% + var(--radiant-width)) 0; |
||||
} |
||||
} |
||||
|
||||
.radiant-animation { |
||||
animation: radiant var(--radiant-anim-duration) infinite; |
||||
} |
||||
</style> |
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
<template> |
||||
<span |
||||
v-if="!props.highlightText" |
||||
:class="cn('inline-block px-1 pb-1 highlight-all', props.class)" |
||||
> |
||||
{{ props.text }} |
||||
</span> |
||||
<span v-else> |
||||
{{ beforeText }} |
||||
<div :class="cn('inline-block highlight-all', props.class)"> |
||||
{{ props.highlightText }} |
||||
</div> |
||||
{{ afterText }} |
||||
</span> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { computed, type HTMLAttributes } from 'vue' |
||||
import { cn } from '@/lib/utils' |
||||
|
||||
interface Props { |
||||
delay?: number |
||||
duration?: number |
||||
class?: HTMLAttributes['class'] |
||||
textEndColor?: string |
||||
highlightText?: string |
||||
text: string |
||||
} |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
delay: 0, |
||||
duration: 2000, |
||||
endColor: 'inherit', |
||||
}) |
||||
|
||||
const delayMs = computed(() => `${props.delay}ms`) |
||||
const durationMs = computed(() => `${props.duration}ms`) |
||||
|
||||
const beforeText = computed(() => { |
||||
if (!props.highlightText || !props.text) return '' |
||||
|
||||
const index = props.text.indexOf(props.highlightText) |
||||
if (index === -1) return props.text |
||||
|
||||
return props.text.substring(0, index) |
||||
}) |
||||
|
||||
const afterText = computed(() => { |
||||
if (!props.highlightText || !props.text) return '' |
||||
|
||||
const index = props.text.indexOf(props.highlightText) |
||||
if (index === -1) return '' |
||||
|
||||
return props.text.substring(index + props.highlightText.length) |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
@keyframes background-expand { |
||||
0% { |
||||
background-size: 0% 100%; |
||||
} |
||||
100% { |
||||
background-size: 100% 100%; |
||||
} |
||||
} |
||||
|
||||
@keyframes text-color-change { |
||||
0% { |
||||
color: inherit; |
||||
} |
||||
100% { |
||||
color: v-bind(textEndColor); |
||||
} |
||||
} |
||||
|
||||
span { |
||||
background-size: 0% 100%; |
||||
background-repeat: no-repeat; |
||||
background-position: left center; |
||||
} |
||||
|
||||
.highlight-all, |
||||
.highlight-text { |
||||
animation: |
||||
background-expand v-bind(durationMs) ease-in-out v-bind(delayMs) forwards, |
||||
text-color-change v-bind(durationMs) ease-in-out v-bind(delayMs) forwards; |
||||
} |
||||
</style> |
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
<template> |
||||
<div |
||||
:class=" |
||||
cn( |
||||
'pointer-events-none absolute -inset-px hidden rounded-[inherit] border opacity-0 transition-opacity', |
||||
glow && 'opacity-100', |
||||
variant === 'white' && 'border-white', |
||||
disabled && '!block', |
||||
) |
||||
" |
||||
/> |
||||
<div |
||||
ref="containerRef" |
||||
:style="containerStyles" |
||||
:class=" |
||||
cn( |
||||
'pointer-events-none absolute inset-0 rounded-[inherit] opacity-100 transition-opacity', |
||||
glow && 'opacity-100', |
||||
blur > 0 && 'blur-[var(--blur)]', |
||||
props.class, |
||||
disabled && '!hidden', |
||||
) |
||||
" |
||||
> |
||||
<div |
||||
:class=" |
||||
cn( |
||||
'glow', |
||||
'rounded-[inherit]', |
||||
`after:content-[''] after:rounded-[inherit] after:absolute after:inset-[calc(-1*var(--glowingeffect-border-width))]`, |
||||
'after:[border:var(--glowingeffect-border-width)_solid_transparent]', |
||||
'after:[background:var(--gradient)] after:[background-attachment:fixed]', |
||||
'after:opacity-[var(--active)] after:transition-opacity after:duration-300', |
||||
'after:[mask-clip:padding-box,border-box]', |
||||
'after:[mask-composite:intersect]', |
||||
'after:[mask-image:linear-gradient(#0000,#0000),conic-gradient(from_calc((var(--start)-var(--spread))*1deg),#00000000_0deg,#fff,#00000000_calc(var(--spread)*2deg))]', |
||||
) |
||||
" |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { cn } from '@/lib/utils' |
||||
import { templateRef } from '@vueuse/core' |
||||
import { animate } from 'motion-v' |
||||
import type { HTMLAttributes } from 'vue' |
||||
|
||||
interface Props { |
||||
blur?: number |
||||
inactiveZone?: number |
||||
proximity?: number |
||||
spread?: number |
||||
variant?: 'default' | 'white' |
||||
glow?: boolean |
||||
class?: HTMLAttributes['class'] |
||||
disabled?: boolean |
||||
movementDuration?: number |
||||
borderWidth?: number |
||||
} |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
blur: 0, |
||||
inactiveZone: 0.7, |
||||
proximity: 0, |
||||
spread: 20, |
||||
variant: 'default', |
||||
glow: false, |
||||
movementDuration: 2, |
||||
borderWidth: 1, |
||||
disabled: true, |
||||
}) |
||||
|
||||
const containerRef = templateRef('containerRef') |
||||
const lastPosition = ref({ |
||||
x: 0, |
||||
y: 0, |
||||
}) |
||||
const animationFrame = ref(0) |
||||
|
||||
const containerStyles = computed(() => { |
||||
return { |
||||
'--blur': `${props.blur}px`, |
||||
'--spread': props.spread, |
||||
'--start': '0', |
||||
'--active': '0', |
||||
'--glowingeffect-border-width': `${props.borderWidth}px`, |
||||
'--repeating-conic-gradient-times': '5', |
||||
'--gradient': |
||||
props.variant === 'white' |
||||
? `repeating-conic-gradient( |
||||
from 236.84deg at 50% 50%, |
||||
var(--black), |
||||
var(--black) calc(25% / var(--repeating-conic-gradient-times)) |
||||
)` |
||||
: `radial-gradient(circle, #dd7bbb 10%, #dd7bbb00 20%), |
||||
radial-gradient(circle at 40% 40%, #d79f1e 5%, #d79f1e00 15%), |
||||
radial-gradient(circle at 60% 60%, #5a922c 10%, #5a922c00 20%), |
||||
radial-gradient(circle at 40% 60%, #4c7894 10%, #4c789400 20%), |
||||
repeating-conic-gradient( |
||||
from 236.84deg at 50% 50%, |
||||
#dd7bbb 0%, |
||||
#d79f1e calc(25% / var(--repeating-conic-gradient-times)), |
||||
#5a922c calc(50% / var(--repeating-conic-gradient-times)), |
||||
#4c7894 calc(75% / var(--repeating-conic-gradient-times)), |
||||
#dd7bbb calc(100% / var(--repeating-conic-gradient-times)) |
||||
)`, |
||||
} |
||||
}) |
||||
|
||||
onMounted(() => { |
||||
if (props.disabled) return |
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true }) |
||||
document.body.addEventListener('pointermove', handlePointerMove, { |
||||
passive: true, |
||||
}) |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
if (animationFrame.value) { |
||||
cancelAnimationFrame(animationFrame.value) |
||||
} |
||||
|
||||
window.removeEventListener('scroll', handleScroll) |
||||
document.body.removeEventListener('pointermove', handlePointerMove) |
||||
}) |
||||
|
||||
function handlePointerMove(e: PointerEvent) { |
||||
handleMove(e) |
||||
} |
||||
|
||||
function handleScroll() { |
||||
handleMove() |
||||
} |
||||
|
||||
function handleMove(e?: MouseEvent | PointerEvent | { x: number; y: number }) { |
||||
if (!containerRef.value) return |
||||
|
||||
if (animationFrame.value) { |
||||
cancelAnimationFrame(animationFrame.value) |
||||
} |
||||
|
||||
animationFrame.value = requestAnimationFrame(() => { |
||||
const element = containerRef.value |
||||
|
||||
if (!element) return |
||||
|
||||
const { left, top, width, height } = element.getBoundingClientRect() |
||||
|
||||
const mouseX = e?.x ?? lastPosition.value.x |
||||
const mouseY = e?.y ?? lastPosition.value.y |
||||
|
||||
if (e) { |
||||
lastPosition.value = { x: mouseX, y: mouseY } |
||||
} |
||||
|
||||
const center = [left + width * 0.5, top + height * 0.5] |
||||
const distanceFromCenter = Math.hypot(mouseX - center[0], mouseY - center[1]) |
||||
const inactiveRadius = 0.5 * Math.min(width, height) * props.inactiveZone |
||||
|
||||
if (distanceFromCenter < inactiveRadius) { |
||||
element.style.setProperty('--active', '0') |
||||
return |
||||
} |
||||
|
||||
const isActive = |
||||
mouseX > left - props.proximity && |
||||
mouseX < left + width + props.proximity && |
||||
mouseY > top - props.proximity && |
||||
mouseY < top + height + props.proximity |
||||
|
||||
element.style.setProperty('--active', isActive ? '1' : '0') |
||||
|
||||
if (!isActive) return |
||||
|
||||
const currentAngle = parseFloat(element.style.getPropertyValue('--start')) || 0 |
||||
let targetAngle = (180 * Math.atan2(mouseY - center[1], mouseX - center[0])) / Math.PI + 90 |
||||
|
||||
const angleDiff = ((targetAngle - currentAngle + 180) % 360) - 180 |
||||
const newAngle = currentAngle + angleDiff |
||||
|
||||
animate(currentAngle, newAngle, { |
||||
duration: props.movementDuration, |
||||
ease: [0.16, 1, 0.3, 1], |
||||
onUpdate: value => { |
||||
element.style.setProperty('--start', String(value)) |
||||
}, |
||||
}) |
||||
}) |
||||
} |
||||
</script> |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export { default as GlowingEffect } from './GlowingEffect.vue'; |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
<template> |
||||
<div class="flex justify-center"> |
||||
<div |
||||
v-for="(letter, index) in letters" |
||||
:key="letter" |
||||
> |
||||
<Motion |
||||
as="h1" |
||||
:initial="pullupVariant.initial" |
||||
:animate="pullupVariant.animate" |
||||
:transition="{ |
||||
delay: index * (props.delay ? props.delay : 0.05), |
||||
}" |
||||
:class=" |
||||
cn( |
||||
'font-display text-center text-4xl font-bold tracking-[-0.02em] text-black drop-shadow-sm md:text-4xl md:leading-[5rem]', |
||||
props.class, |
||||
) |
||||
" |
||||
> |
||||
<span v-if="letter === ' '"> </span> |
||||
<span v-else>{{ letter }}</span> |
||||
</Motion> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { Motion } from 'motion-v'; |
||||
import { cn } from '@/lib/utils'; |
||||
|
||||
interface LetterPullupProps { |
||||
class?: string; |
||||
words: string; |
||||
delay?: number; |
||||
} |
||||
|
||||
const props = defineProps<LetterPullupProps>(); |
||||
|
||||
const letters = props.words.split(''); |
||||
|
||||
const pullupVariant = { |
||||
initial: { y: 100, opacity: 0 }, |
||||
animate: { |
||||
y: 0, |
||||
opacity: 1, |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export { default as LetterPullup } from './LetterPullup.vue'; |
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
<template> |
||||
<div :class="props.class"> |
||||
<span class="relative inline-block"> |
||||
<template v-if="isClientMounted" v-for="sparkle in sparkles" :key="sparkle.id"> |
||||
<motion.svg |
||||
:initial="{ opacity: 0, scale: 0, rotate: 75 }" |
||||
:animate="{ |
||||
opacity: [0, 1, 0], |
||||
scale: [0, sparkle.scale, 0], |
||||
rotate: [75, 120, 150], |
||||
}" |
||||
:transition="{ |
||||
duration: 0.8, |
||||
repeat: Infinity, |
||||
delay: sparkle.delay, |
||||
}" |
||||
class="pointer-events-none absolute z-20" |
||||
:style="{ |
||||
left: sparkle.x, |
||||
top: sparkle.y, |
||||
opacity: 0, |
||||
}" |
||||
width="21" |
||||
height="21" |
||||
viewBox="0 0 21 21" |
||||
> |
||||
<path |
||||
d="M9.82531 0.843845C10.0553 0.215178 10.9446 0.215178 11.1746 0.843845L11.8618 2.72026C12.4006 4.19229 12.3916 6.39157 13.5 7.5C14.6084 8.60843 16.8077 8.59935 18.2797 9.13822L20.1561 9.82534C20.7858 10.0553 20.7858 10.9447 20.1561 11.1747L18.2797 11.8618C16.8077 12.4007 14.6084 12.3916 13.5 13.5C12.3916 14.6084 12.4006 16.8077 11.8618 18.2798L11.1746 20.1562C10.9446 20.7858 10.0553 20.7858 9.82531 20.1562L9.13819 18.2798C8.59932 16.8077 8.60843 14.6084 7.5 13.5C6.39157 12.3916 4.19225 12.4007 2.72023 11.8618L0.843814 11.1747C0.215148 10.9447 0.215148 10.0553 0.843814 9.82534L2.72023 9.13822C4.19225 8.59935 6.39157 8.60843 7.5 7.5C8.60843 6.39157 8.59932 4.19229 9.13819 2.72026L9.82531 0.843845Z" |
||||
:fill="sparkle.color" |
||||
/> |
||||
</motion.svg> |
||||
</template> |
||||
{{ text }} |
||||
</span> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { motion } from 'motion-v' |
||||
import { ref, onMounted, onUnmounted } from 'vue' |
||||
|
||||
interface Sparkle { |
||||
id: string |
||||
x: string |
||||
y: string |
||||
color: string |
||||
delay: number |
||||
scale: number |
||||
lifespan: number |
||||
} |
||||
|
||||
interface Props { |
||||
text: string |
||||
sparklesCount?: number |
||||
colors?: { |
||||
first: string |
||||
second: string |
||||
} |
||||
class?: string |
||||
} |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
sparklesCount: 6, |
||||
colors: () => ({ first: '#9E7AFF', second: '#FE8BBB' }), |
||||
}) |
||||
|
||||
const sparkles = ref<Sparkle[]>([]) |
||||
const isClientMounted = ref(false) |
||||
|
||||
// Generate a new sparkle with randomized properties |
||||
function generateStar(): Sparkle { |
||||
const starX = `${Math.random() * 100}%` |
||||
const starY = `${Math.random() * 100}%` |
||||
const color = Math.random() > 0.5 ? props.colors.first : props.colors.second |
||||
const delay = Math.random() * 2 |
||||
const scale = Math.random() * 1 + 0.3 |
||||
const lifespan = Math.random() * 10 + 5 |
||||
const id = `${starX}-${starY}-${Date.now()}` |
||||
return { id, x: starX, y: starY, color, delay, scale, lifespan } |
||||
} |
||||
|
||||
// Initialize sparkles array with random stars |
||||
function initializeStars() { |
||||
sparkles.value = Array.from({ length: props.sparklesCount }, generateStar) |
||||
} |
||||
|
||||
// Update sparkles - regenerate dead ones and update lifespans |
||||
function updateStars() { |
||||
// 只在客户端运行 |
||||
sparkles.value = sparkles.value.map(star => { |
||||
if (star.lifespan <= 0) { |
||||
return generateStar() |
||||
} else { |
||||
return { ...star, lifespan: star.lifespan - 0.1 } |
||||
} |
||||
}) |
||||
} |
||||
|
||||
let interval: number | undefined |
||||
|
||||
// Start animation loop |
||||
onMounted(() => { |
||||
isClientMounted.value = true |
||||
initializeStars() |
||||
if (typeof window !== 'undefined') { |
||||
interval = window.setInterval(updateStars, 100) // 每100ms更新一次 |
||||
} |
||||
}) |
||||
|
||||
// Cleanup on unmount |
||||
onUnmounted(() => { |
||||
if (interval) { |
||||
clearInterval(interval) |
||||
} |
||||
}) |
||||
</script> |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export { default as SparklesText } from './SparklesText.vue'; |
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
<template> |
||||
<div :class="cn('leading-snug tracking-wide', props.class)"> |
||||
<div ref="scope"> |
||||
<span |
||||
v-for="(word, idx) in wordsArray" |
||||
:key="word + idx" |
||||
class="inline-block" |
||||
:style="spanStyle" |
||||
> |
||||
{{ word }} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { computed, type HTMLAttributes, onMounted, ref } from 'vue'; |
||||
|
||||
import { cn } from '@/lib/utils'; |
||||
|
||||
const props = withDefaults( |
||||
defineProps<{ |
||||
words: string; |
||||
filter?: boolean; |
||||
duration?: number; |
||||
delay?: number; |
||||
class: HTMLAttributes["class"]; |
||||
}>(), |
||||
{ duration: 0.7, delay: 0, filter: true }, |
||||
); |
||||
|
||||
const scope = ref(null); |
||||
const wordsArray = computed(() => props.words.split(' ')); |
||||
|
||||
const spanStyle = computed(() => ({ |
||||
opacity: 0, |
||||
filter: props.filter ? 'blur(10px)' : 'none', |
||||
transition: `opacity ${props.duration}s, filter ${props.duration}s`, |
||||
})); |
||||
|
||||
onMounted(() => { |
||||
if (scope.value) { |
||||
const spans = (scope.value as HTMLElement).querySelectorAll('span'); |
||||
|
||||
setTimeout(() => { |
||||
spans.forEach((span: HTMLElement, index: number) => { |
||||
setTimeout(() => { |
||||
span.style.opacity = '1'; |
||||
span.style.filter = props.filter ? 'blur(0px)' : 'none'; |
||||
}, index * 200); |
||||
}); |
||||
}, props.delay); |
||||
} |
||||
}); |
||||
</script> |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export { default as TextGenerateEffect } from './TextGenerateEffect.vue'; |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
export { default as Vortex } from './Vortex.vue'; |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
<template> |
||||
<ul |
||||
class="grid grid-cols-1 grid-rows-none gap-4 overflow-auto xl:max-h-[56rem] xl:grid-rows-2 lg:gap-4 md:grid-cols-12 md:grid-rows-3" |
||||
> |
||||
<li |
||||
v-for="item in gridItems" |
||||
:key="item.title" |
||||
:class="cn('min-h-[14rem] list-none', item.area)" |
||||
> |
||||
<div class="rounded-2.5xl relative h-full border p-2 md:rounded-3xl md:p-3"> |
||||
<GlowingEffect |
||||
:spread="40" |
||||
:glow="true" |
||||
:disabled="false" |
||||
:proximity="64" |
||||
:inactive-zone="0.01" |
||||
/> |
||||
<div |
||||
class="border-0.75 relative flex h-full flex-col justify-between gap-6 overflow-hidden rounded-xl p-6 md:p-6 dark:shadow-[0px_0px_27px_0px_#2D2D2D]" |
||||
> |
||||
<div class="relative flex flex-1 flex-col justify-between gap-3"> |
||||
<div class="w-fit rounded-lg border border-gray-600 p-2"> |
||||
<Icon class="size-4 text-black dark:text-neutral-500" :name="item.icon"></Icon> |
||||
</div> |
||||
<div class="space-y-3"> |
||||
<h3 |
||||
class="-tracking-4 text-balance pt-0.5 font-sans text-xl/[1.375rem] font-semibold text-black md:text-2xl/[1.875rem] dark:text-white" |
||||
> |
||||
{{ item.title }} |
||||
</h3> |
||||
<h2 |
||||
class="font-sans text-sm/[1.125rem] text-black md:text-base/[1.375rem] dark:text-neutral-400 [&_b]:md:font-semibold [&_strong]:md:font-semibold" |
||||
> |
||||
{{ item.description }} |
||||
</h2> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</li> |
||||
</ul> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import GlowingEffect from '@/components/ui/glowing-effect/GlowingEffect.vue' |
||||
import { cn } from '@/lib/utils' |
||||
|
||||
const gridItems = [ |
||||
{ |
||||
area: 'md:[grid-area:1/1/2/7] xl:[grid-area:1/1/2/5]', |
||||
icon: 'lucide:box', |
||||
title: 'Unbox Endless Possibilities', |
||||
description: |
||||
'Open up Inspira UI to discover so many features, you’ll wonder if you’ve wandered into a magical subscription for infinite goodies.', |
||||
}, |
||||
{ |
||||
area: 'md:[grid-area:1/7/2/13] xl:[grid-area:2/1/3/5]', |
||||
icon: 'lucide:settings', |
||||
title: 'Crank the Dials to Eleven', |
||||
description: |
||||
'We packed Inspira UI with enough customizable settings to keep you tweaking forever. If it’s broken, you probably forgot to flip one more switch!', |
||||
}, |
||||
{ |
||||
area: 'md:[grid-area:2/1/3/7] lg:[grid-area:1/5/3/8]', |
||||
icon: 'lucide:music', |
||||
title: 'Dance Your Way to Better UI', |
||||
description: |
||||
'Forget dull interfaces—Inspira UI brings your Vue and Nuxt apps to life with animations so smooth, you’ll wonder!', |
||||
}, |
||||
{ |
||||
area: 'md:[grid-area:2/7/3/13] xl:[grid-area:1/8/2/13]', |
||||
icon: 'lucide:sparkles', |
||||
title: 'Spark a Little Magic', |
||||
description: |
||||
'Make your interface shine brighter than your future. Inspira UI turns that dull design into an enchanting experience—fairy dust included!', |
||||
}, |
||||
{ |
||||
area: 'md:[grid-area:3/1/4/13] xl:[grid-area:2/8/3/13]', |
||||
icon: 'lucide:search', |
||||
title: 'Seek and You Shall Find', |
||||
description: |
||||
'Our search is so advanced it might unearth your lost socks. Just don’t blame us when you realize they don’t match!', |
||||
}, |
||||
] |
||||
</script> |
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 856 KiB |
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 862 KiB |
Before Width: | Height: | Size: 862 KiB |
After Width: | Height: | Size: 3.1 MiB |
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 916 KiB |
Before Width: | Height: | Size: 916 KiB |
After Width: | Height: | Size: 3.2 MiB |
After Width: | Height: | Size: 400 KiB |
After Width: | Height: | Size: 148 KiB |
After Width: | Height: | Size: 475 KiB |
After Width: | Height: | Size: 201 KiB |