Schreiben Sie mir kurz, worum es geht. Ich melde mich innerhalb von 24 Stunden mit einer ersten Einschätzung.
diff --git a/src/components/Landing/FlowingPath.tsx b/src/components/Landing/FlowingPath.tsx
index 51080e0..4a39ca1 100644
--- a/src/components/Landing/FlowingPath.tsx
+++ b/src/components/Landing/FlowingPath.tsx
@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
-import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
+import { useEffect, useRef, useState, useCallback } from 'react';
import { motion, useScroll, useTransform, useSpring, useAnimationFrame } from 'framer-motion';
interface FlowingPathProps {
@@ -36,26 +36,42 @@ export const FlowingPath: React.FC = ({ className = '' }) => {
scrollYProgress,
(latest) => {
if (dimensions.height === 0 || vh === 0) return latest;
+
+ // Total scrollable distance
const scrollRange = dimensions.height - vh;
const currentScrollY = latest * scrollRange;
- const targetY = currentScrollY + vh / 2;
+
+ // We want the dot to be at y = currentScrollY + vh/2
+ // But at the very top (latest=0), we want it at y=0 (or above)
+ // At the very bottom (latest=1), we want it at y=dimensions.height
+
+ let targetY;
+ if (latest < 0.05) {
+ // Transition from top of page to center of viewport
+ targetY = (latest / 0.05) * (vh / 2);
+ } else if (latest > 0.95) {
+ // Transition from center of viewport to bottom of page
+ const p = (latest - 0.95) / 0.05;
+ targetY = (currentScrollY + vh / 2) * (1 - p) + dimensions.height * p;
+ } else {
+ targetY = currentScrollY + vh / 2;
+ }
+
return Math.max(0, Math.min(1, targetY / dimensions.height));
}
);
// Smooth spring animation for the path drawing
const smoothProgress = useSpring(centeredProgress, {
- stiffness: 40,
- damping: 25,
+ stiffness: 100, // Much higher stiffness for immediate response
+ damping: 40, // Higher damping to prevent any overshoot/backward motion
restDelta: 0.0001,
+ mass: 0.5, // Lower mass for faster acceleration
});
// Transform scroll progress to path drawing
const pathDrawn = useTransform(smoothProgress, [0, 1], [0, 1]);
- // Accent path transform
- const accentPathDrawn = useTransform(smoothProgress, [0, 1], [0, 0.98]);
-
// Dot transforms
const dot1Distance = useTransform(smoothProgress, [0, 1], ['0%', '100%']);
const dot2Distance = useTransform(smoothProgress, [0, 1], ['0%', '98%']);
@@ -105,8 +121,9 @@ export const FlowingPath: React.FC = ({ className = '' }) => {
const curr = nodes[i];
if (curr.type === 'curve') {
- const cp1y = prev.y + (curr.y - prev.y) * 0.5;
- const cp2y = prev.y + (curr.y - prev.y) * 0.5;
+ const dy = curr.y - prev.y;
+ const cp1y = prev.y + dy * 0.4;
+ const cp2y = prev.y + dy * 0.6;
path += ` C ${prev.x} ${cp1y}, ${curr.x} ${cp2y}, ${curr.x} ${curr.y}`;
} else {
path += ` L ${curr.x} ${curr.y}`;
@@ -116,30 +133,45 @@ export const FlowingPath: React.FC = ({ className = '' }) => {
return path;
}, []);
- // Generate secondary decorative paths
- const generateSecondaryPath = useCallback((width: number, height: number, offset: number): string => {
- const margin = width < 768 ? 10 : 40 + offset;
- const contentWidth = Math.min(width * 0.85, 1000);
+ // Generate secondary decorative paths that follow the main path with some divergence
+ const generateSecondaryPath = useCallback((width: number, height: number, seed: number): string => {
+ const margin = width < 768 ? 20 : 80;
+ const contentWidth = Math.min(width * 0.9, 1200);
const leftEdge = (width - contentWidth) / 2 - margin;
const rightEdge = (width + contentWidth) / 2 + margin;
+ const centerX = width / 2;
const sectionHeight = height / 8;
- let path = `M ${width / 2 + offset} ${-100}`;
-
- const points = [
- { x: leftEdge + offset, y: sectionHeight * 1.2 },
- { x: rightEdge - offset, y: sectionHeight * 2.5 },
- { x: leftEdge - offset, y: sectionHeight * 4.0 },
- { x: rightEdge + offset, y: sectionHeight * 5.5 },
- { x: width / 2 + offset, y: height + 100 },
+ const random = (i: number) => {
+ const x = Math.sin(seed + i) * 10000;
+ return x - Math.floor(x);
+ };
+
+ // Base nodes similar to generatePath but with randomized offsets
+ const nodes = [
+ { x: centerX - 100 + (random(0) - 0.5) * 40, y: 0 },
+ { x: centerX - 100 + (random(1) - 0.5) * 40, y: sectionHeight * 0.2 },
+ { x: rightEdge + (random(2) - 0.5) * 60, y: sectionHeight * 0.8 },
+ { x: rightEdge - 50 + (random(3) - 0.5) * 60, y: sectionHeight * 1.5 },
+ { x: leftEdge + 50 + (random(4) - 0.5) * 60, y: sectionHeight * 2.2 },
+ { x: leftEdge + (random(5) - 0.5) * 60, y: sectionHeight * 3.0 },
+ { x: centerX + 150 + (random(6) - 0.5) * 80, y: sectionHeight * 3.8 },
+ { x: centerX - 150 + (random(7) - 0.5) * 80, y: sectionHeight * 4.6 },
+ { x: rightEdge - 20 + (random(8) - 0.5) * 60, y: sectionHeight * 5.4 },
+ { x: rightEdge - 100 + (random(9) - 0.5) * 60, y: sectionHeight * 6.2 },
+ { x: leftEdge + 100 + (random(10) - 0.5) * 60, y: sectionHeight * 7.0 },
+ { x: centerX + (random(11) - 0.5) * 40, y: sectionHeight * 7.6 },
+ { x: centerX + (random(12) - 0.5) * 40, y: height }
];
- for (let i = 0; i < points.length; i++) {
- const curr = points[i];
- const prev = i > 0 ? points[i - 1] : { x: width / 2 + offset, y: -100 };
- const cp1y = prev.y + (curr.y - prev.y) * 0.5;
- const cp2y = prev.y + (curr.y - prev.y) * 0.5;
+ let path = `M ${nodes[0].x} ${nodes[0].y}`;
+ for (let i = 1; i < nodes.length; i++) {
+ const prev = nodes[i - 1];
+ const curr = nodes[i];
+ const dy = curr.y - prev.y;
+ const cp1y = prev.y + dy * 0.4;
+ const cp2y = prev.y + dy * 0.6;
path += ` C ${prev.x} ${cp1y}, ${curr.x} ${cp2y}, ${curr.x} ${curr.y}`;
}
@@ -196,13 +228,13 @@ export const FlowingPath: React.FC = ({ className = '' }) => {
@@ -219,35 +251,36 @@ export const FlowingPath: React.FC
= ({ className = '' }) => {
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
{/* Background decorative paths - static, very subtle */}
= ({ className = '' }) => {
ref={pathRef}
d={pathData}
stroke="url(#lineGradient)"
- strokeWidth="1.5"
+ strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
@@ -268,45 +301,97 @@ export const FlowingPath: React.FC = ({ className = '' }) => {
}}
/>
- {/* Accent path that follows slightly behind */}
-
- {/* Particle System */}
-
+
+ {/* Extra static dashed lines with unique random paths */}
+
+
+
+
+ {/* Pulse System */}
+
{/* Animated dot that travels along the path */}
{/* Secondary traveling dot */}
{/* Node markers at key positions */}
@@ -385,76 +470,62 @@ const NodeMarker: React.FC = ({ pos, pathData, progress }) => {
);
};
-interface Particle {
+interface Pulse {
id: number;
x: number;
y: number;
- vx: number;
- vy: number;
life: number;
- size: number;
}
-const ParticleSystem: React.FC<{
+const PulseSystem: React.FC<{
pathRef: React.RefObject;
progress: any;
pathLength: number;
-}> = ({ pathRef, progress, pathLength }) => {
- const [particles, setParticles] = useState([]);
- const lastPos = useRef({ x: 0, y: 0 });
- const particleId = useRef(0);
+ checkpoints: number[];
+}> = ({ pathRef, progress, pathLength, checkpoints }) => {
+ const [pulses, setPulses] = useState([]);
+ const lastProgress = useRef(0);
+ const pulseId = useRef(0);
useAnimationFrame((time, delta) => {
if (!pathRef.current || pathLength === 0) return;
- // Get current position of the dot
const currentProgress = progress.get();
const point = pathRef.current.getPointAtLength(currentProgress * pathLength);
- // Update and emit particles in one go
- setParticles(prev => {
- const updated = prev
- .map(p => ({
- ...p,
- x: p.x + p.vx,
- y: p.y + p.vy,
- life: p.life - 0.02,
- }))
- .filter(p => p.life > 0);
-
- const dist = Math.hypot(point.x - lastPos.current.x, point.y - lastPos.current.y);
- if (dist > 1) {
- const newParticles: Particle[] = [];
- const count = Math.min(3, Math.floor(dist / 2));
-
- for (let i = 0; i < count; i++) {
- newParticles.push({
- id: particleId.current++,
- x: point.x,
- y: point.y,
- vx: (Math.random() - 0.5) * 2,
- vy: (Math.random() - 0.5) * 2,
- life: 1.0,
- size: Math.random() * 3 + 1,
- });
- }
- lastPos.current = { x: point.x, y: point.y };
- return [...updated, ...newParticles].slice(-60);
- }
+ // Check for checkpoint pulses
+ checkpoints.forEach(cp => {
+ const crossed = (lastProgress.current < cp && currentProgress >= cp) ||
+ (lastProgress.current > cp && currentProgress <= cp);
- return updated;
+ if (crossed) {
+ const checkpointPoint = pathRef.current!.getPointAtLength(cp * pathLength);
+ setPulses(prev => [...prev, {
+ id: pulseId.current++,
+ x: checkpointPoint.x,
+ y: checkpointPoint.y,
+ life: 1.0,
+ }].slice(-8));
+ }
});
+ lastProgress.current = currentProgress;
+
+ // Update pulses
+ setPulses(prev => prev.map(p => ({ ...p, life: p.life - 0.015 })).filter(p => p.life > 0));
});
return (
- {particles.map(p => (
+ {pulses.map(p => (
))}