mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-04-21 17:44:39 -06:00
feat: added <BorderGlow /> component
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import code from '@content/Components/BorderGlow/BorderGlow.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const borderGlow = createCodeObject(code, 'Components/BorderGlow', {
|
||||||
|
usage: `<template>
|
||||||
|
<BorderGlow
|
||||||
|
:edgeSensitivity="30"
|
||||||
|
glowColor="40 80 80"
|
||||||
|
backgroundColor="#060010"
|
||||||
|
:borderRadius="28"
|
||||||
|
:glowRadius="40"
|
||||||
|
:glowIntensity="1"
|
||||||
|
:coneSpread="25"
|
||||||
|
:animated="false"
|
||||||
|
:colors="['#c084fc', '#f472b6', '#38bdf8']"
|
||||||
|
>
|
||||||
|
<div class="p-[2em]">
|
||||||
|
<h2>Your Content Here</h2>
|
||||||
|
<p>Hover near the edges to see the glow.</p>
|
||||||
|
</div>
|
||||||
|
</BorderGlow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import BorderGlow from './BorderGlow.vue'
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface BorderGlowProps {
|
||||||
|
className?: string;
|
||||||
|
edgeSensitivity?: number;
|
||||||
|
glowColor?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
borderRadius?: number;
|
||||||
|
glowRadius?: number;
|
||||||
|
glowIntensity?: number;
|
||||||
|
coneSpread?: number;
|
||||||
|
animated?: boolean;
|
||||||
|
colors?: string[];
|
||||||
|
fillOpacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHSL(hslStr: string): { h: number; s: number; l: number } {
|
||||||
|
const match = hslStr.match(/([\d.]+)\s*([\d.]+)%?\s*([\d.]+)%?/);
|
||||||
|
if (!match) return { h: 40, s: 80, l: 80 };
|
||||||
|
return { h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBoxShadow(glowColor: string, intensity: number): string {
|
||||||
|
const { h, s, l } = parseHSL(glowColor);
|
||||||
|
const base = `${h}deg ${s}% ${l}%`;
|
||||||
|
const layers: [number, number, number, number, number, boolean][] = [
|
||||||
|
[0, 0, 0, 1, 100, true],
|
||||||
|
[0, 0, 1, 0, 60, true],
|
||||||
|
[0, 0, 3, 0, 50, true],
|
||||||
|
[0, 0, 6, 0, 40, true],
|
||||||
|
[0, 0, 15, 0, 30, true],
|
||||||
|
[0, 0, 25, 2, 20, true],
|
||||||
|
[0, 0, 50, 2, 10, true],
|
||||||
|
[0, 0, 1, 0, 60, false],
|
||||||
|
[0, 0, 3, 0, 50, false],
|
||||||
|
[0, 0, 6, 0, 40, false],
|
||||||
|
[0, 0, 15, 0, 30, false],
|
||||||
|
[0, 0, 25, 2, 20, false],
|
||||||
|
[0, 0, 50, 2, 10, false]
|
||||||
|
];
|
||||||
|
return layers
|
||||||
|
.map(([x, y, blur, spread, alpha, inset]) => {
|
||||||
|
const a = Math.min(alpha * intensity, 100);
|
||||||
|
return `${inset ? 'inset ' : ''}${x}px ${y}px ${blur}px ${spread}px hsl(${base} / ${a}%)`;
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function easeOutCubic(x: number) {
|
||||||
|
return 1 - Math.pow(1 - x, 3);
|
||||||
|
}
|
||||||
|
function easeInCubic(x: number) {
|
||||||
|
return x * x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnimateOpts {
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
duration?: number;
|
||||||
|
delay?: number;
|
||||||
|
ease?: (t: number) => number;
|
||||||
|
onUpdate: (v: number) => void;
|
||||||
|
onEnd?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function animateValue({
|
||||||
|
start = 0,
|
||||||
|
end = 100,
|
||||||
|
duration = 1000,
|
||||||
|
delay = 0,
|
||||||
|
ease = easeOutCubic,
|
||||||
|
onUpdate,
|
||||||
|
onEnd
|
||||||
|
}: AnimateOpts) {
|
||||||
|
const t0 = performance.now() + delay;
|
||||||
|
function tick() {
|
||||||
|
const elapsed = performance.now() - t0;
|
||||||
|
const t = Math.min(elapsed / duration, 1);
|
||||||
|
onUpdate(start + (end - start) * ease(t));
|
||||||
|
if (t < 1) requestAnimationFrame(tick);
|
||||||
|
else if (onEnd) onEnd();
|
||||||
|
}
|
||||||
|
setTimeout(() => requestAnimationFrame(tick), delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'];
|
||||||
|
const COLOR_MAP = [0, 1, 2, 0, 1, 2, 1];
|
||||||
|
|
||||||
|
function buildMeshGradients(colors: string[]): string[] {
|
||||||
|
const gradients: string[] = [];
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const c = colors[Math.min(COLOR_MAP[i], colors.length - 1)];
|
||||||
|
gradients.push(`radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${c} 0px, transparent 50%)`);
|
||||||
|
}
|
||||||
|
gradients.push(`linear-gradient(${colors[0]} 0 100%)`);
|
||||||
|
return gradients;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<BorderGlowProps>(), {
|
||||||
|
className: '',
|
||||||
|
edgeSensitivity: 30,
|
||||||
|
glowColor: '40 80 80',
|
||||||
|
backgroundColor: '#060010',
|
||||||
|
borderRadius: 28,
|
||||||
|
glowRadius: 40,
|
||||||
|
glowIntensity: 1.0,
|
||||||
|
coneSpread: 25,
|
||||||
|
animated: false,
|
||||||
|
colors: () => ['#c084fc', '#f472b6', '#38bdf8'],
|
||||||
|
fillOpacity: 0.5
|
||||||
|
});
|
||||||
|
|
||||||
|
const cardRef = useTemplateRef<HTMLDivElement>('cardRef');
|
||||||
|
const isHovered = ref(false);
|
||||||
|
const cursorAngle = ref(45);
|
||||||
|
const edgeProximity = ref(0);
|
||||||
|
const sweepActive = ref(false);
|
||||||
|
|
||||||
|
const getCenterOfElement = (el: HTMLElement) => {
|
||||||
|
const { width, height } = el.getBoundingClientRect();
|
||||||
|
return [width / 2, height / 2];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEdgeProximity = (el: HTMLElement, x: number, y: number) => {
|
||||||
|
const [cx, cy] = getCenterOfElement(el);
|
||||||
|
const dx = x - cx;
|
||||||
|
const dy = y - cy;
|
||||||
|
let kx = Infinity;
|
||||||
|
let ky = Infinity;
|
||||||
|
if (dx !== 0) kx = cx / Math.abs(dx);
|
||||||
|
if (dy !== 0) ky = cy / Math.abs(dy);
|
||||||
|
return Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCursorAngle = (el: HTMLElement, x: number, y: number) => {
|
||||||
|
const [cx, cy] = getCenterOfElement(el);
|
||||||
|
const dx = x - cx;
|
||||||
|
const dy = y - cy;
|
||||||
|
if (dx === 0 && dy === 0) return 0;
|
||||||
|
const radians = Math.atan2(dy, dx);
|
||||||
|
let degrees = radians * (180 / Math.PI) + 90;
|
||||||
|
if (degrees < 0) degrees += 360;
|
||||||
|
return degrees;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (e: PointerEvent) => {
|
||||||
|
const card = cardRef.value;
|
||||||
|
if (!card) return;
|
||||||
|
const rect = card.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
edgeProximity.value = getEdgeProximity(card, x, y);
|
||||||
|
cursorAngle.value = getCursorAngle(card, x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.animated],
|
||||||
|
() => {
|
||||||
|
if (!props.animated) return;
|
||||||
|
const angleStart = 110;
|
||||||
|
const angleEnd = 465;
|
||||||
|
sweepActive.value = true;
|
||||||
|
cursorAngle.value = angleStart;
|
||||||
|
|
||||||
|
animateValue({ duration: 500, onUpdate: v => (edgeProximity.value = v / 100) });
|
||||||
|
animateValue({
|
||||||
|
ease: easeInCubic,
|
||||||
|
duration: 1500,
|
||||||
|
end: 50,
|
||||||
|
onUpdate: v => {
|
||||||
|
cursorAngle.value = (angleEnd - angleStart) * (v / 100) + angleStart;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animateValue({
|
||||||
|
ease: easeOutCubic,
|
||||||
|
delay: 1500,
|
||||||
|
duration: 2250,
|
||||||
|
start: 50,
|
||||||
|
end: 100,
|
||||||
|
onUpdate: v => {
|
||||||
|
cursorAngle.value = (angleEnd - angleStart) * (v / 100) + angleStart;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animateValue({
|
||||||
|
ease: easeInCubic,
|
||||||
|
delay: 2500,
|
||||||
|
duration: 1500,
|
||||||
|
start: 100,
|
||||||
|
end: 0,
|
||||||
|
onUpdate: v => (edgeProximity.value = v / 100),
|
||||||
|
onEnd: () => (sweepActive.value = false)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const colorSensitivity = computed(() => props.edgeSensitivity + 20);
|
||||||
|
const isVisible = computed(() => isHovered.value || sweepActive.value);
|
||||||
|
const borderOpacity = computed(() =>
|
||||||
|
isVisible.value
|
||||||
|
? Math.max(0, (edgeProximity.value * 100 - colorSensitivity.value) / (100 - colorSensitivity.value))
|
||||||
|
: 0
|
||||||
|
);
|
||||||
|
const glowOpacity = computed(() =>
|
||||||
|
isVisible.value ? Math.max(0, (edgeProximity.value * 100 - props.edgeSensitivity) / (100 - props.edgeSensitivity)) : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const meshGradients = computed(() => buildMeshGradients(props.colors));
|
||||||
|
const borderBg = computed(() => meshGradients.value.map(g => `${g} border-box`));
|
||||||
|
const fillBg = computed(() => meshGradients.value.map(g => `${g} padding-box`));
|
||||||
|
const angleDeg = computed(() => `${cursorAngle.value.toFixed(3)}deg`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="cardRef"
|
||||||
|
@pointermove="handlePointerMove"
|
||||||
|
@pointerenter="isHovered = true"
|
||||||
|
@pointerleave="isHovered = false"
|
||||||
|
:class="`relative grid isolate border border-white/15 ${props.className}`"
|
||||||
|
:style="{
|
||||||
|
background: props.backgroundColor,
|
||||||
|
borderRadius: props.borderRadius + 'px',
|
||||||
|
transform: 'translate3d(0, 0, 0.01px)',
|
||||||
|
boxShadow:
|
||||||
|
'rgba(0,0,0,0.1) 0 1px 2px, rgba(0,0,0,0.1) 0 2px 4px, rgba(0,0,0,0.1) 0 4px 8px, rgba(0,0,0,0.1) 0 8px 16px, rgba(0,0,0,0.1) 0 16px 32px, rgba(0,0,0,0.1) 0 32px 64px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- mesh gradient border -->
|
||||||
|
<div
|
||||||
|
class="-z-[1] absolute inset-0 rounded-[inherit]"
|
||||||
|
:style="{
|
||||||
|
border: '1px solid transparent',
|
||||||
|
background: [
|
||||||
|
`linear-gradient(${props.backgroundColor} 0 100%) padding-box`,
|
||||||
|
'linear-gradient(rgb(255 255 255 / 0%) 0% 100%) border-box',
|
||||||
|
...borderBg
|
||||||
|
].join(', '),
|
||||||
|
opacity: borderOpacity,
|
||||||
|
maskImage: `conic-gradient(from ${angleDeg} at center, black ${props.coneSpread}%, transparent ${
|
||||||
|
props.coneSpread + 15
|
||||||
|
}%, transparent ${100 - props.coneSpread - 15}%, black ${100 - props.coneSpread}%)`,
|
||||||
|
WebkitMaskImage: `conic-gradient(from ${angleDeg} at center, black ${props.coneSpread}%, transparent ${
|
||||||
|
props.coneSpread + 15
|
||||||
|
}%, transparent ${100 - props.coneSpread - 15}%, black ${100 - props.coneSpread}%)`,
|
||||||
|
transition: isVisible ? 'opacity 0.25s ease-out' : 'opacity 0.75s ease-in-out'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- mesh gradient fill -->
|
||||||
|
<div
|
||||||
|
class="-z-[1] absolute inset-0 rounded-[inherit]"
|
||||||
|
:style="{
|
||||||
|
border: '1px solid transparent',
|
||||||
|
background: fillBg.join(', '),
|
||||||
|
maskImage: [
|
||||||
|
'linear-gradient(to bottom, black, black)',
|
||||||
|
'radial-gradient(ellipse at 50% 50%, black 40%, transparent 65%)',
|
||||||
|
'radial-gradient(ellipse at 66% 66%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 66% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 66%, black 5%, transparent 40%)',
|
||||||
|
`conic-gradient(from ${angleDeg} at center, transparent 5%, black 15%, black 85%, transparent 95%)`
|
||||||
|
].join(', '),
|
||||||
|
WebkitMaskImage: [
|
||||||
|
'linear-gradient(to bottom, black, black)',
|
||||||
|
'radial-gradient(ellipse at 50% 50%, black 40%, transparent 65%)',
|
||||||
|
'radial-gradient(ellipse at 66% 66%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 66% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 66%, black 5%, transparent 40%)',
|
||||||
|
`conic-gradient(from ${angleDeg} at center, transparent 5%, black 15%, black 85%, transparent 95%)`
|
||||||
|
].join(', '),
|
||||||
|
maskComposite: 'subtract, add, add, add, add, add',
|
||||||
|
WebkitMaskComposite: 'source-out, source-over, source-over, source-over, source-over, source-over',
|
||||||
|
opacity: borderOpacity * props.fillOpacity,
|
||||||
|
mixBlendMode: 'soft-light',
|
||||||
|
transition: isVisible ? 'opacity 0.25s ease-out' : 'opacity 0.75s ease-in-out'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- outer glow -->
|
||||||
|
<span
|
||||||
|
class="z-[1] absolute rounded-[inherit] pointer-events-none"
|
||||||
|
:style="{
|
||||||
|
inset: `-${props.glowRadius}px`,
|
||||||
|
maskImage: `conic-gradient(from ${angleDeg} at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%)`,
|
||||||
|
WebkitMaskImage: `conic-gradient(from ${angleDeg} at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%)`,
|
||||||
|
opacity: glowOpacity,
|
||||||
|
mixBlendMode: 'plus-lighter',
|
||||||
|
transition: isVisible ? 'opacity 0.25s ease-out' : 'opacity 0.75s ease-in-out'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="absolute rounded-[inherit]"
|
||||||
|
:style="{
|
||||||
|
inset: `${props.glowRadius}px`,
|
||||||
|
boxShadow: buildBoxShadow(props.glowColor, props.glowIntensity)
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- content -->
|
||||||
|
<div class="z-[1] relative flex flex-col overflow-auto">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="h-[500px] overflow-hidden demo-container">
|
||||||
|
<BorderGlow v-bind="props">
|
||||||
|
<div class="flex flex-col justify-center items-start p-[2em] min-h-[200px]">
|
||||||
|
<i class="pi pi-sparkles" style="font-size: 34px; margin-bottom: 12px"></i>
|
||||||
|
<p class="font-semibold text-[1.4rem] tracking-[-0.5px]">Hover Near the Edges</p>
|
||||||
|
<p class="mt-1 max-w-[40ch] text-[#a1a1aa] text-[14px]">
|
||||||
|
Move your cursor close to the card border to see the colored glow effect follow your pointer direction.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</BorderGlow>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewSlider title="Edge Sensitivity" :min="0" :max="80" :step="1" v-model="edgeSensitivity" />
|
||||||
|
<PreviewSlider title="Border Radius" :min="0" :max="50" :step="1" v-model="borderRadius" />
|
||||||
|
<PreviewSlider title="Glow Radius" :min="10" :max="80" :step="1" v-model="glowRadius" />
|
||||||
|
<PreviewSlider title="Glow Intensity" :min="0.1" :max="3" :step="0.1" v-model="glowIntensity" />
|
||||||
|
<PreviewSlider title="Cone Spread" :min="5" :max="45" :step="1" v-model="coneSpread" />
|
||||||
|
<PreviewSwitch title="Animated Intro" v-model="animated" />
|
||||||
|
<PreviewColor title="Background" v-model="backgroundColor" class="mb-2" />
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-0">
|
||||||
|
<span class="block font-medium text-sm">Gradient Colors</span>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2 px-1 pt-1">
|
||||||
|
<label
|
||||||
|
v-for="(color, index) in colors"
|
||||||
|
:key="index"
|
||||||
|
class="border-[#222] border-2 rounded-md w-12 h-12 overflow-hidden cursor-pointer"
|
||||||
|
:style="{ backgroundColor: color }"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
:value="color"
|
||||||
|
@input="updateColor(index, ($event.target as HTMLInputElement).value)"
|
||||||
|
class="opacity-0 w-full h-full cursor-pointer"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="borderGlow" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="borderGlow.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { borderGlow } from '@/constants/code/Components/borderGlowCode';
|
||||||
|
import BorderGlow from '@/content/Components/BorderGlow/BorderGlow.vue';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const colors = ref(['#c084fc', '#f472b6', '#38bdf8']);
|
||||||
|
const edgeSensitivity = ref(30);
|
||||||
|
const glowColor = ref('40 80 80');
|
||||||
|
const backgroundColor = ref('#070F07');
|
||||||
|
const borderRadius = ref(28);
|
||||||
|
const glowRadius = ref(40);
|
||||||
|
const glowIntensity = ref(1.0);
|
||||||
|
const coneSpread = ref(25);
|
||||||
|
const animated = ref(false);
|
||||||
|
|
||||||
|
const props = computed(() => ({
|
||||||
|
edgeSensitivity: edgeSensitivity.value,
|
||||||
|
glowColor: glowColor.value,
|
||||||
|
backgroundColor: backgroundColor.value,
|
||||||
|
borderRadius: borderRadius.value,
|
||||||
|
glowRadius: glowRadius.value,
|
||||||
|
glowIntensity: glowIntensity.value,
|
||||||
|
coneSpread: coneSpread.value,
|
||||||
|
animated: animated.value,
|
||||||
|
colors: colors.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
const updateColor = (index: number, newColor: string) => {
|
||||||
|
const newColors = [...colors.value];
|
||||||
|
newColors[index] = newColor;
|
||||||
|
colors.value = newColors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{ name: 'children', type: 'slot', default: '-', description: 'Content rendered inside the card.' },
|
||||||
|
{ name: 'className', type: 'string', default: '""', description: 'Additional CSS classes for the outer wrapper.' },
|
||||||
|
{
|
||||||
|
name: 'edgeSensitivity',
|
||||||
|
type: 'number',
|
||||||
|
default: '30',
|
||||||
|
description: 'How close the pointer must be to the edge for the glow to appear (0-100).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glowColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"40 80 80"',
|
||||||
|
description: 'HSL values for the glow color, as "H S L" (e.g. "40 80 80").'
|
||||||
|
},
|
||||||
|
{ name: 'backgroundColor', type: 'string', default: '"#070F07"', description: 'Background color of the card.' },
|
||||||
|
{ name: 'borderRadius', type: 'number', default: '28', description: 'Corner radius of the card in pixels.' },
|
||||||
|
{
|
||||||
|
name: 'glowRadius',
|
||||||
|
type: 'number',
|
||||||
|
default: '40',
|
||||||
|
description: 'How far the outer glow extends beyond the card in pixels.'
|
||||||
|
},
|
||||||
|
{ name: 'glowIntensity', type: 'number', default: '1.0', description: 'Multiplier for glow opacity (0.1-3.0).' },
|
||||||
|
{
|
||||||
|
name: 'coneSpread',
|
||||||
|
type: 'number',
|
||||||
|
default: '25',
|
||||||
|
description: 'Width of the directional cone mask as a percentage (5-45).'
|
||||||
|
},
|
||||||
|
{ name: 'animated', type: 'boolean', default: 'false', description: 'Play an intro sweep animation on mount.' },
|
||||||
|
{
|
||||||
|
name: 'colors',
|
||||||
|
type: 'string[]',
|
||||||
|
default: '[...]',
|
||||||
|
description: 'Array of 3 hex colors for the mesh gradient border, distributed across positions.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user