11"use client"
22
3- import { useState , useEffect } from "react"
4- import { motion } from "framer-motion"
3+ import { useState , useEffect , useRef } from "react"
4+ import { motion , AnimatePresence } from "framer-motion"
55import { User , Code , Briefcase , Mail , Folder , Sun , Moon } from "lucide-react"
66import { useTheme } from "next-themes"
77
88export default function Navbar ( ) {
99 const [ activeSection , setActiveSection ] = useState ( "about" )
1010 const [ scrolled , setScrolled ] = useState ( false )
1111 const [ isOpen , setIsOpen ] = useState ( false )
12+ const [ isAnimating , setIsAnimating ] = useState ( false )
13+ const [ animationOrigin , setAnimationOrigin ] = useState ( { x : 0 , y : 0 } )
14+ const desktopThemeButtonRef = useRef < HTMLButtonElement > ( null )
15+ const mobileThemeButtonRef = useRef < HTMLButtonElement > ( null )
1216 const { theme, setTheme } = useTheme ( )
1317
1418 const navItems = [
@@ -62,11 +66,68 @@ export default function Navbar() {
6266 }
6367
6468 const toggleTheme = ( ) => {
65- setTheme ( theme === "dark" ? "light" : "dark" )
69+ // Determine which button is currently visible and get its position
70+ const currentButtonRef = window . innerWidth >= 768 ? desktopThemeButtonRef : mobileThemeButtonRef
71+ const buttonElement = currentButtonRef . current
72+
73+ if ( buttonElement ) {
74+ const rect = buttonElement . getBoundingClientRect ( )
75+ setAnimationOrigin ( {
76+ x : rect . left + rect . width / 2 ,
77+ y : rect . top + rect . height / 2
78+ } )
79+ setIsAnimating ( true )
80+
81+ // Change theme after animation starts
82+ setTimeout ( ( ) => {
83+ setTheme ( theme === "dark" ? "light" : "dark" )
84+ } , 300 )
85+
86+ // Reset animation state
87+ setTimeout ( ( ) => {
88+ setIsAnimating ( false )
89+ } , 1400 )
90+ }
6691 }
6792
6893 return (
6994 < >
95+ { /* Theme Transition Overlay */ }
96+ < AnimatePresence >
97+ { isAnimating && (
98+ < motion . div
99+ initial = { {
100+ position : "fixed" ,
101+ top : animationOrigin . y ,
102+ left : animationOrigin . x ,
103+ width : 0 ,
104+ height : 0 ,
105+ borderRadius : "50%" ,
106+ backgroundColor : theme === "dark" ? "#ffffff" : "#000000" ,
107+ zIndex : 9999 ,
108+ opacity : 0.8
109+ } }
110+ animate = { {
111+ width : Math . max ( window . innerWidth , window . innerHeight ) * 2 ,
112+ height : Math . max ( window . innerWidth , window . innerHeight ) * 2 ,
113+ top : animationOrigin . y - Math . max ( window . innerWidth , window . innerHeight ) ,
114+ left : animationOrigin . x - Math . max ( window . innerWidth , window . innerHeight ) ,
115+ opacity : 0
116+ } }
117+ exit = { {
118+ opacity : 0 ,
119+ scale : 0
120+ } }
121+ transition = { {
122+ duration : 1.2 ,
123+ ease : "easeInOut"
124+ } }
125+ style = { {
126+ transformOrigin : "center"
127+ } }
128+ />
129+ ) }
130+ </ AnimatePresence >
70131 { /* Desktop Floating Navbar */ }
71132 < div className = "fixed top-3 left-0 right-0 z-50 hidden md:block" >
72133 < div className = "flex justify-center" >
@@ -91,6 +152,7 @@ export default function Navbar() {
91152 />
92153 ) ) }
93154 < button
155+ ref = { desktopThemeButtonRef }
94156 onClick = { toggleTheme }
95157 aria-label = "Toggle theme"
96158 className = "ml-2 inline-flex items-center justify-center rounded-full border border-border bg-accent hover:bg-accent/80 text-foreground transition-colors h-8 w-8"
@@ -132,6 +194,7 @@ export default function Navbar() {
132194 { /* Theme toggle (mobile) */ }
133195 < li >
134196 < button
197+ ref = { mobileThemeButtonRef }
135198 onClick = { toggleTheme }
136199 aria-label = "Toggle theme"
137200 className = "w-full py-3 flex flex-col items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
0 commit comments