design
This commit is contained in:
14
app/page.tsx
14
app/page.tsx
@@ -26,7 +26,7 @@ export default function LandingPage() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 md:gap-16 items-center">
|
||||
{/* Left Column: Brand & Number */}
|
||||
<div className="md:col-span-5 relative z-10">
|
||||
<div className="md:col-span-5 relative z-10 bg-white/50 backdrop-blur-[2px]">
|
||||
<Reveal>
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-3 text-slate-400 font-mono text-[10px] uppercase tracking-[0.3em]">
|
||||
@@ -59,7 +59,7 @@ export default function LandingPage() {
|
||||
</div>
|
||||
|
||||
<Reveal delay={0.2}>
|
||||
<div className="relative bg-white/80 backdrop-blur-sm p-8 md:p-12 border border-slate-100 rounded-2xl shadow-2xl shadow-slate-100/50 max-w-md mx-auto">
|
||||
<div className="relative bg-white/90 backdrop-blur-md p-8 md:p-12 border border-slate-100 rounded-2xl shadow-2xl shadow-slate-100/50 max-w-md mx-auto z-10">
|
||||
<div className="absolute -top-6 -left-6 w-12 h-12 bg-slate-900 text-white flex items-center justify-center rounded-full font-bold text-xl">
|
||||
01
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@ export default function LandingPage() {
|
||||
</h3>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 relative z-10">
|
||||
<Reveal delay={0.1}>
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-4 text-slate-900 font-bold text-lg">
|
||||
@@ -157,7 +157,7 @@ export default function LandingPage() {
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 relative z-10">
|
||||
<div className="grid grid-cols-1 gap-6 relative z-20">
|
||||
<ComparisonRow
|
||||
negativeLabel="Agentur"
|
||||
negativeText="Konzeptcalls, Meetings, Slides, Warten auf das Angebot."
|
||||
@@ -190,7 +190,7 @@ export default function LandingPage() {
|
||||
<div className="absolute left-0 top-0 w-full h-full -z-10 opacity-30 pointer-events-none">
|
||||
<CirclePattern />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 relative z-10">
|
||||
<Reveal>
|
||||
<div className="p-10 bg-slate-900 text-white rounded-3xl h-full flex flex-col justify-between group hover:scale-[1.02] transition-transform duration-500 shadow-2xl shadow-slate-900/20">
|
||||
<div className="space-y-6">
|
||||
@@ -235,7 +235,7 @@ export default function LandingPage() {
|
||||
<ServicesFlow />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 relative z-10">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 relative z-20">
|
||||
<Reveal delay={0.1}>
|
||||
<div className="bg-white p-8 rounded-2xl border border-slate-100 shadow-lg hover:shadow-xl transition-all duration-300 group h-full">
|
||||
<div className="w-16 h-16 bg-slate-50 rounded-2xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-500">
|
||||
@@ -291,7 +291,7 @@ export default function LandingPage() {
|
||||
<span className="text-slate-300">starten.</span>
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-12 items-start">
|
||||
<div className="flex flex-col md:flex-row gap-12 items-start relative z-10">
|
||||
<div className="space-y-6 flex-1">
|
||||
<p className="text-xl text-slate-600 font-serif italic">
|
||||
Schreiben Sie mir kurz, worum es geht. Ich melde mich innerhalb von 24 Stunden mit einer ersten Einschätzung.
|
||||
|
||||
@@ -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<FlowingPathProps> = ({ 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<FlowingPathProps> = ({ 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<FlowingPathProps> = ({ 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<FlowingPathProps> = ({ className = '' }) => {
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`absolute inset-0 pointer-events-none overflow-visible ${className}`}
|
||||
style={{
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: dimensions.height,
|
||||
zIndex: 5,
|
||||
zIndex: 1,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -219,35 +251,36 @@ export const FlowingPath: React.FC<FlowingPathProps> = ({ className = '' }) => {
|
||||
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over" />
|
||||
</filter>
|
||||
<filter id="dotGlow" x="-100%" y="-100%" width="300%" height="300%">
|
||||
<feGaussianBlur stdDeviation="3" result="blur" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.58 0 0 0 0 0.64 0 0 0 0 0.72 0 0 0 1 0" />
|
||||
<filter id="dotGlow" x="-200%" y="-200%" width="500%" height="500%">
|
||||
<feGaussianBlur stdDeviation="5" result="blur" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.2 0 0 0 0 0.2 0 0 0 0 0.2 0 0 0 1 0" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="rgba(148, 163, 184, 0.05)" />
|
||||
<stop offset="20%" stopColor="rgba(148, 163, 184, 0.3)" />
|
||||
<stop offset="50%" stopColor="rgba(148, 163, 184, 0.6)" />
|
||||
<stop offset="80%" stopColor="rgba(148, 163, 184, 0.3)" />
|
||||
<stop offset="100%" stopColor="rgba(148, 163, 184, 0.05)" />
|
||||
<stop offset="0%" stopColor="rgba(0, 0, 0, 0.05)" />
|
||||
<stop offset="20%" stopColor="rgba(0, 0, 0, 0.2)" />
|
||||
<stop offset="50%" stopColor="rgba(0, 0, 0, 0.4)" />
|
||||
<stop offset="80%" stopColor="rgba(0, 0, 0, 0.2)" />
|
||||
<stop offset="100%" stopColor="rgba(0, 0, 0, 0.05)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{/* Background decorative paths - static, very subtle */}
|
||||
<path
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, 40)}
|
||||
stroke="rgba(148, 163, 184, 0.15)"
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, 10)}
|
||||
stroke="rgba(0, 0, 0, 0.1)"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray="5,10"
|
||||
/>
|
||||
<path
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, -30)}
|
||||
stroke="rgba(148, 163, 184, 0.1)"
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, 20)}
|
||||
stroke="rgba(0, 0, 0, 0.08)"
|
||||
strokeWidth="0.5"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
@@ -258,7 +291,7 @@ export const FlowingPath: React.FC<FlowingPathProps> = ({ 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<FlowingPathProps> = ({ className = '' }) => {
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Accent path that follows slightly behind */}
|
||||
<motion.path
|
||||
{/* Accent path - fully filled, static */}
|
||||
<path
|
||||
d={pathData}
|
||||
stroke="rgba(203, 213, 225, 0.2)"
|
||||
strokeWidth="3"
|
||||
stroke="rgba(0, 0, 0, 0.05)"
|
||||
strokeWidth="4"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
style={{
|
||||
pathLength: accentPathDrawn,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Particle System */}
|
||||
<ParticleSystem
|
||||
{/* Secondary accent path - fully filled, static */}
|
||||
<path
|
||||
d={pathData}
|
||||
stroke="rgba(0, 0, 0, 0.03)"
|
||||
strokeWidth="8"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
transform="translate(5, 5)"
|
||||
/>
|
||||
|
||||
{/* Extra static dashed lines with unique random paths */}
|
||||
<path
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, 123)}
|
||||
stroke="rgba(0, 0, 0, 0.15)"
|
||||
strokeWidth="1.5"
|
||||
fill="none"
|
||||
strokeDasharray="10,20"
|
||||
/>
|
||||
<path
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, 456)}
|
||||
stroke="rgba(0, 0, 0, 0.12)"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
strokeDasharray="5,15"
|
||||
/>
|
||||
<path
|
||||
d={generateSecondaryPath(dimensions.width, dimensions.height, 789)}
|
||||
stroke="rgba(0, 0, 0, 0.1)"
|
||||
strokeWidth="0.8"
|
||||
fill="none"
|
||||
strokeDasharray="2,10"
|
||||
/>
|
||||
|
||||
{/* Pulse System */}
|
||||
<PulseSystem
|
||||
pathRef={pathRef}
|
||||
progress={smoothProgress}
|
||||
pathLength={pathLength}
|
||||
checkpoints={[0.12, 0.28, 0.45, 0.62, 0.82]}
|
||||
/>
|
||||
|
||||
{/* Animated dot that travels along the path */}
|
||||
<motion.circle
|
||||
r="4"
|
||||
fill="rgba(30, 41, 59, 1)"
|
||||
r="6"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="1.5"
|
||||
filter="url(#dotGlow)"
|
||||
style={{
|
||||
offsetPath: `path("${pathData}")`,
|
||||
offsetDistance: dot1Distance,
|
||||
}}
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.8, 1, 0.8],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Secondary traveling dot */}
|
||||
<motion.circle
|
||||
r="2"
|
||||
fill="rgba(148, 163, 184, 0.6)"
|
||||
r="3"
|
||||
fill="rgba(0, 0, 0, 0.4)"
|
||||
style={{
|
||||
offsetPath: `path("${pathData}")`,
|
||||
offsetDistance: dot2Distance,
|
||||
}}
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
translateX: [0, 5, -5, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Node markers at key positions */}
|
||||
@@ -385,76 +470,62 @@ const NodeMarker: React.FC<NodeMarkerProps> = ({ 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<SVGPathElement>;
|
||||
progress: any;
|
||||
pathLength: number;
|
||||
}> = ({ pathRef, progress, pathLength }) => {
|
||||
const [particles, setParticles] = useState<Particle[]>([]);
|
||||
const lastPos = useRef({ x: 0, y: 0 });
|
||||
const particleId = useRef(0);
|
||||
checkpoints: number[];
|
||||
}> = ({ pathRef, progress, pathLength, checkpoints }) => {
|
||||
const [pulses, setPulses] = useState<Pulse[]>([]);
|
||||
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 (
|
||||
<g>
|
||||
{particles.map(p => (
|
||||
{pulses.map(p => (
|
||||
<circle
|
||||
key={p.id}
|
||||
cx={p.x}
|
||||
cy={p.y}
|
||||
r={p.size}
|
||||
fill={`rgba(148, 163, 184, ${p.life * 0.5})`}
|
||||
r={(1 - p.life) * 150}
|
||||
fill="none"
|
||||
stroke="rgba(0, 0, 0, 0.15)"
|
||||
strokeWidth={p.life * 3}
|
||||
style={{ opacity: p.life }}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
|
||||
Reference in New Issue
Block a user