|
1 | 1 | "use client"; |
2 | 2 |
|
3 | 3 | import Link from "next/link"; |
4 | | -import { useEffect, useState } from "react"; |
| 4 | +import { type CSSProperties, useEffect, useState } from "react"; |
5 | 5 |
|
6 | 6 | import AnimatedBorderTrail from "@/animata/container/animated-border-trail"; |
7 | 7 | import Marquee from "@/animata/container/marquee"; |
@@ -87,52 +87,42 @@ function OrbitItem({ |
87 | 87 | duration: number; |
88 | 88 | children: React.ReactNode; |
89 | 89 | }) { |
90 | | - const startAngle = Math.round((index / total) * 360); |
91 | | - const [angle, setAngle] = useState(startAngle); |
92 | | - const [mounted, setMounted] = useState(false); |
| 90 | + const delay = (duration / total) * index * -1; |
93 | 91 |
|
94 | | - useEffect(() => { |
95 | | - setMounted(true); |
96 | | - const id = setInterval(() => setAngle((a) => (a + 1) % 360), duration); |
97 | | - return () => clearInterval(id); |
98 | | - }, [duration]); |
99 | | - |
100 | | - const rad = (angle * Math.PI) / 180; |
101 | | - const x = 35 * Math.cos(rad); |
102 | | - const y = 15 * Math.sin(rad); |
103 | | - const tilt = (-30 * Math.PI) / 180; |
104 | | - const xT = x * Math.cos(tilt) - y * Math.sin(tilt); |
105 | | - const yT = x * Math.sin(tilt) + y * Math.cos(tilt); |
106 | | - const depth = (Math.sin(rad) + 1) / 2; |
107 | | - |
108 | | - // Round to 2 decimals to prevent hydration mismatch |
109 | | - const left = Math.round((50 + xT) * 100) / 100; |
110 | | - const top = Math.round((50 + yT) * 100) / 100; |
111 | | - const scale = Math.round((0.7 + depth * 0.5) * 100) / 100; |
112 | | - const op = Math.round((0.35 + depth * 0.65) * 100) / 100; |
| 92 | + const orbitContainerStyle: CSSProperties = { |
| 93 | + top: "50%", |
| 94 | + left: "50%", |
| 95 | + animation: ` |
| 96 | + orbit-move ${duration}s linear infinite, |
| 97 | + orbit-indexes ${duration}s linear infinite |
| 98 | + `, |
| 99 | + animationDelay: `${delay}s, ${delay}s`, |
| 100 | + transformStyle: "preserve-3d", |
| 101 | + }; |
| 102 | + const itemCorrectionStyle: CSSProperties = { |
| 103 | + animation: `counter-rotate ${duration}s linear infinite`, |
| 104 | + animationDelay: `${delay}s`, |
| 105 | + backfaceVisibility: "hidden", |
| 106 | + }; |
113 | 107 |
|
114 | 108 | return ( |
115 | | - <div |
116 | | - className="absolute flex h-8 w-8 items-center justify-center rounded-full border border-border bg-[hsl(var(--surface-card))] sm:h-10 sm:w-10" |
117 | | - style={{ |
118 | | - left: `${left}%`, |
119 | | - top: `${top}%`, |
120 | | - transform: `translate(-50%, -50%) scale(${scale})`, |
121 | | - zIndex: Math.round(depth * 10), |
122 | | - opacity: op, |
123 | | - }} |
124 | | - > |
125 | | - {children} |
| 109 | + <div className="absolute" style={orbitContainerStyle}> |
| 110 | + <div |
| 111 | + className="flex h-8 w-8 items-center justify-center rounded-full border border-border bg-[hsl(var(--surface-card))] sm:h-10 sm:w-10" |
| 112 | + style={itemCorrectionStyle} |
| 113 | + > |
| 114 | + {children} |
| 115 | + </div> |
126 | 116 | </div> |
127 | 117 | ); |
128 | 118 | } |
129 | 119 |
|
130 | 120 | function FrameworkOrbit() { |
131 | 121 | return ( |
132 | 122 | <div className="relative flex h-40 w-full items-center justify-center overflow-hidden rounded-xl border border-border bg-[hsl(var(--surface-alt))] sm:h-48"> |
133 | | - <Icons.logo className="z-10 h-8 w-8 rounded-full bg-linear-to-br from-violet-500 to-indigo-600 p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" /> |
| 123 | + <Icons.logo className="relative z-10 h-8 w-8 rounded-full bg-linear-to-br from-violet-500 to-indigo-600 p-1.5 shadow-lg sm:h-10 sm:w-10 sm:p-2" /> |
134 | 124 | {frameworkItems.map((item, i) => ( |
135 | | - <OrbitItem key={item.key} index={i} total={frameworkItems.length} duration={35}> |
| 125 | + <OrbitItem key={item.key} index={i} total={frameworkItems.length} duration={12}> |
136 | 126 | {item.icon} |
137 | 127 | </OrbitItem> |
138 | 128 | ))} |
|
0 commit comments