From 30164e0ae7780bb845bd72fb9fead53cce96ef4e Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Tue, 11 Aug 2026 22:14:04 +0200 Subject: [PATCH] feat(login): Netzwerk-Muster driftet langsam Knoten laufen auf eigenen Lissajous-Bahnen, Linien folgen den Knoten. Positionen werden per requestAnimationFrame direkt am DOM gesetzt, damit React nicht pro Frame neu rendert. Bei prefers-reduced-motion bleibt das Muster statisch. Co-Authored-By: Claude Opus 5 (1M context) --- src/components/LoginScreen.tsx | 130 +++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 24 deletions(-) diff --git a/src/components/LoginScreen.tsx b/src/components/LoginScreen.tsx index 6da42a7..ad78ef9 100644 --- a/src/components/LoginScreen.tsx +++ b/src/components/LoginScreen.tsx @@ -1,30 +1,112 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useAuth } from '../lib/auth'; +/** + * Knoten des dekorativen Netzwerks. Jeder Knoten driftet auf einer eigenen + * Lissajous-Bahn, damit sich das Muster nie exakt wiederholt. + */ +const NETWORK_NODES = [ + { x: 80, y: 300, r: 9, ax: 10, ay: 7, sx: 0.055, sy: 0.041, px: 0.0, py: 1.9 }, + { x: 150, y: 210, r: 13, ax: 8, ay: 11, sx: 0.037, sy: 0.049, px: 1.2, py: 0.4 }, + { x: 240, y: 250, r: 10, ax: 12, ay: 8, sx: 0.045, sy: 0.033, px: 2.7, py: 3.3 }, + { x: 310, y: 150, r: 12, ax: 9, ay: 12, sx: 0.031, sy: 0.052, px: 4.1, py: 1.1 }, + { x: 250, y: 80, r: 7, ax: 13, ay: 9, sx: 0.048, sy: 0.036, px: 5.4, py: 2.6 }, + { x: 160, y: 340, r: 6, ax: 11, ay: 10, sx: 0.042, sy: 0.057, px: 0.8, py: 4.8 }, + { x: 360, y: 240, r: 8, ax: 7, ay: 13, sx: 0.058, sy: 0.029, px: 3.5, py: 0.2 }, +]; + +/** Verbindungen zwischen den Knoten (Indizes in NETWORK_NODES). */ +const NETWORK_EDGES: Array<[number, number]> = [ + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 1], + [0, 5], + [5, 2], + [3, 6], + [6, 2], +]; + /** Dekoratives Netzwerk-Muster - greift das Knoten-Motiv des perinco Logos auf. */ -const NetworkPattern: React.FC = () => ( - -); +const NetworkPattern: React.FC = () => { + const circleRefs = useRef>([]); + const lineRefs = useRef>([]); + + useEffect(() => { + const reduced = window.matchMedia('(prefers-reduced-motion: reduce)'); + if (reduced.matches) return; + + let frame = 0; + const start = performance.now(); + + const tick = (now: number) => { + const t = (now - start) / 1000; + const positions = NETWORK_NODES.map((node) => ({ + x: node.x + Math.sin(t * node.sx * Math.PI * 2 + node.px) * node.ax, + y: node.y + Math.cos(t * node.sy * Math.PI * 2 + node.py) * node.ay, + })); + + positions.forEach((pos, index) => { + const circle = circleRefs.current[index]; + if (!circle) return; + circle.setAttribute('cx', pos.x.toFixed(2)); + circle.setAttribute('cy', pos.y.toFixed(2)); + }); + + NETWORK_EDGES.forEach(([from, to], index) => { + const line = lineRefs.current[index]; + if (!line) return; + line.setAttribute('x1', positions[from].x.toFixed(2)); + line.setAttribute('y1', positions[from].y.toFixed(2)); + line.setAttribute('x2', positions[to].x.toFixed(2)); + line.setAttribute('y2', positions[to].y.toFixed(2)); + }); + + frame = requestAnimationFrame(tick); + }; + + frame = requestAnimationFrame(tick); + return () => cancelAnimationFrame(frame); + }, []); + + return ( + + ); +}; export const LoginScreen: React.FC = () => { const { login } = useAuth();