diff --git a/conterminousness/web-portfolio.zip b/conterminousness/web-portfolio.zip new file mode 100644 index 0000000..0517414 Binary files /dev/null and b/conterminousness/web-portfolio.zip differ diff --git a/src/App.tsx b/src/App.tsx index 03c3cbf..0862430 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import Projects from './components/Projects' import Contact from './components/Contact' import ErrorBoundary from './components/ErrorBoundary' import { LanguageProvider } from './contexts/LanguageContext.tsx' +import { ThemeProvider } from './contexts/ThemeContext' import { useState } from 'react' function App() { @@ -14,8 +15,9 @@ function App() { return ( - -
+ + +
@@ -41,8 +43,9 @@ function App() {
-
-
+ + +
) } diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index e1f9b22..d034d17 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -31,7 +31,7 @@ const Hero = () => { } return ( -
+
{/* Squares Background Pattern */} { /> {/* Floating Elements */} -
-
+
+
@@ -57,17 +57,17 @@ const Hero = () => { transition={{ duration: 0.6, delay: 0.2 }} > {/* Terminal Header */} -
+
- mely@portfolio:~$ + mely@portfolio:~$
{/* Terminal Content */} -
+
{/* whoami command */}
@@ -80,7 +80,7 @@ const Hero = () => { />
-
+
{
{/* Description with Typewriter Effect */} -
+
{ > - + - + - + @@ -165,7 +165,7 @@ const Hero = () => { > @@ -175,7 +175,7 @@ const Hero = () => { diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index a977804..07dd424 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -10,9 +10,12 @@ import { ChevronDown, File, Folder, - Globe + Globe, + Sun, + Moon } from 'lucide-react' import { useLanguage } from '../hooks/useLanguage' +import { useTheme } from '../contexts/ThemeContext' interface SidebarProps { onCollapseChange?: (isCollapsed: boolean) => void @@ -23,6 +26,7 @@ const Sidebar = ({ onCollapseChange }: SidebarProps) => { const [isMobileOpen, setIsMobileOpen] = useState(false) const [activeSection, setActiveSection] = useState('hero') const { language, setLanguage } = useLanguage() + const { theme, toggleTheme } = useTheme() useEffect(() => { const handleScroll = () => { @@ -126,7 +130,7 @@ const Sidebar = ({ onCollapseChange }: SidebarProps) => { {/* Mobile Language Toggle Button */} + {/* Mobile Theme Toggle Button */} + + {/* Sidebar */} -
{/* VS Code Header */} -
+
{/* Traffic Lights */}
@@ -149,27 +164,38 @@ const Sidebar = ({ onCollapseChange }: SidebarProps) => { {/* EXPLORER Text - Only when expanded */} {!isCollapsed && ( - EXPLORER + EXPLORER )} {/* Language Toggle Button - Only when expanded */} {!isCollapsed && ( )} + + {/* Theme Toggle Button - Only when expanded */} + {!isCollapsed && ( + + )}
{/* Portfolio Folder */}
{!isCollapsed && ( -
+
- + mely-portfolio
)} @@ -185,12 +211,12 @@ const Sidebar = ({ onCollapseChange }: SidebarProps) => { onClick={() => scrollToSection(item.href)} className={`w-full flex items-center gap-2 px-2 py-1.5 text-left transition-all duration-200 group ${ isActive - ? 'bg-blue-600/20 text-blue-400 border-r-2 border-blue-400' - : 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/50' + ? 'bg-blue-100 dark:bg-blue-600/20 text-blue-600 dark:text-blue-400 border-r-2 border-blue-500 dark:border-blue-400' + : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/50' } ${isCollapsed ? 'justify-center' : ''}`} > {item.type === 'folder' ? ( - + ) : ( )} @@ -210,13 +236,24 @@ const Sidebar = ({ onCollapseChange }: SidebarProps) => { {isCollapsed && ( )} + {/* Desktop Theme Toggle Button - Only when collapsed */} + {isCollapsed && ( + + )} + {/* Collapse Button */} diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx new file mode 100644 index 0000000..32e5762 --- /dev/null +++ b/src/contexts/ThemeContext.tsx @@ -0,0 +1,61 @@ +import { createContext, useContext, useEffect, useState, ReactNode } from 'react' + +type Theme = 'light' | 'dark' + +interface ThemeContextType { + theme: Theme + toggleTheme: () => void +} + +const ThemeContext = createContext(undefined) + +export const useTheme = () => { + const context = useContext(ThemeContext) + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider') + } + return context +} + +interface ThemeProviderProps { + children: ReactNode +} + +export const ThemeProvider = ({ children }: ThemeProviderProps) => { + const [theme, setTheme] = useState(() => { + // Check localStorage first, then system preference + const savedTheme = localStorage.getItem('theme') as Theme + if (savedTheme) { + return savedTheme + } + + // Check system preference + if (window.matchMedia('(prefers-color-scheme: light)').matches) { + return 'light' + } + + return 'dark' + }) + + useEffect(() => { + // Update localStorage when theme changes + localStorage.setItem('theme', theme) + + // Update document class for Tailwind dark mode + if (theme === 'dark') { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + }, [theme]) + + const toggleTheme = () => { + setTheme((prevTheme: Theme) => prevTheme === 'light' ? 'dark' : 'light') + } + + return ( + + {children} + + ) +} diff --git a/tailwind.config.js b/tailwind.config.js index b6431ab..3be3eac 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,6 +4,7 @@ export default { "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], + darkMode: 'class', theme: { extend: { colors: {