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>