forked from BRETT/BRETT
134 lines
3.5 KiB
Vue
134 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import { hexToRgbTuple } from '@/lib/color'
|
|
|
|
// Portiert aus Vue Bits (DavidHDev/vue-bits), vereinfacht auf color-Prop
|
|
const props = withDefaults(defineProps<{ color?: string }>(), {
|
|
color: '#6366f1',
|
|
})
|
|
|
|
const VERT = `#version 300 es
|
|
precision highp float;
|
|
in vec2 position;
|
|
void main() {
|
|
gl_Position = vec4(position, 0.0, 1.0);
|
|
}
|
|
`
|
|
|
|
const FRAG = `#version 300 es
|
|
precision highp float;
|
|
uniform float iTime;
|
|
uniform vec3 uCustomColor;
|
|
uniform float uSpeed;
|
|
uniform float uScale;
|
|
uniform float uOpacity;
|
|
uniform vec2 uMouse;
|
|
uniform float uMouseActive;
|
|
uniform float uIterations;
|
|
uniform float uStepScale;
|
|
uniform vec2 uCardRes;
|
|
uniform float uRadius;
|
|
out vec4 fragColor;
|
|
|
|
void mainImage(out vec4 o, vec2 C) {
|
|
// Pixelkonsistent: Koordinatenrahmen auf Referenzhöhe 180px normieren —
|
|
// r.y ist die Brennweite und würde in flachen Containern die
|
|
// Perspektive extrem verzerren
|
|
float pxScale = 180.0 / uCardRes.y;
|
|
vec2 r = uCardRes * pxScale;
|
|
C *= pxScale;
|
|
vec2 center = r * 0.5;
|
|
C = (C - center) / uScale + center;
|
|
|
|
vec2 mouseOffset = (uMouse * pxScale - center) * 0.0002;
|
|
C += mouseOffset * length(C - center) * uMouseActive;
|
|
|
|
float i, d, z, T = iTime * uSpeed;
|
|
vec3 O, p, S;
|
|
|
|
for (vec2 Q; ++i < 60.; O += o.w/d*o.xyz) {
|
|
p = z*normalize(vec3(C-.5*r,r.y));
|
|
p.z -= 4.;
|
|
S = p;
|
|
d = p.y-T;
|
|
|
|
p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);
|
|
Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));
|
|
z+= d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4)*uStepScale;
|
|
o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));
|
|
if (i >= uIterations) break;
|
|
}
|
|
|
|
o.xyz = tanh(O/1e4);
|
|
}
|
|
|
|
bool finite1(float x){ return !(isnan(x) || isinf(x)); }
|
|
vec3 sanitize(vec3 c){
|
|
return vec3(
|
|
finite1(c.r) ? c.r : 0.0,
|
|
finite1(c.g) ? c.g : 0.0,
|
|
finite1(c.b) ? c.b : 0.0
|
|
);
|
|
}
|
|
|
|
float cardMask(vec2 fc) {
|
|
vec2 p = fc - uCardRes * 0.5;
|
|
vec2 hb = uCardRes * 0.5 - vec2(uRadius);
|
|
float rd = length(max(abs(p) - hb, 0.0)) - uRadius;
|
|
return 1.0 - smoothstep(-0.75, 0.75, rd);
|
|
}
|
|
|
|
void main() {
|
|
vec4 o = vec4(0.0);
|
|
mainImage(o, gl_FragCoord.xy);
|
|
vec3 rgb = sanitize(o.rgb);
|
|
|
|
float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;
|
|
vec3 finalColor = intensity * uCustomColor;
|
|
|
|
float a = length(rgb) * uOpacity * cardMask(gl_FragCoord.xy);
|
|
fragColor = vec4(finalColor * a, a);
|
|
}`
|
|
|
|
const ctnDom = ref<HTMLDivElement | null>(null)
|
|
|
|
let elapsed = 0
|
|
let lastT = 0
|
|
|
|
useCardShader(ctnDom, {
|
|
vertex: VERT,
|
|
fragment: FRAG,
|
|
mouse: true,
|
|
renderScale: 0.55,
|
|
uniforms: {
|
|
iTime: { value: 0 },
|
|
uCustomColor: { value: new Float32Array(hexToRgbTuple(props.color)) },
|
|
uSpeed: { value: 0.4 },
|
|
uScale: { value: 1 },
|
|
uOpacity: { value: 1 },
|
|
uMouse: { value: new Float32Array([0, 0]) },
|
|
uMouseActive: { value: 0 },
|
|
uIterations: { value: 48 },
|
|
uStepScale: { value: 60 / 48 },
|
|
},
|
|
color: () => props.color,
|
|
colorUniforms: { uCustomColor: 0 },
|
|
frame: ({ t, program, mouse }) => {
|
|
const u = program.uniforms
|
|
// Zeit akkumulieren, damit Pausen (Tab-Wechsel) nicht springen
|
|
const delta = Math.min((t - lastT) * 0.001, 0.1)
|
|
lastT = t
|
|
elapsed += delta
|
|
u.iTime.value = elapsed
|
|
const res = u.uCardRes.value as Float32Array
|
|
const m = u.uMouse.value as Float32Array
|
|
m[0] = mouse.x * res[0]
|
|
m[1] = (1 - mouse.y) * res[1]
|
|
u.uMouseActive.value = mouse.active
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="ctnDom" class="relative size-full overflow-hidden rounded-[inherit]" />
|
|
</template>
|