mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-04-22 09:54:39 -06:00
Compare commits
22 Commits
97a28813c6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f86eaff8cb | |||
| 35d50b69e8 | |||
| 18b2debeab | |||
| 20e879c1ac | |||
| 256828c2ec | |||
| 7baf50277c | |||
| 7a66154264 | |||
| bbb22644e7 | |||
| 4ea0141cdf | |||
| 9474d94c7b | |||
| d1c65883a4 | |||
| 7bd8509413 | |||
| bbd0f9c948 | |||
| 2f96ec7384 | |||
| 02ab1970a6 | |||
| 54bc49cd2b | |||
| aef640afd7 | |||
| ed85a56d48 | |||
| c5d66b6279 | |||
| ab74ecd1e2 | |||
| c1d22edb61 | |||
| 88b0150b23 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -19,10 +19,10 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="hero-main-content">
|
<div class="hero-main-content">
|
||||||
<router-link to="/backgrounds/grainient" class="hero-new-badge-container">
|
<router-link to="/backgrounds/magic-rings" class="hero-new-badge-container">
|
||||||
<span class="hero-new-badge">New 🎉</span>
|
<span class="hero-new-badge">New 🎉</span>
|
||||||
<div class="hero-new-badge-text">
|
<div class="hero-new-badge-text">
|
||||||
<span>Grainient</span>
|
<span>Magic Rings</span>
|
||||||
<i class="pi-arrow-right pi" style="font-size: 0.8rem"></i>
|
<i class="pi-arrow-right pi" style="font-size: 0.8rem"></i>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
+23
-15
@@ -1,12 +1,14 @@
|
|||||||
// Highlighted sidebar items
|
// Highlighted sidebar items
|
||||||
export const NEW = [
|
export const NEW = [
|
||||||
'Color Bends',
|
'Evil Eye',
|
||||||
'Floating Lines',
|
'Border Glow',
|
||||||
'Light Pillar',
|
'Soft Aurora',
|
||||||
|
'Radar',
|
||||||
|
'Line Waves',
|
||||||
'Antigravity',
|
'Antigravity',
|
||||||
'Reflective Card',
|
|
||||||
'Pixel Snow',
|
|
||||||
'Grainient',
|
'Grainient',
|
||||||
|
'Orbit Images',
|
||||||
|
'Magic Rings'
|
||||||
];
|
];
|
||||||
export const UPDATED = ['Metallic Paint'];
|
export const UPDATED = ['Metallic Paint'];
|
||||||
|
|
||||||
@@ -14,7 +16,7 @@ export const UPDATED = ['Metallic Paint'];
|
|||||||
export const CATEGORIES = [
|
export const CATEGORIES = [
|
||||||
{
|
{
|
||||||
name: 'Get Started',
|
name: 'Get Started',
|
||||||
subcategories: [ 'Index' ],
|
subcategories: ['Index']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Text Animations',
|
name: 'Text Animations',
|
||||||
@@ -42,8 +44,8 @@ export const CATEGORIES = [
|
|||||||
'Text Trail',
|
'Text Trail',
|
||||||
'Text Type',
|
'Text Type',
|
||||||
'True Focus',
|
'True Focus',
|
||||||
'Variable Proximity',
|
'Variable Proximity'
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Animations',
|
name: 'Animations',
|
||||||
@@ -62,6 +64,7 @@ export const CATEGORIES = [
|
|||||||
'Image Trail',
|
'Image Trail',
|
||||||
'Laser Flow',
|
'Laser Flow',
|
||||||
'Logo Loop',
|
'Logo Loop',
|
||||||
|
'Magic Rings',
|
||||||
'Magnet',
|
'Magnet',
|
||||||
'Magnet Lines',
|
'Magnet Lines',
|
||||||
'Metallic Paint',
|
'Metallic Paint',
|
||||||
@@ -75,13 +78,14 @@ export const CATEGORIES = [
|
|||||||
'Star Border',
|
'Star Border',
|
||||||
'Sticker Peel',
|
'Sticker Peel',
|
||||||
'Target Cursor',
|
'Target Cursor',
|
||||||
'Orbit Images',
|
'Orbit Images'
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Components',
|
name: 'Components',
|
||||||
subcategories: [
|
subcategories: [
|
||||||
'Animated List',
|
'Animated List',
|
||||||
|
'Border Glow',
|
||||||
'Bounce Cards',
|
'Bounce Cards',
|
||||||
'Bubble Menu',
|
'Bubble Menu',
|
||||||
'Card Nav',
|
'Card Nav',
|
||||||
@@ -113,8 +117,8 @@ export const CATEGORIES = [
|
|||||||
'Stack',
|
'Stack',
|
||||||
'Staggered Menu',
|
'Staggered Menu',
|
||||||
'Stepper',
|
'Stepper',
|
||||||
'Tilted Card',
|
'Tilted Card'
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Backgrounds',
|
name: 'Backgrounds',
|
||||||
@@ -127,6 +131,7 @@ export const CATEGORIES = [
|
|||||||
'Dark Veil',
|
'Dark Veil',
|
||||||
'Dither',
|
'Dither',
|
||||||
'Dot Grid',
|
'Dot Grid',
|
||||||
|
'Evil Eye',
|
||||||
'Faulty Terminal',
|
'Faulty Terminal',
|
||||||
'Floating Lines',
|
'Floating Lines',
|
||||||
'Galaxy',
|
'Galaxy',
|
||||||
@@ -141,6 +146,7 @@ export const CATEGORIES = [
|
|||||||
'Light Pillar',
|
'Light Pillar',
|
||||||
'Light Rays',
|
'Light Rays',
|
||||||
'Lightning',
|
'Lightning',
|
||||||
|
'Line Waves',
|
||||||
'Liquid Chrome',
|
'Liquid Chrome',
|
||||||
'Liquid Ether',
|
'Liquid Ether',
|
||||||
'Orb',
|
'Orb',
|
||||||
@@ -150,11 +156,13 @@ export const CATEGORIES = [
|
|||||||
'Plasma',
|
'Plasma',
|
||||||
'Prism',
|
'Prism',
|
||||||
'Prismatic Burst',
|
'Prismatic Burst',
|
||||||
|
'Radar',
|
||||||
'Ripple Grid',
|
'Ripple Grid',
|
||||||
'Silk',
|
'Silk',
|
||||||
|
'Soft Aurora',
|
||||||
'Squares',
|
'Squares',
|
||||||
'Threads',
|
'Threads',
|
||||||
'Waves',
|
'Waves'
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const animations = {
|
|||||||
'antigravity': () => import('../demo/Animations/AntigravityDemo.vue'),
|
'antigravity': () => import('../demo/Animations/AntigravityDemo.vue'),
|
||||||
'pixel-trail': () => import('../demo/Animations/PixelTrailDemo.vue'),
|
'pixel-trail': () => import('../demo/Animations/PixelTrailDemo.vue'),
|
||||||
'orbit-images': () => import('../demo/Animations/OrbitImagesDemo.vue'),
|
'orbit-images': () => import('../demo/Animations/OrbitImagesDemo.vue'),
|
||||||
|
'magic-rings': () => import('../demo/Animations/MagicRingsDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const textAnimations = {
|
const textAnimations = {
|
||||||
@@ -90,6 +91,7 @@ const components = {
|
|||||||
'bubble-menu': () => import('../demo/Components/BubbleMenuDemo.vue'),
|
'bubble-menu': () => import('../demo/Components/BubbleMenuDemo.vue'),
|
||||||
'staggered-menu': () => import('../demo/Components/StaggeredMenuDemo.vue'),
|
'staggered-menu': () => import('../demo/Components/StaggeredMenuDemo.vue'),
|
||||||
'infinite-menu': () => import('../demo/Components/InfiniteMenuDemo.vue'),
|
'infinite-menu': () => import('../demo/Components/InfiniteMenuDemo.vue'),
|
||||||
|
'border-glow': () => import('../demo/Components/BorderGlowDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const backgrounds = {
|
const backgrounds = {
|
||||||
@@ -129,6 +131,10 @@ const backgrounds = {
|
|||||||
'pixel-snow': () => import('../demo/Backgrounds/PixelSnowDemo.vue'),
|
'pixel-snow': () => import('../demo/Backgrounds/PixelSnowDemo.vue'),
|
||||||
'grid-scan': () => import('../demo/Backgrounds/GridScanDemo.vue'),
|
'grid-scan': () => import('../demo/Backgrounds/GridScanDemo.vue'),
|
||||||
'grainient': () => import('../demo/Backgrounds/GrainientDemo.vue'),
|
'grainient': () => import('../demo/Backgrounds/GrainientDemo.vue'),
|
||||||
|
'line-waves': () => import('../demo/Backgrounds/LineWavesDemo.vue'),
|
||||||
|
'radar': () => import('../demo/Backgrounds/RadarDemo.vue'),
|
||||||
|
'soft-aurora': () => import('../demo/Backgrounds/SoftAuroraDemo.vue'),
|
||||||
|
'evil-eye': () => import('../demo/Backgrounds/EvilEyeDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
|
|||||||
@@ -222,6 +222,14 @@ export const componentMetadata: ComponentMetadata = {
|
|||||||
docsUrl: 'https://vue-bits.dev/animations/pixel-trail',
|
docsUrl: 'https://vue-bits.dev/animations/pixel-trail',
|
||||||
tags: []
|
tags: []
|
||||||
},
|
},
|
||||||
|
'Animations/MagicRings': {
|
||||||
|
videoUrl: '/assets/videos/magicrings.webm',
|
||||||
|
description: 'Interactive magic rings effect with customizable parameters.',
|
||||||
|
category: 'Animations',
|
||||||
|
name: 'MagicRings',
|
||||||
|
docsUrl: 'https://vue-bits.dev/animations/magic-rings',
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
|
||||||
//! Text Animations -------------------------------------------------------------------------------------------------------------------------------
|
//! Text Animations -------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -667,6 +675,14 @@ export const componentMetadata: ComponentMetadata = {
|
|||||||
docsUrl: 'https://vue-bits.dev/components/staggered-menu',
|
docsUrl: 'https://vue-bits.dev/components/staggered-menu',
|
||||||
tags: []
|
tags: []
|
||||||
},
|
},
|
||||||
|
'Components/BorderGlow': {
|
||||||
|
videoUrl: '/assets/videos/borderglow.webm',
|
||||||
|
description: 'Glowing mesh-gradient border that follows cursor direction and intensifies near edges.',
|
||||||
|
category: 'Components',
|
||||||
|
name: 'BorderGlow',
|
||||||
|
docsUrl: 'https://vue-bits.dev/components/border-glow',
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
|
||||||
//! Backgrounds -------------------------------------------------------------------------------------------------------------------------------
|
//! Backgrounds -------------------------------------------------------------------------------------------------------------------------------
|
||||||
'Backgrounds/Aurora': {
|
'Backgrounds/Aurora': {
|
||||||
@@ -948,5 +964,37 @@ export const componentMetadata: ComponentMetadata = {
|
|||||||
name: 'PixelSnow',
|
name: 'PixelSnow',
|
||||||
docsUrl: 'https://vue-bits.dev/backgrounds/pixel-snow',
|
docsUrl: 'https://vue-bits.dev/backgrounds/pixel-snow',
|
||||||
tags: []
|
tags: []
|
||||||
|
},
|
||||||
|
'Backgrounds/LineWaves': {
|
||||||
|
videoUrl: '/assets/video/linewaves.webm',
|
||||||
|
description: 'Animated line wave pattern with colorful warped distortion.',
|
||||||
|
category: 'Backgrounds',
|
||||||
|
name: 'LineWaves',
|
||||||
|
docsUrl: 'https://reactbits.dev/backgrounds/line-waves',
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
'Backgrounds/EvilEye': {
|
||||||
|
videoUrl: '/assets/video/evileye.webm',
|
||||||
|
description: 'Procedural evil eye shader with animated iris, slit pupil, and fiery outer glow.',
|
||||||
|
category: 'Backgrounds',
|
||||||
|
name: 'EvilEye',
|
||||||
|
docsUrl: 'https://reactbits.dev/backgrounds/evil-eye',
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
'Backgrounds/Radar': {
|
||||||
|
videoUrl: '/assets/video/radar.webm',
|
||||||
|
description: 'Radar sweep effect with concentric rings, radial spokes, and a rotating beam.',
|
||||||
|
category: 'Backgrounds',
|
||||||
|
name: 'Radar',
|
||||||
|
docsUrl: 'https://reactbits.dev/backgrounds/radar',
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
'Backgrounds/SoftAurora': {
|
||||||
|
videoUrl: '/assets/video/softaurora.webm',
|
||||||
|
description: 'Soft aurora borealis shader with 3D Perlin noise and cosine gradient palettes.',
|
||||||
|
category: 'Backgrounds',
|
||||||
|
name: 'SoftAurora',
|
||||||
|
docsUrl: 'https://reactbits.dev/backgrounds/soft-aurora',
|
||||||
|
tags: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import code from '@/content/Animations/MagicRings/MagicRings.vue?raw';
|
||||||
|
import { createCodeObject } from '@/types/code';
|
||||||
|
|
||||||
|
export const magicRings = createCodeObject(code, 'Animations/MagicRings', {
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 600px; height: 400px; position: relative;">
|
||||||
|
<MagicRings
|
||||||
|
color="#7cff67"
|
||||||
|
colorTwo="#42fcff"
|
||||||
|
:ringCount="6"
|
||||||
|
:speed="1"
|
||||||
|
:attenuation="10"
|
||||||
|
:lineThickness="2"
|
||||||
|
:baseRadius="0.35"
|
||||||
|
:radiusStep="0.1"
|
||||||
|
:scaleRate="0.1"
|
||||||
|
:opacity="1"
|
||||||
|
:blur="0"
|
||||||
|
:noiseAmount="0.1"
|
||||||
|
:rotation="0"
|
||||||
|
:ringGap="1.5"
|
||||||
|
:fadeIn="0.7"
|
||||||
|
:fadeOut="0.5"
|
||||||
|
:followMouse="false"
|
||||||
|
:mouseInfluence="0.2"
|
||||||
|
:hoverScale="1.2"
|
||||||
|
:parallax="0.05"
|
||||||
|
:clickBurst="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import MagicRings from './MagicRings.vue'
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import code from '@content/Backgrounds/EvilEye/EvilEye.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const evilEye = createCodeObject(code, 'Backgrounds/EvilEye', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative">
|
||||||
|
<EvilEye
|
||||||
|
eyeColor="#FF6F37"
|
||||||
|
:intensity="1.5"
|
||||||
|
:pupilSize="0.6"
|
||||||
|
:irisWidth="0.25"
|
||||||
|
:glowIntensity="0.35"
|
||||||
|
:scale="0.8"
|
||||||
|
:noiseScale="1"
|
||||||
|
:pupilFollow="1"
|
||||||
|
:flameSpeed="1"
|
||||||
|
backgroundColor="#060010"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import EvilEye from './EvilEye.vue';
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import code from '@content/Backgrounds/LineWaves/LineWaves.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const lineWaves = createCodeObject(code, 'Backgrounds/LineWaves', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<LineWaves
|
||||||
|
:speed="0.3"
|
||||||
|
:innerLineCount="32"
|
||||||
|
:outerLineCount="36"
|
||||||
|
:warpIntensity="1"
|
||||||
|
:rotation="-45"
|
||||||
|
:edgeFadeWidth="0"
|
||||||
|
:colorCycleSpeed="1"
|
||||||
|
:brightness="0.2"
|
||||||
|
:color1="#ffffff"
|
||||||
|
:color2="#ffffff"
|
||||||
|
:color3="#ffffff"
|
||||||
|
:enableMouseInteraction="true"
|
||||||
|
:mouseInfluence="2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LineWaves from "./LineWaves.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import code from '@content/Backgrounds/Radar/Radar.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const radar = createCodeObject(code, 'Backgrounds/Radar', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<Radar
|
||||||
|
:speed="1"
|
||||||
|
:scale="0.5"
|
||||||
|
:ringCount="10"
|
||||||
|
:spokeCount="10"
|
||||||
|
:ringThickness="0.05"
|
||||||
|
:spokeThickness="0.01"
|
||||||
|
:sweepSpeed="1"
|
||||||
|
:sweepWidth="2"
|
||||||
|
:sweepLobes="1"
|
||||||
|
color="#27FF64"
|
||||||
|
backgroundColor="#000000"
|
||||||
|
:falloff="2"
|
||||||
|
:brightness="1"
|
||||||
|
:enableMouseInteraction="true"
|
||||||
|
:mouseInfluence="0.1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Radar from "./Radar.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import code from '@content/Backgrounds/SoftAurora/SoftAurora.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const softAurora = createCodeObject(code, 'Backgrounds/SoftAurora', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<SoftAurora
|
||||||
|
:speed="0.6"
|
||||||
|
:scale="1.5"
|
||||||
|
:brightness="1"
|
||||||
|
:color1="'#f7f7f7'"
|
||||||
|
:color2="'#27FF64'"
|
||||||
|
:noise-frequency="2.5"
|
||||||
|
:noise-amplitude="1"
|
||||||
|
:band-height="0.5"
|
||||||
|
:band-spread="1"
|
||||||
|
:octave-decay="0.1"
|
||||||
|
:layer-offset="0"
|
||||||
|
:color-speed="1"
|
||||||
|
:enable-mouse-interaction="true"
|
||||||
|
:mouse-influence="0.25"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SoftAurora from "./SoftAurora.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import code from '@content/Components/BorderGlow/BorderGlow.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const borderGlow = createCodeObject(code, 'Components/BorderGlow', {
|
||||||
|
usage: `<template>
|
||||||
|
<BorderGlow
|
||||||
|
:edgeSensitivity="30"
|
||||||
|
glowColor="40 80 80"
|
||||||
|
backgroundColor="#060010"
|
||||||
|
:borderRadius="28"
|
||||||
|
:glowRadius="40"
|
||||||
|
:glowIntensity="1"
|
||||||
|
:coneSpread="25"
|
||||||
|
:animated="false"
|
||||||
|
:colors="['#c084fc', '#f472b6', '#38bdf8']"
|
||||||
|
>
|
||||||
|
<div class="p-[2em]">
|
||||||
|
<h2>Your Content Here</h2>
|
||||||
|
<p>Hover near the edges to see the glow.</p>
|
||||||
|
</div>
|
||||||
|
</BorderGlow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import BorderGlow from './BorderGlow.vue'
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
void main() {
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime, uAttenuation, uLineThickness;
|
||||||
|
uniform float uBaseRadius, uRadiusStep, uScaleRate;
|
||||||
|
uniform float uOpacity, uNoiseAmount, uRotation, uRingGap;
|
||||||
|
uniform float uFadeIn, uFadeOut;
|
||||||
|
uniform float uMouseInfluence, uHoverAmount, uHoverScale, uParallax, uBurst;
|
||||||
|
uniform vec2 uResolution, uMouse;
|
||||||
|
uniform vec3 uColor, uColorTwo;
|
||||||
|
uniform int uRingCount;
|
||||||
|
|
||||||
|
const float HP = 1.5707963;
|
||||||
|
const float CYCLE = 3.45;
|
||||||
|
|
||||||
|
float fade(float t) {
|
||||||
|
return t < uFadeIn ? smoothstep(0.0, uFadeIn, t) : 1.0 - smoothstep(uFadeOut, CYCLE - 0.2, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ring(vec2 p, float ri, float cut, float t0, float px) {
|
||||||
|
float t = mod(uTime + t0, CYCLE);
|
||||||
|
float r = ri + t / CYCLE * uScaleRate;
|
||||||
|
float d = abs(length(p) - r);
|
||||||
|
float a = atan(abs(p.y), abs(p.x)) / HP;
|
||||||
|
float th = max(1.0 - a, 0.5) * px * uLineThickness;
|
||||||
|
float h = (1.0 - smoothstep(th, th * 1.5, d)) + 1.0;
|
||||||
|
d += pow(cut * a, 3.0) * r;
|
||||||
|
return h * exp(-uAttenuation * d) * fade(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float px = 1.0 / min(uResolution.x, uResolution.y);
|
||||||
|
vec2 p = (gl_FragCoord.xy - 0.5 * uResolution.xy) * px;
|
||||||
|
float cr = cos(uRotation), sr = sin(uRotation);
|
||||||
|
p = mat2(cr, -sr, sr, cr) * p;
|
||||||
|
p -= uMouse * uMouseInfluence;
|
||||||
|
float sc = mix(1.0, uHoverScale, uHoverAmount) + uBurst * 0.3;
|
||||||
|
p /= sc;
|
||||||
|
vec3 c = vec3(0.0);
|
||||||
|
float rcf = max(float(uRingCount) - 1.0, 1.0);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
if (i >= uRingCount) break;
|
||||||
|
float fi = float(i);
|
||||||
|
vec2 pr = p - fi * uParallax * uMouse;
|
||||||
|
vec3 rc = mix(uColor, uColorTwo, fi / rcf);
|
||||||
|
c = mix(c, rc, vec3(ring(pr, uBaseRadius + fi * uRadiusStep, pow(uRingGap, fi), i == 0 ? 0.0 : 2.95 * fi, px)));
|
||||||
|
}
|
||||||
|
c *= 1.0 + uBurst * 2.0;
|
||||||
|
float n = fract(sin(dot(gl_FragCoord.xy + uTime * 100.0, vec2(12.9898, 78.233))) * 43758.5453);
|
||||||
|
c += (n - 0.5) * uNoiseAmount;
|
||||||
|
gl_FragColor = vec4(c, max(c.r, max(c.g, c.b)) * uOpacity);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface MagicRingsProps {
|
||||||
|
color?: string;
|
||||||
|
colorTwo?: string;
|
||||||
|
speed?: number;
|
||||||
|
ringCount?: number;
|
||||||
|
attenuation?: number;
|
||||||
|
lineThickness?: number;
|
||||||
|
baseRadius?: number;
|
||||||
|
radiusStep?: number;
|
||||||
|
scaleRate?: number;
|
||||||
|
opacity?: number;
|
||||||
|
blur?: number;
|
||||||
|
noiseAmount?: number;
|
||||||
|
rotation?: number;
|
||||||
|
ringGap?: number;
|
||||||
|
fadeIn?: number;
|
||||||
|
fadeOut?: number;
|
||||||
|
followMouse?: boolean;
|
||||||
|
mouseInfluence?: number;
|
||||||
|
hoverScale?: number;
|
||||||
|
parallax?: number;
|
||||||
|
clickBurst?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<MagicRingsProps>(), {
|
||||||
|
color: '#7cff67',
|
||||||
|
colorTwo: '#42fcff',
|
||||||
|
speed: 1,
|
||||||
|
ringCount: 6,
|
||||||
|
attenuation: 10,
|
||||||
|
lineThickness: 2,
|
||||||
|
baseRadius: 0.35,
|
||||||
|
radiusStep: 0.1,
|
||||||
|
scaleRate: 0.1,
|
||||||
|
opacity: 1,
|
||||||
|
blur: 0,
|
||||||
|
noiseAmount: 0.1,
|
||||||
|
rotation: 0,
|
||||||
|
ringGap: 1.5,
|
||||||
|
fadeIn: 0.7,
|
||||||
|
fadeOut: 0.5,
|
||||||
|
followMouse: false,
|
||||||
|
mouseInfluence: 0.2,
|
||||||
|
hoverScale: 1.2,
|
||||||
|
parallax: 0.05,
|
||||||
|
clickBurst: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const mountRef = useTemplateRef('mountRef');
|
||||||
|
|
||||||
|
const mouseRef = ref<[number, number]>([0, 0]);
|
||||||
|
const smoothMouseRef = ref<[number, number]>([0, 0]);
|
||||||
|
const hoverAmountRef = ref(0);
|
||||||
|
const isHoveredRef = ref(false);
|
||||||
|
const burstRef = ref(0);
|
||||||
|
|
||||||
|
const propsRef = computed(() => ({
|
||||||
|
color: props.color,
|
||||||
|
colorTwo: props.colorTwo,
|
||||||
|
speed: props.speed,
|
||||||
|
ringCount: props.ringCount,
|
||||||
|
attenuation: props.attenuation,
|
||||||
|
lineThickness: props.lineThickness,
|
||||||
|
baseRadius: props.baseRadius,
|
||||||
|
radiusStep: props.radiusStep,
|
||||||
|
scaleRate: props.scaleRate,
|
||||||
|
opacity: props.opacity,
|
||||||
|
blur: props.blur,
|
||||||
|
noiseAmount: props.noiseAmount,
|
||||||
|
rotation: props.rotation,
|
||||||
|
ringGap: props.ringGap,
|
||||||
|
fadeIn: props.fadeIn,
|
||||||
|
fadeOut: props.fadeOut,
|
||||||
|
followMouse: props.followMouse,
|
||||||
|
mouseInfluence: props.mouseInfluence,
|
||||||
|
hoverScale: props.hoverScale,
|
||||||
|
parallax: props.parallax,
|
||||||
|
clickBurst: props.clickBurst
|
||||||
|
}));
|
||||||
|
|
||||||
|
let renderer: THREE.WebGLRenderer | null = null;
|
||||||
|
let frameId = 0;
|
||||||
|
let ro: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const cleanupFns: (() => void)[] = [];
|
||||||
|
onMounted(() => {
|
||||||
|
const mount = mountRef.value;
|
||||||
|
if (!mount) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
renderer = new THREE.WebGLRenderer({ alpha: true });
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!renderer.capabilities.isWebGL2) {
|
||||||
|
renderer.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.setClearColor(0x000000, 0);
|
||||||
|
mount.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
|
||||||
|
const camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10);
|
||||||
|
camera.position.z = 1;
|
||||||
|
|
||||||
|
const uniforms = {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uAttenuation: { value: 0 },
|
||||||
|
uResolution: { value: new THREE.Vector2() },
|
||||||
|
uColor: { value: new THREE.Color() },
|
||||||
|
uColorTwo: { value: new THREE.Color() },
|
||||||
|
uLineThickness: { value: 0 },
|
||||||
|
uBaseRadius: { value: 0 },
|
||||||
|
uRadiusStep: { value: 0 },
|
||||||
|
uScaleRate: { value: 0 },
|
||||||
|
uRingCount: { value: 0 },
|
||||||
|
uOpacity: { value: 1 },
|
||||||
|
uNoiseAmount: { value: 0 },
|
||||||
|
uRotation: { value: 0 },
|
||||||
|
uRingGap: { value: 1.6 },
|
||||||
|
uFadeIn: { value: 0.5 },
|
||||||
|
uFadeOut: { value: 0.75 },
|
||||||
|
uMouse: { value: new THREE.Vector2() },
|
||||||
|
uMouseInfluence: { value: 0 },
|
||||||
|
uHoverAmount: { value: 0 },
|
||||||
|
uHoverScale: { value: 1 },
|
||||||
|
uParallax: { value: 0 },
|
||||||
|
uBurst: { value: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
|
uniforms,
|
||||||
|
transparent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
|
||||||
|
scene.add(quad);
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
const w = mount.clientWidth;
|
||||||
|
const h = mount.clientHeight;
|
||||||
|
const dpr = Math.min(window.devicePixelRatio, 2);
|
||||||
|
|
||||||
|
renderer!.setSize(w, h);
|
||||||
|
renderer!.setPixelRatio(dpr);
|
||||||
|
|
||||||
|
uniforms.uResolution.value.set(w * dpr, h * dpr);
|
||||||
|
};
|
||||||
|
|
||||||
|
resize();
|
||||||
|
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
ro = new ResizeObserver(resize);
|
||||||
|
ro.observe(mount);
|
||||||
|
|
||||||
|
const onMouseMove = (e: MouseEvent) => {
|
||||||
|
const rect = mount.getBoundingClientRect();
|
||||||
|
|
||||||
|
mouseRef.value[0] = (e.clientX - rect.left) / rect.width - 0.5;
|
||||||
|
mouseRef.value[1] = -((e.clientY - rect.top) / rect.height - 0.5);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
isHoveredRef.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
isHoveredRef.value = false;
|
||||||
|
mouseRef.value = [0, 0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
burstRef.value = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
mount.addEventListener('mousemove', onMouseMove);
|
||||||
|
mount.addEventListener('mouseenter', onMouseEnter);
|
||||||
|
mount.addEventListener('mouseleave', onMouseLeave);
|
||||||
|
mount.addEventListener('click', onClick);
|
||||||
|
|
||||||
|
const animate = (t: number) => {
|
||||||
|
frameId = requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
const p = propsRef.value;
|
||||||
|
|
||||||
|
smoothMouseRef.value[0] += (mouseRef.value[0] - smoothMouseRef.value[0]) * 0.08;
|
||||||
|
smoothMouseRef.value[1] += (mouseRef.value[1] - smoothMouseRef.value[1]) * 0.08;
|
||||||
|
|
||||||
|
hoverAmountRef.value += ((isHoveredRef.value ? 1 : 0) - hoverAmountRef.value) * 0.08;
|
||||||
|
|
||||||
|
burstRef.value *= 0.95;
|
||||||
|
if (burstRef.value < 0.001) burstRef.value = 0;
|
||||||
|
|
||||||
|
uniforms.uTime.value = t * 0.001 * p.speed;
|
||||||
|
uniforms.uAttenuation.value = p.attenuation;
|
||||||
|
|
||||||
|
uniforms.uColor.value.set(p.color);
|
||||||
|
uniforms.uColorTwo.value.set(p.colorTwo);
|
||||||
|
|
||||||
|
uniforms.uLineThickness.value = p.lineThickness;
|
||||||
|
uniforms.uBaseRadius.value = p.baseRadius;
|
||||||
|
uniforms.uRadiusStep.value = p.radiusStep;
|
||||||
|
uniforms.uScaleRate.value = p.scaleRate;
|
||||||
|
|
||||||
|
uniforms.uRingCount.value = p.ringCount;
|
||||||
|
|
||||||
|
uniforms.uOpacity.value = p.opacity;
|
||||||
|
uniforms.uNoiseAmount.value = p.noiseAmount;
|
||||||
|
|
||||||
|
uniforms.uRotation.value = (p.rotation * Math.PI) / 180;
|
||||||
|
|
||||||
|
uniforms.uRingGap.value = p.ringGap;
|
||||||
|
uniforms.uFadeIn.value = p.fadeIn;
|
||||||
|
uniforms.uFadeOut.value = p.fadeOut;
|
||||||
|
|
||||||
|
uniforms.uMouse.value.set(smoothMouseRef.value[0], smoothMouseRef.value[1]);
|
||||||
|
|
||||||
|
uniforms.uMouseInfluence.value = p.followMouse ? p.mouseInfluence : 0;
|
||||||
|
|
||||||
|
uniforms.uHoverAmount.value = hoverAmountRef.value;
|
||||||
|
uniforms.uHoverScale.value = p.hoverScale;
|
||||||
|
|
||||||
|
uniforms.uParallax.value = p.parallax;
|
||||||
|
uniforms.uBurst.value = p.clickBurst ? burstRef.value : 0;
|
||||||
|
|
||||||
|
renderer!.render(scene, camera);
|
||||||
|
};
|
||||||
|
|
||||||
|
frameId = requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
cleanupFns.push(() => {
|
||||||
|
cancelAnimationFrame(frameId);
|
||||||
|
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
|
||||||
|
ro?.disconnect();
|
||||||
|
|
||||||
|
mount.removeEventListener('mousemove', onMouseMove);
|
||||||
|
mount.removeEventListener('mouseenter', onMouseEnter);
|
||||||
|
mount.removeEventListener('mouseleave', onMouseLeave);
|
||||||
|
mount.removeEventListener('click', onClick);
|
||||||
|
|
||||||
|
mount.removeChild(renderer!.domElement);
|
||||||
|
|
||||||
|
renderer?.dispose();
|
||||||
|
material.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanupFns.forEach(fn => fn());
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="mountRef" class="w-full h-full" :style="props.blur > 0 ? { filter: `blur(${props.blur}px)` } : undefined" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Mesh, Program, Renderer, Texture, Triangle } from 'ogl';
|
||||||
|
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface EvilEyeProps {
|
||||||
|
eyeColor?: string;
|
||||||
|
intensity?: number;
|
||||||
|
pupilSize?: number;
|
||||||
|
irisWidth?: number;
|
||||||
|
glowIntensity?: number;
|
||||||
|
scale?: number;
|
||||||
|
noiseScale?: number;
|
||||||
|
pupilFollow?: number;
|
||||||
|
flameSpeed?: number;
|
||||||
|
backgroundColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToVec3(hex: string): [number, number, number] {
|
||||||
|
const h = hex.replace('#', '');
|
||||||
|
return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNoiseTexture(size = 256): Uint8Array {
|
||||||
|
const data = new Uint8Array(size * size * 4);
|
||||||
|
|
||||||
|
function hash(x: number, y: number, s: number): number {
|
||||||
|
let n = x * 374761393 + y * 668265263 + s * 1274126177;
|
||||||
|
n = Math.imul(n ^ (n >>> 13), 1274126177);
|
||||||
|
return ((n ^ (n >>> 16)) >>> 0) / 4294967296;
|
||||||
|
}
|
||||||
|
|
||||||
|
function noise(px: number, py: number, freq: number, seed: number): number {
|
||||||
|
const fx = (px / size) * freq;
|
||||||
|
const fy = (py / size) * freq;
|
||||||
|
const ix = Math.floor(fx);
|
||||||
|
const iy = Math.floor(fy);
|
||||||
|
const tx = fx - ix;
|
||||||
|
const ty = fy - iy;
|
||||||
|
const w = freq | 0;
|
||||||
|
const v00 = hash(((ix % w) + w) % w, ((iy % w) + w) % w, seed);
|
||||||
|
const v10 = hash((((ix + 1) % w) + w) % w, ((iy % w) + w) % w, seed);
|
||||||
|
const v01 = hash(((ix % w) + w) % w, (((iy + 1) % w) + w) % w, seed);
|
||||||
|
const v11 = hash((((ix + 1) % w) + w) % w, (((iy + 1) % w) + w) % w, seed);
|
||||||
|
return v00 * (1 - tx) * (1 - ty) + v10 * tx * (1 - ty) + v01 * (1 - tx) * ty + v11 * tx * ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let y = 0; y < size; y++) {
|
||||||
|
for (let x = 0; x < size; x++) {
|
||||||
|
let v = 0;
|
||||||
|
let amp = 0.4;
|
||||||
|
let totalAmp = 0;
|
||||||
|
for (let o = 0; o < 8; o++) {
|
||||||
|
const f = 32 * (1 << o);
|
||||||
|
v += amp * noise(x, y, f, o * 31);
|
||||||
|
totalAmp += amp;
|
||||||
|
amp *= 0.65;
|
||||||
|
}
|
||||||
|
v /= totalAmp;
|
||||||
|
v = (v - 0.5) * 2.2 + 0.5;
|
||||||
|
v = Math.max(0, Math.min(1, v));
|
||||||
|
const val = Math.round(v * 255);
|
||||||
|
const i = (y * size + x) * 4;
|
||||||
|
data[i] = val;
|
||||||
|
data[i + 1] = val;
|
||||||
|
data[i + 2] = val;
|
||||||
|
data[i + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec2 position;
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0, 1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec3 uResolution;
|
||||||
|
uniform sampler2D uNoiseTexture;
|
||||||
|
uniform float uPupilSize;
|
||||||
|
uniform float uIrisWidth;
|
||||||
|
uniform float uGlowIntensity;
|
||||||
|
uniform float uIntensity;
|
||||||
|
uniform float uScale;
|
||||||
|
uniform float uNoiseScale;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform float uPupilFollow;
|
||||||
|
uniform float uFlameSpeed;
|
||||||
|
uniform vec3 uEyeColor;
|
||||||
|
uniform vec3 uBgColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution.xy) / uResolution.y;
|
||||||
|
uv /= uScale;
|
||||||
|
float ft = uTime * uFlameSpeed;
|
||||||
|
|
||||||
|
float polarRadius = length(uv) * 2.0;
|
||||||
|
float polarAngle = (2.0 * atan(uv.x, uv.y)) / 6.28 * 0.3;
|
||||||
|
vec2 polarUv = vec2(polarRadius, polarAngle);
|
||||||
|
|
||||||
|
vec4 noiseA = texture2D(uNoiseTexture, polarUv * vec2(0.2, 7.0) * uNoiseScale + vec2(-ft * 0.1, 0.0));
|
||||||
|
vec4 noiseB = texture2D(uNoiseTexture, polarUv * vec2(0.3, 4.0) * uNoiseScale + vec2(-ft * 0.2, 0.0));
|
||||||
|
vec4 noiseC = texture2D(uNoiseTexture, polarUv * vec2(0.1, 5.0) * uNoiseScale + vec2(-ft * 0.1, 0.0));
|
||||||
|
|
||||||
|
float distanceMask = 1.0 - length(uv);
|
||||||
|
|
||||||
|
// Inner ring
|
||||||
|
float innerRing = clamp(-1.0 * ((distanceMask - 0.7) / uIrisWidth), 0.0, 1.0);
|
||||||
|
innerRing = (innerRing * distanceMask - 0.2) / 0.28;
|
||||||
|
innerRing += noiseA.r - 0.5;
|
||||||
|
innerRing *= 1.3;
|
||||||
|
innerRing = clamp(innerRing, 0.0, 1.0);
|
||||||
|
|
||||||
|
float outerRing = clamp(-1.0 * ((distanceMask - 0.5) / 0.2), 0.0, 1.0);
|
||||||
|
outerRing = (outerRing * distanceMask - 0.1) / 0.38;
|
||||||
|
outerRing += noiseC.r - 0.5;
|
||||||
|
outerRing *= 1.3;
|
||||||
|
outerRing = clamp(outerRing, 0.0, 1.0);
|
||||||
|
|
||||||
|
innerRing += outerRing;
|
||||||
|
|
||||||
|
// Inner eye
|
||||||
|
float innerEye = distanceMask - 0.1 * 2.0;
|
||||||
|
innerEye *= noiseB.r * 2.0;
|
||||||
|
|
||||||
|
// Pupil with cursor tracking
|
||||||
|
vec2 pupilOffset = uMouse * uPupilFollow * 0.12;
|
||||||
|
vec2 pupilUv = uv - pupilOffset;
|
||||||
|
float pupil = 1.0 - length(pupilUv * vec2(9.0, 2.3));
|
||||||
|
pupil *= uPupilSize;
|
||||||
|
pupil = clamp(pupil, 0.0, 1.0);
|
||||||
|
pupil /= 0.35;
|
||||||
|
|
||||||
|
// Outer eye
|
||||||
|
float outerEyeGlow = 1.0 - length(uv * vec2(0.5, 1.5));
|
||||||
|
outerEyeGlow = clamp(outerEyeGlow + 0.5, 0.0, 1.0);
|
||||||
|
outerEyeGlow += noiseC.r - 0.5;
|
||||||
|
float outerBgGlow = outerEyeGlow;
|
||||||
|
outerEyeGlow = pow(outerEyeGlow, 2.0);
|
||||||
|
outerEyeGlow += distanceMask;
|
||||||
|
outerEyeGlow *= uGlowIntensity;
|
||||||
|
outerEyeGlow = clamp(outerEyeGlow, 0.0, 1.0);
|
||||||
|
outerEyeGlow *= pow(1.0 - distanceMask, 2.0) * 2.5;
|
||||||
|
|
||||||
|
// Outer eye bg glow
|
||||||
|
outerBgGlow += distanceMask;
|
||||||
|
outerBgGlow = pow(outerBgGlow, 0.5);
|
||||||
|
outerBgGlow *= 0.15;
|
||||||
|
|
||||||
|
vec3 color = uEyeColor * uIntensity * clamp(max(innerRing + innerEye, outerEyeGlow + outerBgGlow) - pupil, 0.0, 3.0);
|
||||||
|
color += uBgColor;
|
||||||
|
|
||||||
|
gl_FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<EvilEyeProps>(), {
|
||||||
|
eyeColor: '#FF6F37',
|
||||||
|
intensity: 1.5,
|
||||||
|
pupilSize: 0.6,
|
||||||
|
irisWidth: 0.25,
|
||||||
|
glowIntensity: 0.35,
|
||||||
|
scale: 0.8,
|
||||||
|
noiseScale: 1.0,
|
||||||
|
pupilFollow: 1.0,
|
||||||
|
flameSpeed: 1.0,
|
||||||
|
backgroundColor: '#000000'
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
const setup = () => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
const container = containerRef.value;
|
||||||
|
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const noiseData = generateNoiseTexture(256);
|
||||||
|
const noiseTexture = new Texture(gl, {
|
||||||
|
image: noiseData,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
generateMipmaps: false,
|
||||||
|
flipY: false
|
||||||
|
});
|
||||||
|
noiseTexture.minFilter = gl.LINEAR;
|
||||||
|
noiseTexture.magFilter = gl.LINEAR;
|
||||||
|
noiseTexture.wrapS = gl.REPEAT;
|
||||||
|
noiseTexture.wrapT = gl.REPEAT;
|
||||||
|
|
||||||
|
const mouse = { x: 0, y: 0, tx: 0, ty: 0 };
|
||||||
|
|
||||||
|
function onMouseMove(e: MouseEvent) {
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
mouse.tx = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||||
|
mouse.ty = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseLeave() {
|
||||||
|
mouse.tx = 0;
|
||||||
|
mouse.ty = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addEventListener('mousemove', onMouseMove);
|
||||||
|
container.addEventListener('mouseleave', onMouseLeave);
|
||||||
|
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
|
||||||
|
uNoiseTexture: { value: noiseTexture },
|
||||||
|
uPupilSize: { value: props.pupilSize },
|
||||||
|
uIrisWidth: { value: props.irisWidth },
|
||||||
|
uGlowIntensity: { value: props.glowIntensity },
|
||||||
|
uIntensity: { value: props.intensity },
|
||||||
|
uScale: { value: props.scale },
|
||||||
|
uNoiseScale: { value: props.noiseScale },
|
||||||
|
uMouse: { value: [0, 0] },
|
||||||
|
uPupilFollow: { value: props.pupilFollow },
|
||||||
|
uFlameSpeed: { value: props.flameSpeed },
|
||||||
|
uEyeColor: { value: hexToVec3(props.eyeColor) },
|
||||||
|
uBgColor: { value: hexToVec3(props.backgroundColor) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
mouse.x += (mouse.tx - mouse.x) * 0.05;
|
||||||
|
mouse.y += (mouse.ty - mouse.y) * 0.05;
|
||||||
|
program.uniforms.uMouse.value = [mouse.x, mouse.y];
|
||||||
|
program.uniforms.uTime.value = time * 0.001;
|
||||||
|
renderer.render({ scene: mesh });
|
||||||
|
}
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
cleanup = () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
container.removeEventListener('mousemove', onMouseMove);
|
||||||
|
container.removeEventListener('mouseleave', onMouseLeave);
|
||||||
|
container.removeChild(gl.canvas);
|
||||||
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.eyeColor,
|
||||||
|
props.intensity,
|
||||||
|
props.pupilSize,
|
||||||
|
props.irisWidth,
|
||||||
|
props.glowIntensity,
|
||||||
|
props.scale,
|
||||||
|
props.noiseScale,
|
||||||
|
props.pupilFollow,
|
||||||
|
props.flameSpeed,
|
||||||
|
props.backgroundColor
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="w-full h-full" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Renderer, Program, Mesh, Triangle } from 'ogl';
|
||||||
|
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface LineWavesProps {
|
||||||
|
speed?: number;
|
||||||
|
innerLineCount?: number;
|
||||||
|
outerLineCount?: number;
|
||||||
|
warpIntensity?: number;
|
||||||
|
rotation?: number;
|
||||||
|
edgeFadeWidth?: number;
|
||||||
|
colorCycleSpeed?: number;
|
||||||
|
brightness?: number;
|
||||||
|
color1?: string;
|
||||||
|
color2?: string;
|
||||||
|
color3?: string;
|
||||||
|
enableMouseInteraction?: boolean;
|
||||||
|
mouseInfluence?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToVec3(hex: string): [number, number, number] {
|
||||||
|
const h = hex.replace('#', '');
|
||||||
|
return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec2 position;
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0, 1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec3 uResolution;
|
||||||
|
uniform float uSpeed;
|
||||||
|
uniform float uInnerLines;
|
||||||
|
uniform float uOuterLines;
|
||||||
|
uniform float uWarpIntensity;
|
||||||
|
uniform float uRotation;
|
||||||
|
uniform float uEdgeFadeWidth;
|
||||||
|
uniform float uColorCycleSpeed;
|
||||||
|
uniform float uBrightness;
|
||||||
|
uniform vec3 uColor1;
|
||||||
|
uniform vec3 uColor2;
|
||||||
|
uniform vec3 uColor3;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform float uMouseInfluence;
|
||||||
|
uniform bool uEnableMouse;
|
||||||
|
|
||||||
|
#define HALF_PI 1.5707963
|
||||||
|
|
||||||
|
float hashF(float n) {
|
||||||
|
return fract(sin(n * 127.1) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
float smoothNoise(float x) {
|
||||||
|
float i = floor(x);
|
||||||
|
float f = fract(x);
|
||||||
|
float u = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(hashF(i), hashF(i + 1.0), u);
|
||||||
|
}
|
||||||
|
|
||||||
|
float displaceA(float coord, float t) {
|
||||||
|
float result = sin(coord * 2.123) * 0.2;
|
||||||
|
result += sin(coord * 3.234 + t * 4.345) * 0.1;
|
||||||
|
result += sin(coord * 0.589 + t * 0.934) * 0.5;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float displaceB(float coord, float t) {
|
||||||
|
float result = sin(coord * 1.345) * 0.3;
|
||||||
|
result += sin(coord * 2.734 + t * 3.345) * 0.2;
|
||||||
|
result += sin(coord * 0.189 + t * 0.934) * 0.3;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 rotate2D(vec2 p, float angle) {
|
||||||
|
float c = cos(angle);
|
||||||
|
float s = sin(angle);
|
||||||
|
return vec2(p.x * c - p.y * s, p.x * s + p.y * c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coords = gl_FragCoord.xy / uResolution.xy;
|
||||||
|
coords = coords * 2.0 - 1.0;
|
||||||
|
coords = rotate2D(coords, uRotation);
|
||||||
|
|
||||||
|
float halfT = uTime * uSpeed * 0.5;
|
||||||
|
float fullT = uTime * uSpeed;
|
||||||
|
|
||||||
|
float mouseWarp = 0.0;
|
||||||
|
if (uEnableMouse) {
|
||||||
|
vec2 mPos = rotate2D(uMouse * 2.0 - 1.0, uRotation);
|
||||||
|
float mDist = length(coords - mPos);
|
||||||
|
mouseWarp = uMouseInfluence * exp(-mDist * mDist * 4.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float warpAx = coords.x + displaceA(coords.y, halfT) * uWarpIntensity + mouseWarp;
|
||||||
|
float warpAy = coords.y - displaceA(coords.x * cos(fullT) * 1.235, halfT) * uWarpIntensity;
|
||||||
|
float warpBx = coords.x + displaceB(coords.y, halfT) * uWarpIntensity + mouseWarp;
|
||||||
|
float warpBy = coords.y - displaceB(coords.x * sin(fullT) * 1.235, halfT) * uWarpIntensity;
|
||||||
|
|
||||||
|
vec2 fieldA = vec2(warpAx, warpAy);
|
||||||
|
vec2 fieldB = vec2(warpBx, warpBy);
|
||||||
|
vec2 blended = mix(fieldA, fieldB, mix(fieldA, fieldB, 0.5));
|
||||||
|
|
||||||
|
float fadeTop = smoothstep(uEdgeFadeWidth, uEdgeFadeWidth + 0.4, blended.y);
|
||||||
|
float fadeBottom = smoothstep(-uEdgeFadeWidth, -(uEdgeFadeWidth + 0.4), blended.y);
|
||||||
|
float vMask = 1.0 - max(fadeTop, fadeBottom);
|
||||||
|
|
||||||
|
float tileCount = mix(uOuterLines, uInnerLines, vMask);
|
||||||
|
float scaledY = blended.y * tileCount;
|
||||||
|
float nY = smoothNoise(abs(scaledY));
|
||||||
|
|
||||||
|
float ridge = pow(
|
||||||
|
step(abs(nY - blended.x) * 2.0, HALF_PI) * cos(2.0 * (nY - blended.x)),
|
||||||
|
5.0
|
||||||
|
);
|
||||||
|
|
||||||
|
float lines = 0.0;
|
||||||
|
for (float i = 1.0; i < 3.0; i += 1.0) {
|
||||||
|
lines += pow(max(fract(scaledY), fract(-scaledY)), i * 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float pattern = vMask * lines;
|
||||||
|
|
||||||
|
float cycleT = fullT * uColorCycleSpeed;
|
||||||
|
float rChannel = (pattern + lines * ridge) * (cos(blended.y + cycleT * 0.234) * 0.5 + 1.0);
|
||||||
|
float gChannel = (pattern + vMask * ridge) * (sin(blended.x + cycleT * 1.745) * 0.5 + 1.0);
|
||||||
|
float bChannel = (pattern + lines * ridge) * (cos(blended.x + cycleT * 0.534) * 0.5 + 1.0);
|
||||||
|
|
||||||
|
vec3 col = (rChannel * uColor1 + gChannel * uColor2 + bChannel * uColor3) * uBrightness;
|
||||||
|
float alpha = clamp(length(col), 0.0, 1.0);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(col, alpha);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<LineWavesProps>(), {
|
||||||
|
speed: 0.3,
|
||||||
|
innerLineCount: 32.0,
|
||||||
|
outerLineCount: 36.0,
|
||||||
|
warpIntensity: 1.0,
|
||||||
|
rotation: -45,
|
||||||
|
edgeFadeWidth: 0.0,
|
||||||
|
colorCycleSpeed: 1.0,
|
||||||
|
brightness: 0.2,
|
||||||
|
color1: '#ffffff',
|
||||||
|
color2: '#ffffff',
|
||||||
|
color3: '#ffffff',
|
||||||
|
enableMouseInteraction: true,
|
||||||
|
mouseInfluence: 2.0
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
const setup = () => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
const container = containerRef.value;
|
||||||
|
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const currentMouse = [0.5, 0.5];
|
||||||
|
let targetMouse = [0.5, 0.5];
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
const rect = gl.canvas.getBoundingClientRect();
|
||||||
|
targetMouse = [(e.clientX - rect.left) / rect.width, 1.0 - (e.clientY - rect.top) / rect.height];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
targetMouse = [0.5, 0.5];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const rotationRad = (props.rotation * Math.PI) / 180;
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
|
||||||
|
uSpeed: { value: props.speed },
|
||||||
|
uInnerLines: { value: props.innerLineCount },
|
||||||
|
uOuterLines: { value: props.outerLineCount },
|
||||||
|
uWarpIntensity: { value: props.warpIntensity },
|
||||||
|
uRotation: { value: rotationRad },
|
||||||
|
uEdgeFadeWidth: { value: props.edgeFadeWidth },
|
||||||
|
uColorCycleSpeed: { value: props.colorCycleSpeed },
|
||||||
|
uBrightness: { value: props.brightness },
|
||||||
|
uColor1: { value: hexToVec3(props.color1) },
|
||||||
|
uColor2: { value: hexToVec3(props.color2) },
|
||||||
|
uColor3: { value: hexToVec3(props.color3) },
|
||||||
|
uMouse: { value: new Float32Array([0.5, 0.5]) },
|
||||||
|
uMouseInfluence: { value: props.mouseInfluence },
|
||||||
|
uEnableMouse: { value: props.enableMouseInteraction }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.addEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
program.uniforms.uTime.value = time * 0.001;
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
|
||||||
|
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
|
||||||
|
program.uniforms.uMouse.value[0] = currentMouse[0];
|
||||||
|
program.uniforms.uMouse.value[1] = currentMouse[1];
|
||||||
|
} else {
|
||||||
|
program.uniforms.uMouse.value[0] = 0.5;
|
||||||
|
program.uniforms.uMouse.value[1] = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.render({ scene: mesh });
|
||||||
|
}
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
cleanup = () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
container.removeChild(gl.canvas);
|
||||||
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.speed,
|
||||||
|
props.innerLineCount,
|
||||||
|
props.outerLineCount,
|
||||||
|
props.warpIntensity,
|
||||||
|
props.rotation,
|
||||||
|
props.edgeFadeWidth,
|
||||||
|
props.colorCycleSpeed,
|
||||||
|
props.brightness,
|
||||||
|
props.color1,
|
||||||
|
props.color2,
|
||||||
|
props.color3,
|
||||||
|
props.enableMouseInteraction,
|
||||||
|
props.mouseInfluence
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="w-full h-full" />
|
||||||
|
</template>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';
|
import { ref, onMounted, onUnmounted, watch, useTemplateRef, nextTick } from 'vue';
|
||||||
import { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';
|
import { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';
|
||||||
|
|
||||||
interface ParticlesProps {
|
interface ParticlesProps {
|
||||||
@@ -296,7 +296,9 @@ const cleanup = () => {
|
|||||||
program = null;
|
program = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// CHANGED onMounted
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick(); // wait for Vue to flush its DOM updates first
|
||||||
initParticles();
|
initParticles();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Renderer, Program, Mesh, Triangle } from 'ogl';
|
||||||
|
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface RadarProps {
|
||||||
|
speed?: number;
|
||||||
|
scale?: number;
|
||||||
|
ringCount?: number;
|
||||||
|
spokeCount?: number;
|
||||||
|
ringThickness?: number;
|
||||||
|
spokeThickness?: number;
|
||||||
|
sweepSpeed?: number;
|
||||||
|
sweepWidth?: number;
|
||||||
|
sweepLobes?: number;
|
||||||
|
color?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
falloff?: number;
|
||||||
|
brightness?: number;
|
||||||
|
enableMouseInteraction?: boolean;
|
||||||
|
mouseInfluence?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToVec3(hex: string): [number, number, number] {
|
||||||
|
const h = hex.replace('#', '');
|
||||||
|
return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec2 position;
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0, 1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec3 uResolution;
|
||||||
|
uniform float uSpeed;
|
||||||
|
uniform float uScale;
|
||||||
|
uniform float uRingCount;
|
||||||
|
uniform float uSpokeCount;
|
||||||
|
uniform float uRingThickness;
|
||||||
|
uniform float uSpokeThickness;
|
||||||
|
uniform float uSweepSpeed;
|
||||||
|
uniform float uSweepWidth;
|
||||||
|
uniform float uSweepLobes;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform vec3 uBgColor;
|
||||||
|
uniform float uFalloff;
|
||||||
|
uniform float uBrightness;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform float uMouseInfluence;
|
||||||
|
uniform bool uEnableMouse;
|
||||||
|
|
||||||
|
#define TAU 6.28318530718
|
||||||
|
#define PI 3.14159265359
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 st = gl_FragCoord.xy / uResolution.xy;
|
||||||
|
st = st * 2.0 - 1.0;
|
||||||
|
st.x *= uResolution.x / uResolution.y;
|
||||||
|
|
||||||
|
if (uEnableMouse) {
|
||||||
|
vec2 mShift = (uMouse * 2.0 - 1.0);
|
||||||
|
mShift.x *= uResolution.x / uResolution.y;
|
||||||
|
st -= mShift * uMouseInfluence;
|
||||||
|
}
|
||||||
|
|
||||||
|
st *= uScale;
|
||||||
|
|
||||||
|
float dist = length(st);
|
||||||
|
float theta = atan(st.y, st.x);
|
||||||
|
float t = uTime * uSpeed;
|
||||||
|
|
||||||
|
float ringPhase = dist * uRingCount - t;
|
||||||
|
float ringDist = abs(fract(ringPhase) - 0.5);
|
||||||
|
float ringGlow = 1.0 - smoothstep(0.0, uRingThickness, ringDist);
|
||||||
|
|
||||||
|
float spokeAngle = abs(fract(theta * uSpokeCount / TAU + 0.5) - 0.5) * TAU / uSpokeCount;
|
||||||
|
float arcDist = spokeAngle * dist;
|
||||||
|
float spokeGlow = (1.0 - smoothstep(0.0, uSpokeThickness, arcDist)) * smoothstep(0.0, 0.1, dist);
|
||||||
|
|
||||||
|
float sweepPhase = t * uSweepSpeed;
|
||||||
|
float sweepBeam = pow(max(0.5 * sin(uSweepLobes * theta + sweepPhase) + 0.5, 0.0), uSweepWidth);
|
||||||
|
|
||||||
|
float fade = smoothstep(1.05, 0.85, dist) * pow(max(1.0 - dist, 0.0), uFalloff);
|
||||||
|
|
||||||
|
float intensity = max((ringGlow + spokeGlow + sweepBeam) * fade * uBrightness, 0.0);
|
||||||
|
vec3 col = uColor * intensity + uBgColor;
|
||||||
|
|
||||||
|
float alpha = clamp(length(col), 0.0, 1.0);
|
||||||
|
gl_FragColor = vec4(col, alpha);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<RadarProps>(), {
|
||||||
|
speed: 1.0,
|
||||||
|
scale: 0.5,
|
||||||
|
ringCount: 10.0,
|
||||||
|
spokeCount: 10.0,
|
||||||
|
ringThickness: 0.05,
|
||||||
|
spokeThickness: 0.01,
|
||||||
|
sweepSpeed: 1.0,
|
||||||
|
sweepWidth: 2.0,
|
||||||
|
sweepLobes: 1.0,
|
||||||
|
color: '#27FF64',
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
falloff: 2.0,
|
||||||
|
brightness: 1.0,
|
||||||
|
enableMouseInteraction: true,
|
||||||
|
mouseInfluence: 0.1
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
const setup = () => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
const container = containerRef.value;
|
||||||
|
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const currentMouse = [0.5, 0.5];
|
||||||
|
let targetMouse = [0.5, 0.5];
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
const rect = gl.canvas.getBoundingClientRect();
|
||||||
|
targetMouse = [(e.clientX - rect.left) / rect.width, 1.0 - (e.clientY - rect.top) / rect.height];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
targetMouse = [0.5, 0.5];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
|
||||||
|
uSpeed: { value: props.speed },
|
||||||
|
uScale: { value: props.scale },
|
||||||
|
uRingCount: { value: props.ringCount },
|
||||||
|
uSpokeCount: { value: props.spokeCount },
|
||||||
|
uRingThickness: { value: props.ringThickness },
|
||||||
|
uSpokeThickness: { value: props.spokeThickness },
|
||||||
|
uSweepSpeed: { value: props.sweepSpeed },
|
||||||
|
uSweepWidth: { value: props.sweepWidth },
|
||||||
|
uSweepLobes: { value: props.sweepLobes },
|
||||||
|
uColor: { value: hexToVec3(props.color) },
|
||||||
|
uBgColor: { value: hexToVec3(props.backgroundColor) },
|
||||||
|
uFalloff: { value: props.falloff },
|
||||||
|
uBrightness: { value: props.brightness },
|
||||||
|
uMouse: { value: new Float32Array([0.5, 0.5]) },
|
||||||
|
uMouseInfluence: { value: props.mouseInfluence },
|
||||||
|
uEnableMouse: { value: props.enableMouseInteraction }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.addEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
program.uniforms.uTime.value = time * 0.001;
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
|
||||||
|
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
|
||||||
|
program.uniforms.uMouse.value[0] = currentMouse[0];
|
||||||
|
program.uniforms.uMouse.value[1] = currentMouse[1];
|
||||||
|
} else {
|
||||||
|
program.uniforms.uMouse.value[0] = 0.5;
|
||||||
|
program.uniforms.uMouse.value[1] = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.render({ scene: mesh });
|
||||||
|
}
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
cleanup = () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
container.removeChild(gl.canvas);
|
||||||
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.speed,
|
||||||
|
props.scale,
|
||||||
|
props.ringCount,
|
||||||
|
props.spokeCount,
|
||||||
|
props.ringThickness,
|
||||||
|
props.spokeThickness,
|
||||||
|
props.sweepSpeed,
|
||||||
|
props.sweepWidth,
|
||||||
|
props.sweepLobes,
|
||||||
|
props.color,
|
||||||
|
props.backgroundColor,
|
||||||
|
props.falloff,
|
||||||
|
props.brightness,
|
||||||
|
props.enableMouseInteraction,
|
||||||
|
props.mouseInfluence
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="w-full h-full" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Mesh, Program, Renderer, Triangle } from 'ogl';
|
||||||
|
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface SoftAuroraProps {
|
||||||
|
speed?: number;
|
||||||
|
scale?: number;
|
||||||
|
brightness?: number;
|
||||||
|
color1?: string;
|
||||||
|
color2?: string;
|
||||||
|
noiseFrequency?: number;
|
||||||
|
noiseAmplitude?: number;
|
||||||
|
bandHeight?: number;
|
||||||
|
bandSpread?: number;
|
||||||
|
octaveDecay?: number;
|
||||||
|
layerOffset?: number;
|
||||||
|
colorSpeed?: number;
|
||||||
|
enableMouseInteraction?: boolean;
|
||||||
|
mouseInfluence?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToVec3(hex: string): [number, number, number] {
|
||||||
|
const h = hex.replace('#', '');
|
||||||
|
return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec2 position;
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0, 1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec3 uResolution;
|
||||||
|
uniform float uSpeed;
|
||||||
|
uniform float uScale;
|
||||||
|
uniform float uBrightness;
|
||||||
|
uniform vec3 uColor1;
|
||||||
|
uniform vec3 uColor2;
|
||||||
|
uniform float uNoiseFreq;
|
||||||
|
uniform float uNoiseAmp;
|
||||||
|
uniform float uBandHeight;
|
||||||
|
uniform float uBandSpread;
|
||||||
|
uniform float uOctaveDecay;
|
||||||
|
uniform float uLayerOffset;
|
||||||
|
uniform float uColorSpeed;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform float uMouseInfluence;
|
||||||
|
uniform bool uEnableMouse;
|
||||||
|
|
||||||
|
#define TAU 6.28318
|
||||||
|
|
||||||
|
vec3 gradientHash(vec3 p) {
|
||||||
|
p = vec3(
|
||||||
|
dot(p, vec3(127.1, 311.7, 234.6)),
|
||||||
|
dot(p, vec3(269.5, 183.3, 198.3)),
|
||||||
|
dot(p, vec3(169.5, 283.3, 156.9))
|
||||||
|
);
|
||||||
|
vec3 h = fract(sin(p) * 43758.5453123);
|
||||||
|
float phi = acos(2.0 * h.x - 1.0);
|
||||||
|
float theta = TAU * h.y;
|
||||||
|
return vec3(cos(theta) * sin(phi), sin(theta) * cos(phi), cos(phi));
|
||||||
|
}
|
||||||
|
|
||||||
|
float quinticSmooth(float t) {
|
||||||
|
float t2 = t * t;
|
||||||
|
float t3 = t * t2;
|
||||||
|
return 6.0 * t3 * t2 - 15.0 * t2 * t2 + 10.0 * t3;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 cosineGradient(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
|
||||||
|
return a + b * cos(TAU * (c * t + d));
|
||||||
|
}
|
||||||
|
|
||||||
|
float perlin3D(float amplitude, float frequency, float px, float py, float pz) {
|
||||||
|
float x = px * frequency;
|
||||||
|
float y = py * frequency;
|
||||||
|
|
||||||
|
float fx = floor(x); float fy = floor(y); float fz = floor(pz);
|
||||||
|
float cx = ceil(x); float cy = ceil(y); float cz = ceil(pz);
|
||||||
|
|
||||||
|
vec3 g000 = gradientHash(vec3(fx, fy, fz));
|
||||||
|
vec3 g100 = gradientHash(vec3(cx, fy, fz));
|
||||||
|
vec3 g010 = gradientHash(vec3(fx, cy, fz));
|
||||||
|
vec3 g110 = gradientHash(vec3(cx, cy, fz));
|
||||||
|
vec3 g001 = gradientHash(vec3(fx, fy, cz));
|
||||||
|
vec3 g101 = gradientHash(vec3(cx, fy, cz));
|
||||||
|
vec3 g011 = gradientHash(vec3(fx, cy, cz));
|
||||||
|
vec3 g111 = gradientHash(vec3(cx, cy, cz));
|
||||||
|
|
||||||
|
float d000 = dot(g000, vec3(x - fx, y - fy, pz - fz));
|
||||||
|
float d100 = dot(g100, vec3(x - cx, y - fy, pz - fz));
|
||||||
|
float d010 = dot(g010, vec3(x - fx, y - cy, pz - fz));
|
||||||
|
float d110 = dot(g110, vec3(x - cx, y - cy, pz - fz));
|
||||||
|
float d001 = dot(g001, vec3(x - fx, y - fy, pz - cz));
|
||||||
|
float d101 = dot(g101, vec3(x - cx, y - fy, pz - cz));
|
||||||
|
float d011 = dot(g011, vec3(x - fx, y - cy, pz - cz));
|
||||||
|
float d111 = dot(g111, vec3(x - cx, y - cy, pz - cz));
|
||||||
|
|
||||||
|
float sx = quinticSmooth(x - fx);
|
||||||
|
float sy = quinticSmooth(y - fy);
|
||||||
|
float sz = quinticSmooth(pz - fz);
|
||||||
|
|
||||||
|
float lx00 = mix(d000, d100, sx);
|
||||||
|
float lx10 = mix(d010, d110, sx);
|
||||||
|
float lx01 = mix(d001, d101, sx);
|
||||||
|
float lx11 = mix(d011, d111, sx);
|
||||||
|
|
||||||
|
float ly0 = mix(lx00, lx10, sy);
|
||||||
|
float ly1 = mix(lx01, lx11, sy);
|
||||||
|
|
||||||
|
return amplitude * mix(ly0, ly1, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
float auroraGlow(float t, vec2 shift) {
|
||||||
|
vec2 uv = gl_FragCoord.xy / uResolution.y;
|
||||||
|
uv += shift;
|
||||||
|
|
||||||
|
float noiseVal = 0.0;
|
||||||
|
float freq = uNoiseFreq;
|
||||||
|
float amp = uNoiseAmp;
|
||||||
|
vec2 samplePos = uv * uScale;
|
||||||
|
|
||||||
|
for (float i = 0.0; i < 3.0; i += 1.0) {
|
||||||
|
noiseVal += perlin3D(amp, freq, samplePos.x, samplePos.y, t);
|
||||||
|
amp *= uOctaveDecay;
|
||||||
|
freq *= 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float yBand = uv.y * 10.0 - uBandHeight * 10.0;
|
||||||
|
return 0.3 * max(exp(uBandSpread * (1.0 - 1.1 * abs(noiseVal + yBand))), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / uResolution.xy;
|
||||||
|
float t = uSpeed * 0.4 * uTime;
|
||||||
|
|
||||||
|
vec2 shift = vec2(0.0);
|
||||||
|
if (uEnableMouse) {
|
||||||
|
shift = (uMouse - 0.5) * uMouseInfluence;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 col = vec3(0.0);
|
||||||
|
col += 0.99 * auroraGlow(t, shift) * cosineGradient(uv.x + uTime * uSpeed * 0.2 * uColorSpeed, vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.3, 0.20, 0.20)) * uColor1;
|
||||||
|
col += 0.99 * auroraGlow(t + uLayerOffset, shift) * cosineGradient(uv.x + uTime * uSpeed * 0.1 * uColorSpeed, vec3(0.5), vec3(0.5), vec3(2.0, 1.0, 0.0), vec3(0.5, 0.20, 0.25)) * uColor2;
|
||||||
|
|
||||||
|
col *= uBrightness;
|
||||||
|
float alpha = clamp(length(col), 0.0, 1.0);
|
||||||
|
gl_FragColor = vec4(col, alpha);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<SoftAuroraProps>(), {
|
||||||
|
speed: 0.6,
|
||||||
|
scale: 1.5,
|
||||||
|
brightness: 1.0,
|
||||||
|
color1: '#f7f7f7',
|
||||||
|
color2: '#27FF64',
|
||||||
|
noiseFrequency: 2.5,
|
||||||
|
noiseAmplitude: 1.0,
|
||||||
|
bandHeight: 0.5,
|
||||||
|
bandSpread: 1.0,
|
||||||
|
octaveDecay: 0.1,
|
||||||
|
layerOffset: 0,
|
||||||
|
colorSpeed: 1.0,
|
||||||
|
enableMouseInteraction: true,
|
||||||
|
mouseInfluence: 0.25
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
const setup = () => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
const container = containerRef.value;
|
||||||
|
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const currentMouse = [0.5, 0.5];
|
||||||
|
let targetMouse = [0.5, 0.5];
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
const rect = gl.canvas.getBoundingClientRect();
|
||||||
|
targetMouse = [(e.clientX - rect.left) / rect.width, 1.0 - (e.clientY - rect.top) / rect.height];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
targetMouse = [0.5, 0.5];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
|
||||||
|
uSpeed: { value: props.speed },
|
||||||
|
uScale: { value: props.scale },
|
||||||
|
uBrightness: { value: props.brightness },
|
||||||
|
uColor1: { value: hexToVec3(props.color1) },
|
||||||
|
uColor2: { value: hexToVec3(props.color2) },
|
||||||
|
uNoiseFreq: { value: props.noiseFrequency },
|
||||||
|
uNoiseAmp: { value: props.noiseAmplitude },
|
||||||
|
uBandHeight: { value: props.bandHeight },
|
||||||
|
uBandSpread: { value: props.bandSpread },
|
||||||
|
uOctaveDecay: { value: props.octaveDecay },
|
||||||
|
uLayerOffset: { value: props.layerOffset },
|
||||||
|
uColorSpeed: { value: props.colorSpeed },
|
||||||
|
uMouse: { value: new Float32Array([0.5, 0.5]) },
|
||||||
|
uMouseInfluence: { value: props.mouseInfluence },
|
||||||
|
uEnableMouse: { value: props.enableMouseInteraction }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.addEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
program.uniforms.uTime.value = time * 0.001;
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
|
||||||
|
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
|
||||||
|
program.uniforms.uMouse.value[0] = currentMouse[0];
|
||||||
|
program.uniforms.uMouse.value[1] = currentMouse[1];
|
||||||
|
} else {
|
||||||
|
program.uniforms.uMouse.value[0] = 0.5;
|
||||||
|
program.uniforms.uMouse.value[1] = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.render({ scene: mesh });
|
||||||
|
}
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
cleanup = () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
container.removeChild(gl.canvas);
|
||||||
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.speed,
|
||||||
|
props.scale,
|
||||||
|
props.brightness,
|
||||||
|
props.color1,
|
||||||
|
props.color2,
|
||||||
|
props.noiseFrequency,
|
||||||
|
props.noiseAmplitude,
|
||||||
|
props.bandHeight,
|
||||||
|
props.bandSpread,
|
||||||
|
props.octaveDecay,
|
||||||
|
props.layerOffset,
|
||||||
|
props.colorSpeed,
|
||||||
|
props.enableMouseInteraction,
|
||||||
|
props.mouseInfluence
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="w-full h-full" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface BorderGlowProps {
|
||||||
|
className?: string;
|
||||||
|
edgeSensitivity?: number;
|
||||||
|
glowColor?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
borderRadius?: number;
|
||||||
|
glowRadius?: number;
|
||||||
|
glowIntensity?: number;
|
||||||
|
coneSpread?: number;
|
||||||
|
animated?: boolean;
|
||||||
|
colors?: string[];
|
||||||
|
fillOpacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHSL(hslStr: string): { h: number; s: number; l: number } {
|
||||||
|
const match = hslStr.match(/([\d.]+)\s*([\d.]+)%?\s*([\d.]+)%?/);
|
||||||
|
if (!match) return { h: 40, s: 80, l: 80 };
|
||||||
|
return { h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBoxShadow(glowColor: string, intensity: number): string {
|
||||||
|
const { h, s, l } = parseHSL(glowColor);
|
||||||
|
const base = `${h}deg ${s}% ${l}%`;
|
||||||
|
const layers: [number, number, number, number, number, boolean][] = [
|
||||||
|
[0, 0, 0, 1, 100, true],
|
||||||
|
[0, 0, 1, 0, 60, true],
|
||||||
|
[0, 0, 3, 0, 50, true],
|
||||||
|
[0, 0, 6, 0, 40, true],
|
||||||
|
[0, 0, 15, 0, 30, true],
|
||||||
|
[0, 0, 25, 2, 20, true],
|
||||||
|
[0, 0, 50, 2, 10, true],
|
||||||
|
[0, 0, 1, 0, 60, false],
|
||||||
|
[0, 0, 3, 0, 50, false],
|
||||||
|
[0, 0, 6, 0, 40, false],
|
||||||
|
[0, 0, 15, 0, 30, false],
|
||||||
|
[0, 0, 25, 2, 20, false],
|
||||||
|
[0, 0, 50, 2, 10, false]
|
||||||
|
];
|
||||||
|
return layers
|
||||||
|
.map(([x, y, blur, spread, alpha, inset]) => {
|
||||||
|
const a = Math.min(alpha * intensity, 100);
|
||||||
|
return `${inset ? 'inset ' : ''}${x}px ${y}px ${blur}px ${spread}px hsl(${base} / ${a}%)`;
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function easeOutCubic(x: number) {
|
||||||
|
return 1 - Math.pow(1 - x, 3);
|
||||||
|
}
|
||||||
|
function easeInCubic(x: number) {
|
||||||
|
return x * x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnimateOpts {
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
duration?: number;
|
||||||
|
delay?: number;
|
||||||
|
ease?: (t: number) => number;
|
||||||
|
onUpdate: (v: number) => void;
|
||||||
|
onEnd?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function animateValue({
|
||||||
|
start = 0,
|
||||||
|
end = 100,
|
||||||
|
duration = 1000,
|
||||||
|
delay = 0,
|
||||||
|
ease = easeOutCubic,
|
||||||
|
onUpdate,
|
||||||
|
onEnd
|
||||||
|
}: AnimateOpts) {
|
||||||
|
const t0 = performance.now() + delay;
|
||||||
|
function tick() {
|
||||||
|
const elapsed = performance.now() - t0;
|
||||||
|
const t = Math.min(elapsed / duration, 1);
|
||||||
|
onUpdate(start + (end - start) * ease(t));
|
||||||
|
if (t < 1) requestAnimationFrame(tick);
|
||||||
|
else if (onEnd) onEnd();
|
||||||
|
}
|
||||||
|
setTimeout(() => requestAnimationFrame(tick), delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'];
|
||||||
|
const COLOR_MAP = [0, 1, 2, 0, 1, 2, 1];
|
||||||
|
|
||||||
|
function buildMeshGradients(colors: string[]): string[] {
|
||||||
|
const gradients: string[] = [];
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const c = colors[Math.min(COLOR_MAP[i], colors.length - 1)];
|
||||||
|
gradients.push(`radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${c} 0px, transparent 50%)`);
|
||||||
|
}
|
||||||
|
gradients.push(`linear-gradient(${colors[0]} 0 100%)`);
|
||||||
|
return gradients;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<BorderGlowProps>(), {
|
||||||
|
className: '',
|
||||||
|
edgeSensitivity: 30,
|
||||||
|
glowColor: '40 80 80',
|
||||||
|
backgroundColor: '#060010',
|
||||||
|
borderRadius: 28,
|
||||||
|
glowRadius: 40,
|
||||||
|
glowIntensity: 1.0,
|
||||||
|
coneSpread: 25,
|
||||||
|
animated: false,
|
||||||
|
colors: () => ['#c084fc', '#f472b6', '#38bdf8'],
|
||||||
|
fillOpacity: 0.5
|
||||||
|
});
|
||||||
|
|
||||||
|
const cardRef = useTemplateRef<HTMLDivElement>('cardRef');
|
||||||
|
const isHovered = ref(false);
|
||||||
|
const cursorAngle = ref(45);
|
||||||
|
const edgeProximity = ref(0);
|
||||||
|
const sweepActive = ref(false);
|
||||||
|
|
||||||
|
const getCenterOfElement = (el: HTMLElement) => {
|
||||||
|
const { width, height } = el.getBoundingClientRect();
|
||||||
|
return [width / 2, height / 2];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEdgeProximity = (el: HTMLElement, x: number, y: number) => {
|
||||||
|
const [cx, cy] = getCenterOfElement(el);
|
||||||
|
const dx = x - cx;
|
||||||
|
const dy = y - cy;
|
||||||
|
let kx = Infinity;
|
||||||
|
let ky = Infinity;
|
||||||
|
if (dx !== 0) kx = cx / Math.abs(dx);
|
||||||
|
if (dy !== 0) ky = cy / Math.abs(dy);
|
||||||
|
return Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCursorAngle = (el: HTMLElement, x: number, y: number) => {
|
||||||
|
const [cx, cy] = getCenterOfElement(el);
|
||||||
|
const dx = x - cx;
|
||||||
|
const dy = y - cy;
|
||||||
|
if (dx === 0 && dy === 0) return 0;
|
||||||
|
const radians = Math.atan2(dy, dx);
|
||||||
|
let degrees = radians * (180 / Math.PI) + 90;
|
||||||
|
if (degrees < 0) degrees += 360;
|
||||||
|
return degrees;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (e: PointerEvent) => {
|
||||||
|
const card = cardRef.value;
|
||||||
|
if (!card) return;
|
||||||
|
const rect = card.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
edgeProximity.value = getEdgeProximity(card, x, y);
|
||||||
|
cursorAngle.value = getCursorAngle(card, x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.animated],
|
||||||
|
() => {
|
||||||
|
if (!props.animated) return;
|
||||||
|
const angleStart = 110;
|
||||||
|
const angleEnd = 465;
|
||||||
|
sweepActive.value = true;
|
||||||
|
cursorAngle.value = angleStart;
|
||||||
|
|
||||||
|
animateValue({ duration: 500, onUpdate: v => (edgeProximity.value = v / 100) });
|
||||||
|
animateValue({
|
||||||
|
ease: easeInCubic,
|
||||||
|
duration: 1500,
|
||||||
|
end: 50,
|
||||||
|
onUpdate: v => {
|
||||||
|
cursorAngle.value = (angleEnd - angleStart) * (v / 100) + angleStart;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animateValue({
|
||||||
|
ease: easeOutCubic,
|
||||||
|
delay: 1500,
|
||||||
|
duration: 2250,
|
||||||
|
start: 50,
|
||||||
|
end: 100,
|
||||||
|
onUpdate: v => {
|
||||||
|
cursorAngle.value = (angleEnd - angleStart) * (v / 100) + angleStart;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animateValue({
|
||||||
|
ease: easeInCubic,
|
||||||
|
delay: 2500,
|
||||||
|
duration: 1500,
|
||||||
|
start: 100,
|
||||||
|
end: 0,
|
||||||
|
onUpdate: v => (edgeProximity.value = v / 100),
|
||||||
|
onEnd: () => (sweepActive.value = false)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const colorSensitivity = computed(() => props.edgeSensitivity + 20);
|
||||||
|
const isVisible = computed(() => isHovered.value || sweepActive.value);
|
||||||
|
const borderOpacity = computed(() =>
|
||||||
|
isVisible.value
|
||||||
|
? Math.max(0, (edgeProximity.value * 100 - colorSensitivity.value) / (100 - colorSensitivity.value))
|
||||||
|
: 0
|
||||||
|
);
|
||||||
|
const glowOpacity = computed(() =>
|
||||||
|
isVisible.value ? Math.max(0, (edgeProximity.value * 100 - props.edgeSensitivity) / (100 - props.edgeSensitivity)) : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const meshGradients = computed(() => buildMeshGradients(props.colors));
|
||||||
|
const borderBg = computed(() => meshGradients.value.map(g => `${g} border-box`));
|
||||||
|
const fillBg = computed(() => meshGradients.value.map(g => `${g} padding-box`));
|
||||||
|
const angleDeg = computed(() => `${cursorAngle.value.toFixed(3)}deg`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="cardRef"
|
||||||
|
@pointermove="handlePointerMove"
|
||||||
|
@pointerenter="isHovered = true"
|
||||||
|
@pointerleave="isHovered = false"
|
||||||
|
:class="`relative grid isolate border border-white/15 ${props.className}`"
|
||||||
|
:style="{
|
||||||
|
background: props.backgroundColor,
|
||||||
|
borderRadius: props.borderRadius + 'px',
|
||||||
|
transform: 'translate3d(0, 0, 0.01px)',
|
||||||
|
boxShadow:
|
||||||
|
'rgba(0,0,0,0.1) 0 1px 2px, rgba(0,0,0,0.1) 0 2px 4px, rgba(0,0,0,0.1) 0 4px 8px, rgba(0,0,0,0.1) 0 8px 16px, rgba(0,0,0,0.1) 0 16px 32px, rgba(0,0,0,0.1) 0 32px 64px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- mesh gradient border -->
|
||||||
|
<div
|
||||||
|
class="-z-[1] absolute inset-0 rounded-[inherit]"
|
||||||
|
:style="{
|
||||||
|
border: '1px solid transparent',
|
||||||
|
background: [
|
||||||
|
`linear-gradient(${props.backgroundColor} 0 100%) padding-box`,
|
||||||
|
'linear-gradient(rgb(255 255 255 / 0%) 0% 100%) border-box',
|
||||||
|
...borderBg
|
||||||
|
].join(', '),
|
||||||
|
opacity: borderOpacity,
|
||||||
|
maskImage: `conic-gradient(from ${angleDeg} at center, black ${props.coneSpread}%, transparent ${
|
||||||
|
props.coneSpread + 15
|
||||||
|
}%, transparent ${100 - props.coneSpread - 15}%, black ${100 - props.coneSpread}%)`,
|
||||||
|
WebkitMaskImage: `conic-gradient(from ${angleDeg} at center, black ${props.coneSpread}%, transparent ${
|
||||||
|
props.coneSpread + 15
|
||||||
|
}%, transparent ${100 - props.coneSpread - 15}%, black ${100 - props.coneSpread}%)`,
|
||||||
|
transition: isVisible ? 'opacity 0.25s ease-out' : 'opacity 0.75s ease-in-out'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- mesh gradient fill -->
|
||||||
|
<div
|
||||||
|
class="-z-[1] absolute inset-0 rounded-[inherit]"
|
||||||
|
:style="{
|
||||||
|
border: '1px solid transparent',
|
||||||
|
background: fillBg.join(', '),
|
||||||
|
maskImage: [
|
||||||
|
'linear-gradient(to bottom, black, black)',
|
||||||
|
'radial-gradient(ellipse at 50% 50%, black 40%, transparent 65%)',
|
||||||
|
'radial-gradient(ellipse at 66% 66%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 66% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 66%, black 5%, transparent 40%)',
|
||||||
|
`conic-gradient(from ${angleDeg} at center, transparent 5%, black 15%, black 85%, transparent 95%)`
|
||||||
|
].join(', '),
|
||||||
|
WebkitMaskImage: [
|
||||||
|
'linear-gradient(to bottom, black, black)',
|
||||||
|
'radial-gradient(ellipse at 50% 50%, black 40%, transparent 65%)',
|
||||||
|
'radial-gradient(ellipse at 66% 66%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 66% 33%, black 5%, transparent 40%)',
|
||||||
|
'radial-gradient(ellipse at 33% 66%, black 5%, transparent 40%)',
|
||||||
|
`conic-gradient(from ${angleDeg} at center, transparent 5%, black 15%, black 85%, transparent 95%)`
|
||||||
|
].join(', '),
|
||||||
|
maskComposite: 'subtract, add, add, add, add, add',
|
||||||
|
WebkitMaskComposite: 'source-out, source-over, source-over, source-over, source-over, source-over',
|
||||||
|
opacity: borderOpacity * props.fillOpacity,
|
||||||
|
mixBlendMode: 'soft-light',
|
||||||
|
transition: isVisible ? 'opacity 0.25s ease-out' : 'opacity 0.75s ease-in-out'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- outer glow -->
|
||||||
|
<span
|
||||||
|
class="z-[1] absolute rounded-[inherit] pointer-events-none"
|
||||||
|
:style="{
|
||||||
|
inset: `-${props.glowRadius}px`,
|
||||||
|
maskImage: `conic-gradient(from ${angleDeg} at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%)`,
|
||||||
|
WebkitMaskImage: `conic-gradient(from ${angleDeg} at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%)`,
|
||||||
|
opacity: glowOpacity,
|
||||||
|
mixBlendMode: 'plus-lighter',
|
||||||
|
transition: isVisible ? 'opacity 0.25s ease-out' : 'opacity 0.75s ease-in-out'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="absolute rounded-[inherit]"
|
||||||
|
:style="{
|
||||||
|
inset: `${props.glowRadius}px`,
|
||||||
|
boxShadow: buildBoxShadow(props.glowColor, props.glowIntensity)
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- content -->
|
||||||
|
<div class="z-[1] relative flex flex-col overflow-auto">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -20,15 +20,16 @@
|
|||||||
class="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none bg-white translate-y-[101%]"
|
class="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none bg-white translate-y-[101%]"
|
||||||
:ref="el => (marqueeRefs[idx] = el as HTMLDivElement)"
|
:ref="el => (marqueeRefs[idx] = el as HTMLDivElement)"
|
||||||
>
|
>
|
||||||
<div class="h-full w-[200%] flex" :ref="el => (marqueeInnerRefs[idx] = el as HTMLDivElement)">
|
<div class="h-full flex" :ref="el => (marqueeInnerRefs[idx] = el as HTMLDivElement)">
|
||||||
<div class="flex items-center relative h-full w-[200%] will-change-transform animate-marquee">
|
<div class="flex items-center relative h-full w-max will-change-transform animate-marquee">
|
||||||
<template v-for="i in 4" :key="`${idx}-${i}`">
|
<div v-for="g in 2" :key="`${idx}-g${g}`" class="flex items-center h-full shrink-0">
|
||||||
<span class="text-[#0b0b0b] uppercase font-normal text-[4vh] leading-[1.2] p-[1vh_1vw_0]">
|
<template v-for="i in 8" :key="`${idx}-g${g}-${i}`">
|
||||||
|
<span class="text-[#0b0b0b] uppercase font-normal text-[4vh] leading-[1.2] p-[1vh_1vw_0] whitespace-nowrap">
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="w-[200px] h-[7vh] my-[2em] mx-[2vw] p-[1em_0] rounded-[50px] bg-cover bg-center"
|
class="w-[200px] h-[7vh] my-[2em] mx-[2vw] p-[1em_0] rounded-[50px] bg-cover bg-center shrink-0"
|
||||||
:style="{ backgroundImage: `url(${item.image})` }"
|
:style="{ backgroundImage: `url(${item.image})` }"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -121,6 +123,6 @@ const handleMouseLeave = (ev: MouseEvent, idx: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.animate-marquee {
|
.animate-marquee {
|
||||||
animation: marquee 15s linear infinite;
|
animation: marquee 30s linear infinite;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -102,3 +102,110 @@ body {
|
|||||||
line-height: 1.25 !important;
|
line-height: 1.25 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MagicRings demo card */
|
||||||
|
.mr-demo-card {
|
||||||
|
width: 340px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
|
||||||
|
border: 1px solid #271e37;
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-visual {
|
||||||
|
background-color: #17251420;
|
||||||
|
border: 1px solid #271e37;
|
||||||
|
position: relative;
|
||||||
|
margin: 14px;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
opacity: 0.85;
|
||||||
|
stroke: #aeffc5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-body {
|
||||||
|
padding: 10px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #e9f8ff;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-cta {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 0;
|
||||||
|
background: #aeffc5;
|
||||||
|
color: black;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 120ms ease,
|
||||||
|
filter 120ms ease,
|
||||||
|
box-shadow 120ms ease;
|
||||||
|
box-shadow: 0 4px 16px rgba(82, 39, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-cta:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 24px rgba(82, 39, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-heart {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #271e37;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: border-color 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-demo-card-heart:hover {
|
||||||
|
border-color: #aeffc5;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<RefreshButton @click="forceRerender" />
|
||||||
|
|
||||||
|
<!-- Card example -->
|
||||||
|
<template v-if="example === 'card'">
|
||||||
|
<div class="mr-demo-card">
|
||||||
|
<div class="mr-demo-card-visual">
|
||||||
|
<MagicRings
|
||||||
|
:key="key"
|
||||||
|
:color="color"
|
||||||
|
:color-two="colorTwo"
|
||||||
|
:ring-count="ringCount"
|
||||||
|
:speed="speed"
|
||||||
|
:attenuation="attenuation"
|
||||||
|
:line-thickness="lineThickness"
|
||||||
|
:base-radius="baseRadius"
|
||||||
|
:radius-step="radiusStep"
|
||||||
|
:scale-rate="scaleRate"
|
||||||
|
:opacity="opacity"
|
||||||
|
:blur="blur"
|
||||||
|
:noise-amount="noiseAmount"
|
||||||
|
:rotation="rotation"
|
||||||
|
:ring-gap="ringGap"
|
||||||
|
:fade-in="fadeIn"
|
||||||
|
:fade-out="fadeOut"
|
||||||
|
:follow-mouse="followMouse"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
:hover-scale="hoverScale"
|
||||||
|
:parallax="parallax"
|
||||||
|
:click-burst="clickBurst"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="mr-demo-card-icon"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#aeffc5"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<!-- Sparkles icon -->
|
||||||
|
<path
|
||||||
|
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
|
||||||
|
/>
|
||||||
|
<path d="M20 3v4" />
|
||||||
|
<path d="M22 5h-4" />
|
||||||
|
<path d="M4 17v2" />
|
||||||
|
<path d="M5 18H3" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="mr-demo-card-body">
|
||||||
|
<h3 class="mr-demo-card-title">Magic Rings</h3>
|
||||||
|
<p class="mr-demo-card-subtitle">Interactive WebGL effect</p>
|
||||||
|
<div class="mr-demo-card-meta">
|
||||||
|
<span>
|
||||||
|
<i class="pi pi-github" style="font-size: 12px"></i>
|
||||||
|
Free & open source
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<i class="pi pi-google" style="font-size: 12px"></i>
|
||||||
|
Google
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mr-demo-card-actions">
|
||||||
|
<button class="mr-demo-card-cta">Copy to clipboard</button>
|
||||||
|
<div class="mr-demo-card-heart">
|
||||||
|
<i class="pi pi-heart" style="font-size: 16px"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Basic example -->
|
||||||
|
<template v-else>
|
||||||
|
<MagicRings
|
||||||
|
:key="key"
|
||||||
|
:color="color"
|
||||||
|
:color-two="colorTwo"
|
||||||
|
:ring-count="ringCount"
|
||||||
|
:speed="speed"
|
||||||
|
:attenuation="attenuation"
|
||||||
|
:line-thickness="lineThickness"
|
||||||
|
:base-radius="baseRadius"
|
||||||
|
:radius-step="radiusStep"
|
||||||
|
:scale-rate="scaleRate"
|
||||||
|
:opacity="opacity"
|
||||||
|
:blur="blur"
|
||||||
|
:noise-amount="noiseAmount"
|
||||||
|
:rotation="rotation"
|
||||||
|
:ring-gap="ringGap"
|
||||||
|
:fade-in="fadeIn"
|
||||||
|
:fade-out="fadeOut"
|
||||||
|
:follow-mouse="followMouse"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
:hover-scale="hoverScale"
|
||||||
|
:parallax="parallax"
|
||||||
|
:click-burst="clickBurst"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewSelect title="Example" v-model="example" :options="exampleOptions" />
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-3">
|
||||||
|
<PreviewColor title="Color" v-model="color" />
|
||||||
|
<PreviewColor title="Color Two" v-model="colorTwo" />
|
||||||
|
|
||||||
|
<PreviewSlider title="Ring Count" :min="1" :max="10" :step="1" v-model="ringCount" />
|
||||||
|
<PreviewSlider title="Speed" :min="0" :max="3" :step="0.1" v-model="speed" />
|
||||||
|
<PreviewSlider title="Attenuation" :min="1" :max="30" :step="0.5" v-model="attenuation" />
|
||||||
|
<PreviewSlider title="Line Thickness" :min="1" :max="10" :step="0.5" v-model="lineThickness" />
|
||||||
|
<PreviewSlider title="Base Radius" :min="0.1" :max="0.5" :step="0.01" v-model="baseRadius" />
|
||||||
|
<PreviewSlider title="Radius Step" :min="0.05" :max="0.3" :step="0.01" v-model="radiusStep" />
|
||||||
|
<PreviewSlider title="Scale Rate" :min="0" :max="0.2" :step="0.01" v-model="scaleRate" />
|
||||||
|
<PreviewSlider title="Opacity" :min="0" :max="1" :step="0.05" v-model="opacity" />
|
||||||
|
<PreviewSlider title="Blur" :min="0" :max="10" :step="0.5" v-model="blur" />
|
||||||
|
<PreviewSlider title="Noise Amount" :min="0" :max="0.5" :step="0.01" v-model="noiseAmount" />
|
||||||
|
<PreviewSlider title="Rotation" :min="0" :max="360" :step="1" v-model="rotation" />
|
||||||
|
<PreviewSlider title="Ring Gap" :min="1" :max="3" :step="0.1" v-model="ringGap" />
|
||||||
|
<PreviewSlider title="Fade In" :min="0.1" :max="1.5" :step="0.05" v-model="fadeIn" />
|
||||||
|
<PreviewSlider title="Fade Out" :min="0.5" :max="3" :step="0.05" v-model="fadeOut" />
|
||||||
|
<PreviewSlider title="Mouse Influence" :min="0" :max="1" :step="0.05" v-model="mouseInfluence" />
|
||||||
|
<PreviewSlider title="Hover Scale" :min="1" :max="2" :step="0.05" v-model="hoverScale" />
|
||||||
|
<PreviewSlider title="Parallax" :min="0" :max="0.1" :step="0.005" v-model="parallax" />
|
||||||
|
|
||||||
|
<PreviewSwitch title="Follow Mouse" v-model="followMouse" />
|
||||||
|
<PreviewSwitch title="Click Burst" v-model="clickBurst" />
|
||||||
|
</div>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="magicRings" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="magicRings.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSelect from '@/components/common/PreviewSelect.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import RefreshButton from '@/components/common/RefreshButton.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { useForceRerender } from '@/composables/useForceRerender';
|
||||||
|
import { magicRings } from '@/constants/code/Animations/magicRingsCode';
|
||||||
|
import MagicRings from '@/content/Animations/MagicRings/MagicRings.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const { rerenderKey: key, forceRerender } = useForceRerender();
|
||||||
|
|
||||||
|
const example = ref<'basic' | 'card'>('basic');
|
||||||
|
const color = ref('#7cff67');
|
||||||
|
const colorTwo = ref('#42fcff');
|
||||||
|
const ringCount = ref(6);
|
||||||
|
const speed = ref(1);
|
||||||
|
const attenuation = ref(10);
|
||||||
|
const lineThickness = ref(2);
|
||||||
|
const baseRadius = ref(0.35);
|
||||||
|
const radiusStep = ref(0.1);
|
||||||
|
const scaleRate = ref(0.1);
|
||||||
|
const opacity = ref(1);
|
||||||
|
const blur = ref(0);
|
||||||
|
const noiseAmount = ref(0.1);
|
||||||
|
const rotation = ref(0);
|
||||||
|
const ringGap = ref(1.5);
|
||||||
|
const fadeIn = ref(0.7);
|
||||||
|
const fadeOut = ref(0.5);
|
||||||
|
const followMouse = ref(false);
|
||||||
|
const mouseInfluence = ref(0.2);
|
||||||
|
const hoverScale = ref(1.2);
|
||||||
|
const parallax = ref(0.05);
|
||||||
|
const clickBurst = ref(false);
|
||||||
|
|
||||||
|
const exampleOptions = [
|
||||||
|
{ label: 'Basic', value: 'basic' },
|
||||||
|
{ label: 'Card', value: 'card' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{ name: 'color', type: 'string', default: '"#7cff67"', description: 'Hex color for the rings.' },
|
||||||
|
{
|
||||||
|
name: 'colorTwo',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#42fcff"',
|
||||||
|
description: 'Second color — rings interpolate from color to colorTwo.'
|
||||||
|
},
|
||||||
|
{ name: 'ringCount', type: 'number', default: '6', description: 'Number of concentric rings to draw (1-10).' },
|
||||||
|
{ name: 'speed', type: 'number', default: '1', description: 'Animation speed multiplier.' },
|
||||||
|
{
|
||||||
|
name: 'attenuation',
|
||||||
|
type: 'number',
|
||||||
|
default: '10',
|
||||||
|
description: 'Glow falloff — higher values produce tighter glow.'
|
||||||
|
},
|
||||||
|
{ name: 'lineThickness', type: 'number', default: '2', description: 'Thickness of each ring line.' },
|
||||||
|
{ name: 'baseRadius', type: 'number', default: '0.35', description: 'Radius of the innermost ring (normalized).' },
|
||||||
|
{ name: 'radiusStep', type: 'number', default: '0.1', description: 'Spacing between successive rings.' },
|
||||||
|
{ name: 'scaleRate', type: 'number', default: '0.1', description: 'How much rings expand over time.' },
|
||||||
|
{ name: 'opacity', type: 'number', default: '1', description: 'Overall opacity of the effect (0-1).' },
|
||||||
|
{ name: 'blur', type: 'number', default: '0', description: 'CSS blur in px — creates a bloom/glow effect.' },
|
||||||
|
{ name: 'noiseAmount', type: 'number', default: '0.1', description: 'Film-grain noise intensity.' },
|
||||||
|
{ name: 'rotation', type: 'number', default: '0', description: 'Static rotation of the pattern in degrees.' },
|
||||||
|
{ name: 'ringGap', type: 'number', default: '1.5', description: 'Exponential base for angular cutaway per ring.' },
|
||||||
|
{ name: 'fadeIn', type: 'number', default: '0.7', description: 'Duration of ring fade-in within cycle.' },
|
||||||
|
{ name: 'fadeOut', type: 'number', default: '0.5', description: 'Start time of ring fade-out within cycle.' },
|
||||||
|
{ name: 'followMouse', type: 'boolean', default: 'false', description: 'Rings shift toward the mouse cursor.' },
|
||||||
|
{
|
||||||
|
name: 'mouseInfluence',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.2',
|
||||||
|
description: 'Strength of mouse follow (when followMouse is true).'
|
||||||
|
},
|
||||||
|
{ name: 'hoverScale', type: 'number', default: '1.2', description: 'Scale multiplier on hover.' },
|
||||||
|
{ name: 'parallax', type: 'number', default: '0.05', description: 'Per-ring depth offset based on mouse position.' },
|
||||||
|
{
|
||||||
|
name: 'clickBurst',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Click triggers a brightness flash and scale pulse.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
@@ -124,6 +124,7 @@ import PreviewSwitch from '../../components/common/PreviewSwitch.vue';
|
|||||||
import PreviewSelect from '../../components/common/PreviewSelect.vue';
|
import PreviewSelect from '../../components/common/PreviewSelect.vue';
|
||||||
import RefreshButton from '../../components/common/RefreshButton.vue';
|
import RefreshButton from '../../components/common/RefreshButton.vue';
|
||||||
import OrbitImages from '../../content/Animations/OrbitImages/OrbitImages.vue';
|
import OrbitImages from '../../content/Animations/OrbitImages/OrbitImages.vue';
|
||||||
|
import type { OrbitShape } from '../../content/Animations/OrbitImages/OrbitImages.vue';
|
||||||
import { orbitImages } from '@/constants/code/Animations/orbitImagesCode';
|
import { orbitImages } from '@/constants/code/Animations/orbitImagesCode';
|
||||||
import { useForceRerender } from '@/composables/useForceRerender';
|
import { useForceRerender } from '@/composables/useForceRerender';
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ const images = [
|
|||||||
'https://picsum.photos/300/300?grayscale&random=6'
|
'https://picsum.photos/300/300?grayscale&random=6'
|
||||||
];
|
];
|
||||||
|
|
||||||
const shape = ref<string>('ellipse');
|
const shape = ref<OrbitShape>('ellipse');
|
||||||
const radiusX = ref(340);
|
const radiusX = ref(340);
|
||||||
const radiusY = ref(80);
|
const radiusY = ref(80);
|
||||||
const radius = ref(160);
|
const radius = ref(160);
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<EvilEye
|
||||||
|
:eye-color="eyeColor"
|
||||||
|
:intensity="intensity"
|
||||||
|
:pupil-size="pupilSize"
|
||||||
|
:iris-width="irisWidth"
|
||||||
|
:glow-intensity="glowIntensity"
|
||||||
|
:scale="scale"
|
||||||
|
:noise-scale="noiseScale"
|
||||||
|
:pupil-follow="pupilFollow"
|
||||||
|
:flame-speed="flameSpeed"
|
||||||
|
:background-color="backgroundColor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="flex items-center gap-4 mt-4">
|
||||||
|
<PreviewColor title="Eye Color" v-model="eyeColor" />
|
||||||
|
<PreviewColor title="Background" v-model="backgroundColor" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSlider title="Intensity" :min="0.5" :max="5" :step="0.1" v-model="intensity" />
|
||||||
|
<PreviewSlider title="Pupil Size" :min="0.1" :max="2" :step="0.05" v-model="pupilSize" />
|
||||||
|
<PreviewSlider title="Iris Width" :min="0.1" :max="0.8" :step="0.05" v-model="irisWidth" />
|
||||||
|
<PreviewSlider title="Glow Intensity" :min="0" :max="1.5" :step="0.05" v-model="glowIntensity" />
|
||||||
|
<PreviewSlider title="Scale" :min="0.2" :max="3" :step="0.1" v-model="scale" />
|
||||||
|
<PreviewSlider title="Noise Scale" :min="0.1" :max="3" :step="0.1" v-model="noiseScale" />
|
||||||
|
<PreviewSlider title="Pupil Follow" :min="0" :max="2" :step="0.1" v-model="pupilFollow" />
|
||||||
|
<PreviewSlider title="Flame Speed" :min="0.1" :max="5" :step="0.1" v-model="flameSpeed" />
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="evilEye" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="evilEye.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
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 PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { evilEye } from '@/constants/code/Backgrounds/evilEyeCode';
|
||||||
|
import EvilEye from '@/content/Backgrounds/EvilEye/EvilEye.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const eyeColor = ref('#FF6F37');
|
||||||
|
const intensity = ref(1.5);
|
||||||
|
const pupilSize = ref(0.6);
|
||||||
|
const irisWidth = ref(0.25);
|
||||||
|
const glowIntensity = ref(0.35);
|
||||||
|
const scale = ref(0.8);
|
||||||
|
const noiseScale = ref(1.0);
|
||||||
|
const pupilFollow = ref(1.0);
|
||||||
|
const flameSpeed = ref(1.0);
|
||||||
|
const backgroundColor = ref('#060010');
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'eyeColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#FF6F37"',
|
||||||
|
description: 'Primary eye color in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'intensity',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.5',
|
||||||
|
description: 'Brightness / HDR intensity of the eye color.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pupilSize',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.6',
|
||||||
|
description: 'Size and darkness of the pupil slit.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'irisWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.25',
|
||||||
|
description: 'Width of the main iris ring.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glowIntensity',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.35',
|
||||||
|
description: 'Strength of the outer eye glow.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scale',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.8',
|
||||||
|
description: 'Zoom level of the eye. Values > 1 zoom in, < 1 zoom out.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noiseScale',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Scale of the fire/noise texture sampling.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pupilFollow',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Intensity of pupil cursor tracking. 0 disables it.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'flameSpeed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Independent flame flicker animation speed.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backgroundColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#000000"',
|
||||||
|
description: 'Background color in HEX format.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<LineWaves
|
||||||
|
:speed="speed"
|
||||||
|
:inner-line-count="innerLineCount"
|
||||||
|
:outer-line-count="outerLineCount"
|
||||||
|
:warp-intensity="warpIntensity"
|
||||||
|
:rotation="rotation"
|
||||||
|
:edge-fade-width="edgeFadeWidth"
|
||||||
|
:color-cycle-speed="colorCycleSpeed"
|
||||||
|
:brightness="brightness"
|
||||||
|
:color1="color1"
|
||||||
|
:color2="color2"
|
||||||
|
:color3="color3"
|
||||||
|
:enable-mouse-interaction="enableMouseInteraction"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="Ride the waves of your creativity!" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="flex items-center gap-4 mt-4">
|
||||||
|
<PreviewColor title="Color 1" v-model="color1" />
|
||||||
|
<PreviewColor title="Color 2" v-model="color2" />
|
||||||
|
<PreviewColor title="Color 3" v-model="color3" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSlider title="Speed" :min="0.1" :max="3" :step="0.1" v-model="speed" />
|
||||||
|
<PreviewSlider title="Inner Line Count" :min="2" :max="40" :step="1" v-model="innerLineCount" />
|
||||||
|
<PreviewSlider title="Outer Line Count" :min="2" :max="40" :step="1" v-model="outerLineCount" />
|
||||||
|
<PreviewSlider title="Warp Intensity" :min="0.1" :max="3" :step="0.1" v-model="warpIntensity" />
|
||||||
|
<PreviewSlider title="Rotation" :min="-180" :max="180" :step="1" v-model="rotation" />
|
||||||
|
<PreviewSlider title="Edge Fade Width" :min="0.0" :max="1.0" :step="0.05" v-model="edgeFadeWidth" />
|
||||||
|
<PreviewSlider title="Color Cycle Speed" :min="0.1" :max="5" :step="0.1" v-model="colorCycleSpeed" />
|
||||||
|
<PreviewSlider title="Brightness" :min="0.1" :max="3" :step="0.1" v-model="brightness" />
|
||||||
|
<PreviewSwitch title="Mouse Interaction" v-model="enableMouseInteraction" />
|
||||||
|
<PreviewSlider
|
||||||
|
v-if="enableMouseInteraction"
|
||||||
|
title="Mouse Influence"
|
||||||
|
:min="0.1"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
v-model="mouseInfluence"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="lineWaves" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="lineWaves.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Dependencies from '@/components/code/Dependencies.vue';
|
||||||
|
import BackgroundContent from '@/components/common/BackgroundContent.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { lineWaves } from '@/constants/code/Backgrounds/lineWavesCode';
|
||||||
|
import LineWaves from '@/content/Backgrounds/LineWaves/LineWaves.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const speed = ref(0.3);
|
||||||
|
const innerLineCount = ref(32.0);
|
||||||
|
const outerLineCount = ref(36.0);
|
||||||
|
const warpIntensity = ref(1.0);
|
||||||
|
const rotation = ref(-45);
|
||||||
|
const edgeFadeWidth = ref(0.0);
|
||||||
|
const colorCycleSpeed = ref(1.0);
|
||||||
|
const brightness = ref(0.2);
|
||||||
|
const color1 = ref('#ffffff');
|
||||||
|
const color2 = ref('#ffffff');
|
||||||
|
const color3 = ref('#ffffff');
|
||||||
|
const enableMouseInteraction = ref(true);
|
||||||
|
const mouseInfluence = ref(2.0);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'speed',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.3',
|
||||||
|
description: 'Overall animation speed multiplier.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'innerLineCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '32.0',
|
||||||
|
description: 'Number of lines in the inner (center) wave region.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'outerLineCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '36.0',
|
||||||
|
description: 'Number of lines in the outer (edge) wave region.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'warpIntensity',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Intensity of the wave distortion effect.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rotation',
|
||||||
|
type: 'number',
|
||||||
|
default: '-45',
|
||||||
|
description: 'Rotation of the wave pattern in degrees.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edgeFadeWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.0',
|
||||||
|
description: 'Width of the edge fade between inner and outer regions.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'colorCycleSpeed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Speed of color cycling animation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'brightness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.2',
|
||||||
|
description: 'Overall brightness multiplier.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color1',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#ffffff"',
|
||||||
|
description: 'First color channel in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color2',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#ffffff"',
|
||||||
|
description: 'Second color channel in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color3',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#ffffff"',
|
||||||
|
description: 'Third color channel in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableMouseInteraction',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'true',
|
||||||
|
description: 'Enable cursor-reactive wave distortion.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mouseInfluence',
|
||||||
|
type: 'number',
|
||||||
|
default: '2.0',
|
||||||
|
description: 'Strength of mouse influence on the wave pattern.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<Radar
|
||||||
|
:speed="speed"
|
||||||
|
:scale="scale"
|
||||||
|
:ring-count="ringCount"
|
||||||
|
:spoke-count="spokeCount"
|
||||||
|
:ring-thickness="ringThickness"
|
||||||
|
:spoke-thickness="spokeThickness"
|
||||||
|
:sweep-speed="sweepSpeed"
|
||||||
|
:sweep-width="sweepWidth"
|
||||||
|
:sweep-lobes="sweepLobes"
|
||||||
|
:color="color"
|
||||||
|
:background-color="backgroundColor"
|
||||||
|
:falloff="falloff"
|
||||||
|
:brightness="brightness"
|
||||||
|
:enable-mouse-interaction="enableMouseInteraction"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="Scan the horizon of your imagination!" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="flex items-center gap-4 mt-4">
|
||||||
|
<PreviewColor title="Color" v-model="color" />
|
||||||
|
<PreviewColor title="Background" v-model="backgroundColor" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSlider title="Speed" :min="0.1" :max="5" :step="0.1" v-model="speed" />
|
||||||
|
<PreviewSlider title="Scale" :min="0.1" :max="3" :step="0.1" v-model="scale" />
|
||||||
|
<PreviewSlider title="Ring Count" :min="1" :max="30" :step="1" v-model="ringCount" />
|
||||||
|
<PreviewSlider title="Spoke Count" :min="1" :max="36" :step="1" v-model="spokeCount" />
|
||||||
|
<PreviewSlider title="Ring Thickness" :min="0.01" :max="0.3" :step="0.01" v-model="ringThickness" />
|
||||||
|
<PreviewSlider title="Spoke Thickness" :min="0.01" :max="0.2" :step="0.01" v-model="spokeThickness" />
|
||||||
|
<PreviewSlider title="Sweep Speed" :min="0.1" :max="5" :step="0.1" v-model="sweepSpeed" />
|
||||||
|
<PreviewSlider title="Sweep Width" :min="1" :max="20" :step="1" v-model="sweepWidth" />
|
||||||
|
<PreviewSlider title="Sweep Lobes" :min="1" :max="6" :step="1" v-model="sweepLobes" />
|
||||||
|
<PreviewSlider title="Falloff" :min="0.1" :max="3" :step="0.1" v-model="falloff" />
|
||||||
|
<PreviewSlider title="Brightness" :min="0.1" :max="3" :step="0.1" v-model="brightness" />
|
||||||
|
<PreviewSwitch title="Mouse Interaction" v-model="enableMouseInteraction" />
|
||||||
|
<PreviewSlider
|
||||||
|
v-if="enableMouseInteraction"
|
||||||
|
title="Mouse Influence"
|
||||||
|
:min="0.1"
|
||||||
|
:max="1"
|
||||||
|
:step="0.05"
|
||||||
|
v-model="mouseInfluence"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="radar" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="radar.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Dependencies from '@/components/code/Dependencies.vue';
|
||||||
|
import BackgroundContent from '@/components/common/BackgroundContent.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { radar } from '@/constants/code/Backgrounds/radarCode';
|
||||||
|
import Radar from '@/content/Backgrounds/Radar/Radar.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const speed = ref(1.0);
|
||||||
|
const scale = ref(0.5);
|
||||||
|
const ringCount = ref(10.0);
|
||||||
|
const spokeCount = ref(10.0);
|
||||||
|
const ringThickness = ref(0.05);
|
||||||
|
const spokeThickness = ref(0.01);
|
||||||
|
const sweepSpeed = ref(1.0);
|
||||||
|
const sweepWidth = ref(2.0);
|
||||||
|
const sweepLobes = ref(1.0);
|
||||||
|
const color = ref('#27FF64');
|
||||||
|
const backgroundColor = ref('#000000');
|
||||||
|
const falloff = ref(2.0);
|
||||||
|
const brightness = ref(1.0);
|
||||||
|
const enableMouseInteraction = ref(true);
|
||||||
|
const mouseInfluence = ref(0.1);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'speed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Overall animation speed multiplier.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scale',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'Zoom level of the radar pattern.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ringCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '10.0',
|
||||||
|
description: 'Number of concentric rings.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spokeCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '10.0',
|
||||||
|
description: 'Number of radial spoke lines.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ringThickness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.05',
|
||||||
|
description: 'Thickness of the concentric ring lines.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spokeThickness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.01',
|
||||||
|
description: 'Thickness of the radial spoke lines.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sweepSpeed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Rotation speed of the sweep beam.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sweepWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '2.0',
|
||||||
|
description: 'Width of the sweep trail (higher = thinner).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sweepLobes',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Number of sweep beams around the radar.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#27FF64"',
|
||||||
|
description: 'Primary radar color in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backgroundColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#000000"',
|
||||||
|
description: 'Background color in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'falloff',
|
||||||
|
type: 'number',
|
||||||
|
default: '2.0',
|
||||||
|
description: 'Edge fade intensity based on distance from center.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'brightness',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Overall brightness multiplier.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableMouseInteraction',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'true',
|
||||||
|
description: 'Enable cursor-reactive center offset.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mouseInfluence',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.1',
|
||||||
|
description: 'Strength of the mouse offset effect.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<SoftAurora
|
||||||
|
:speed="speed"
|
||||||
|
:scale="scale"
|
||||||
|
:brightness="brightness"
|
||||||
|
:color1="color1"
|
||||||
|
:color2="color2"
|
||||||
|
:noise-frequency="noiseFrequency"
|
||||||
|
:noise-amplitude="noiseAmplitude"
|
||||||
|
:band-height="bandHeight"
|
||||||
|
:band-spread="bandSpread"
|
||||||
|
:octave-decay="octaveDecay"
|
||||||
|
:layer-offset="layerOffset"
|
||||||
|
:color-speed="colorSpeed"
|
||||||
|
:enable-mouse-interaction="enableMouseInteraction"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="A gentle glow to light your way!" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="flex items-center gap-4 mt-4">
|
||||||
|
<PreviewColor title="Color 1" v-model="color1" />
|
||||||
|
<PreviewColor title="Color 2" v-model="color2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSlider title="Speed" :min="0.1" :max="5" :step="0.1" v-model="speed" />
|
||||||
|
<PreviewSlider title="Scale" :min="0.1" :max="3" :step="0.1" v-model="scale" />
|
||||||
|
<PreviewSlider title="Brightness" :min="0.1" :max="3" :step="0.1" v-model="brightness" />
|
||||||
|
<PreviewSlider title="Noise Frequency" :min="0.5" :max="10" :step="0.5" v-model="noiseFrequency" />
|
||||||
|
<PreviewSlider title="Noise Amplitude" :min="0.5" :max="10" :step="0.5" v-model="noiseAmplitude" />
|
||||||
|
<PreviewSlider title="Band Height" :min="0" :max="1" :step="0.05" v-model="bandHeight" />
|
||||||
|
<PreviewSlider title="Band Spread" :min="0.1" :max="3" :step="0.1" v-model="bandSpread" />
|
||||||
|
<PreviewSlider title="Octave Decay" :min="0.01" :max="0.5" :step="0.01" v-model="octaveDecay" />
|
||||||
|
<PreviewSlider title="Layer Offset" :min="0" :max="1" :step="0.05" v-model="layerOffset" />
|
||||||
|
<PreviewSlider title="Color Speed" :min="0.1" :max="5" :step="0.1" v-model="colorSpeed" />
|
||||||
|
<PreviewSwitch title="Mouse Interaction" v-model="enableMouseInteraction" />
|
||||||
|
<PreviewSlider
|
||||||
|
v-if="enableMouseInteraction"
|
||||||
|
title="Mouse Influence"
|
||||||
|
:min="0.1"
|
||||||
|
:max="1"
|
||||||
|
:step="0.05"
|
||||||
|
v-model="mouseInfluence"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="softAurora" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="softAurora.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Dependencies from '@/components/code/Dependencies.vue';
|
||||||
|
import BackgroundContent from '@/components/common/BackgroundContent.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { softAurora } from '@/constants/code/Backgrounds/softAuroraCode';
|
||||||
|
import SoftAurora from '@/content/Backgrounds/SoftAurora/SoftAurora.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const speed = ref(0.6);
|
||||||
|
const scale = ref(1.5);
|
||||||
|
const brightness = ref(1.0);
|
||||||
|
const color1 = ref('#f7f7f7');
|
||||||
|
const color2 = ref('#27FF64');
|
||||||
|
const noiseFrequency = ref(2.5);
|
||||||
|
const noiseAmplitude = ref(1.0);
|
||||||
|
const bandHeight = ref(0.5);
|
||||||
|
const bandSpread = ref(1.0);
|
||||||
|
const octaveDecay = ref(0.1);
|
||||||
|
const layerOffset = ref(0);
|
||||||
|
const colorSpeed = ref(1.0);
|
||||||
|
const enableMouseInteraction = ref(true);
|
||||||
|
const mouseInfluence = ref(0.25);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{ name: 'speed', type: 'number', default: '0.6', description: 'Overall animation speed multiplier.' },
|
||||||
|
{ name: 'scale', type: 'number', default: '1.5', description: 'Scale of the noise pattern.' },
|
||||||
|
{ name: 'brightness', type: 'number', default: '1.0', description: 'Overall brightness multiplier.' },
|
||||||
|
{ name: 'color1', type: 'string', default: '"#f7f7f7"', description: 'Tint color for the first aurora layer.' },
|
||||||
|
{ name: 'color2', type: 'string', default: '"#27FF64"', description: 'Tint color for the second aurora layer.' },
|
||||||
|
{ name: 'noiseFrequency', type: 'number', default: '2.5', description: 'Base frequency of the Perlin noise.' },
|
||||||
|
{ name: 'noiseAmplitude', type: 'number', default: '1.0', description: 'Base amplitude of the Perlin noise.' },
|
||||||
|
{ name: 'bandHeight', type: 'number', default: '0.5', description: 'Vertical position of the aurora band (0-1).' },
|
||||||
|
{ name: 'bandSpread', type: 'number', default: '1.0', description: 'Vertical spread of the aurora glow.' },
|
||||||
|
{ name: 'octaveDecay', type: 'number', default: '0.1', description: 'Amplitude decay per noise octave.' },
|
||||||
|
{ name: 'layerOffset', type: 'number', default: '0', description: 'Time offset between the two aurora layers.' },
|
||||||
|
{ name: 'colorSpeed', type: 'number', default: '1.0', description: 'Speed of palette color shifting.' },
|
||||||
|
{
|
||||||
|
name: 'enableMouseInteraction',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'true',
|
||||||
|
description: 'Enable cursor-reactive aurora offset.'
|
||||||
|
},
|
||||||
|
{ name: 'mouseInfluence', type: 'number', default: '0.25', description: 'Strength of the mouse offset effect.' }
|
||||||
|
];
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="h-[500px] overflow-hidden demo-container">
|
||||||
|
<BorderGlow v-bind="props">
|
||||||
|
<div class="flex flex-col justify-center items-start p-[2em] min-h-[200px]">
|
||||||
|
<i class="pi pi-sparkles" style="font-size: 34px; margin-bottom: 12px"></i>
|
||||||
|
<p class="font-semibold text-[1.4rem] tracking-[-0.5px]">Hover Near the Edges</p>
|
||||||
|
<p class="mt-1 max-w-[40ch] text-[#a1a1aa] text-[14px]">
|
||||||
|
Move your cursor close to the card border to see the colored glow effect follow your pointer direction.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</BorderGlow>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewSlider title="Edge Sensitivity" :min="0" :max="80" :step="1" v-model="edgeSensitivity" />
|
||||||
|
<PreviewSlider title="Border Radius" :min="0" :max="50" :step="1" v-model="borderRadius" />
|
||||||
|
<PreviewSlider title="Glow Radius" :min="10" :max="80" :step="1" v-model="glowRadius" />
|
||||||
|
<PreviewSlider title="Glow Intensity" :min="0.1" :max="3" :step="0.1" v-model="glowIntensity" />
|
||||||
|
<PreviewSlider title="Cone Spread" :min="5" :max="45" :step="1" v-model="coneSpread" />
|
||||||
|
<PreviewSwitch title="Animated Intro" v-model="animated" />
|
||||||
|
<PreviewColor title="Background" v-model="backgroundColor" class="mb-2" />
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-0">
|
||||||
|
<span class="block font-medium text-sm">Gradient Colors</span>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2 px-1 pt-1">
|
||||||
|
<label
|
||||||
|
v-for="(color, index) in colors"
|
||||||
|
:key="index"
|
||||||
|
class="border-[#222] border-2 rounded-md w-12 h-12 overflow-hidden cursor-pointer"
|
||||||
|
:style="{ backgroundColor: color }"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
:value="color"
|
||||||
|
@input="updateColor(index, ($event.target as HTMLInputElement).value)"
|
||||||
|
class="opacity-0 w-full h-full cursor-pointer"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="borderGlow" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="borderGlow.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { borderGlow } from '@/constants/code/Components/borderGlowCode';
|
||||||
|
import BorderGlow from '@/content/Components/BorderGlow/BorderGlow.vue';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const colors = ref(['#c084fc', '#f472b6', '#38bdf8']);
|
||||||
|
const edgeSensitivity = ref(30);
|
||||||
|
const glowColor = ref('40 80 80');
|
||||||
|
const backgroundColor = ref('#070F07');
|
||||||
|
const borderRadius = ref(28);
|
||||||
|
const glowRadius = ref(40);
|
||||||
|
const glowIntensity = ref(1.0);
|
||||||
|
const coneSpread = ref(25);
|
||||||
|
const animated = ref(false);
|
||||||
|
|
||||||
|
const props = computed(() => ({
|
||||||
|
edgeSensitivity: edgeSensitivity.value,
|
||||||
|
glowColor: glowColor.value,
|
||||||
|
backgroundColor: backgroundColor.value,
|
||||||
|
borderRadius: borderRadius.value,
|
||||||
|
glowRadius: glowRadius.value,
|
||||||
|
glowIntensity: glowIntensity.value,
|
||||||
|
coneSpread: coneSpread.value,
|
||||||
|
animated: animated.value,
|
||||||
|
colors: colors.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
const updateColor = (index: number, newColor: string) => {
|
||||||
|
const newColors = [...colors.value];
|
||||||
|
newColors[index] = newColor;
|
||||||
|
colors.value = newColors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{ name: 'children', type: 'slot', default: '-', description: 'Content rendered inside the card.' },
|
||||||
|
{ name: 'className', type: 'string', default: '""', description: 'Additional CSS classes for the outer wrapper.' },
|
||||||
|
{
|
||||||
|
name: 'edgeSensitivity',
|
||||||
|
type: 'number',
|
||||||
|
default: '30',
|
||||||
|
description: 'How close the pointer must be to the edge for the glow to appear (0-100).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glowColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"40 80 80"',
|
||||||
|
description: 'HSL values for the glow color, as "H S L" (e.g. "40 80 80").'
|
||||||
|
},
|
||||||
|
{ name: 'backgroundColor', type: 'string', default: '"#070F07"', description: 'Background color of the card.' },
|
||||||
|
{ name: 'borderRadius', type: 'number', default: '28', description: 'Corner radius of the card in pixels.' },
|
||||||
|
{
|
||||||
|
name: 'glowRadius',
|
||||||
|
type: 'number',
|
||||||
|
default: '40',
|
||||||
|
description: 'How far the outer glow extends beyond the card in pixels.'
|
||||||
|
},
|
||||||
|
{ name: 'glowIntensity', type: 'number', default: '1.0', description: 'Multiplier for glow opacity (0.1-3.0).' },
|
||||||
|
{
|
||||||
|
name: 'coneSpread',
|
||||||
|
type: 'number',
|
||||||
|
default: '25',
|
||||||
|
description: 'Width of the directional cone mask as a percentage (5-45).'
|
||||||
|
},
|
||||||
|
{ name: 'animated', type: 'boolean', default: 'false', description: 'Play an intro sweep animation on mount.' },
|
||||||
|
{
|
||||||
|
name: 'colors',
|
||||||
|
type: 'string[]',
|
||||||
|
default: '[...]',
|
||||||
|
description: 'Array of 3 hex colors for the mesh gradient border, distributed across positions.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user