feat: added <EvilEye /> background

This commit is contained in:
Utkarsh-Singhal-26
2026-03-22 12:56:17 +05:30
parent 7a66154264
commit 7baf50277c
4 changed files with 475 additions and 11 deletions
@@ -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>`
});
+303
View File
@@ -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>
+135
View File
@@ -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>
+11 -11
View File
@@ -30,14 +30,14 @@
<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="1" :max="30" :step="1" v-model="brightness" />
<PreviewSlider title="Noise Frequency" :min="1" :max="36" :step="1" v-model="noiseFrequency" />
<PreviewSlider title="Noise Amplitude" :min="0.01" :max="0.3" :step="0.01" v-model="noiseAmplitude" />
<PreviewSlider title="Band Height" :min="0.01" :max="0.2" :step="0.01" v-model="bandHeight" />
<PreviewSlider title="Band Spread" :min="0.1" :max="5" :step="0.1" v-model="bandSpread" />
<PreviewSlider title="Octave Decay" :min="1" :max="20" :step="1" v-model="octaveDecay" />
<PreviewSlider title="Layer Offset" :min="1" :max="6" :step="1" v-model="layerOffset" />
<PreviewSlider title="Color Speed" :min="0.1" :max="3" :step="0.1" v-model="colorSpeed" />
<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"
@@ -54,11 +54,11 @@
</template>
<template #code>
<CodeExample :code-object="radar" />
<CodeExample :code-object="softAurora" />
</template>
<template #cli>
<CliInstallation :command="radar.cli" />
<CliInstallation :command="softAurora.cli" />
</template>
</TabbedLayout>
</template>
@@ -74,7 +74,7 @@ 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 { softAurora } from '@/constants/code/Backgrounds/softAuroraCode';
import SoftAurora from '@/content/Backgrounds/SoftAurora/SoftAurora.vue';
import { ref } from 'vue';