mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-04-21 09:34:39 -06:00
Merge pull request #150 from Utkarsh-Singhal-26/feat/new-components
🎉 5 New Components: LineWaves, EvilEye, BorderGlow, Radar, SoftAurora
This commit is contained in:
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
+21
-12
@@ -1,10 +1,14 @@
|
||||
// Highlighted sidebar items
|
||||
export const NEW = [
|
||||
'Color Bends',
|
||||
'Evil Eye',
|
||||
'Border Glow',
|
||||
'Soft Aurora',
|
||||
'Radar',
|
||||
'Line Waves',
|
||||
'Antigravity',
|
||||
'Grainient',
|
||||
'Orbit Images',
|
||||
'Magic Rings',
|
||||
'Magic Rings'
|
||||
];
|
||||
export const UPDATED = ['Metallic Paint'];
|
||||
|
||||
@@ -12,7 +16,7 @@ export const UPDATED = ['Metallic Paint'];
|
||||
export const CATEGORIES = [
|
||||
{
|
||||
name: 'Get Started',
|
||||
subcategories: [ 'Index' ],
|
||||
subcategories: ['Index']
|
||||
},
|
||||
{
|
||||
name: 'Text Animations',
|
||||
@@ -40,8 +44,8 @@ export const CATEGORIES = [
|
||||
'Text Trail',
|
||||
'Text Type',
|
||||
'True Focus',
|
||||
'Variable Proximity',
|
||||
],
|
||||
'Variable Proximity'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Animations',
|
||||
@@ -74,13 +78,14 @@ export const CATEGORIES = [
|
||||
'Star Border',
|
||||
'Sticker Peel',
|
||||
'Target Cursor',
|
||||
'Orbit Images',
|
||||
],
|
||||
'Orbit Images'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Components',
|
||||
subcategories: [
|
||||
'Animated List',
|
||||
'Border Glow',
|
||||
'Bounce Cards',
|
||||
'Bubble Menu',
|
||||
'Card Nav',
|
||||
@@ -112,8 +117,8 @@ export const CATEGORIES = [
|
||||
'Stack',
|
||||
'Staggered Menu',
|
||||
'Stepper',
|
||||
'Tilted Card',
|
||||
],
|
||||
'Tilted Card'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Backgrounds',
|
||||
@@ -126,6 +131,7 @@ export const CATEGORIES = [
|
||||
'Dark Veil',
|
||||
'Dither',
|
||||
'Dot Grid',
|
||||
'Evil Eye',
|
||||
'Faulty Terminal',
|
||||
'Floating Lines',
|
||||
'Galaxy',
|
||||
@@ -140,6 +146,7 @@ export const CATEGORIES = [
|
||||
'Light Pillar',
|
||||
'Light Rays',
|
||||
'Lightning',
|
||||
'Line Waves',
|
||||
'Liquid Chrome',
|
||||
'Liquid Ether',
|
||||
'Orb',
|
||||
@@ -149,11 +156,13 @@ export const CATEGORIES = [
|
||||
'Plasma',
|
||||
'Prism',
|
||||
'Prismatic Burst',
|
||||
'Radar',
|
||||
'Ripple Grid',
|
||||
'Silk',
|
||||
'Soft Aurora',
|
||||
'Squares',
|
||||
'Threads',
|
||||
'Waves',
|
||||
],
|
||||
},
|
||||
'Waves'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -91,6 +91,7 @@ const components = {
|
||||
'bubble-menu': () => import('../demo/Components/BubbleMenuDemo.vue'),
|
||||
'staggered-menu': () => import('../demo/Components/StaggeredMenuDemo.vue'),
|
||||
'infinite-menu': () => import('../demo/Components/InfiniteMenuDemo.vue'),
|
||||
'border-glow': () => import('../demo/Components/BorderGlowDemo.vue'),
|
||||
};
|
||||
|
||||
const backgrounds = {
|
||||
@@ -130,6 +131,10 @@ const backgrounds = {
|
||||
'pixel-snow': () => import('../demo/Backgrounds/PixelSnowDemo.vue'),
|
||||
'grid-scan': () => import('../demo/Backgrounds/GridScanDemo.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 = {
|
||||
|
||||
@@ -675,6 +675,14 @@ export const componentMetadata: ComponentMetadata = {
|
||||
docsUrl: 'https://vue-bits.dev/components/staggered-menu',
|
||||
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/Aurora': {
|
||||
@@ -956,5 +964,37 @@ export const componentMetadata: ComponentMetadata = {
|
||||
name: 'PixelSnow',
|
||||
docsUrl: 'https://vue-bits.dev/backgrounds/pixel-snow',
|
||||
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,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,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>
|
||||
@@ -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>
|
||||
@@ -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