Files
2026-10-03 20:07:49 +02:00

111 lines
2.7 KiB
Vue

<script setup lang="ts">
import { hexToRgbTuple } from '@/lib/color'
// Portiert aus Vue Bits (DavidHDev/vue-bits), Plane+Camera auf Fullscreen-Triangle
// vereinfacht, nur color-Prop
const props = withDefaults(defineProps<{ color?: string }>(), {
color: '#6366f1',
})
const VERT = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`
const FRAG = `
precision highp float;
varying vec2 vUv;
uniform float uTime;
uniform vec3 uColor;
uniform float uSpeed;
uniform float uScale;
uniform float uRotation;
uniform float uNoiseIntensity;
uniform vec2 uCardRes;
uniform float uRadius;
const float e = 2.71828182845904523536;
float noise(vec2 texCoord) {
float G = e;
vec2 r = (G * sin(G * texCoord));
return fract(r.x * r.y * (1.0 + texCoord.x));
}
vec2 rotateUvs(vec2 uv, float angle) {
float c = cos(angle);
float s = sin(angle);
mat2 rot = mat2(c, -s, s, c);
return rot * uv;
}
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() {
float rnd = noise(gl_FragCoord.xy);
// Pixelkonsistent: Muster-Koordinaten auf eine Referenzgröße von 180px
// normieren, damit flache Container den Effekt nicht vertikal stauchen
vec2 suv = vUv * (uCardRes / 180.0);
vec2 uv = rotateUvs(suv * uScale, uRotation);
vec2 tex = uv * uScale;
float tOffset = uSpeed * uTime;
tex.y += 0.03 * sin(8.0 * tex.x - tOffset);
float pattern = 0.6 +
0.4 * sin(5.0 * (tex.x + tex.y +
cos(3.0 * tex.x + 5.0 * tex.y) +
0.02 * tOffset) +
sin(20.0 * (tex.x + tex.y - 0.1 * tOffset)));
vec4 col = vec4(uColor, 1.0) * vec4(pattern) - rnd / 15.0 * uNoiseIntensity;
float m = cardMask(gl_FragCoord.xy);
float a = clamp(pattern * 0.45, 0.0, 1.0) * m;
gl_FragColor = vec4(col.rgb * a, a);
}
`
const ctnDom = ref<HTMLDivElement | null>(null)
let elapsed = 0
let lastT = 0
useCardShader(ctnDom, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uColor: { value: new Float32Array(hexToRgbTuple(props.color)) },
uSpeed: { value: 5 },
uScale: { value: 1 },
uNoiseIntensity: { value: 1.5 },
uRotation: { value: 0 },
},
color: () => props.color,
colorUniforms: { uColor: 0 },
frame: ({ t, program }) => {
const delta = Math.min((t - lastT) * 0.001, 0.1)
lastT = t
elapsed += 0.1 * delta
program.uniforms.uTime.value = elapsed
},
})
</script>
<template>
<div ref="ctnDom" class="relative size-full overflow-hidden rounded-[inherit]" />
</template>