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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
41e9f6898f
commit
30164e0ae7
@@ -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<Array<SVGCircleElement | null>>([]);
|
||||
const lineRefs = useRef<Array<SVGLineElement | null>>([]);
|
||||
|
||||
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 (
|
||||
<svg
|
||||
className="pointer-events-none absolute inset-0 h-full w-full opacity-20"
|
||||
viewBox="0 0 400 400"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g stroke="white" strokeWidth="1.2">
|
||||
<path d="M80 300 L150 210 L240 250 L310 150 L250 80 L150 210" />
|
||||
<path d="M80 300 L160 340 L240 250" />
|
||||
<path d="M310 150 L360 240 L240 250" />
|
||||
<g stroke="white" strokeWidth="1.2" strokeLinecap="round">
|
||||
{NETWORK_EDGES.map(([from, to], index) => (
|
||||
<line
|
||||
key={`${from}-${to}`}
|
||||
ref={(element) => {
|
||||
lineRefs.current[index] = element;
|
||||
}}
|
||||
x1={NETWORK_NODES[from].x}
|
||||
y1={NETWORK_NODES[from].y}
|
||||
x2={NETWORK_NODES[to].x}
|
||||
y2={NETWORK_NODES[to].y}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
<g fill="white">
|
||||
<circle cx="80" cy="300" r="9" />
|
||||
<circle cx="150" cy="210" r="13" />
|
||||
<circle cx="240" cy="250" r="10" />
|
||||
<circle cx="310" cy="150" r="12" />
|
||||
<circle cx="250" cy="80" r="7" />
|
||||
<circle cx="160" cy="340" r="6" />
|
||||
<circle cx="360" cy="240" r="8" />
|
||||
{NETWORK_NODES.map((node, index) => (
|
||||
<circle
|
||||
key={`${node.x}-${node.y}`}
|
||||
ref={(element) => {
|
||||
circleRefs.current[index] = element;
|
||||
}}
|
||||
cx={node.x}
|
||||
cy={node.y}
|
||||
r={node.r}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export const LoginScreen: React.FC = () => {
|
||||
const { login } = useAuth();
|
||||
|
||||
Reference in New Issue
Block a user