diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index ab4000a..bce058e 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -2,6 +2,7 @@ import { Link } from "react-router-dom"; import { Button } from "@/components/ui/button"; import { ArrowRight } from "lucide-react"; import { useState, useEffect } from "react"; +import Silk from "@/components/Silk"; const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026 @@ -35,10 +36,18 @@ const Hero = () => { }, []); return ( -
- {/* Subtle grid lines */} -
- +
+ {/* Silk animated background */} +
+ +
+
{/* Label */} diff --git a/src/components/Silk.tsx b/src/components/Silk.tsx new file mode 100644 index 0000000..470c388 --- /dev/null +++ b/src/components/Silk.tsx @@ -0,0 +1,158 @@ +/* eslint-disable react/no-unknown-property */ +import { Canvas, useFrame, useThree } from "@react-three/fiber"; +import { forwardRef, useRef, useMemo, useLayoutEffect } from "react"; +import { Color, type Mesh, type ShaderMaterial } from "three"; + +const hexToNormalizedRGB = (hex: string): [number, number, number] => { + hex = hex.replace("#", ""); + return [ + parseInt(hex.slice(0, 2), 16) / 255, + parseInt(hex.slice(2, 4), 16) / 255, + parseInt(hex.slice(4, 6), 16) / 255, + ]; +}; + +const vertexShader = ` +varying vec2 vUv; +varying vec3 vPosition; + +void main() { + vPosition = position; + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +} +`; + +const fragmentShader = ` +varying vec2 vUv; +varying vec3 vPosition; + +uniform float uTime; +uniform vec3 uColor; +uniform float uSpeed; +uniform float uScale; +uniform float uRotation; +uniform float uNoiseIntensity; + +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; +} + +void main() { + float rnd = noise(gl_FragCoord.xy); + vec2 uv = rotateUvs(vUv * 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; + col.a = 1.0; + gl_FragColor = col; +} +`; + +type SilkPlaneProps = { + uniforms: { + uSpeed: { value: number }; + uScale: { value: number }; + uNoiseIntensity: { value: number }; + uColor: { value: Color }; + uRotation: { value: number }; + uTime: { value: number }; + }; +}; + +const SilkPlane = forwardRef(function SilkPlane( + { uniforms }, + ref +) { + const { viewport } = useThree(); + + useLayoutEffect(() => { + if (ref && typeof ref !== "function" && ref.current) { + ref.current.scale.set(viewport.width, viewport.height, 1); + } + }, [ref, viewport]); + + useFrame((_, delta) => { + if (ref && typeof ref !== "function" && ref.current) { + const mat = ref.current.material as ShaderMaterial & { + uniforms: { uTime: { value: number } }; + }; + if (mat.uniforms?.uTime) mat.uniforms.uTime.value += 0.1 * delta; + } + }); + + return ( + + + + + ); +}); +SilkPlane.displayName = "SilkPlane"; + +type SilkProps = { + speed?: number; + scale?: number; + color?: string; + noiseIntensity?: number; + rotation?: number; +}; + +const Silk = ({ + speed = 5, + scale = 1, + color = "#7B7481", + noiseIntensity = 1.5, + rotation = 0, +}: SilkProps) => { + const meshRef = useRef(null); + + const uniforms = useMemo( + () => ({ + uSpeed: { value: speed }, + uScale: { value: scale }, + uNoiseIntensity: { value: noiseIntensity }, + uColor: { value: new Color(...hexToNormalizedRGB(color)) }, + uRotation: { value: rotation }, + uTime: { value: 0 }, + }), + [speed, scale, noiseIntensity, color, rotation] + ); + + return ( + + + + ); +}; + +export default Silk;