Component Boom

This commit is contained in:
David Haz
2025-07-10 15:36:38 +03:00
parent a4982577ad
commit 9b3465b04d
135 changed files with 16697 additions and 60 deletions

View File

@@ -0,0 +1,197 @@
<template>
<div class="animated-content-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative py-6 overflow-hidden">
<RefreshButton @click="forceRerender" />
<div :key="key" class="flex justify-center items-center h-96">
<AnimatedContent :direction="direction" :delay="delay" :distance="distance" :reverse="reverse"
:duration="duration" :ease="ease" :initial-opacity="initialOpacity" :animate-opacity="animateOpacity"
:scale="scale" :threshold="threshold" @complete="() => console.log('✅ Animation Complete!')">
<div class="demo-content">
<h4>Animated Content</h4>
<p>It will animate in when it enters the viewport.</p>
</div>
</AnimatedContent>
</div>
</div>
<Customize>
<PreviewSelect title="Animation Direction" v-model="direction" :options="directionOptions"
@update:model-value="(val) => { direction = val as 'vertical' | 'horizontal'; forceRerender(); }" />
<PreviewSelect title="Easing Function" v-model="ease" :options="easeOptions"
@update:model-value="(val) => { ease = val as string; forceRerender(); }" />
<PreviewSlider title="Distance" v-model="distance" :min="50" :max="300" :step="10"
@update:model-value="forceRerender" />
<PreviewSlider title="Duration" v-model="duration" :min="0.1" :max="3" :step="0.1" value-unit="s"
@update:model-value="forceRerender" />
<PreviewSlider title="Delay" v-model="delay" :min="0" :max="2" :step="0.1" value-unit="s"
@update:model-value="forceRerender" />
<PreviewSlider title="Initial Opacity" v-model="initialOpacity" :min="0" :max="1" :step="0.1"
@update:model-value="forceRerender" />
<PreviewSlider title="Initial Scale" v-model="scale" :min="0.1" :max="2" :step="0.1"
@update:model-value="forceRerender" />
<PreviewSlider title="Threshold" v-model="threshold" :min="0.1" :max="1" :step="0.1"
@update:model-value="forceRerender" />
<PreviewSwitch title="Reverse Direction" v-model="reverse" @update:model-value="forceRerender" />
<PreviewSwitch title="Animate Opacity" v-model="animateOpacity" @update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="animatedContent" />
</template>
<template #cli>
<CliInstallation :command="animatedContent.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewSelect from '../../components/common/PreviewSelect.vue'
import RefreshButton from '../../components/common/RefreshButton.vue'
import AnimatedContent from '../../content/Animations/AnimatedContent/AnimatedContent.vue'
import { animatedContent } from '@/constants/code/Animations/animatedContentCode'
import { useForceRerender } from '@/composables/useForceRerender'
const { rerenderKey: key, forceRerender } = useForceRerender()
const direction = ref<'vertical' | 'horizontal'>('vertical')
const distance = ref(100)
const delay = ref(0)
const reverse = ref(false)
const duration = ref(0.8)
const ease = ref('power3.out')
const initialOpacity = ref(0)
const animateOpacity = ref(true)
const scale = ref(1)
const threshold = ref(0.1)
const directionOptions = [
{ label: 'Vertical', value: 'vertical' },
{ label: 'Horizontal', value: 'horizontal' }
]
const easeOptions = [
{ label: 'Power3 Out', value: 'power3.out' },
{ label: 'Bounce Out', value: 'bounce.out' },
{ label: 'Elastic Out', value: 'elastic.out(1, 0.3)' }
]
const propData = [
{
name: 'distance',
type: 'number',
default: '100',
description: 'Distance (in pixels) the component moves during animation.'
},
{
name: 'direction',
type: '"vertical" | "horizontal"',
default: '"vertical"',
description: 'Animation direction. Can be "vertical" or "horizontal".'
},
{
name: 'reverse',
type: 'boolean',
default: 'false',
description: 'Whether the animation moves in the reverse direction.'
},
{
name: 'duration',
type: 'number',
default: '0.8',
description: 'Duration of the animation in seconds.'
},
{
name: 'ease',
type: 'string | function',
default: '"power3.out"',
description: 'GSAP easing function for the animation.'
},
{
name: 'initialOpacity',
type: 'number',
default: '0',
description: 'Initial opacity before animation begins.'
},
{
name: 'animateOpacity',
type: 'boolean',
default: 'true',
description: 'Whether to animate opacity during transition.'
},
{
name: 'scale',
type: 'number',
default: '1',
description: 'Initial scale of the component.'
},
{
name: 'threshold',
type: 'number',
default: '0.1',
description: 'Intersection threshold to trigger animation (0-1).'
},
{
name: 'delay',
type: 'number',
default: '0',
description: 'Delay before animation starts (in seconds).'
},
{
name: 'className',
type: 'string',
default: '""',
description: 'Additional CSS classes for styling.'
}
]
</script>
<style scoped>
.demo-content {
text-align: center;
padding: 2rem;
border: 1px solid #ffffff1c;
border-radius: 12px;
background: rgba(255, 255, 255, 0.02);
max-width: 400px;
}
.demo-content h4 {
color: #fff;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.demo-content p {
color: #a1a1aa;
text-align: center;
max-width: 25ch;
line-height: 1.6;
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<div class="click-spark-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<ClickSpark :key="rerenderKey" :spark-color="sparkColor" :spark-size="sparkSize" :spark-radius="sparkRadius"
:spark-count="sparkCount" :duration="duration" :easing="easing" :extra-scale="extraScale"
class="click-spark-demo-area">
</ClickSpark>
<div
class="absolute inset-0 flex items-center justify-center pointer-events-none text-[4rem] font-[900] text-[#222] select-none">
Click Around!
</div>
</div>
<Customize>
<PreviewColor title="Spark Color" v-model="sparkColor" @update:model-value="forceRerender" />
<PreviewSlider title="Spark Size" v-model="sparkSize" :min="5" :max="30" :step="1"
@update:model-value="forceRerender" />
<PreviewSlider title="Spark Radius" v-model="sparkRadius" :min="10" :max="50" :step="5"
@update:model-value="forceRerender" />
<PreviewSlider title="Spark Count" v-model="sparkCount" :min="4" :max="20" :step="1"
@update:model-value="forceRerender" />
<PreviewSlider title="Duration (ms)" v-model="duration" :min="200" :max="1000" :step="50"
@update:model-value="forceRerender" />
<PreviewSlider title="Extra Scale" v-model="extraScale" :min="0.5" :max="2" :step="0.1"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="clickSpark" />
</template>
<template #cli>
<CliInstallation :command="clickSpark.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
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 ClickSpark from '../../content/Animations/ClickSpark/ClickSpark.vue'
import { clickSpark } from '@/constants/code/Animations/clickSparkCode'
import { useForceRerender } from '@/composables/useForceRerender'
const sparkColor = ref('#ffffff')
const sparkSize = ref(10)
const sparkRadius = ref(15)
const sparkCount = ref(8)
const duration = ref(400)
const easing = ref<"linear" | "ease-in" | "ease-out" | "ease-in-out">('ease-out')
const extraScale = ref(1)
const { rerenderKey, forceRerender } = useForceRerender()
const propData = [
{ name: 'sparkColor', type: 'string', default: "'#fff'", description: 'Color of the spark lines.' },
{ name: 'sparkSize', type: 'number', default: '10', description: 'Length of each spark line.' },
{ name: 'sparkRadius', type: 'number', default: '15', description: 'Distance sparks travel from the click center.' },
{ name: 'sparkCount', type: 'number', default: '8', description: 'Number of spark lines per click.' },
{ name: 'duration', type: 'number', default: '400', description: 'Animation duration in milliseconds.' },
{ name: 'easing', type: 'string', default: "'ease-out'", description: 'Easing function: "linear", "ease-in", "ease-out", or "ease-in-out".' },
{ name: 'extraScale', type: 'number', default: '1.0', description: 'Scale multiplier for spark distance and size.' }
]
</script>
<style scoped>
.click-spark-demo-area {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
cursor: pointer;
}
.demo-text {
text-align: center;
pointer-events: none;
user-select: none;
}
.demo-text h3 {
font-size: 1.2rem;
color: #fff;
margin-bottom: 0.5rem;
}
.demo-text p {
font-size: 0.9rem;
color: #999;
margin: 0;
}
.demo-content {
padding: 2rem;
}
</style>

View File

@@ -0,0 +1,132 @@
<template>
<div class="count-up-demo">
<TabbedLayout>
<template #preview>
<h2 class="demo-title-extra">Default</h2>
<div class="demo-container relative">
<CountUp :key="keyDefault" :from="0" :to="100" separator="," direction="up" :duration="1"
class-name="count-up-text" />
<RefreshButton @click="forceRerenderDefault" />
</div>
<h2 class="demo-title-extra">Start Programatically</h2>
<div class="demo-container flex flex-col justify-center items-center relative min-h-[200px]">
<button class="bg-[#0b0b0b] cursor-pointer rounded-[10px] border border-[#222] text-white px-4 py-2 mb-4"
@click="setStartCounting(true)">
Count to 500!
</button>
<CountUp :key="keyProgramatically" :from="100" :to="500" :start-when="startCounting" :duration="5"
class-name="count-up-text" />
<RefreshButton v-if="startCounting" @click="forceRerenderProgramatically" />
</div>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="countup" />
</template>
<template #cli>
<CliInstallation :command="countup.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import RefreshButton from '../../components/common/RefreshButton.vue'
import CountUp from '../../content/Animations/CountUp/CountUp.vue'
import { countup } from '@/constants/code/Animations/countUpCode'
import { useForceRerender } from '@/composables/useForceRerender'
const startCounting = ref(false)
const { rerenderKey: keyDefault, forceRerender: forceRerenderDefault } = useForceRerender()
const { rerenderKey: keyProgramatically, forceRerender: forceRerenderProgramatically } = useForceRerender()
const setStartCounting = (value: boolean) => {
startCounting.value = value
if (value) {
forceRerenderProgramatically()
}
}
const propData = [
{
name: 'to',
type: 'number',
default: '—',
description: 'The target number to count up to.'
},
{
name: 'from',
type: 'number',
default: '0',
description: 'The initial number from which the count starts.'
},
{
name: 'direction',
type: 'string',
default: '"up"',
description: 'Direction of the count; can be "up" or "down". When this is set to "down", "from" and "to" become reversed, in order to count down.'
},
{
name: 'delay',
type: 'number',
default: '0',
description: 'Delay in seconds before the counting starts.'
},
{
name: 'duration',
type: 'number',
default: '2',
description: 'Duration of the count animation - based on the damping and stiffness configured inside the component.'
},
{
name: 'className',
type: 'string',
default: '""',
description: 'CSS class to apply to the component for additional styling.'
},
{
name: 'startWhen',
type: 'boolean',
default: 'true',
description: 'A boolean to control whether the animation should start when the component is in view. It basically works like an if statement, if this is true, the count will start.'
},
{
name: 'separator',
type: 'string',
default: '""',
description: 'Character to use as a thousands separator in the displayed number.'
},
{
name: 'onStart',
type: 'function',
default: '—',
description: 'Callback function that is called when the count animation starts.'
},
{
name: 'onEnd',
type: 'function',
default: '—',
description: 'Callback function that is called when the count animation ends.'
}
]
</script>
<style scoped>
.demo-container {
min-height: 200px;
height: 200px;
}
</style>

View File

@@ -0,0 +1,151 @@
<template>
<TabbedLayout>
<template #preview>
<div class="relative demo-container h-[650px] overflow-hidden">
<Cubes :borderStyle="borderStyle" :gridSize="gridSize" :maxAngle="maxAngle" :radius="radius"
:autoAnimate="autoAnimate" :rippleOnClick="rippleOnClick" />
</div>
<Customize>
<PreviewSelect title="Border Preference" :options="borderOptions" v-model="borderStyle" :width="150" />
<PreviewSlider title="Grid Size" :min="6" :max="12" :step="1" v-model="gridSize" :width="150" />
<PreviewSlider title="Max Angle" :min="15" :max="180" :step="5" v-model="maxAngle" valueUnit="°" :width="150" />
<PreviewSlider title="Radius" :min="1" :max="5" :step="1" v-model="radius" :width="150" />
<PreviewSwitch title="Auto Animate" v-model="autoAnimate" />
<PreviewSwitch title="Ripple On Click" v-model="rippleOnClick" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependencyList="['gsap']" />
</template>
<template #code>
<CodeExample :codeObject="cubes" />
</template>
<template #cli>
<CliInstallation v-bind="cubes" />
</template>
</TabbedLayout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSelect from '../../components/common/PreviewSelect.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import { cubes } from '../../constants/code/Animations/cubesCode'
import Cubes from '../../content/Animations/Cubes/Cubes.vue'
const borderStyle = ref("2px dashed #A7EF9E")
const gridSize = ref(10)
const maxAngle = ref(45)
const radius = ref(3)
const autoAnimate = ref(true)
const rippleOnClick = ref(true)
const borderOptions = [
{ value: "2px dotted #fff", label: "Dotted White" },
{ value: "2px dashed #A7EF9E", label: "Dashed Green" },
{ value: "3px solid #fff", label: "Solid White" }
]
const propData = [
{
name: "gridSize",
type: "number",
default: "10",
description: "The size of the grid (number of cubes per row/column)"
},
{
name: "cubeSize",
type: "number",
default: "undefined",
description: "Fixed size of each cube in pixels. If not provided, cubes will be responsive"
},
{
name: "maxAngle",
type: "number",
default: "45",
description: "Maximum rotation angle for the tilt effect in degrees"
},
{
name: "radius",
type: "number",
default: "3",
description: "Radius of the tilt effect (how many cubes around the cursor are affected)"
},
{
name: "easing",
type: "string",
default: "'power3.out'",
description: "GSAP easing function for the tilt animation"
},
{
name: "duration",
type: "object",
default: "{ enter: 0.3, leave: 0.6 }",
description: "Animation duration for enter and leave effects"
},
{
name: "cellGap",
type: "number | object",
default: "undefined",
description: "Gap between cubes. Can be a number or object with row/col properties"
},
{
name: "borderStyle",
type: "string",
default: "'1px solid #fff'",
description: "CSS border style for cube faces"
},
{
name: "faceColor",
type: "string",
default: "'#060010'",
description: "Background color for cube faces"
},
{
name: "shadow",
type: "boolean | string",
default: "false",
description: "Shadow effect for cubes. Can be boolean or custom CSS shadow"
},
{
name: "autoAnimate",
type: "boolean",
default: "true",
description: "Whether to automatically animate when user is idle"
},
{
name: "rippleOnClick",
type: "boolean",
default: "true",
description: "Whether to show ripple effect on click"
},
{
name: "rippleColor",
type: "string",
default: "'#fff'",
description: "Color of the ripple effect"
},
{
name: "rippleSpeed",
type: "number",
default: "2",
description: "Speed multiplier for the ripple animation"
}
]
</script>

View File

@@ -0,0 +1,143 @@
<template>
<div class="glare-hover-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[600px] overflow-hidden">
<div class="flex justify-center items-center h-full">
<GlareHover background="#111" border-color="#222" border-radius="20px" width="400px" height="300px"
:glare-color="glareColor" :glare-opacity="glareOpacity" :glare-size="glareSize"
:transition-duration="transitionDuration" :play-once="playOnce">
<div class="text-center text-5xl font-black text-[#222] m-0">
Hover Me
</div>
</GlareHover>
</div>
</div>
<Customize>
<PreviewColor title="Glare Color" v-model="glareColor" />
<PreviewSlider title="Glare Opacity" v-model="glareOpacity" :min="0" :max="1" :step="0.1" />
<PreviewSlider title="Glare Size" v-model="glareSize" :min="100" :max="500" :step="25" value-unit="%" />
<PreviewSlider title="Transition Duration" v-model="transitionDuration" :min="200" :max="2000" :step="50"
value-unit="ms" />
<PreviewSwitch title="Play Once" v-model="playOnce" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="glareHover" />
</template>
<template #cli>
<CliInstallation :command="glareHover.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewColor from '../../components/common/PreviewColor.vue'
import GlareHover from '../../content/Animations/GlareHover/GlareHover.vue'
import { glareHover } from '@/constants/code/Animations/glareHoverCode'
const glareColor = ref('#ffffff')
const glareOpacity = ref(0.3)
const glareSize = ref(300)
const transitionDuration = ref(800)
const playOnce = ref(false)
const propData = [
{
name: 'width',
type: 'string',
default: '500px',
description: 'The width of the hover element.'
},
{
name: 'height',
type: 'string',
default: '500px',
description: 'The height of the hover element.'
},
{
name: 'background',
type: 'string',
default: '#000',
description: 'The background color of the element.'
},
{
name: 'borderRadius',
type: 'string',
default: '10px',
description: 'The border radius of the element.'
},
{
name: 'borderColor',
type: 'string',
default: '#333',
description: 'The border color of the element.'
},
{
name: 'glareColor',
type: 'string',
default: '#ffffff',
description: 'The color of the glare effect (hex format).'
},
{
name: 'glareOpacity',
type: 'number',
default: '0.5',
description: 'The opacity of the glare effect (0-1).'
},
{
name: 'glareAngle',
type: 'number',
default: '-45',
description: 'The angle of the glare effect in degrees.'
},
{
name: 'glareSize',
type: 'number',
default: '250',
description: 'The size of the glare effect as a percentage (e.g. 250 = 250%).'
},
{
name: 'transitionDuration',
type: 'number',
default: '650',
description: 'The duration of the transition in milliseconds.'
},
{
name: 'playOnce',
type: 'boolean',
default: 'false',
description: 'If true, the glare only animates on hover and doesn\'t return on mouse leave.'
},
{
name: 'className',
type: 'string',
default: '""',
description: 'Additional CSS class names.'
},
{
name: 'style',
type: 'object',
default: '{}',
description: 'Additional inline styles.'
}
]
</script>

View File

@@ -0,0 +1,155 @@
<template>
<div class="magnet-demo">
<TabbedLayout>
<template #preview>
<h2 class="demo-title-extra">Container</h2>
<div class="demo-container">
<Magnet :key="rerenderKey" :padding="padding" :disabled="disabled" :magnetStrength="magnetStrength">
<div class="magnet-container">
Hover Me!
</div>
</Magnet>
</div>
<h2 class="demo-title-extra">Link</h2>
<div class="demo-container">
<Magnet :key="rerenderKey + 1" :padding="Math.floor(padding / 2)" :disabled="disabled"
:magnetStrength="magnetStrength">
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" rel="noreferrer" class="magnet-link">
Star <span class="accent">Vue Bits</span> on GitHub!
</a>
</Magnet>
</div>
<Customize>
<PreviewSwitch title="Disabled" v-model="disabled" @update:model-value="forceRerender" />
<PreviewSlider title="Padding" v-model="padding" :min="0" :max="300" :step="10" value-unit="px"
@update:model-value="forceRerender" />
<PreviewSlider title="Strength" v-model="magnetStrength" :min="1" :max="10" :step="1"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="magnet" />
</template>
<template #cli>
<CliInstallation :command="magnet.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import Magnet from '../../content/Animations/Magnet/Magnet.vue'
import { magnet } from '@/constants/code/Animations/magnetCode'
import { useForceRerender } from '@/composables/useForceRerender'
const disabled = ref(false)
const padding = ref(100)
const magnetStrength = ref(2)
const { rerenderKey, forceRerender } = useForceRerender()
const propData = [
{
name: 'padding',
type: 'number',
default: '100',
description: 'Specifies the distance (in pixels) around the element that activates the magnet pull.',
},
{
name: 'disabled',
type: 'boolean',
default: 'false',
description: 'Disables the magnet effect when set to true.',
},
{
name: 'magnetStrength',
type: 'number',
default: '2',
description: 'Controls the strength of the pull; higher values reduce movement, lower values increase it.',
},
{
name: 'activeTransition',
type: 'string',
default: '"transform 0.3s ease-out"',
description: 'CSS transition applied to the element when the magnet is active.',
},
{
name: 'inactiveTransition',
type: 'string',
default: '"transform 0.5s ease-in-out"',
description: 'CSS transition applied when the magnet is inactive (mouse out of range).',
},
{
name: 'wrapperClassName',
type: 'string',
default: '""',
description: 'Optional CSS class name for the outermost wrapper element.',
},
{
name: 'innerClassName',
type: 'string',
default: '""',
description: 'Optional CSS class name for the moving (inner) element.',
}
]
</script>
<style scoped>
.demo-title-extra {
font-size: 1.1rem;
color: #fff;
margin: 2rem 0 1rem 0;
font-weight: 600;
}
.demo-container {
position: relative;
min-height: 300px;
}
.magnet-container {
width: 200px;
height: 100px;
font-size: 1.25rem;
font-weight: bold;
color: #fff;
background: #111;
border: 1px solid #222;
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.magnet-link {
font-size: 1.125rem;
color: #fff;
text-decoration: none;
transition: color 0.3s ease;
}
.magnet-link:hover {
color: #f0f0f0;
}
.accent {
color: #27ff56;
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<div class="magnet-lines-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container overflow-hidden flex justify-center pb-4 items-center">
<MagnetLines
:rows="10"
:columns="12"
container-size="40vmin"
line-width="2px"
line-height="30px"
/>
</div>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="magnetLines" />
</template>
<template #cli>
<CliInstallation :command="magnetLines.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import MagnetLines from '../../content/Animations/MagnetLines/MagnetLines.vue'
import { magnetLines } from '@/constants/code/Animations/magnetLinesCode'
const propData = [
{
name: 'rows',
type: 'number',
default: '9',
description: 'Number of grid rows.'
},
{
name: 'columns',
type: 'number',
default: '9',
description: 'Number of grid columns.'
},
{
name: 'containerSize',
type: 'string',
default: '80vmin',
description: 'Specifies the width and height of the entire grid container.'
},
{
name: 'lineColor',
type: 'string',
default: '#efefef',
description: 'Color for each line (the <span> elements).'
},
{
name: 'lineWidth',
type: 'string',
default: '1vmin',
description: "Specifies each line's thickness."
},
{
name: 'lineHeight',
type: 'string',
default: '6vmin',
description: "Specifies each line's length."
},
{
name: 'baseAngle',
type: 'number',
default: '-10',
description: 'Initial rotation angle (in degrees) before pointer movement.'
},
{
name: 'className',
type: 'string',
default: '""',
description: 'Additional class name(s) applied to the container.'
},
{
name: 'style',
type: 'object',
default: '{}',
description: 'Inline styles for the container.'
}
]
</script>

View File

@@ -0,0 +1,122 @@
<template>
<div class="pixel-transition-demo">
<TabbedLayout>
<template #preview>
<div
class="demo-container flex flex-col items-center justify-center min-h-[400px] max-h-[400px] relative overflow-hidden">
<PixelTransition :key="key" :grid-size="gridSize" :pixel-color="pixelColor"
:animation-step-duration="animationStepDuration" class-name="custom-pixel-card">
<template #firstContent>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"
alt="Default" style="width: 100%; height: 100%; object-fit: cover;" />
</template>
<template #secondContent>
<div style="width: 100%; height: 100%; display: grid; place-items: center; background-color: #111;">
<p style="font-weight: 900; font-size: 3rem; color: #fff;">Meow!</p>
</div>
</template>
</PixelTransition>
<div class="mt-2 text-[#a6a6a6]">Psst, hover the card!</div>
</div>
<Customize>
<PreviewSlider title="Grid Size" v-model="gridSize" :min="2" :max="50" :step="1"
@update:model-value="forceRerender" width="200" />
<PreviewSlider title="Animation Duration" v-model="animationStepDuration" :min="0.1" :max="2" :step="0.1"
value-unit="s" @update:model-value="forceRerender" width="200" />
<PreviewColor title="Pixel Color" v-model="pixelColor" @update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="pixelTransition" />
</template>
<template #cli>
<CliInstallation :command="pixelTransition.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PixelTransition from '../../content/Animations/PixelTransition/PixelTransition.vue'
import { pixelTransition } from '@/constants/code/Animations/pixelTransitionCode'
import { useForceRerender } from '@/composables/useForceRerender'
import PreviewColor from '../../components/common/PreviewColor.vue'
const { rerenderKey: key, forceRerender } = useForceRerender()
const gridSize = ref(8)
const pixelColor = ref('#ffffff')
const animationStepDuration = ref(0.4)
const propData = [
{
name: 'firstContent',
type: 'VNode | string',
default: '—',
description: 'Content to show by default (e.g., an <img> or text).'
},
{
name: 'secondContent',
type: 'VNode | string',
default: '—',
description: 'Content revealed upon hover or click.'
},
{
name: 'gridSize',
type: 'number',
default: '7',
description: 'Number of rows/columns in the pixel grid.'
},
{
name: 'pixelColor',
type: 'string',
default: 'currentColor',
description: 'Background color used for each pixel block.'
},
{
name: 'animationStepDuration',
type: 'number',
default: '0.3',
description: 'Length of the pixel reveal/hide in seconds.'
},
{
name: 'aspectRatio',
type: 'string',
default: '"100%"',
description: "Sets the 'padding-top' (or aspect-ratio) for the container."
},
{
name: 'className',
type: 'string',
default: '—',
description: 'Optional additional class names for styling.'
},
{
name: 'style',
type: 'object',
default: '{}',
description: 'Optional inline styles for the container.'
}
]
</script>
<style scoped>
.custom-pixel-card {
box-shadow: 0 2px 16px 0 #00000033;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div class="aurora-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<Aurora :color-stops="colorStops" :amplitude="amplitude" :blend="blend" :speed="speed"
:intensity="intensity" class="w-full h-96" />
</div>
<Customize>
<div class="space-y-2">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
Color Stops
</h3>
<div class="flex gap-4">
<PreviewColor v-for="(color, index) in colorStops" :key="index" :title="`Color ${index + 1}`"
:model-value="color" @update:model-value="(value) => updateColorStop(index, value)" />
</div>
</div>
<PreviewSlider title="Amplitude" :model-value="amplitude" @update:model-value="amplitude = $event" :min="0"
:max="2" :step="0.1" />
<PreviewSlider title="Blend" :model-value="blend" @update:model-value="blend = $event" :min="0" :max="1"
:step="0.1" />
<PreviewSlider title="Speed" :model-value="speed" @update:model-value="speed = $event" :min="0" :max="3"
:step="0.1" />
<PreviewSlider title="Intensity" :model-value="intensity" @update:model-value="intensity = $event" :min="0"
:max="2" :step="0.1" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['ogl']" />
</template>
<template #code>
<CodeExample :code-object="aurora" />
</template>
<template #cli>
<CliInstallation :command="aurora.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import Aurora from '@/content/Backgrounds/Aurora/Aurora.vue'
import PreviewColor from '@/components/common/PreviewColor.vue'
import PreviewSlider from '@/components/common/PreviewSlider.vue'
import { aurora } from '@/constants/code/Backgrounds/auroraCode'
const colorStops = ref(['#171D22', '#7cff67', '#171D22'])
const amplitude = ref(1.0)
const blend = ref(0.5)
const speed = ref(1.0)
const intensity = ref(1.0)
const updateColorStop = (index: number, color: string) => {
colorStops.value[index] = color
}
const propData = [
{ name: 'colorStops', type: 'string[]', default: "['#171D22', '#7cff67', '#171D22']", description: 'Array of color stops for the aurora gradient.' },
{ name: 'amplitude', type: 'number', default: '1.0', description: 'Controls the height variation of the aurora effect.' },
{ name: 'blend', type: 'number', default: '0.5', description: 'Controls the blending/smoothness of the aurora effect.' },
{ name: 'speed', type: 'number', default: '1.0', description: 'Controls the animation speed of the aurora effect.' },
{ name: 'intensity', type: 'number', default: '1.0', description: 'Controls the overall intensity/opacity of the aurora effect.' },
{ name: 'time', type: 'number', default: 'undefined', description: 'Optional time override for the animation.' },
{ name: 'className', type: 'string', default: '""', description: 'Additional CSS class names for styling.' },
{ name: 'style', type: 'CSSProperties', default: '{}', description: 'Inline styles for the component.' }
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -3,8 +3,6 @@
<TabbedLayout>
<template #preview>
<div class="demo-container" style="height: 500px; overflow: hidden;">
<RefreshButton @refresh="forceRerender" />
<DotGrid :key="rerenderKey" :dot-size="dotSize" :gap="gap" :base-color="baseColor" :active-color="activeColor"
:proximity="proximity" :speed-trigger="speedTrigger" :shock-radius="shockRadius"
:shock-strength="shockStrength" :max-speed="maxSpeed" :resistance="resistance"
@@ -13,14 +11,8 @@
<Customize>
<div class="color-controls">
<div class="color-input">
<label>Base Color</label>
<input type="color" v-model="baseColor" @change="forceRerender" />
</div>
<div class="color-input">
<label>Active Color</label>
<input type="color" v-model="activeColor" @change="forceRerender" />
</div>
<PreviewColor title="Base Color" v-model="baseColor" @update:model-value="forceRerender" />
<PreviewColor title="Active Color" v-model="activeColor" @update:model-value="forceRerender" />
</div>
<PreviewSlider title="Dot Size" v-model="dotSize" :min="2" :max="50" :step="1"
@@ -68,13 +60,13 @@
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import RefreshButton from '../../components/common/RefreshButton.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewColor from '../../components/common/PreviewColor.vue'
import DotGrid from '../../content/Backgrounds/DotGrid/DotGrid.vue'
import { dotGrid } from '@/constants/code/Backgrounds/dotGridCode'
import { useForceRerender } from '@/composables/useForceRerender'
@@ -120,34 +112,4 @@ const propData = [
gap: 1rem;
margin-bottom: 1rem;
}
.color-input {
display: flex;
align-items: center;
gap: 0.5rem;
}
.color-input label {
font-size: 0.875rem;
color: #a1a1aa;
white-space: nowrap;
}
.color-input input[type="color"] {
width: 50px;
height: 32px;
border: 1px solid #333;
border-radius: 6px;
background: transparent;
cursor: pointer;
}
.color-input input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
.color-input input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 4px;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<TabbedLayout>
<template #preview>
<div class="relative demo-container h-[500px] p-0 overflow-hidden">
<Iridescence :key="key" :speed="speed" :color="colors" :mouseReact="mouseInteraction" :amplitude="amplitude" />
</div>
<Customize>
<PreviewSlider :min="0" :max="1" :step="0.1" v-model="colors[0]" title="Red" />
<PreviewSlider :min="0" :max="1" :step="0.1" v-model="colors[1]" title="Green" />
<PreviewSlider :min="0" :max="1" :step="0.1" v-model="colors[2]" title="Blue" />
<PreviewSlider :min="0" :max="2" :step="0.1" v-model="speed" title="Speed" @update:modelValue="forceRerender" />
<PreviewSlider :min="0" :max="0.5" :step="0.01" v-model="amplitude" title="Amplitude"
@update:modelValue="forceRerender" />
<PreviewSwitch v-model="mouseInteraction" title="Enable Mouse Interaction" @update:modelValue="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependencyList="['ogl']" />
</template>
<template #code>
<CodeExample :codeObject="iridescence" />
</template>
<template #cli>
<CliInstallation v-bind="iridescence" />
</template>
</TabbedLayout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import Iridescence from '../../content/Backgrounds/Iridescence/Iridescence.vue'
import { iridescence } from '../../constants/code/Backgrounds/iridescenceCode'
import { useForceRerender } from '../../composables/useForceRerender'
const colors = ref<[number, number, number]>([1, 1, 1])
const speed = ref(1)
const amplitude = ref(0.1)
const mouseInteraction = ref(true)
const { rerenderKey: key, forceRerender } = useForceRerender()
const propData = [
{
name: "color",
type: "Array<number>",
default: "[1, 1, 1]",
description: "Base color as an array of RGB values (each between 0 and 1)."
},
{
name: "speed",
type: "number",
default: "1.0",
description: "Speed multiplier for the animation."
},
{
name: "amplitude",
type: "number",
default: "0.1",
description: "Amplitude for the mouse-driven effect."
},
{
name: "mouseReact",
type: "boolean",
default: "true",
description: "Enable or disable mouse interaction with the shader."
}
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,128 @@
<template>
<div class="letter-glitch-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<LetterGlitch
:key="rerenderKey"
:glitch-colors="colors"
:glitch-speed="speed"
:center-vignette="showCenterVignette"
:outer-vignette="showOuterVignette"
:smooth="smooth"
class="w-full h-96"
/>
</div>
<Customize>
<button
@click="randomizeColors"
class="px-3 py-2 text-xs bg-[#111] hover:bg-[#222] text-white rounded-lg border border-[#333] transition-colors"
>
Randomize Colors
</button>
<PreviewSlider
title="Glitch Speed"
:model-value="speed"
@update:model-value="speed = $event"
:min="0"
:max="100"
:step="5"
/>
<PreviewSwitch
title="Smooth Animation"
:model-value="smooth"
@update:model-value="updateSmooth"
/>
<PreviewSwitch
title="Show Center Vignette"
:model-value="showCenterVignette"
@update:model-value="updateCenterVignette"
/>
<PreviewSwitch
title="Show Outer Vignette"
:model-value="showOuterVignette"
@update:model-value="updateOuterVignette"
/>
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="[]" />
</template>
<template #code>
<CodeExample :code-object="letterGlitch" />
</template>
<template #cli>
<CliInstallation :command="letterGlitch.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import LetterGlitch from '@/content/Backgrounds/LetterGlitch/LetterGlitch.vue'
import PreviewSlider from '@/components/common/PreviewSlider.vue'
import PreviewSwitch from '@/components/common/PreviewSwitch.vue'
import { letterGlitch } from '@/constants/code/Backgrounds/letterGlitchCode'
import { useForceRerender } from '@/composables/useForceRerender'
const smooth = ref(true)
const speed = ref(10)
const colors = ref(['#2b4539', '#61dca3', '#61b3dc'])
const showCenterVignette = ref(true)
const showOuterVignette = ref(false)
const { rerenderKey, forceRerender } = useForceRerender()
const randomHex = () => {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')
}
const randomizeColors = () => {
colors.value = [randomHex(), randomHex(), randomHex()]
forceRerender()
}
const updateSmooth = (value: boolean) => {
smooth.value = value
forceRerender()
}
const updateCenterVignette = (value: boolean) => {
showCenterVignette.value = value
forceRerender()
}
const updateOuterVignette = (value: boolean) => {
showOuterVignette.value = value
forceRerender()
}
const propData = [
{ name: 'glitchColors', type: 'string[]', default: "['#2b4539', '#61dca3', '#61b3dc']", description: 'Controls the colors of the letters rendered in the canvas.' },
{ name: 'glitchSpeed', type: 'number', default: '50', description: 'Controls the speed at which letters scramble in the animation.' },
{ name: 'centerVignette', type: 'boolean', default: 'false', description: 'When true, renders a radial gradient in the center of the container' },
{ name: 'outerVignette', type: 'boolean', default: 'true', description: 'When true, renders an inner radial gradient around the edges of the container.' },
{ name: 'smooth', type: 'boolean', default: 'true', description: 'When true, smoothens the animation of the letters for a more subtle feel.' }
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div class="lightning-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<Lightning :hue="hue" :x-offset="xOffset" :speed="speed" :intensity="intensity"
:size="size" class="w-full h-96" />
</div>
<Customize>
<PreviewSlider title="Hue" :model-value="hue" @update:model-value="hue = $event" :min="0" :max="360"
:step="1" />
<PreviewSlider title="X Offset" :model-value="xOffset" @update:model-value="xOffset = $event" :min="-2"
:max="2" :step="0.1" />
<PreviewSlider title="Speed" :model-value="speed" @update:model-value="speed = $event" :min="0.5" :max="2"
:step="0.1" />
<PreviewSlider title="Intensity" :model-value="intensity" @update:model-value="intensity = $event" :min="0.1"
:max="2" :step="0.1" />
<PreviewSlider title="Size" :model-value="size" @update:model-value="size = $event" :min="0.1" :max="3"
:step="0.1" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="[]" />
</template>
<template #code>
<CodeExample :code-object="lightning" />
</template>
<template #cli>
<CliInstallation :command="lightning.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import Lightning from '@/content/Backgrounds/Lightning/Lightning.vue'
import PreviewSlider from '@/components/common/PreviewSlider.vue'
import { lightning } from '@/constants/code/Backgrounds/lightningCode'
const hue = ref(160)
const xOffset = ref(0)
const speed = ref(1)
const intensity = ref(1)
const size = ref(1)
const propData = [
{ name: 'hue', type: 'number', default: '230', description: 'Hue of the lightning in degrees (0 to 360).' },
{ name: 'xOffset', type: 'number', default: '0', description: 'Horizontal offset of the lightning in normalized units.' },
{ name: 'speed', type: 'number', default: '1', description: 'Animation speed multiplier for the lightning.' },
{ name: 'intensity', type: 'number', default: '1', description: 'Brightness multiplier for the lightning.' },
{ name: 'size', type: 'number', default: '1', description: 'Scale factor for the bolt size.' }
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div class="particles-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<Particles :key="rerenderKey" :particle-colors="[color]" :particle-count="particleCount"
:particle-spread="particleSpread" :speed="speed" :particle-base-size="baseSize"
:move-particles-on-hover="moveParticlesOnHover" :alpha-particles="alphaParticles"
:disable-rotation="disableRotation" class="w-full h-96" />
</div>
<Customize>
<div class="flex gap-4 items-center">
<PreviewColor title="Color" :model-value="color" @update:model-value="updateColor" />
</div>
<PreviewSlider title="Count" :model-value="particleCount" @update:model-value="particleCount = $event"
:min="100" :max="1000" :step="100" />
<PreviewSlider title="Spread" :model-value="particleSpread" @update:model-value="particleSpread = $event"
:min="10" :max="100" :step="10" />
<PreviewSlider title="Speed" :model-value="speed" @update:model-value="speed = $event" :min="0" :max="2"
:step="0.1" />
<PreviewSlider title="Base Size" :model-value="baseSize" @update:model-value="baseSize = $event" :min="100"
:max="1000" :step="100" />
<PreviewSwitch title="Mouse Interaction" :model-value="moveParticlesOnHover"
@update:model-value="moveParticlesOnHover = $event" />
<PreviewSwitch title="Particle Transparency" :model-value="alphaParticles"
@update:model-value="alphaParticles = $event" />
<PreviewSwitch title="Disable Rotation" :model-value="disableRotation"
@update:model-value="disableRotation = $event" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['ogl']" />
</template>
<template #code>
<CodeExample :code-object="particles" />
</template>
<template #cli>
<CliInstallation :command="particles.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import Particles from '@/content/Backgrounds/Particles/Particles.vue'
import PreviewSlider from '@/components/common/PreviewSlider.vue'
import PreviewSwitch from '@/components/common/PreviewSwitch.vue'
import PreviewColor from '@/components/common/PreviewColor.vue'
import { particles } from '@/constants/code/Backgrounds/particlesCode'
import { useForceRerender } from '@/composables/useForceRerender'
const color = ref('#ffffff')
const particleCount = ref(200)
const particleSpread = ref(10)
const speed = ref(0.1)
const baseSize = ref(100)
const moveParticlesOnHover = ref(true)
const alphaParticles = ref(false)
const disableRotation = ref(false)
const { rerenderKey, forceRerender } = useForceRerender()
const updateColor = (value: string) => {
color.value = value
forceRerender()
}
const propData = [
{ name: 'particleCount', type: 'number', default: '200', description: 'The number of particles to generate.' },
{ name: 'particleSpread', type: 'number', default: '10', description: 'Controls how far particles are spread from the center.' },
{ name: 'speed', type: 'number', default: '0.1', description: 'Speed factor controlling the animation pace.' },
{ name: 'particleColors', type: 'string[]', default: "['#ffffff']", description: 'An array of hex color strings used to color the particles.' },
{ name: 'moveParticlesOnHover', type: 'boolean', default: 'false', description: 'Determines if particles should move in response to mouse hover.' },
{ name: 'particleHoverFactor', type: 'number', default: '1', description: 'Multiplier for the particle movement when hovering.' },
{ name: 'alphaParticles', type: 'boolean', default: 'false', description: 'If true, particles are rendered with varying transparency; otherwise, as solid circles.' },
{ name: 'particleBaseSize', type: 'number', default: '100', description: 'The base size of the particles.' },
{ name: 'sizeRandomness', type: 'number', default: '1', description: 'Controls the variation in particle sizes (0 means all particles have the same size).' },
{ name: 'cameraDistance', type: 'number', default: '20', description: 'Distance from the camera to the particle system.' },
{ name: 'disableRotation', type: 'boolean', default: 'false', description: 'If true, stops the particle system from rotating.' }
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="silk-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<Silk :speed="speed" :scale="scale" :color="color" :noise-intensity="noiseIntensity" :rotation="rotation"
class="w-full h-96" />
</div>
<Customize>
<PreviewSlider title="Speed" :model-value="speed" @update:model-value="speed = $event" :min="0" :max="10"
:step="0.1" />
<PreviewSlider title="Scale" :model-value="scale" @update:model-value="scale = $event" :min="0.1" :max="3"
:step="0.1" />
<PreviewSlider title="Noise Intensity" :model-value="noiseIntensity"
@update:model-value="noiseIntensity = $event" :min="0" :max="3" :step="0.1" />
<PreviewSlider title="Rotation" :model-value="rotation" @update:model-value="rotation = $event" :min="0"
:max="6.28" :step="0.1" />
<PreviewColor title="Color" :model-value="color" @update:model-value="color = $event" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['ogl']" />
</template>
<template #code>
<CodeExample :code-object="silk" />
</template>
<template #cli>
<CliInstallation :command="silk.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import Silk from '@/content/Backgrounds/Silk/Silk.vue'
import PreviewColor from '@/components/common/PreviewColor.vue'
import PreviewSlider from '@/components/common/PreviewSlider.vue'
import { silk } from '@/constants/code/Backgrounds/silkCode'
const speed = ref(5)
const scale = ref(1)
const color = ref('#7B7481')
const noiseIntensity = ref(1.5)
const rotation = ref(0)
const propData = [
{ name: 'speed', type: 'number', default: '5', description: 'Animation speed multiplier' },
{ name: 'scale', type: 'number', default: '1', description: 'Scale factor for the pattern' },
{ name: 'color', type: 'string', default: "'#7B7481'", description: 'Base color of the silk pattern' },
{ name: 'noiseIntensity', type: 'number', default: '1.5', description: 'Intensity of the noise effect' },
{ name: 'rotation', type: 'number', default: '0', description: 'Rotation angle in radians' },
{ name: 'className', type: 'string', default: '""', description: 'Additional CSS class names for styling.' },
{ name: 'style', type: 'CSSProperties', default: '{}', description: 'Inline styles for the component.' }
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<TabbedLayout>
<template #preview>
<div class="relative min-h-[200px] demo-container overflow-hidden p-0">
<div class="w-full h-[500px] overflow-hidden">
<Squares :squareSize="size" :speed="speed" :direction="direction" :borderColor="borderColor"
:hoverFillColor="hoverColor" />
</div>
</div>
<Customize>
<div class="flex items-center mb-6">
<span class="text-sm mr-2">Direction</span>
<div class="flex">
<button v-for="dir in directions" :key="dir.value" @click="direction = dir.value" :class="[
'px-3 py-2 text-xs h-8 text-white border-r border-[#333] cursor-pointer last:border-r-0',
direction === dir.value
? 'bg-[#222] hover:bg-[#222]'
: 'bg-[#111] hover:bg-[#111]'
]" class="first:rounded-l last:rounded-r">
{{ dir.label }}
</button>
</div>
</div>
<PreviewSlider :min="10" :max="100" :step="1" v-model="size" title="Square Size" />
<PreviewSlider :min="0.1" :max="2" :step="0.01" v-model="speed" title="Animation Speed" />
<div class="flex gap-4">
<PreviewColor v-model="borderColor" title="Border Color" />
<PreviewColor v-model="hoverColor" title="Hover Color" />
</div>
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :codeObject="squares" />
</template>
<template #cli>
<CliInstallation v-bind="squares" />
</template>
</TabbedLayout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewColor from '../../components/common/PreviewColor.vue'
import PropTable from '../../components/common/PropTable.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import Squares from '../../content/Backgrounds/Squares/Squares.vue'
import { squares } from '../../constants/code/Backgrounds/squaresCode'
const direction = ref<'diagonal' | 'up' | 'right' | 'down' | 'left'>('diagonal')
const borderColor = ref('#333')
const hoverColor = ref('#27FF64')
const size = ref(40)
const speed = ref(0.5)
const directions = [
{ value: 'diagonal', label: 'Diagonal' },
{ value: 'up', label: 'Up' },
{ value: 'right', label: 'Right' },
{ value: 'down', label: 'Down' },
{ value: 'left', label: 'Left' }
] as const
const propData = [
{
name: "direction",
type: "string",
default: "'right'",
description: "Direction of square animation. Options: 'diagonal', 'up', 'right', 'down', 'left'."
},
{
name: "speed",
type: "number",
default: "1",
description: "Animation speed multiplier."
},
{
name: "borderColor",
type: "string",
default: "'#999'",
description: "Color of the square borders."
},
{
name: "squareSize",
type: "number",
default: "40",
description: "Size of individual squares in pixels."
},
{
name: "hoverFillColor",
type: "string",
default: "'#222'",
description: "Fill color when hovering over squares."
}
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<TabbedLayout>
<template #preview>
<div class="relative demo-container h-[500px] overflow-hidden p-0">
<Threads :amplitude="amplitude" :distance="distance" :enableMouseInteraction="enableMouseInteraction"
:color="[1, 1, 1]" />
</div>
<Customize>
<PreviewSlider :min="0" :max="5" :step="0.1" v-model="amplitude" title="Amplitude" />
<PreviewSlider :min="0" :max="2" :step="0.1" v-model="distance" title="Distance" />
<PreviewSwitch v-model="enableMouseInteraction" title="Enable Mouse Interaction" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependencyList="['ogl']" />
</template>
<template #code>
<CodeExample :codeObject="threads" />
</template>
<template #cli>
<CliInstallation :command="threads.cli" />
</template>
</TabbedLayout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import Threads from '../../content/Backgrounds/Threads/Threads.vue'
import { threads } from '../../constants/code/Backgrounds/threadsCode'
const amplitude = ref(1)
const distance = ref(0)
const enableMouseInteraction = ref(true)
const propData = [
{
name: "color",
type: "[number, number, number]",
default: "[1, 1, 1]",
description: "Customizes the color of the lines (RGB)."
},
{
name: "amplitude",
type: "number",
default: "1",
description: "Adjusts the intensity of the wave effect on the lines."
},
{
name: "distance",
type: "number",
default: "0",
description: "Controls the spacing between the lines. A value of 0 means no offset."
},
{
name: "enableMouseInteraction",
type: "boolean",
default: "false",
description: "Enables smooth mouse hover effects that modulate the line's movement and amplitude."
}
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<div class="waves-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container">
<Waves :wave-speed-x="waveSpeedX" :line-color="color" class="w-full h-96" />
</div>
<Customize>
<PreviewSlider title="Wave Speed X" :model-value="waveSpeedX" @update:model-value="waveSpeedX = $event"
:min="0" :max="0.1" :step="0.01" />
<div class="flex gap-4 items-center">
<PreviewColor title="Waves Color" :model-value="color" @update:model-value="color = $event" />
</div>
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="[]" />
</template>
<template #code>
<CodeExample :code-object="waves" />
</template>
<template #cli>
<CliInstallation :command="waves.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import Waves from '@/content/Backgrounds/Waves/Waves.vue'
import PreviewSlider from '@/components/common/PreviewSlider.vue'
import PreviewColor from '@/components/common/PreviewColor.vue'
import { waves } from '@/constants/code/Backgrounds/wavesCode'
const color = ref('#ffffff')
const waveSpeedX = ref(0.0125)
const propData = [
{ name: 'lineColor', type: 'string', default: 'black', description: 'Defines the color of the wave lines drawn on the canvas.' },
{ name: 'backgroundColor', type: 'string', default: 'transparent', description: 'Sets the background color of the waves container.' },
{ name: 'waveSpeedX', type: 'number', default: '0.0125', description: 'Horizontal speed factor for the wave animation.' },
{ name: 'waveSpeedY', type: 'number', default: '0.005', description: 'Vertical speed factor for the wave animation.' },
{ name: 'waveAmpX', type: 'number', default: '32', description: 'Horizontal amplitude of each wave.' },
{ name: 'waveAmpY', type: 'number', default: '16', description: 'Vertical amplitude of each wave.' },
{ name: 'xGap', type: 'number', default: '10', description: 'Horizontal gap between individual wave lines.' },
{ name: 'yGap', type: 'number', default: '32', description: 'Vertical gap between points on each wave line.' },
{ name: 'friction', type: 'number', default: '0.925', description: 'Controls how quickly the cursor effect slows down.' },
{ name: 'tension', type: 'number', default: '0.005', description: 'Determines the "springiness" of the cursor effect on points.' },
{ name: 'maxCursorMove', type: 'number', default: '100', description: 'Limits how far each point can shift due to cursor movement.' },
{ name: 'style', type: 'object', default: '{}', description: 'Inline styles applied to the container element.' },
{ name: 'className', type: 'string', default: '""', description: 'Custom class name(s) applied to the container element.' }
]
</script>
<style scoped>
.demo-container {
overflow: hidden;
padding: 0;
}
</style>

View File

@@ -0,0 +1,196 @@
<template>
<div class="card-swap-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container h-[500px] overflow-hidden flex flex-col lg:flex-row relative">
<div
class="w-full lg:w-1/2 h-auto lg:h-full flex flex-col justify-center items-center lg:items-start text-center lg:text-left pt-8 lg:pt-0 pb-4 lg:pb-0 px-4 lg:px-4">
<h2 class="text-2xl md:text-3xl lg:text-4xl mb-4 font-medium leading-tight pl-0 lg:pl-24">
Card stacks have never
<span class="inline lg:block">looked so good</span>
</h2>
<p class="text-lg lg:text-xl mb-4 font-normal leading-tight text-gray-400 pl-0 lg:pl-24">
Just look at it go!
</p>
</div>
<div class="w-full lg:w-1/2 h-96 lg:h-full relative">
<CardSwap :key="rerenderKey" :width="500" :height="400" :card-distance="cardDistance"
:vertical-distance="verticalDistance" :delay="delay" :skew-amount="skewAmount" :easing="easing"
:pause-on-hover="pauseOnHover" @card-click="handleCardClick">
<template #card-0>
<div class="border-b border-white bg-gradient-to-t from-[#222] to-[#0b0b0b]">
<div class="m-2 flex items-center">
<i class="pi pi-circle-fill mr-2"></i>
<span>Smooth</span>
</div>
</div>
<div class="relative p-2">
<video autoplay loop muted playsinline class="rounded-[15px] w-full h-full">
<source
src="https://cdn.dribbble.com/userupload/7053861/file/original-7956be57144058795db6bb24875bdab9.mp4"
type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</template>
<template #card-1>
<div class="border-b border-white bg-gradient-to-t from-[#222] to-[#0b0b0b]">
<div class="m-2 flex items-center">
<i class="pi pi-code mr-2"></i>
<span>Reliable</span>
</div>
</div>
<div class="relative p-2">
<video autoplay loop muted playsinline class="rounded-[15px] w-full h-full">
<source
src="https://cdn.dribbble.com/userupload/7078020/file/original-b071e9063d9e3ba86a85a61b9d5a7c42.mp4"
type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</template>
<template #card-2>
<div class="border-b border-white bg-gradient-to-t from-[#222] to-[#0b0b0b]">
<div class="m-2 flex items-center">
<i class="pi pi-sliders-h mr-2"></i>
<span>Customizable</span>
</div>
</div>
<div class="relative p-2">
<video autoplay loop muted playsinline class="rounded-[15px] w-full h-full">
<source
src="https://cdn.dribbble.com/userupload/7098541/file/original-0b063b12ca835421580e6034368ad95a.mp4"
type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</template>
</CardSwap>
</div>
</div>
<Customize>
<PreviewSwitch title="Pause On Hover" v-model="pauseOnHover" @update:model-value="forceRerender" />
<PreviewSlider title="Card Distance" v-model="cardDistance" :min="30" :max="100" :step="5" />
<PreviewSlider title="Vertical Distance" v-model="verticalDistance" :min="40" :max="120" :step="5" />
<PreviewSlider title="Delay (ms)" v-model="delay" :min="3000" :max="8000" :step="500" />
<PreviewSlider title="Skew Amount" v-model="skewAmount" :min="0" :max="12" :step="1" />
<button
class="text-xs bg-[#111] rounded-[10px] border border-[#333] hover:bg-[#222] text-white h-8 px-3 transition-colors"
@click="toggleEasing">
Easing: <span class="text-zinc-400">&nbsp;{{ easing }}</span>
</button>
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="cardSwap" />
</template>
<template #cli>
<CliInstallation :command="cardSwap.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import CardSwap from '../../content/Components/CardSwap/CardSwap.vue'
import { cardSwap } from '@/constants/code/Components/cardSwapCode'
import { useForceRerender } from '@/composables/useForceRerender'
const { rerenderKey, forceRerender } = useForceRerender()
const cardDistance = ref(60)
const verticalDistance = ref(70)
const delay = ref(5000)
const skewAmount = ref(6)
const easing = ref<'elastic' | 'linear'>('elastic')
const pauseOnHover = ref(false)
const toggleEasing = () => {
easing.value = easing.value === 'elastic' ? 'linear' : 'elastic'
forceRerender()
}
const handleCardClick = (index: number) => {
console.log(`Card ${index} clicked`)
}
const propData = [
{
name: "width",
type: "number | string",
default: "500",
description: "Width of the card container"
},
{
name: "height",
type: "number | string",
default: "400",
description: "Height of the card container"
},
{
name: "cardDistance",
type: "number",
default: "60",
description: "X-axis spacing between cards"
},
{
name: "verticalDistance",
type: "number",
default: "70",
description: "Y-axis spacing between cards"
},
{
name: "delay",
type: "number",
default: "5000",
description: "Milliseconds between card swaps"
},
{
name: "pauseOnHover",
type: "boolean",
default: "false",
description: "Whether to pause animation on hover"
},
{
name: "onCardClick",
type: "(idx: number) => void",
default: "undefined",
description: "Callback function when a card is clicked"
},
{
name: "skewAmount",
type: "number",
default: "6",
description: "Degree of slope for top/bottom edges"
},
{
name: "easing",
type: "'linear' | 'elastic'",
default: "'elastic'",
description: "Animation easing type"
}
]
</script>

View File

@@ -0,0 +1,116 @@
<template>
<div class="carousel-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden flex items-center justify-center">
<Carousel :key="rerenderKey" :base-width="width" :autoplay="autoplay" :autoplay-delay="autoplayDelay"
:pause-on-hover="pauseOnHover" :loop="loop" :round="round" />
</div>
<Customize>
<PreviewSlider title="Width" v-model="width" :min="250" :max="330" :step="10"
@update:model-value="forceRerender" />
<PreviewSwitch title="Round Variant" v-model="round" @update:model-value="forceRerender" />
<PreviewSwitch title="Loop" v-model="loop" @update:model-value="forceRerender" />
<PreviewSwitch title="Autoplay" v-model="autoplay" @update:model-value="forceRerender" />
<PreviewSlider title="Delay" v-model="autoplayDelay" :min="1000" :max="4000" :step="1000"
:disabled="!autoplay" @update:model-value="forceRerender" />
<PreviewSwitch title="Pause On Hover" v-model="pauseOnHover" :disabled="!autoplay"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['motion-v']" />
</template>
<template #code>
<CodeExample :code-object="carousel" />
</template>
<template #cli>
<CliInstallation :command="carousel.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import Carousel from '../../content/Components/Carousel/Carousel.vue'
import { carousel } from '@/constants/code/Components/carouselCode'
import { useForceRerender } from '@/composables/useForceRerender'
const width = ref(300)
const autoplay = ref(false)
const autoplayDelay = ref(3000)
const pauseOnHover = ref(false)
const loop = ref(false)
const round = ref(false)
const { rerenderKey, forceRerender } = useForceRerender()
const propData = [
{
name: "items",
type: "CarouselItem[]",
default: "DEFAULT_ITEMS",
description:
"An array of carousel items. Each item must include title, description, id, and icon."
},
{
name: "baseWidth",
type: "number",
default: "300",
description:
"Total width (in px) of the carousel container. Effective item width is baseWidth minus padding."
},
{
name: "autoplay",
type: "boolean",
default: "false",
description:
"Enables automatic scrolling to the next item at a fixed interval."
},
{
name: "autoplayDelay",
type: "number",
default: "3000",
description:
"Delay in milliseconds between automatic scrolls when autoplay is enabled."
},
{
name: "pauseOnHover",
type: "boolean",
default: "false",
description:
"Pauses the autoplay functionality when the carousel is hovered."
},
{
name: "loop",
type: "boolean",
default: "false",
description:
"When true, the carousel loops seamlessly from the last item back to the first."
},
{
name: "round",
type: "boolean",
default: "false",
description:
"When true, the carousel is rendered with a 1:1 aspect ratio and circular container/items."
}
]
</script>

View File

@@ -0,0 +1,63 @@
<template>
<div class="decay-card-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container" style="overflow: hidden;">
<DecayCard>
<div class="text-[2rem]">
Decay<br />Card
</div>
</DecayCard>
</div>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="decayCard" />
</template>
<template #cli>
<CliInstallation :command="decayCard.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import DecayCard from '../../content/Components/DecayCard/DecayCard.vue'
import { decayCard } from '@/constants/code/Components/decayCardCode'
const propData = [
{
name: 'children',
type: 'Slot',
default: '',
description: 'The content to be rendered inside the card.'
},
{
name: 'width',
type: 'number',
default: '300',
description: 'The width of the card in pixels.'
},
{
name: 'height',
type: 'number',
default: '400',
description: 'The height of the card in pixels.'
},
{
name: 'image',
type: 'string',
default: 'https://picsum.photos/300/400?grayscale',
description: 'Allows setting the background image of the card.'
}
]
</script>

View File

@@ -0,0 +1,164 @@
<template>
<div class="dock-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container" style="min-height: 400px; position: relative;">
<div class="demo-content">
<Dock :key="rerenderKey" :items="items" :panel-height="panelHeight" :base-item-size="baseItemSize"
:magnification="magnification" :distance="200" :dock-height="256"
:spring="{ mass: 0.1, stiffness: 150, damping: 12 }" />
</div>
<div
class="absolute inset-0 flex items-center justify-center pointer-events-none text-[4rem] font-[900] text-[#222] select-none">
Try It!
</div>
</div>
<Customize>
<PreviewSlider title="Background Height" :min="30" :max="200" :step="10" :model-value="panelHeight"
@update:model-value="(val: number) => { panelHeight = val; forceRerender(); }" />
<PreviewSlider title="Item Size" :min="20" :max="60" :step="10" :model-value="baseItemSize"
@update:model-value="(val: number) => { baseItemSize = val; forceRerender(); }" />
<PreviewSlider title="Magnification" :min="50" :max="100" :step="10" :model-value="magnification"
@update:model-value="(val: number) => { magnification = val; forceRerender(); }" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['motion-v']" />
</template>
<template #code>
<CodeExample :code-object="dock" />
</template>
<template #cli>
<CliInstallation :command="dock.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref, h } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import Dock from '../../content/Components/Dock/Dock.vue'
import { dock } from '@/constants/code/Components/dockCode'
import { useToast } from 'primevue/usetoast'
import { useForceRerender } from '@/composables/useForceRerender'
const panelHeight = ref(68)
const baseItemSize = ref(50)
const magnification = ref(70)
const toast = useToast()
const { rerenderKey, forceRerender } = useForceRerender()
const HomeIcon = () => h('i', { class: 'pi pi-home', style: { fontSize: '18px', color: 'white' } })
const ArchiveIcon = () => h('i', { class: 'pi pi-inbox', style: { fontSize: '18px', color: 'white' } })
const ProfileIcon = () => h('i', { class: 'pi pi-user', style: { fontSize: '18px', color: 'white' } })
const SettingsIcon = () => h('i', { class: 'pi pi-cog', style: { fontSize: '18px', color: 'white' } })
const showAlert = (message: string) => {
toast.add({
severity: 'secondary',
summary: `${message} button was clicked`,
life: 3000
})
}
const items = [
{ icon: HomeIcon, label: 'Home', onClick: () => showAlert('Home') },
{ icon: ArchiveIcon, label: 'Archive', onClick: () => showAlert('Archive') },
{ icon: ProfileIcon, label: 'Profile', onClick: () => showAlert('Profile') },
{ icon: SettingsIcon, label: 'Settings', onClick: () => showAlert('Settings') },
]
const propData = [
{
name: "items",
type: "DockItemData[]",
default: "[]",
description: "Array of dock items. Each item should include an icon, label, onClick handler, and an optional className."
},
{
name: "className",
type: "string",
default: '""',
description: "Additional CSS classes for the dock panel."
},
{
name: "distance",
type: "number",
default: "200",
description: "Pixel distance used to calculate the magnification effect based on mouse proximity."
},
{
name: "panelHeight",
type: "number",
default: "68",
description: "Height (in pixels) of the dock panel."
},
{
name: "baseItemSize",
type: "number",
default: "50",
description: "The base size (in pixels) for each dock item."
},
{
name: "dockHeight",
type: "number",
default: "256",
description: "Maximum height (in pixels) of the dock container."
},
{
name: "magnification",
type: "number",
default: "70",
description: "The magnified size (in pixels) applied to a dock item when hovered."
},
{
name: "spring",
type: "SpringOptions",
default: "{ mass: 0.1, stiffness: 150, damping: 12 }",
description: "Configuration options for the spring animation."
}
]
</script>
<style scoped>
.demo-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding: 2rem;
}
.demo-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
width: 100%;
max-width: 800px;
}
.demo-title {
font-size: 2rem;
font-weight: 900;
color: #271E37;
margin: 0;
text-align: center;
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<TabbedLayout>
<template #preview>
<div class="space-y-8">
<div class="demo-section">
<div class="demo-container relative min-h-[200px] flex items-center justify-center">
<ElasticSlider />
</div>
</div>
<div class="demo-section">
<h2 class="demo-title-extra">Using Steps</h2>
<div class="demo-container relative min-h-[200px] flex items-center justify-center">
<ElasticSlider :is-stepped="true" :step-size="10" />
</div>
</div>
<div class="demo-section">
<h2 class="demo-title-extra">Custom Range & Icons</h2>
<div class="demo-container relative min-h-[200px] flex items-center justify-center">
<ElasticSlider :starting-value="500" :default-value="750" :max-value="1000">
<template #left-icon>
<i class="pi pi-thumbs-down text-xl"></i>
</template>
<template #right-icon>
<i class="pi pi-thumbs-up text-xl"></i>
</template>
</ElasticSlider>
</div>
</div>
<PropTable :data="propData" />
</div>
</template>
<template #code>
<CodeExample :code-object="elasticSlider" />
</template>
<template #cli>
<CliInstallation v-bind="elasticSlider" />
</template>
</TabbedLayout>
</template>
<script setup lang="ts">
import TabbedLayout from '@/components/common/TabbedLayout.vue'
import PropTable from '@/components/common/PropTable.vue'
import CodeExample from '@/components/code/CodeExample.vue'
import CliInstallation from '@/components/code/CliInstallation.vue'
import ElasticSlider from '@/content/Components/ElasticSlider/ElasticSlider.vue'
import { elasticSlider } from '@/constants/code/Components/elasticSliderCode'
const propData = [
{
name: 'defaultValue',
type: 'number',
default: '50',
description: 'The initial value of the slider. It can be less than startingValue or greater than maxValue.'
},
{
name: 'startingValue',
type: 'number',
default: '0',
description: 'The starting point for the slider\'s range, e.g., startingValue=100 allows the slider to start at 100.'
},
{
name: 'maxValue',
type: 'number',
default: '100',
description: 'The maximum value the slider can reach.'
},
{
name: 'className',
type: 'string',
default: "''",
description: 'Allows passing custom class names to style the component.'
},
{
name: 'isStepped',
type: 'boolean',
default: 'false',
description: 'Enables or disables stepped increments on the slider.'
},
{
name: 'stepSize',
type: 'number',
default: '1',
description: 'The size of the increments for the slider when isStepped is enabled.'
},
{
name: 'leftIcon',
type: 'Component | string',
default: '\'-\'',
description: 'Custom Vue component or string to display on the left side of the slider.'
},
{
name: 'rightIcon',
type: 'Component | string',
default: '\'+\'',
description: 'Custom Vue component or string to display on the right side of the slider.'
}
]
</script>
<style scoped>
.demo-container {
height: 300px;
min-height: 300px;
overflow: hidden;
}
.demo-title-extra {
font-size: 1.25rem;
font-weight: 500;
color: #fff;
margin-bottom: 0.5rem;
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<div class="flowing-menu-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container" style="height: 600px; overflow: hidden; padding: 100px 0;">
<FlowingMenu :items="demoItems" />
</div>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="flowingMenu" />
</template>
<template #cli>
<CliInstallation :command="flowingMenu.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import FlowingMenu from '../../content/Components/FlowingMenu/FlowingMenu.vue'
import { flowingMenu } from '@/constants/code/Components/flowingMenuCode'
const propData = [
{
name: 'items',
type: 'object[]',
default: '[]',
description: 'An array of objects containing: link, text, image.'
}
]
const demoItems = [
{ link: '#', text: 'Mojave', image: 'https://picsum.photos/600/400?random=1' },
{ link: '#', text: 'Sonoma', image: 'https://picsum.photos/600/400?random=2' },
{ link: '#', text: 'Monterey', image: 'https://picsum.photos/600/400?random=3' },
{ link: '#', text: 'Sequoia', image: 'https://picsum.photos/600/400?random=4' }
]
</script>

View File

@@ -0,0 +1,149 @@
<template>
<div class="flying-posters-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[600px] overflow-hidden flex items-center justify-center">
<FlyingPosters :key="rerenderKey" :items="items" :plane-width="planeWidth" :plane-height="planeHeight"
:distortion="distortion" :scroll-ease="scrollEase" :camera-fov="cameraFov" :camera-z="cameraZ"
class="h-full w-full" />
<div
class="absolute inset-0 flex items-center justify-center pointer-events-none text-[4rem] font-[900] text-[#222] select-none">
Scroll.
</div>
</div>
<Customize>
<PreviewSlider title="Plane Width" v-model="planeWidth" :min="200" :max="400" :step="20"
@update:model-value="forceRerender" />
<PreviewSlider title="Plane Height" v-model="planeHeight" :min="200" :max="400" :step="20"
@update:model-value="forceRerender" />
<PreviewSlider title="Distortion" v-model="distortion" :min="0" :max="10" :step="0.5"
@update:model-value="forceRerender" />
<PreviewSlider title="Scroll Ease" v-model="scrollEase" :min="0.005" :max="0.05" :step="0.005"
@update:model-value="forceRerender" />
<PreviewSlider title="Camera FOV" v-model="cameraFov" :min="30" :max="90" :step="5"
@update:model-value="forceRerender" />
<PreviewSlider title="Camera Z" v-model="cameraZ" :min="10" :max="40" :step="2"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['ogl']" />
</template>
<template #code>
<CodeExample :code-object="flyingPosters" />
</template>
<template #cli>
<CliInstallation :command="flyingPosters.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import FlyingPosters from '../../content/Components/FlyingPosters/FlyingPosters.vue'
import { flyingPosters } from '@/constants/code/Components/flyingPostersCode'
import { useForceRerender } from '@/composables/useForceRerender'
const planeWidth = ref(320)
const planeHeight = ref(320)
const distortion = ref(3)
const scrollEase = ref(0.01)
const cameraFov = ref(45)
const cameraZ = ref(20)
const { rerenderKey, forceRerender } = useForceRerender()
const items = ref([
'https://picsum.photos/800/800?grayscale&random=1',
'https://picsum.photos/800/800?grayscale&random=2',
'https://picsum.photos/800/800?grayscale&random=3',
'https://picsum.photos/800/800?grayscale&random=4',
'https://picsum.photos/800/800?grayscale&random=5',
'https://picsum.photos/800/800?grayscale&random=6',
'https://picsum.photos/800/800?grayscale&random=7',
'https://picsum.photos/800/800?grayscale&random=8',
'https://picsum.photos/800/800?grayscale&random=9',
'https://picsum.photos/800/800?grayscale&random=10',
'https://picsum.photos/800/800?grayscale&random=11',
'https://picsum.photos/800/800?grayscale&random=12',
'https://picsum.photos/800/800?grayscale&random=13',
'https://picsum.photos/800/800?grayscale&random=14',
'https://picsum.photos/800/800?grayscale&random=15',
])
const propData = [
{
name: "items",
type: "string[]",
default: "[]",
description:
"An array of image URLs to display in the 3D poster gallery. Each URL should point to a valid image resource."
},
{
name: "planeWidth",
type: "number",
default: "320",
description:
"The width of each poster plane in pixels. This affects the visual size of the posters in the 3D space."
},
{
name: "planeHeight",
type: "number",
default: "320",
description:
"The height of each poster plane in pixels. This affects the visual size of the posters in the 3D space."
},
{
name: "distortion",
type: "number",
default: "3",
description:
"The amount of distortion effect applied to the posters during animation. Higher values create more dramatic effects."
},
{
name: "scrollEase",
type: "number",
default: "0.01",
description:
"The easing factor for scroll animation. Lower values create smoother, more fluid scroll transitions."
},
{
name: "cameraFov",
type: "number",
default: "45",
description:
"The field of view for the 3D camera in degrees. A wider FOV shows more of the scene but with perspective distortion."
},
{
name: "cameraZ",
type: "number",
default: "20",
description:
"The Z-axis position of the 3D camera. Higher values move the camera further back, showing more posters at once."
},
{
name: "className",
type: "string",
default: "''",
description:
"Additional CSS classes to apply to the component container."
}
]
</script>

View File

@@ -0,0 +1,63 @@
<template>
<div class="glass-icons-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container" style="height: 500px; overflow: hidden;">
<GlassIcons :items="items" class="my-glass-icons" />
</div>
<Customize>
<PreviewSwitch title="Colorful" v-model="colorful" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="glassIcons" />
</template>
<template #cli>
<CliInstallation :command="glassIcons.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import GlassIcons from '../../content/Components/GlassIcons/GlassIcons.vue'
import { glassIcons } from '@/constants/code/Components/glassIconsCode'
const colorful = ref(false)
const items = computed(() => [
{ icon: 'pi pi-file', color: colorful.value ? 'blue' : '#444', label: 'Files' },
{ icon: 'pi pi-book', color: colorful.value ? 'purple' : '#444', label: 'Books' },
{ icon: 'pi pi-heart', color: colorful.value ? 'red' : '#444', label: 'Health' },
{ icon: 'pi pi-cloud', color: colorful.value ? 'indigo' : '#444', label: 'Weather' },
{ icon: 'pi pi-pencil', color: colorful.value ? 'orange' : '#444', label: 'Notes' },
{ icon: 'pi pi-chart-bar', color: colorful.value ? 'green' : '#444', label: 'Stats' }
])
const propData = [
{
name: 'items',
type: 'GlassIconsItem[]',
default: '[]',
description: 'Array of items to render. Each item should include: an icon (string), a color (string), a label (string), and an optional customClass (string).'
},
{
name: 'className',
type: 'string',
default: "''",
description: 'Optional additional CSS class(es) to be added to the container.'
}
]
</script>

View File

@@ -0,0 +1,145 @@
<template>
<div class="gooey-nav-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 flex items-center justify-center">
<GooeyNav
:key="rerenderKey"
:items="navItems"
:animation-time="500"
:particle-count="particleCount"
:particle-distances="[90, 0]"
:particle-r="particleR"
:time-variance="timeVariance"
:initial-active-index="0"
:colors="[1, 2, 3, 1, 2, 3, 1, 4]"
/>
</div>
<Customize>
<PreviewSlider
title="Particle Count"
v-model="particleCount"
:min="1"
:max="50"
:step="1"
@update:model-value="forceRerender"
/>
<PreviewSlider
title="Animation Variance"
v-model="timeVariance"
:min="0"
:max="2000"
:step="100"
@update:model-value="forceRerender"
/>
<PreviewSlider
title="Radius Factor"
v-model="particleR"
:min="0"
:max="1000"
:step="100"
@update:model-value="forceRerender"
/>
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="gooeyNav" />
</template>
<template #cli>
<CliInstallation :command="gooeyNav.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import GooeyNav from '../../content/Components/GooeyNav/GooeyNav.vue'
import { gooeyNav } from '@/constants/code/Components/gooeyNavCode'
import { useForceRerender } from '@/composables/useForceRerender'
const particleCount = ref(15)
const timeVariance = ref(300)
const particleR = ref(100)
const { rerenderKey, forceRerender } = useForceRerender()
const navItems = [
{ label: "Home", href: null },
{ label: "About", href: null },
{ label: "Contact", href: null },
]
const propData = [
{
name: "items",
type: "GooeyNavItem[]",
default: "[]",
description: "Array of navigation items with label and href properties."
},
{
name: "animationTime",
type: "number",
default: "600",
description: "Duration (ms) of the main animation."
},
{
name: "particleCount",
type: "number",
default: "15",
description: "Number of bubble particles per transition."
},
{
name: "particleDistances",
type: "[number, number]",
default: "[90, 10]",
description: "Outer and inner distances of bubble spread."
},
{
name: "particleR",
type: "number",
default: "100",
description: "Radius factor influencing random particle rotation."
},
{
name: "timeVariance",
type: "number",
default: "300",
description: "Random time variance (ms) for particle animations."
},
{
name: "colors",
type: "number[]",
default: "[1, 2, 3, 1, 2, 3, 1, 4]",
description: "Color indices used when creating bubble particles."
},
{
name: "initialActiveIndex",
type: "number",
default: "0",
description: "Which item is selected on mount."
}
]
</script>
<style>
/* Add color variables for the particles */
:root {
--color-1: #ff6b6b;
--color-2: #4ecdc4;
--color-3: #45b7d1;
--color-4: #f9ca24;
}
</style>

View File

@@ -0,0 +1,155 @@
<template>
<div class="infinite-scroll-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container"
style="height: 500px; overflow: hidden; display: flex; justify-content: center; align-items: center;">
<InfiniteScroll :items="items" :is-tilted="isTilted" :tilt-direction="tiltDirection" :autoplay="autoplay"
:autoplay-speed="1" :autoplay-direction="autoplayDirection" :pause-on-hover="pauseOnHover" />
</div>
<Customize>
<PreviewSwitch title="Tilt" v-model="isTilted" />
<PreviewSelect v-if="isTilted" title="Tilt Direction" :options="tiltOptions" v-model="tiltDirection" />
<hr style="margin: 1rem 0; border-color: #222;" />
<PreviewSwitch title="Autoplay" v-model="autoplay" />
<template v-if="autoplay">
<PreviewSelect title="Autoplay Direction" :options="autoplayOptions" v-model="autoplayDirection" />
<PreviewSwitch title="Pause on Hover" v-model="pauseOnHover" />
</template>
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="infiniteScroll" />
</template>
<template #cli>
<CliInstallation :command="infiniteScroll.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewSelect from '../../components/common/PreviewSelect.vue'
import InfiniteScroll from '../../content/Components/InfiniteScroll/InfiniteScroll.vue'
import { infiniteScroll } from '@/constants/code/Components/infiniteScrollCode'
const isTilted = ref(true)
const tiltDirection = ref<'left' | 'right'>('left')
const autoplay = ref(true)
const autoplayDirection = ref<'up' | 'down'>('up')
const pauseOnHover = ref(true)
const items = [
{ content: 'Paragraph Item 2' },
{ content: 'Text Item 3' },
{ content: 'Paragraph Item 4' },
{ content: 'Text Item 5' },
{ content: 'Paragraph Item 6' },
{ content: 'Text Item 7' },
{ content: 'Paragraph Item 8' },
{ content: 'Text Item 9' },
{ content: 'Paragraph Item 10' },
{ content: 'Text Item 11' },
{ content: 'Paragraph Item 12' },
{ content: 'Text Item 13' },
{ content: 'Paragraph Item 14' }
]
const autoplayOptions = [
{ value: 'up', label: 'Up' },
{ value: 'down', label: 'Down' }
]
const tiltOptions = [
{ value: 'left', label: 'Left' },
{ value: 'right', label: 'Right' }
]
const propData = [
{
name: 'width',
type: 'string',
default: '"30rem"',
description: 'Width of the outer wrapper.'
},
{
name: 'maxHeight',
type: 'string',
default: '"100%"',
description: 'Maximum height of the outer wrapper.'
},
{
name: 'items',
type: 'array',
default: '[]',
description: 'Array of items with custom content. Each item should have a "content" property containing a string or Vue component.'
},
{
name: 'itemMinHeight',
type: 'number',
default: '150',
description: 'Fixed height for each item in pixels.'
},
{
name: 'isTilted',
type: 'boolean',
default: 'false',
description: 'Whether the container has a skewed perspective.'
},
{
name: 'tiltDirection',
type: '"left" | "right"',
default: '"left"',
description: 'Direction of the tilt if "isTilted" is true.'
},
{
name: 'autoplay',
type: 'boolean',
default: 'false',
description: 'Whether the scroll should autoplay.'
},
{
name: 'autoplaySpeed',
type: 'number',
default: '0.5',
description: 'Speed of autoplay in pixels/frame.'
},
{
name: 'autoplayDirection',
type: '"up" | "down"',
default: '"down"',
description: 'Direction of autoplay scrolling.'
},
{
name: 'pauseOnHover',
type: 'boolean',
default: 'false',
description: 'Pause autoplay when hovering over the component.'
},
{
name: 'negativeMargin',
type: 'string',
default: '"-0.5em"',
description: 'Negative margin to reduce spacing between items.'
}
]
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div class="pixel-card-demo">
<TabbedLayout>
<template #preview>
<div
class="demo-container relative min-h-[500px] max-h-[500px] overflow-hidden flex items-center justify-center">
<PixelCard :key="rerenderKey" :variant="selectedVariant">
<div class="absolute mix-blend-screen z-10 inset-0 flex items-center justify-center w-full h-full">
<h2 class="text-5xl font-black select-none text-[#222]">
Hover Me.
</h2>
</div>
</PixelCard>
</div>
<Customize>
<PreviewSelect title="Animation Variant" :options="variantOptions" v-model="selectedVariant"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="pixelCard" />
</template>
<template #cli>
<CliInstallation :command="pixelCard.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSelect from '../../components/common/PreviewSelect.vue'
import PixelCard from '../../content/Components/PixelCard/PixelCard.vue'
import { pixelCard } from '@/constants/code/Components/pixelCardCode'
import { useForceRerender } from '@/composables/useForceRerender'
const selectedVariant = ref<'default' | 'blue' | 'yellow' | 'pink'>('default')
const { rerenderKey, forceRerender } = useForceRerender()
const variantOptions = [
{ value: 'default', label: 'Default' },
{ value: 'yellow', label: 'Yellow' },
{ value: 'blue', label: 'Blue' },
{ value: 'pink', label: 'Pink' }
]
const propData = [
{
name: "variant",
type: "string",
default: '"default"',
description: "Defines the color scheme and animation style.",
options: "default | yellow | blue | pink"
},
{
name: "gap",
type: "number",
default: "varies by variant",
description: "Pixel grid gap size in pixels."
},
{
name: "speed",
type: "number",
default: "varies by variant",
description: "Animation speed modifier (lower is slower)."
},
{
name: "colors",
type: "string",
default: '"#f8fafc,#f1f5f9,#cbd5e1"',
description: "Comma-separated list of colors for the pixel effect."
},
{
name: "noFocus",
type: "boolean",
default: "false",
description: "If true, prevents animation from triggering on focus."
},
{
name: "className",
type: "string",
default: '""',
description: "Additional CSS class for the wrapper."
}
]
</script>

View File

@@ -0,0 +1,207 @@
<template>
<div class="profile-card-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container" style="height: 700px; overflow: hidden; position: relative;">
<ProfileCard :key="rerenderKey" name="Javi A. Torres" title="Software Engineer" handle="javicodes"
status="Online" contact-text="Contact Me" avatar-url="/assets/person.png"
:icon-url="showIcon ? '/assets/iconpattern.png' : ''" :show-user-info="showUserInfo"
:show-behind-gradient="showBehindGradient" grain-url="/assets/grain.webp"
:behind-gradient="customBehindGradient" :inner-gradient="customInnerGradient"
@contact-click="handleContactClick" />
</div>
<Customize>
<button @click="generateRandomGradients" class="randomize-btn">
Randomize Colors
</button>
<PreviewSwitch title="Show Icon Pattern" v-model="showIcon" @update:model-value="forceRerender" />
<PreviewSwitch title="Show User Info" v-model="showUserInfo" @update:model-value="forceRerender" />
<PreviewSwitch title="Show BG Gradient" v-model="showBehindGradient" @update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="profileCard" />
</template>
<template #cli>
<CliInstallation :command="profileCard.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import ProfileCard from '../../content/Components/ProfileCard/ProfileCard.vue'
import { profileCard } from '@/constants/code/Components/profileCardCode'
import { useToast } from 'primevue/usetoast'
import { useForceRerender } from '@/composables/useForceRerender'
const showIcon = ref(true)
const showUserInfo = ref(true)
const showBehindGradient = ref(true)
const customBehindGradient = ref("radial-gradient(farthest-side circle at var(--pointer-x) var(--pointer-y),hsla(266,100%,90%,var(--card-opacity)) 4%,hsla(266,50%,80%,calc(var(--card-opacity)*0.75)) 10%,hsla(266,25%,70%,calc(var(--card-opacity)*0.5)) 50%,hsla(266,0%,60%,0) 100%),radial-gradient(35% 52% at 55% 20%,#00ffaac4 0%,#073aff00 100%),radial-gradient(100% 100% at 50% 50%,#00c1ffff 1%,#073aff00 76%),conic-gradient(from 124deg at 50% 50%,#c137ffff 0%,#07c6ffff 40%,#07c6ffff 60%,#c137ffff 100%)")
const customInnerGradient = ref("linear-gradient(145deg,#60496e8c 0%,#71C4FF44 100%)")
const toast = useToast()
const { rerenderKey, forceRerender } = useForceRerender()
const generateRandomGradients = () => {
const randomHue1 = Math.floor(Math.random() * 360)
const randomHue2 = Math.floor(Math.random() * 360)
const randomHue3 = Math.floor(Math.random() * 360)
const randomHue4 = Math.floor(Math.random() * 360)
const newBehindGradient = `radial-gradient(farthest-side circle at var(--pointer-x) var(--pointer-y),hsla(${randomHue1},100%,90%,var(--card-opacity)) 4%,hsla(${randomHue1},50%,80%,calc(var(--card-opacity)*0.75)) 10%,hsla(${randomHue1},25%,70%,calc(var(--card-opacity)*0.5)) 50%,hsla(${randomHue1},0%,60%,0) 100%),radial-gradient(35% 52% at 55% 20%,hsl(${randomHue2}, 100%, 70%) 0%,transparent 100%),radial-gradient(100% 100% at 50% 50%,hsl(${randomHue3}, 100%, 65%) 1%,transparent 76%),conic-gradient(from 124deg at 50% 50%,hsl(${randomHue4}, 100%, 70%) 0%,hsl(${randomHue2}, 100%, 70%) 40%,hsl(${randomHue2}, 100%, 70%) 60%,hsl(${randomHue4}, 100%, 70%) 100%)`
const newInnerGradient = `linear-gradient(145deg,hsla(${randomHue1}, 40%, 45%, 0.55) 0%,hsla(${randomHue3}, 60%, 70%, 0.27) 100%)`
customBehindGradient.value = newBehindGradient
customInnerGradient.value = newInnerGradient
forceRerender()
}
const handleContactClick = () => {
toast.add({
severity: 'info',
summary: 'Contact Clicked!',
detail: 'Contact button was clicked',
life: 3000
})
}
const propData = [
{
name: 'avatarUrl',
type: 'string',
default: '"<Placeholder for avatar URL>"',
description: 'URL for the main avatar image displayed on the card'
},
{
name: 'iconUrl',
type: 'string',
default: '"<Placeholder for icon URL>"',
description: 'Optional URL for an icon pattern overlay on the card background'
},
{
name: 'grainUrl',
type: 'string',
default: '"<Placeholder for grain URL>"',
description: 'Optional URL for a grain texture overlay effect'
},
{
name: 'behindGradient',
type: 'string',
default: 'undefined',
description: 'Custom CSS gradient string for the background gradient effect'
},
{
name: 'innerGradient',
type: 'string',
default: 'undefined',
description: 'Custom CSS gradient string for the inner card gradient'
},
{
name: 'showBehindGradient',
type: 'boolean',
default: 'true',
description: 'Whether to display the background gradient effect'
},
{
name: 'className',
type: 'string',
default: '""',
description: 'Additional CSS classes to apply to the card wrapper'
},
{
name: 'enableTilt',
type: 'boolean',
default: 'true',
description: 'Enable or disable the 3D tilt effect on mouse hover'
},
{
name: 'miniAvatarUrl',
type: 'string',
default: 'undefined',
description: 'Optional URL for a smaller avatar in the user info section'
},
{
name: 'name',
type: 'string',
default: '"Javi A. Torres"',
description: 'User\'s display name'
},
{
name: 'title',
type: 'string',
default: '"Software Engineer"',
description: 'User\'s job title or role'
},
{
name: 'handle',
type: 'string',
default: '"javicodes"',
description: 'User\'s handle or username (displayed with @ prefix)'
},
{
name: 'status',
type: 'string',
default: '"Online"',
description: 'User\'s current status'
},
{
name: 'contactText',
type: 'string',
default: '"Contact"',
description: 'Text displayed on the contact button'
},
{
name: 'showUserInfo',
type: 'boolean',
default: 'true',
description: 'Whether to display the user information section'
},
{
name: 'onContactClick',
type: 'function',
default: 'undefined',
description: 'Callback function called when the contact button is clicked'
}
]
</script>
<style scoped>
.randomize-btn {
font-size: 12px;
background: #111;
border-radius: 10px;
border: 1px solid #333;
color: #fff;
height: 32px;
padding: 0 12px;
cursor: pointer;
transition: background-color 0.2s;
}
.randomize-btn:hover {
background: #222;
}
.demo-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<div class="spotlight-card-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative py-10">
<SpotlightCard class-name="custom-spotlight-card">
<div class="flex h-full flex-col items-start justify-center">
<i class="pi pi-star-fill text-4xl mb-3 text-white"></i>
<h3 class="text-2xl font-semibold tracking-tight mb-1 text-white">
Boost Your Experience
</h3>
<p class="text-sm text-zinc-400">
Get exclusive benefits, features & 24/7 support as a permanent club member.
</p>
</div>
</SpotlightCard>
</div>
<h2 class="text-xl font-semibold text-white mb-4 mt-8">Custom Color</h2>
<div class="demo-container relative py-10">
<SpotlightCard class-name="custom-spotlight-card" spotlight-color="rgba(39, 255, 100, 0.326)">
<div class="flex h-full flex-col items-start justify-center">
<i class="pi pi-lock text-3xl mb-3 text-white"></i>
<h3 class="text-2xl font-semibold tracking-tight mb-1 text-white">
Enhanced Security
</h3>
<p class="text-sm text-zinc-400">
Our state of the art software offers peace of mind through strict security measures.
</p>
</div>
</SpotlightCard>
</div>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="spotlightCard" />
</template>
<template #cli>
<CliInstallation :command="spotlightCard.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import SpotlightCard from '../../content/Components/SpotlightCard/SpotlightCard.vue'
import { spotlightCard } from '@/constants/code/Components/spotlightCardCode'
const propData = [
{
name: 'spotlightColor',
type: 'string',
default: 'rgba(255, 255, 255, 0.25)',
description: 'Controls the color of the radial gradient used for the spotlight effect.',
},
{
name: 'className',
type: 'string',
default: '',
description: 'Allows adding custom classes to the component.',
}
]
</script>
<style>
.custom-spotlight-card {
min-height: 200px;
max-width: 400px;
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<div class="blur-text-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative min-h-[400px] overflow-hidden">
<RefreshButton @refresh="forceRerender" />
<BlurText :key="rerenderKey" text="Isn't this so cool?!" :delay="delay"
class-name="text-3xl md:text-6xl font-bold text-center" :animate-by="animateBy" :direction="direction"
:threshold="threshold" :root-margin="rootMargin" :step-duration="stepDuration"
@animation-complete="() => { showCallback && showToast() }" />
</div>
<Customize>
<PreviewSwitch title="Show Completion Toast" v-model="showCallback" @update:model-value="forceRerender" />
<div class="flex gap-4 flex-wrap">
<button
class="text-xs bg-[#0b0b0b] rounded-[10px] border border-[#1e3721] hover:bg-[#1e3721] text-white h-8 px-3 transition-colors"
@click="toggleAnimateBy">
Animate By: <span class="text-[#a1a1aa]">&nbsp;{{ animateBy }}</span>
</button>
<button
class="text-xs bg-[#0b0b0b] rounded-[10px] border border-[#1e3721] hover:bg-[#1e3721] text-white h-8 px-3 transition-colors"
@click="toggleDirection">
Direction: <span class="text-[#a1a1aa]">&nbsp;{{ direction }}</span>
</button>
</div>
<PreviewSlider title="Delay (ms)" v-model="delay" :min="50" :max="500" :step="10"
@update:model-value="forceRerender" />
<PreviewSlider title="Step Duration (s)" v-model="stepDuration" :min="0.1" :max="1" :step="0.05"
@update:model-value="forceRerender" />
<PreviewSlider title="Threshold" v-model="threshold" :min="0.1" :max="1" :step="0.1"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['motion-v']" />
</template>
<template #code>
<CodeExample :code-object="blurText" />
</template>
<template #cli>
<CliInstallation :command="blurText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import RefreshButton from '../../components/common/RefreshButton.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import BlurText from '../../content/TextAnimations/BlurText/BlurText.vue'
import { blurText } from '@/constants/code/TextAnimations/blurTextCode'
import { useToast } from 'primevue/usetoast'
import { useForceRerender } from '@/composables/useForceRerender'
const animateBy = ref<'words' | 'letters'>('words')
const direction = ref<'top' | 'bottom'>('top')
const delay = ref(200)
const stepDuration = ref(0.35)
const threshold = ref(0.1)
const rootMargin = ref('0px')
const showCallback = ref(true)
const toast = useToast()
const { rerenderKey, forceRerender } = useForceRerender()
const toggleAnimateBy = () => {
animateBy.value = animateBy.value === 'words' ? 'letters' : 'words'
forceRerender()
}
const toggleDirection = () => {
direction.value = direction.value === 'top' ? 'bottom' : 'top'
forceRerender()
}
const showToast = () => {
toast.add({
severity: 'secondary',
summary: 'Animation Finished!',
life: 3000
})
}
const propData = [
{ name: 'text', type: 'string', default: '""', description: 'The text content to animate.' },
{ name: 'animateBy', type: 'string', default: '"words"', description: 'Determines whether to animate by "words" or "letters".' },
{ name: 'direction', type: 'string', default: '"top"', description: 'Direction from which the words/letters appear ("top" or "bottom").' },
{ name: 'delay', type: 'number', default: '200', description: 'Delay between animations for each word/letter (in ms).' },
{ name: 'stepDuration', type: 'number', default: '0.35', description: 'The time taken for each letter/word to animate (in seconds).' },
{ name: 'threshold', type: 'number', default: '0.1', description: 'Intersection threshold for triggering the animation.' },
{ name: 'rootMargin', type: 'string', default: '"0px"', description: 'Root margin for the intersection observer.' },
{ name: 'className', type: 'string', default: '""', description: 'Additional class names to style the component.' },
{ name: 'animationFrom', type: 'object', default: 'undefined', description: 'Custom initial animation properties.' },
{ name: 'animationTo', type: 'array', default: 'undefined', description: 'Custom target animation properties array.' },
{ name: 'easing', type: 'function', default: '(t) => t', description: 'Custom easing function for the animation.' },
{ name: 'onAnimationComplete', type: 'function', default: 'undefined', description: 'Callback function triggered when all animations complete.' }
]
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div class="circular-text-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative min-h-[400px] overflow-hidden flex items-center justify-center">
<CircularText :key="rerenderKey" text="VUE * BITS * IS * AWESOME * " :spin-duration="spinDuration"
:on-hover="onHover" class-name="text-blue-500" />
</div>
<Customize>
<div class="flex gap-4 flex-wrap">
<button
class="text-xs bg-[#0b0b0b] rounded-[10px] border border-[#1e3721] hover:bg-[#1e3721] text-white h-8 px-3 transition-colors"
@click="toggleOnHover">
On Hover: <span class="text-[#a1a1aa]">&nbsp;{{ onHover }}</span>
</button>
</div>
<PreviewSlider title="Spin Duration (s)" v-model="spinDuration" :min="1" :max="50" :step="1"
@update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['motion-v']" />
</template>
<template #code>
<CodeExample :code-object="circularText" />
</template>
<template #cli>
<CliInstallation :command="circularText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import CircularText from '../../content/TextAnimations/CircularText/CircularText.vue'
import { circularText } from '@/constants/code/TextAnimations/circularTextCode'
import { useForceRerender } from '@/composables/useForceRerender'
const onHover = ref<'slowDown' | 'speedUp' | 'pause' | 'goBonkers'>('speedUp')
const spinDuration = ref(20)
const { rerenderKey, forceRerender } = useForceRerender()
const hoverOptions: Array<'slowDown' | 'speedUp' | 'pause' | 'goBonkers'> = [
'slowDown',
'speedUp',
'pause',
'goBonkers'
]
const toggleOnHover = () => {
const currentIndex = hoverOptions.indexOf(onHover.value)
const nextIndex = (currentIndex + 1) % hoverOptions.length
onHover.value = hoverOptions[nextIndex]
forceRerender()
}
const propData = [
{ name: 'text', type: 'string', default: '""', description: 'The text content to display in a circular pattern.' },
{ name: 'spinDuration', type: 'number', default: '20', description: 'Duration of one full rotation in seconds.' },
{ name: 'onHover', type: 'string', default: '"speedUp"', description: 'Hover behavior: "slowDown", "speedUp", "pause", or "goBonkers".' },
{ name: 'className', type: 'string', default: '""', description: 'Additional class names to style the component.' }
]
</script>

View File

@@ -0,0 +1,100 @@
<template>
<div class="curved-loop-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden p-0">
<CurvedLoop :key="rerenderKey" :marquee-text="marqueeText" :speed="speed" :curve-amount="curveAmount"
:interactive="interactive" />
</div>
<Customize>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Marquee Text</label>
<input v-model="marqueeText" type="text" placeholder="Enter text..."
class="w-[300px] px-3 py-2 bg-[#0b0b0b] border border-[#333] rounded-md text-white focus:outline-none focus:border-[#666]"
@input="forceRerender" />
</div>
<PreviewSlider title="Speed" v-model="speed" :min="0" :max="10" :step="0.1"
@update:model-value="forceRerender" />
<PreviewSlider title="Curve Amount" v-model="curveAmount" :min="-400" :max="400" :step="10" value-unit="px"
@update:model-value="forceRerender" />
<PreviewSwitch title="Draggable" :model-value="interactive"
@update:model-value="(val: boolean) => { interactive = val; forceRerender() }" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="curvedLoop" />
</template>
<template #cli>
<CliInstallation :command="curvedLoop.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import CurvedLoop from '../../content/TextAnimations/CurvedLoop/CurvedLoop.vue'
import { curvedLoop } from '@/constants/code/TextAnimations/curvedLoopCode'
import { useForceRerender } from '@/composables/useForceRerender'
const marqueeText = ref('Be ✦ Creative ✦ With ✦ Vue ✦ Bits ✦')
const speed = ref(2)
const curveAmount = ref(400)
const interactive = ref(true)
const { rerenderKey, forceRerender } = useForceRerender()
const propData = [
{
name: 'marqueeText',
type: 'string',
default: '""',
description: 'The text to display in the curved marquee'
},
{
name: 'speed',
type: 'number',
default: '2',
description: 'Animation speed of the marquee text'
},
{
name: 'className',
type: 'string',
default: 'undefined',
description: 'CSS class name for styling the text'
},
{
name: 'curveAmount',
type: 'number',
default: '400',
description: 'Amount of curve in the text path'
},
{
name: 'direction',
type: '"left" | "right"',
default: '"left"',
description: 'Initial direction of the marquee animation'
},
{
name: 'interactive',
type: 'boolean',
default: 'true',
description: 'Whether the marquee can be dragged by the user'
}
]
</script>

View File

@@ -0,0 +1,160 @@
<template>
<div class="decrypted-text-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative py-6 overflow-hidden">
<RefreshButton @click="forceRerender" />
<div :key="key" class="pl-6 m-8 w-full flex flex-col justify-start items-start">
<DecryptedText :speed="speed" text="Ahoy, matey!" :max-iterations="maxIterations" :sequential="sequential"
:reveal-direction="revealDirection" parent-class-name="decrypted-text"
:use-original-chars-only="useOriginalCharsOnly" :animate-on="animateOn" />
<DecryptedText :speed="speed" text="Set yer eyes on this" :max-iterations="maxIterations"
:sequential="sequential" :reveal-direction="revealDirection" parent-class-name="decrypted-text"
:use-original-chars-only="useOriginalCharsOnly" :animate-on="animateOn" />
<DecryptedText :speed="speed" text="And try tinkerin' round'" :max-iterations="maxIterations"
:sequential="sequential" :reveal-direction="revealDirection" parent-class-name="decrypted-text"
:use-original-chars-only="useOriginalCharsOnly" :animate-on="animateOn" />
<DecryptedText :speed="speed" text="with these here props, arr!" :max-iterations="maxIterations"
:sequential="sequential" :reveal-direction="revealDirection" parent-class-name="decrypted-text"
:use-original-chars-only="useOriginalCharsOnly" :animate-on="animateOn"
@animation-complete="() => console.log('✅ Animation Finished!')" />
</div>
</div>
<Customize>
<PreviewSwitch title="Sequential" v-model="sequential" @update:model-value="forceRerender" />
<PreviewSwitch title="Original Chars" v-model="useOriginalCharsOnly" @update:model-value="forceRerender" />
<PreviewSlider title="Speed" v-model="speed" :min="10" :max="200" :step="10" value-unit="ms"
@update:model-value="forceRerender" />
<PreviewSlider title="Iterations" v-model="maxIterations" :min="1" :max="50" :step="1"
@update:model-value="forceRerender" />
<PreviewSelect title="Animation Trigger" v-model="animateOn" :options="animateOptions"
@update:model-value="(val) => { animateOn = val as 'view' | 'hover'; forceRerender(); }" />
<PreviewSelect title="Animation Direction" v-model="revealDirection" :options="directionOptions"
@update:model-value="(val) => { revealDirection = val as 'start' | 'end' | 'center'; forceRerender(); }" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="decryptedText" />
</template>
<template #cli>
<CliInstallation :command="decryptedText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewSelect from '../../components/common/PreviewSelect.vue'
import RefreshButton from '../../components/common/RefreshButton.vue'
import DecryptedText from '../../content/TextAnimations/DecryptedText/DecryptedText.vue'
import { decryptedText } from '@/constants/code/TextAnimations/decryptedTextCode'
import { useForceRerender } from '@/composables/useForceRerender'
const { rerenderKey: key, forceRerender } = useForceRerender()
const speed = ref(60)
const maxIterations = ref(10)
const sequential = ref(true)
const useOriginalCharsOnly = ref(false)
const revealDirection = ref<'start' | 'end' | 'center'>('start')
const animateOn = ref<'view' | 'hover'>('view')
const animateOptions = [
{ label: 'View', value: 'view' },
{ label: 'Hover', value: 'hover' }
]
const directionOptions = [
{ label: 'Start', value: 'start' },
{ label: 'End', value: 'end' },
{ label: 'Center', value: 'center' }
]
const propData = [
{
name: 'text',
type: 'string',
default: '""',
description: 'The text content to decrypt.'
},
{
name: 'speed',
type: 'number',
default: '50',
description: 'Time in ms between each iteration.'
},
{
name: 'maxIterations',
type: 'number',
default: '10',
description: 'Max # of random iterations (non-sequential mode).'
},
{
name: 'sequential',
type: 'boolean',
default: 'false',
description: 'Whether to reveal one character at a time in sequence.'
},
{
name: 'revealDirection',
type: '"start" | "end" | "center"',
default: '"start"',
description: 'From which position characters begin to reveal in sequential mode.'
},
{
name: 'useOriginalCharsOnly',
type: 'boolean',
default: 'false',
description: 'Restrict scrambling to only the characters already in the text.'
},
{
name: 'className',
type: 'string',
default: '""',
description: 'CSS class for revealed characters.'
},
{
name: 'parentClassName',
type: 'string',
default: '""',
description: 'CSS class for the main characters container.'
},
{
name: 'encryptedClassName',
type: 'string',
default: '""',
description: 'CSS class for encrypted characters.'
},
{
name: 'animateOn',
type: '"view" | "hover"',
default: '"hover"',
description: 'Trigger scrambling on hover or scroll-into-view.'
}
]
</script>
<style scoped>
.decrypted-text {
font-size: 2rem;
line-height: 1.6;
}
</style>

View File

@@ -0,0 +1,123 @@
<template>
<div class="falling-text-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[400px] overflow-hidden p-0 flex justify-center items-center">
<FallingText :key="key"
text="Vue Bits is a library of animated and interactive Vue components designed to streamline UI development and simplify your workflow."
:highlight-words="['Vue', 'Bits', 'animated', 'components', 'simplify']" :trigger="trigger"
:gravity="gravity" font-size="2rem" :mouse-constraint-stiffness="mouseConstraintStiffness" />
<div class="absolute z-0 text-[4rem] font-[900] text-[#222] select-none" v-if="!effectStarted">
{{ trigger === 'hover' ? 'Hover Me' : trigger === 'click' ? 'Click Me' : 'Auto Start' }}
</div>
</div>
<Customize>
<PreviewSelect title="Animation Trigger" v-model="trigger" :options="triggerOptions"
@update:model-value="forceRerender" />
<PreviewSlider title="Gravity" v-model="gravity" :min="0.1" :max="2" :step="0.01"
@update:model-value="forceRerender" />
<PreviewSlider title="Mouse Constraint Stiffness" v-model="mouseConstraintStiffness" :min="0.1" :max="2"
:step="0.1" @update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['matter-js']" />
</template>
<template #code>
<CodeExample :code-object="fallingText" />
</template>
<template #cli>
<CliInstallation :command="fallingText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSelect from '../../components/common/PreviewSelect.vue'
import FallingText from '../../content/TextAnimations/FallingText/FallingText.vue'
import { fallingText } from '@/constants/code/TextAnimations/fallingTextCode'
import { useForceRerender } from '@/composables/useForceRerender'
const { rerenderKey: key, forceRerender } = useForceRerender()
const gravity = ref(0.56)
const mouseConstraintStiffness = ref(0.9)
const trigger = ref<'hover' | 'click' | 'auto' | 'scroll'>('hover')
const effectStarted = computed(() => trigger.value === 'auto')
const triggerOptions = [
{ value: 'hover', label: 'Hover' },
{ value: 'click', label: 'Click' },
{ value: 'auto', label: 'Auto' },
{ value: 'scroll', label: 'Scroll' }
]
const propData = [
{
name: 'text',
type: 'string',
default: '""',
description: 'The text content to display and eventually animate.'
},
{
name: 'highlightWords',
type: 'string[]',
default: '[]',
description: 'List of words or substrings to apply a highlight style.'
},
{
name: 'trigger',
type: "'click' | 'hover' | 'auto' | 'scroll'",
default: '"auto"',
description: 'Defines how the falling effect is activated.'
},
{
name: 'backgroundColor',
type: 'string',
default: '"transparent"',
description: 'Canvas background color for the physics world.'
},
{
name: 'wireframes',
type: 'boolean',
default: 'false',
description: 'Whether to render the physics bodies in wireframe mode.'
},
{
name: 'gravity',
type: 'number',
default: '1',
description: 'Vertical gravity factor for the physics engine.'
},
{
name: 'mouseConstraintStiffness',
type: 'number',
default: '0.2',
description: 'Stiffness for the mouse drag constraint.'
},
{
name: 'fontSize',
type: 'string',
default: '"1rem"',
description: 'Font size applied to the text before it falls.'
}
]
</script>
<style scoped></style>

View File

@@ -0,0 +1,109 @@
<template>
<div class="fuzzy-text-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden">
<div class="flex flex-col items-center justify-center h-full">
<FuzzyText :key="`main-${rerenderKey}`" text="404" :base-intensity="baseIntensity"
:hover-intensity="hoverIntensity" :enable-hover="enableHover" :font-size="140" />
<div class="my-1" />
<FuzzyText :key="`sub-${rerenderKey}`" text="not found" :base-intensity="baseIntensity"
:hover-intensity="hoverIntensity" :enable-hover="enableHover" :font-size="70" font-family="Gochi Hand" />
</div>
</div>
<Customize>
<PreviewSlider title="Base Intensity" v-model="baseIntensity" :min="0" :max="1" :step="0.01"
@update:model-value="forceRerender" />
<PreviewSlider title="Hover Intensity" v-model="hoverIntensity" :min="0" :max="2" :step="0.01"
@update:model-value="forceRerender" />
<PreviewSwitch title="Enable Hover" :model-value="enableHover"
@update:model-value="(val: boolean) => { enableHover = val; forceRerender() }" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="fuzzyText" />
</template>
<template #cli>
<CliInstallation :command="fuzzyText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import FuzzyText from '../../content/TextAnimations/FuzzyText/FuzzyText.vue'
import { fuzzyText } from '@/constants/code/TextAnimations/fuzzyTextCode'
import { useForceRerender } from '@/composables/useForceRerender'
const baseIntensity = ref(0.2)
const hoverIntensity = ref(0.5)
const enableHover = ref(true)
const { rerenderKey, forceRerender } = useForceRerender()
const propData = [
{
name: 'text',
type: 'string',
default: '""',
description: 'The text content to display inside the fuzzy text component.'
},
{
name: 'fontSize',
type: 'number | string',
default: '"clamp(2rem, 8vw, 8rem)"',
description: 'Specifies the font size of the text. Accepts any valid CSS font-size value or a number (interpreted as pixels).'
},
{
name: 'fontWeight',
type: 'string | number',
default: '900',
description: 'Specifies the font weight of the text.'
},
{
name: 'fontFamily',
type: 'string',
default: '"inherit"',
description: 'Specifies the font family of the text. "inherit" uses the computed style from the parent.'
},
{
name: 'color',
type: 'string',
default: '#fff',
description: 'Specifies the text color.'
},
{
name: 'enableHover',
type: 'boolean',
default: 'true',
description: 'Enables the hover effect for the fuzzy text.'
},
{
name: 'baseIntensity',
type: 'number',
default: '0.18',
description: 'The fuzz intensity when the text is not hovered.'
},
{
name: 'hoverIntensity',
type: 'number',
default: '0.5',
description: 'The fuzz intensity when the text is hovered.'
}
]
</script>

View File

@@ -0,0 +1,121 @@
<template>
<div class="gradient-text-demo">
<TabbedLayout>
<template #preview>
<h2 class="demo-title-extra">Default</h2>
<div class="demo-container relative min-h-[150px] flex items-center justify-center">
<div class="text-[2rem]">
<GradientText text="Add a splash of color!" :colors="gradientPreview" :animation-speed="speed"
:show-border="false" />
</div>
</div>
<h2 class="demo-title-extra">Border Animation</h2>
<div class="demo-container relative min-h-[150px] flex items-center justify-center">
<div class="text-[2rem]">
<GradientText text="Now with a cool border!" :colors="gradientPreview" :animation-speed="speed"
:show-border="true" class-name="custom-gradient-class" />
</div>
</div>
<Customize>
<PreviewSlider title="Loop Duration" v-model="speed" :min="1" :max="10" :step="0.5" value-unit="s" />
<div class="flex flex-col gap-0">
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Colors</label>
<input v-model="colors" type="text" placeholder="Enter colors separated by commas" maxlength="100"
class="w-[300px] px-3 py-2 bg-[#0b0b0b] border border-[#333] rounded-md text-white focus:outline-none focus:border-[#666]" />
</div>
<div class="w-[300px] h-3 rounded-md border border-[#271E37]" :style="{
background: `linear-gradient(to right, ${gradientPreview.join(', ')})`
}" />
</div>
</Customize>
<p class="demo-extra-info mt-4 flex items-center gap-2">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clip-rule="evenodd" />
</svg>
For a smoother animation, the gradient should start and end with the same color.
</p>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="gradientText" />
</template>
<template #cli>
<CliInstallation :command="gradientText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import GradientText from '../../content/TextAnimations/GradientText/GradientText.vue'
import { gradientText } from '@/constants/code/TextAnimations/gradientTextCode'
const colors = ref('#40ffaa, #4079ff, #40ffaa, #4079ff, #40ffaa')
const speed = ref(3)
const gradientPreview = computed(() => colors.value.split(',').map(color => color.trim()))
const propData = [
{
name: 'text',
type: 'string',
default: '""',
description: 'The text content to be displayed with gradient effect.'
},
{
name: 'className',
type: 'string',
default: "''",
description: 'Adds custom classes to the root element for additional styling.'
},
{
name: 'colors',
type: 'string[]',
default: '["#ffaa40", "#9c40ff", "#ffaa40"]',
description: 'Defines the gradient colors for the text or border.'
},
{
name: 'animationSpeed',
type: 'number',
default: '8',
description: 'The duration of the gradient animation in seconds.'
},
{
name: 'showBorder',
type: 'boolean',
default: 'false',
description: 'Determines whether a border with the gradient effect is displayed.'
}
]
</script>
<style scoped>
.demo-title-extra {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
color: #e5e7eb;
}
.demo-extra-info {
color: #a1a1aa;
font-size: 0.875rem;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="shiny-text-demo">
<TabbedLayout>
<template #preview>
<h2 class="demo-title-extra">Basic</h2>
<div class="demo-container relative min-h-[150px] text-2xl flex items-center justify-center">
<ShinyText text="Just some shiny text!" :disabled="false" :speed="3" class-name="shiny-text-demo" />
</div>
<h2 class="demo-title-extra">Button Text</h2>
<div class="demo-container relative min-h-[150px] text-2xl flex items-center justify-center">
<div class="shiny-button">
<ShinyText text="Shiny Button" :disabled="false" :speed="3" class-name="shiny-text-demo" />
</div>
</div>
<h2 class="demo-title-extra">Configurable Speed</h2>
<div class="demo-container relative min-h-[150px] text-2xl flex items-center justify-center">
<ShinyText :text="speed < 2.5 ? '🐎 This is fast!' : '🐌 This is slow!'" :disabled="false" :speed="speed"
class-name="shiny-text-demo" />
</div>
<Customize>
<PreviewSlider title="Animation Duration" v-model="speed" :min="1" :max="5" :step="0.1" value-unit="s" />
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="shinyText" />
</template>
<template #cli>
<CliInstallation :command="shinyText.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import ShinyText from '../../content/TextAnimations/ShinyText/ShinyText.vue'
import { shinyText } from '@/constants/code/TextAnimations/shinyTextCode'
const speed = ref(3)
const propData = [
{
name: 'text',
type: 'string',
default: '-',
description: 'The text to be displayed with the shiny effect.'
},
{
name: 'disabled',
type: 'boolean',
default: 'false',
description: 'Disables the shiny effect when set to true.'
},
{
name: 'speed',
type: 'number',
default: '5',
description: 'Specifies the duration of the animation in seconds.'
},
{
name: 'className',
type: 'string',
default: "''",
description: 'Adds custom classes to the root element.'
}
]
</script>
<style scoped>
.shiny-button {
padding: 12px 24px;
border-radius: 50px;
cursor: pointer;
transition: transform 0.2s ease;
}
.shiny-button:hover {
transform: translateY(-2px);
}
.shiny-text-demo {
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div class="text-cursor-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden">
<TextCursor :key="key" :text="text" :follow-mouse-direction="followMouseDirection"
:random-float="randomFloat" />
<div
class="absolute inset-0 flex items-center justify-center pointer-events-none text-[4rem] font-[900] text-[#222] select-none">
Hover Around!
</div>
</div>
<Customize>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Text</label>
<input v-model="text" type="text" placeholder="Enter text..." maxlength="10"
class="w-[160px] px-3 py-2 bg-[#0b0b0b] border border-[#333] rounded-md text-white focus:outline-none focus:border-[#666]" />
</div>
<PreviewSwitch title="Follow Mouse Direction" v-model="followMouseDirection"
@update:model-value="forceRerender" />
<PreviewSwitch title="Enable Random Floating" v-model="randomFloat" @update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['motion-v']" />
</template>
<template #code>
<CodeExample :code-object="textCursor" />
</template>
<template #cli>
<CliInstallation :command="textCursor.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import TextCursor from '../../content/TextAnimations/TextCursor/TextCursor.vue'
import { textCursor } from '@/constants/code/TextAnimations/textCursorCode'
import { useForceRerender } from '@/composables/useForceRerender'
const { rerenderKey: key, forceRerender } = useForceRerender()
const text = ref('💚')
const followMouseDirection = ref(true)
const randomFloat = ref(true)
const propData = [
{
name: 'text',
type: 'string',
default: '⚛️',
description: 'The text string to display as the trail.'
},
{
name: 'delay',
type: 'number',
default: '0.01',
description: 'The entry stagger delay in seconds for the fade-out animation.'
},
{
name: 'spacing',
type: 'number',
default: '100',
description: 'The spacing in pixels between each trail point.'
},
{
name: 'followMouseDirection',
type: 'boolean',
default: 'true',
description: 'If true, each text rotates to follow the mouse direction.'
},
{
name: 'randomFloat',
type: 'boolean',
default: 'true',
description: 'If true, enables random floating offsets in position and rotation for a dynamic effect.'
},
{
name: 'exitDuration',
type: 'number',
default: '0.5',
description: 'The duration in seconds for the exit animation of each trail item.'
},
{
name: 'removalInterval',
type: 'number',
default: '30',
description: 'The interval in milliseconds between removing trail items when the mouse stops moving.'
},
{
name: 'maxPoints',
type: 'number',
default: '5',
description: 'The maximum number of trail points to display.'
}
]
</script>

View File

@@ -0,0 +1,176 @@
<template>
<div class="text-pressure-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative bg-[#060010] min-h-[400px] max-h-[450px] overflow-hidden mb-6">
<div class="w-full h-full">
<TextPressure :key="rerenderKey" :text="text" :flex="flex" :alpha="alpha" :stroke="stroke" :width="width"
:weight="weight" :italic="italic" :text-color="textColor" :stroke-color="strokeColor"
:min-font-size="36" />
</div>
</div>
<Customize>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Text</label>
<input v-model="text" type="text" placeholder="Your text here..." maxlength="10"
class="w-[200px] px-3 py-2 bg-[#0b0b0b] border border-[#333] rounded-md text-white focus:outline-none focus:border-[#666]" />
</div>
<div class="color-controls">
<PreviewColor title="Text Color" v-model="textColor" @update:model-value="forceRerender" />
<PreviewColor title="Stroke Color" v-model="strokeColor" @update:model-value="forceRerender" />
</div>
<p class="mt-6 text-[#999] text-sm">Animation Settings</p>
<div class="flex gap-4 flex-wrap">
<PreviewSwitch title="Flex" :model-value="flex"
@update:model-value="(val: boolean) => { flex = val; forceRerender() }" />
<PreviewSwitch title="Alpha" :model-value="alpha"
@update:model-value="(val: boolean) => { alpha = val; forceRerender() }" />
<PreviewSwitch title="Stroke" :model-value="stroke"
@update:model-value="(val: boolean) => { stroke = val; forceRerender() }" />
<PreviewSwitch title="Width" :model-value="width"
@update:model-value="(val: boolean) => { width = val; forceRerender() }" />
<PreviewSwitch title="Weight" :model-value="weight"
@update:model-value="(val: boolean) => { weight = val; forceRerender() }" />
<PreviewSwitch title="Italic" :model-value="italic"
@update:model-value="(val: boolean) => { italic = val; forceRerender() }" />
</div>
</Customize>
<PropTable :data="propData" />
</template>
<template #code>
<CodeExample :code-object="textPressure" />
</template>
<template #cli>
<CliInstallation :command="textPressure.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import PreviewColor from '../../components/common/PreviewColor.vue'
import TextPressure from '../../content/TextAnimations/TextPressure/TextPressure.vue'
import { textPressure } from '@/constants/code/TextAnimations/textPressureCode'
import { useForceRerender } from '@/composables/useForceRerender'
const text = ref('Hello!')
const flex = ref(true)
const alpha = ref(false)
const stroke = ref(false)
const width = ref(true)
const weight = ref(true)
const italic = ref(true)
const textColor = ref('#ffffff')
const strokeColor = ref('#27FF64')
const { rerenderKey, forceRerender } = useForceRerender()
const propData = [
{
name: 'text',
type: 'string',
default: '"Hello!"',
description: 'Text content that will be displayed and animated.'
},
{
name: 'fontFamily',
type: 'string',
default: 'Compressa VF',
description: 'Name of the variable font family.'
},
{
name: 'fontUrl',
type: 'string',
default: 'URL to a .woff2 or .ttf file',
description: 'URL for the variable font file (needed)'
},
{
name: 'flex',
type: 'boolean',
default: 'true',
description: 'Whether the characters are spaced using flex layout.'
},
{
name: 'scale',
type: 'boolean',
default: 'false',
description: 'If true, vertically scales the text to fill its container height.'
},
{
name: 'alpha',
type: 'boolean',
default: 'false',
description: 'If true, applies an opacity effect based on cursor distance.'
},
{
name: 'stroke',
type: 'boolean',
default: 'false',
description: 'If true, adds a stroke effect around characters.'
},
{
name: 'width',
type: 'boolean',
default: 'true',
description: 'If true, varies the variable-font "width" axis.'
},
{
name: 'weight',
type: 'boolean',
default: 'true',
description: 'If true, varies the variable-font "weight" axis.'
},
{
name: 'italic',
type: 'boolean',
default: 'true',
description: 'If true, varies the variable-font "italics" axis.'
},
{
name: 'textColor',
type: 'string',
default: '#FFFFFF',
description: 'The fill color of the text'
},
{
name: 'strokeColor',
type: 'string',
default: '#FF0000',
description: 'The stroke color that will be applied to the text when "stroke" is set to true'
},
{
name: 'className',
type: 'string',
default: "''",
description: 'Additional class for styling the <h1> wrapper.'
},
{
name: 'minFontSize',
type: 'number',
default: '24',
description: 'Sets a minimum font-size to avoid overly tiny text on smaller screens.'
}
]
</script>
<style scoped>
.color-controls {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
</style>

View File

@@ -0,0 +1,150 @@
<template>
<div class="text-trail-demo">
<TabbedLayout>
<template #preview>
<div class="demo-container relative h-[500px] overflow-hidden p-0">
<TextTrail :key="`${key}-${animateColor}`" :noise-factor="noiseFactor" :noise-scale="noiseScale / 10000"
:font-weight="fontWeight" :alpha-persist-factor="alphaPersistFactor" :animate-color="animateColor"
:text-color="animateColor ? undefined : '#ffffff'" />
</div>
<Customize>
<PreviewSlider title="Noise Factor" v-model="noiseFactor" :min="1" :max="25" :step="1"
@update:model-value="forceRerender" />
<PreviewSlider title="Noise Scale" v-model="noiseScale" :min="0" :max="100" :step="1"
@update:model-value="forceRerender" />
<PreviewSlider title="Font Weight" v-model="fontWeight" :min="100" :max="900" :step="100"
@update:model-value="forceRerender" />
<PreviewSlider title="Alpha Persist Factor" v-model="alphaPersistFactor" :min="0.5" :max="0.95" :step="0.01"
@update:model-value="forceRerender" />
<PreviewSwitch title="Animate Color" v-model="animateColor" @update:model-value="forceRerender" />
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['three']" />
</template>
<template #code>
<CodeExample :code-object="textTrail" />
</template>
<template #cli>
<CliInstallation :command="textTrail.cli" />
</template>
</TabbedLayout>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TabbedLayout from '../../components/common/TabbedLayout.vue'
import PropTable from '../../components/common/PropTable.vue'
import Dependencies from '../../components/code/Dependencies.vue'
import CliInstallation from '../../components/code/CliInstallation.vue'
import CodeExample from '../../components/code/CodeExample.vue'
import Customize from '../../components/common/Customize.vue'
import PreviewSlider from '../../components/common/PreviewSlider.vue'
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
import TextTrail from '../../content/TextAnimations/TextTrail/TextTrail.vue'
import { textTrail } from '@/constants/code/TextAnimations/textTrailCode'
import { useForceRerender } from '@/composables/useForceRerender'
const { rerenderKey: key, forceRerender } = useForceRerender()
const noiseFactor = ref(1)
const noiseScale = ref(5)
const fontWeight = ref(900)
const alphaPersistFactor = ref(0.95)
const animateColor = ref(false)
const propData = [
{
name: 'text',
type: 'string',
default: '"Vibe"',
description: 'The text to display with the trail effect'
},
{
name: 'fontFamily',
type: 'string',
default: '"Figtree"',
description: 'Font family for the text'
},
{
name: 'fontWeight',
type: 'string | number',
default: '"900"',
description: 'Font weight for the text'
},
{
name: 'noiseFactor',
type: 'number',
default: '1',
description: 'Controls the intensity of the noise effect'
},
{
name: 'noiseScale',
type: 'number',
default: '0.0005',
description: 'Scale factor for the noise distortion (very small values like 0.0005)'
},
{
name: 'rgbPersistFactor',
type: 'number',
default: '0.98',
description: 'RGB persistence factor for the trail effect (0-1)'
},
{
name: 'alphaPersistFactor',
type: 'number',
default: '0.95',
description: 'Alpha persistence factor for the trail effect (0-1)'
},
{
name: 'animateColor',
type: 'boolean',
default: 'false',
description: 'Whether to animate color changes over time'
},
{
name: 'startColor',
type: 'string',
default: '"#ffffff"',
description: 'Starting color for the text (hex format)'
},
{
name: 'textColor',
type: 'string',
default: '"#ffffff"',
description: 'Static color for the text (hex format)'
},
{
name: 'backgroundColor',
type: 'number | string',
default: '0x271e37',
description: 'Background color (hex number or string)'
},
{
name: 'colorCycleInterval',
type: 'number',
default: '3000',
description: 'Interval in milliseconds for color cycling when animateColor is true'
},
{
name: 'supersample',
type: 'number',
default: '2',
description: 'Supersampling factor for text quality (higher = better quality)'
}
]
</script>
<style scoped>
.demo-container {
padding: 0;
}
</style>