@@ -27,34 +27,56 @@ export function AnimeNavBar({
27
27
} : NavBarProps ) {
28
28
const router = useRouter ( )
29
29
const pathname = usePathname ( )
30
-
30
+
31
+ // State Management
31
32
const [ mounted , setMounted ] = useState ( false )
32
33
const [ hoveredTab , setHoveredTab ] = useState < string | null > ( null )
33
34
const [ activeTab , setActiveTab ] = useState < string > ( defaultActive )
35
+ const [ isMobile , setIsMobile ] = useState ( false )
34
36
37
+ // Mounted Effect
35
38
useEffect ( ( ) => {
36
39
setMounted ( true )
40
+ const handleResize = ( ) => setIsMobile ( window . innerWidth < 768 )
41
+ handleResize ( )
42
+ window . addEventListener ( "resize" , handleResize )
43
+ return ( ) => window . removeEventListener ( "resize" , handleResize )
37
44
} , [ ] )
38
45
46
+ // Pathname Change Effect
39
47
useEffect ( ( ) => {
40
48
const currentItem = items . find ( item => item . url === pathname )
41
49
if ( currentItem ) {
42
50
setActiveTab ( currentItem . name )
43
51
}
44
52
} , [ pathname , items ] )
45
53
54
+ // Prevent rendering before client-side mount
46
55
if ( ! mounted ) return null
47
56
57
+ // Navigation Handler
48
58
const handleNavigation = ( item : NavItem ) => {
49
59
setActiveTab ( item . name )
50
60
router . push ( item . url )
51
61
}
52
62
53
63
return (
54
- < div className = { cn ( "fixed top-5 left-0 right-0 z-[9999]" , className ) } >
55
- < div className = "flex justify-center pt-6" >
64
+ < div
65
+ className = { cn (
66
+ // Lower on mobile for mascot space, tighter on desktop
67
+ "fixed left-0 right-0 z-[9999]" ,
68
+ isMobile ? "top-3" : "top-5" ,
69
+ className
70
+ ) }
71
+ >
72
+ < div className = { cn ( "flex justify-center" , isMobile ? "pt-2" : "pt-6" ) } >
56
73
< motion . div
57
- className = "flex items-center gap-2 bg-black/50 border border-white/10 backdrop-blur-lg py-2 px-2 rounded-full shadow-lg relative overflow-x-auto scrollbar-thin scrollbar-thumb-gray-700 scrollbar-track-transparent max-w-full"
74
+ className = { cn (
75
+ "flex items-center bg-black/50 border border-white/10 backdrop-blur-lg rounded-full shadow-lg relative" ,
76
+ isMobile
77
+ ? "gap-1 py-1 px-1 max-w-[95vw] overflow-x-auto"
78
+ : "gap-3 py-2 px-2"
79
+ ) }
58
80
initial = { { y : - 20 , opacity : 0 } }
59
81
animate = { { y : 0 , opacity : 1 } }
60
82
transition = { {
@@ -75,7 +97,10 @@ export function AnimeNavBar({
75
97
onMouseEnter = { ( ) => setHoveredTab ( item . name ) }
76
98
onMouseLeave = { ( ) => setHoveredTab ( null ) }
77
99
className = { cn (
78
- "relative cursor-pointer text-xs sm:text-sm font-semibold px-3 sm:px-6 py-2 sm:py-3 rounded-full transition-all duration-300 whitespace-nowrap" ,
100
+ "relative cursor-pointer font-semibold rounded-full transition-all duration-300 whitespace-nowrap select-none" ,
101
+ isMobile
102
+ ? "text-base px-3 py-2"
103
+ : "text-sm px-6 py-3" ,
79
104
"text-white/70 hover:text-white" ,
80
105
isActive && "text-white"
81
106
) }
@@ -101,17 +126,32 @@ export function AnimeNavBar({
101
126
< div className = "absolute inset-[-12px] bg-primary/5 rounded-full blur-3xl" />
102
127
< div
103
128
className = "absolute inset-0 bg-gradient-to-r from-primary/0 via-primary/20 to-primary/0"
104
- style = { { animation : "shine 3s ease-in-out infinite" } }
129
+ style = { {
130
+ animation : "shine 3s ease-in-out infinite"
131
+ } }
105
132
/>
106
133
</ motion . div >
107
134
) }
108
135
109
- { /* Icon and Text always visible */ }
110
- < span className = "flex items-center gap-1 relative z-10" >
111
- < Icon size = { 18 } strokeWidth = { 2.5 } />
112
- < span > { item . name } </ span >
113
- </ span >
136
+ { /* Desktop Text */ }
137
+ < motion . span
138
+ className = "hidden md:inline relative z-10"
139
+ initial = { { opacity : 0 } }
140
+ animate = { { opacity : 1 } }
141
+ transition = { { duration : 0.2 } }
142
+ >
143
+ { item . name }
144
+ </ motion . span >
114
145
146
+ { /* Mobile Icon */ }
147
+ < motion . span
148
+ className = "md:hidden relative z-10"
149
+ whileHover = { { scale : 1.2 } }
150
+ whileTap = { { scale : 0.9 } }
151
+ >
152
+ < Icon size = { 22 } strokeWidth = { 2.5 } />
153
+ </ motion . span >
154
+
115
155
{ /* Hover Effect */ }
116
156
< AnimatePresence >
117
157
{ isHovered && ! isActive && (
@@ -128,22 +168,34 @@ export function AnimeNavBar({
128
168
{ isActive && (
129
169
< motion . div
130
170
layoutId = "anime-mascot"
131
- className = "absolute -top-12 left-1/2 -translate-x-1/2 pointer-events-none"
171
+ className = { cn (
172
+ "absolute left-1/2 -translate-x-1/2 pointer-events-none z-[10000]" ,
173
+ isMobile ? "-top-4" : "-top-12"
174
+ ) }
132
175
initial = { false }
133
176
transition = { {
134
177
type : "spring" ,
135
178
stiffness : 300 ,
136
179
damping : 30 ,
137
180
} }
138
181
>
139
- < div className = "relative w-12 h-12" >
182
+ < div className = { cn (
183
+ "relative" ,
184
+ isMobile ? "w-8 h-8" : "w-12 h-12"
185
+ ) } >
140
186
< motion . div
141
- className = "absolute w-10 h-10 bg-white rounded-full left-1/2 -translate-x-1/2"
187
+ className = { cn (
188
+ "absolute bg-white rounded-full left-1/2 -translate-x-1/2" ,
189
+ isMobile ? "w-7 h-7" : "w-10 h-10"
190
+ ) }
142
191
animate = {
143
192
hoveredTab ? {
144
193
scale : [ 1 , 1.1 , 1 ] ,
145
194
rotate : [ 0 , - 5 , 5 , 0 ] ,
146
- transition : { duration : 0.5 , ease : "easeInOut" }
195
+ transition : {
196
+ duration : 0.5 ,
197
+ ease : "easeInOut"
198
+ }
147
199
} : {
148
200
y : [ 0 , - 3 , 0 ] ,
149
201
transition : {
@@ -154,9 +206,12 @@ export function AnimeNavBar({
154
206
}
155
207
}
156
208
>
157
- { /* Eyes */ }
209
+ { /* Mascot Eyes */ }
158
210
< motion . div
159
- className = "absolute w-2 h-2 bg-black rounded-full"
211
+ className = { cn (
212
+ "absolute bg-black rounded-full" ,
213
+ isMobile ? "w-1 h-1" : "w-2 h-2"
214
+ ) }
160
215
animate = {
161
216
hoveredTab ? {
162
217
scaleY : [ 1 , 0.2 , 1 ] ,
@@ -166,10 +221,13 @@ export function AnimeNavBar({
166
221
}
167
222
} : { }
168
223
}
169
- style = { { left : '25%' , top : '40%' } }
224
+ style = { { left : isMobile ? '22%' : '25%' , top : '40%' } }
170
225
/>
171
226
< motion . div
172
- className = "absolute w-2 h-2 bg-black rounded-full"
227
+ className = { cn (
228
+ "absolute bg-black rounded-full" ,
229
+ isMobile ? "w-1 h-1" : "w-2 h-2"
230
+ ) }
173
231
animate = {
174
232
hoveredTab ? {
175
233
scaleY : [ 1 , 0.2 , 1 ] ,
@@ -179,39 +237,61 @@ export function AnimeNavBar({
179
237
}
180
238
} : { }
181
239
}
182
- style = { { right : '25%' , top : '40%' } }
240
+ style = { { right : isMobile ? '22%' : '25%' , top : '40%' } }
183
241
/>
184
242
185
- { /* Blush */ }
243
+ { /* Mascot Blush */ }
186
244
< motion . div
187
- className = "absolute w-2 h-1.5 bg-pink-300 rounded-full"
188
- animate = { { opacity : hoveredTab ? 0.8 : 0.6 } }
189
- style = { { left : '15%' , top : '55%' } }
245
+ className = { cn (
246
+ "absolute bg-pink-300 rounded-full" ,
247
+ isMobile ? "w-1.5 h-0.5" : "w-2 h-1.5"
248
+ ) }
249
+ animate = { {
250
+ opacity : hoveredTab ? 0.8 : 0.6
251
+ } }
252
+ style = { { left : isMobile ? '10%' : '15%' , top : isMobile ? '55%' : '55%' } }
190
253
/>
191
254
< motion . div
192
- className = "absolute w-2 h-1.5 bg-pink-300 rounded-full"
193
- animate = { { opacity : hoveredTab ? 0.8 : 0.6 } }
194
- style = { { right : '15%' , top : '55%' } }
255
+ className = { cn (
256
+ "absolute bg-pink-300 rounded-full" ,
257
+ isMobile ? "w-1.5 h-0.5" : "w-2 h-1.5"
258
+ ) }
259
+ animate = { {
260
+ opacity : hoveredTab ? 0.8 : 0.6
261
+ } }
262
+ style = { { right : isMobile ? '10%' : '15%' , top : isMobile ? '55%' : '55%' } }
195
263
/>
196
-
197
- { /* Mouth */ }
264
+
265
+ { /* Mascot Mouth */ }
198
266
< motion . div
199
- className = "absolute w-4 h-2 border-b-2 border-black rounded-full"
267
+ className = { cn (
268
+ "absolute border-b-2 border-black rounded-full" ,
269
+ isMobile ? "w-2 h-1" : "w-4 h-2"
270
+ ) }
200
271
animate = {
201
- hoveredTab ? { scaleY : 1.5 , y : - 1 } : { scaleY : 1 , y : 0 }
272
+ hoveredTab ? {
273
+ scaleY : 1.5 ,
274
+ y : - 1
275
+ } : {
276
+ scaleY : 1 ,
277
+ y : 0
278
+ }
202
279
}
203
- style = { { left : ' 30%', top : '60%' } }
280
+ style = { { left : isMobile ? '25%' : ' 30%', top : isMobile ? '62%' : '60%' } }
204
281
/>
205
282
206
- { /* Sparkles */ }
283
+ { /* Sparkle Effects */ }
207
284
< AnimatePresence >
208
285
{ hoveredTab && (
209
286
< >
210
287
< motion . div
211
288
initial = { { opacity : 0 , scale : 0 } }
212
289
animate = { { opacity : 1 , scale : 1 } }
213
290
exit = { { opacity : 0 , scale : 0 } }
214
- className = "absolute -top-1 -right-1 w-2 h-2 text-yellow-300"
291
+ className = { cn (
292
+ "absolute text-yellow-300" ,
293
+ isMobile ? "-top-1 -right-1 w-1 h-1" : "-top-1 -right-1 w-2 h-2"
294
+ ) }
215
295
>
216
296
✨
217
297
</ motion . div >
@@ -220,7 +300,10 @@ export function AnimeNavBar({
220
300
animate = { { opacity : 1 , scale : 1 } }
221
301
exit = { { opacity : 0 , scale : 0 } }
222
302
transition = { { delay : 0.1 } }
223
- className = "absolute -top-2 left-0 w-2 h-2 text-yellow-300"
303
+ className = { cn (
304
+ "absolute text-yellow-300" ,
305
+ isMobile ? "-top-2 left-0 w-1 h-1" : "-top-2 left-0 w-2 h-2"
306
+ ) }
224
307
>
225
308
✨
226
309
</ motion . div >
@@ -229,9 +312,12 @@ export function AnimeNavBar({
229
312
</ AnimatePresence >
230
313
</ motion . div >
231
314
232
- { /* Base */ }
315
+ { /* Mascot Base */ }
233
316
< motion . div
234
- className = "absolute -bottom-1 left-1/2 w-4 h-4 -translate-x-1/2"
317
+ className = { cn (
318
+ "absolute left-1/2 -translate-x-1/2" ,
319
+ isMobile ? "-bottom-1 w-2 h-2" : "-bottom-1 w-4 h-4"
320
+ ) }
235
321
animate = {
236
322
hoveredTab ? {
237
323
y : [ 0 , - 4 , 0 ] ,
0 commit comments