diff --git a/src/App.css b/src/App.css index 79b52c1..da5fd18 100644 --- a/src/App.css +++ b/src/App.css @@ -3,8 +3,8 @@ .app { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; - color: #ffffff; - background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%); + color: var(--text-primary); + background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 50%, var(--bg-tertiary) 100%); min-height: 100vh; } @@ -13,9 +13,9 @@ position: fixed; top: 0; width: 100%; - background: rgba(0, 0, 0, 0.9); + background: var(--bg-navbar); backdrop-filter: blur(10px); - border-bottom: 1px solid #ff4655; + border-bottom: 1px solid var(--border-color); z-index: 1000; padding: 1rem 0; } @@ -31,7 +31,7 @@ .nav-logo h2 { margin: 0; - color: #ff4655; + color: var(--accent-color); font-size: 1.5rem; font-weight: bold; } @@ -43,17 +43,17 @@ } .nav-links a { - color: #ffffff; + color: var(--text-primary); text-decoration: none; transition: color 0.3s ease; } .nav-links a:hover { - color: #ff4655; + color: var(--accent-color); } .join-btn { - background: linear-gradient(45deg, #ff4655, #ff6b6b); + background: linear-gradient(45deg, var(--accent-color), var(--accent-hover)); color: white; border: none; padding: 0.5rem 1.5rem; @@ -65,7 +65,7 @@ .join-btn:hover { transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(255, 70, 85, 0.4); + box-shadow: 0 5px 15px var(--accent-shadow); } /* Hero Section */ @@ -87,8 +87,8 @@ } .highlight { - color: #ff4655; - background: linear-gradient(45deg, #ff4655, #ff6b6b); + color: var(--accent-color); + background: linear-gradient(45deg, var(--accent-color), var(--accent-hover)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -96,7 +96,7 @@ .hero-subtitle { font-size: 1.2rem; - color: #b8c5d1; + color: var(--text-secondary); margin-bottom: 2rem; line-height: 1.6; } @@ -107,7 +107,7 @@ } .btn-primary { - background: linear-gradient(45deg, #ff4655, #ff6b6b); + background: linear-gradient(45deg, var(--accent-color), var(--accent-hover)); color: white; border: none; padding: 1rem 2rem; @@ -120,13 +120,13 @@ .btn-primary:hover { transform: translateY(-3px); - box-shadow: 0 8px 25px rgba(255, 70, 85, 0.4); + box-shadow: 0 8px 25px var(--accent-shadow); } .btn-secondary { background: transparent; - color: #ffffff; - border: 2px solid #ff4655; + color: var(--text-primary); + border: 2px solid var(--accent-color); padding: 1rem 2rem; border-radius: 8px; font-size: 1.1rem; @@ -136,7 +136,7 @@ } .btn-secondary:hover { - background: #ff4655; + background: var(--accent-color); transform: translateY(-3px); } @@ -154,8 +154,8 @@ .preview-screen { width: 400px; height: 250px; - background: linear-gradient(45deg, #1a1a2e, #16213e); - border: 3px solid #ff4655; + background: linear-gradient(45deg, var(--bg-card), var(--bg-secondary)); + border: 3px solid var(--accent-color); border-radius: 12px; position: relative; overflow: hidden; @@ -167,12 +167,12 @@ left: 50%; transform: translate(-50%, -50%); text-align: center; - color: #ffffff; + color: var(--text-primary); } .crosshair { font-size: 3rem; - color: #ff4655; + color: var(--accent-color); margin-bottom: 1rem; } @@ -194,7 +194,7 @@ /* Statistics Section */ .stats-section { padding: 60px 2rem; - background: rgba(255, 255, 255, 0.02); + background: var(--bg-section); } .stats-container { @@ -206,8 +206,8 @@ } .stat-card { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 70, 85, 0.3); + background: var(--bg-card); + border: 1px solid var(--border-color); border-radius: 12px; padding: 2rem; text-align: center; @@ -216,7 +216,7 @@ .stat-card:hover { transform: translateY(-5px); - box-shadow: 0 10px 30px rgba(255, 70, 85, 0.2); + box-shadow: 0 10px 30px var(--accent-shadow); } .stat-icon { @@ -227,12 +227,12 @@ .stat-number { font-size: 2.5rem; font-weight: bold; - color: #ff4655; + color: var(--accent-color); margin-bottom: 0.5rem; } .stat-label { - color: #b8c5d1; + color: var(--text-secondary); font-size: 1.1rem; } @@ -250,7 +250,7 @@ font-size: 2.5rem; text-align: center; margin-bottom: 3rem; - color: #ffffff; + color: var(--text-primary); } .features-grid { @@ -260,8 +260,8 @@ } .feature-card { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 70, 85, 0.2); + background: var(--bg-card); + border: 1px solid var(--border-color); border-radius: 12px; padding: 2rem; text-align: center; @@ -270,8 +270,8 @@ .feature-card:hover { transform: translateY(-5px); - border-color: #ff4655; - box-shadow: 0 10px 30px rgba(255, 70, 85, 0.2); + border-color: var(--accent-color); + box-shadow: 0 10px 30px var(--accent-shadow); } .feature-icon { @@ -280,21 +280,21 @@ } .feature-card h3 { - color: #ffffff; + color: var(--text-primary); margin-bottom: 1rem; font-size: 1.5rem; } .feature-card p { - color: #b8c5d1; + color: var(--text-secondary); margin-bottom: 1.5rem; line-height: 1.6; } .feature-btn { background: transparent; - color: #ff4655; - border: 2px solid #ff4655; + color: var(--accent-color); + border: 2px solid var(--accent-color); padding: 0.75rem 1.5rem; border-radius: 6px; cursor: pointer; @@ -303,14 +303,14 @@ } .feature-btn:hover { - background: #ff4655; + background: var(--accent-color); color: white; } /* Activity Section */ .activity-section { padding: 80px 2rem; - background: rgba(255, 255, 255, 0.02); + background: var(--bg-section); } .activity-container { @@ -326,8 +326,8 @@ } .chat-feed { - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(255, 70, 85, 0.3); + background: var(--bg-card); + border: 1px solid var(--border-color); border-radius: 12px; padding: 1.5rem; max-height: 400px; @@ -343,7 +343,7 @@ } .chat-message:hover { - background: rgba(255, 70, 85, 0.1); + background: var(--accent-shadow); } .chat-message:last-child { @@ -352,17 +352,17 @@ .chat-user { font-weight: bold; - color: #ff4655; + color: var(--accent-color); min-width: 120px; } .chat-text { flex: 1; - color: #ffffff; + color: var(--text-primary); } .chat-time { - color: #888; + color: var(--text-secondary); font-size: 0.9rem; margin-left: 1rem; } @@ -374,14 +374,14 @@ } .sidebar-card { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 70, 85, 0.2); + background: var(--bg-card); + border: 1px solid var(--border-color); border-radius: 12px; padding: 1.5rem; } .sidebar-card h3 { - color: #ffffff; + color: var(--text-primary); margin-bottom: 1rem; font-size: 1.3rem; } @@ -393,9 +393,9 @@ } .leader-item { - color: #b8c5d1; + color: var(--text-secondary); padding: 0.5rem; - background: rgba(0, 0, 0, 0.3); + background: var(--bg-secondary); border-radius: 6px; } @@ -406,15 +406,15 @@ } .quick-stats div { - color: #b8c5d1; + color: var(--text-secondary); padding: 0.5rem; - background: rgba(0, 0, 0, 0.3); + background: var(--bg-secondary); border-radius: 6px; } /* Footer */ .footer { - background: rgba(0, 0, 0, 0.8); + background: var(--bg-footer); padding: 3rem 2rem 1rem; margin-top: 4rem; } @@ -429,18 +429,18 @@ } .footer-section h4 { - color: #ff4655; + color: var(--accent-color); margin-bottom: 1rem; font-size: 1.2rem; } .footer-section p { - color: #b8c5d1; + color: var(--text-secondary); line-height: 1.6; } .footer-section a { - color: #b8c5d1; + color: var(--text-secondary); text-decoration: none; display: block; margin-bottom: 0.5rem; @@ -448,14 +448,14 @@ } .footer-section a:hover { - color: #ff4655; + color: var(--accent-color); } .footer-bottom { - border-top: 1px solid rgba(255, 70, 85, 0.3); + border-top: 1px solid var(--border-color); padding-top: 2rem; text-align: center; - color: #888; + color: var(--text-secondary); } /* Responsive Design */ @@ -511,15 +511,15 @@ } .chat-feed::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.1); + background: var(--bg-secondary); border-radius: 3px; } .chat-feed::-webkit-scrollbar-thumb { - background: #ff4655; + background: var(--accent-color); border-radius: 3px; } .chat-feed::-webkit-scrollbar-thumb:hover { - background: #ff6b6b; + background: var(--accent-hover); } diff --git a/src/App.tsx b/src/App.tsx index 211b117..13695a4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next' import LanguageSelector from './components/LanguageSelector' +import ThemeToggle from './components/ThemeToggle' import './App.css' function App() { @@ -34,6 +35,7 @@ function App() { {t('nav.blog')} {t('nav.git')} {t('nav.forum')} + diff --git a/src/components/ThemeToggle.css b/src/components/ThemeToggle.css new file mode 100644 index 0000000..13cb93e --- /dev/null +++ b/src/components/ThemeToggle.css @@ -0,0 +1,43 @@ +.theme-toggle { + background: transparent; + border: 1px solid var(--border-color); + color: var(--text-primary); + padding: 0.5rem; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + width: 40px; + height: 40px; +} + +.theme-toggle:hover { + background: var(--primary-red); + border-color: var(--primary-red); + transform: scale(1.05); +} + +.theme-icon { + width: 20px; + height: 20px; + transition: transform 0.3s ease; +} + +.theme-toggle:hover .theme-icon { + transform: rotate(15deg); +} + +/* Mobile responsive */ +@media (max-width: 768px) { + .theme-toggle { + width: 36px; + height: 36px; + } + + .theme-icon { + width: 18px; + height: 18px; + } +} \ No newline at end of file diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..3b08a20 --- /dev/null +++ b/src/components/ThemeToggle.tsx @@ -0,0 +1,29 @@ +import { useTheme } from '../hooks/useTheme' +import './ThemeToggle.css' + +function ThemeToggle() { + const { theme, toggleTheme } = useTheme() + + return ( + + ) +} + +export default ThemeToggle \ No newline at end of file diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx new file mode 100644 index 0000000..55ab5b8 --- /dev/null +++ b/src/contexts/ThemeContext.tsx @@ -0,0 +1,68 @@ +import React, { createContext, useState, useEffect } from 'react' +import type { ReactNode } from 'react' + +type Theme = 'light' | 'dark' + +interface ThemeContextType { + theme: Theme + toggleTheme: () => void +} + +const ThemeContext = createContext(undefined) + +export { ThemeContext } + +interface ThemeProviderProps { + children: ReactNode +} + +export const ThemeProvider: React.FC = ({ children }) => { + // Get initial theme from localStorage or system preference + const getInitialTheme = (): Theme => { + const savedTheme = localStorage.getItem('theme') as Theme + if (savedTheme) { + return savedTheme + } + // Check system preference + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + } + + const [theme, setTheme] = useState(getInitialTheme) + + // Update document class and localStorage when theme changes + useEffect(() => { + const root = document.documentElement + root.classList.remove('light', 'dark') + root.classList.add(theme) + localStorage.setItem('theme', theme) + }, [theme]) + + // Listen for system theme changes + useEffect(() => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + const handleChange = (e: MediaQueryListEvent) => { + // Only update if no manual preference is saved + if (!localStorage.getItem('theme')) { + setTheme(e.matches ? 'dark' : 'light') + } + } + + mediaQuery.addEventListener('change', handleChange) + return () => mediaQuery.removeEventListener('change', handleChange) + }, []) + + const toggleTheme = () => { + setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light') + } + + const value = { + theme, + toggleTheme + } + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts new file mode 100644 index 0000000..b1cba12 --- /dev/null +++ b/src/hooks/useTheme.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react' +import { ThemeContext } from '../contexts/ThemeContext' + +export const useTheme = () => { + const context = useContext(ThemeContext) + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider') + } + return context +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 9cafb8a..ee0e23b 100644 --- a/src/index.css +++ b/src/index.css @@ -1,26 +1,85 @@ :root { + /* Light theme variables */ + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-tertiary: #e9ecef; + --bg-navbar: rgba(255, 255, 255, 0.95); + --bg-card: rgba(255, 255, 255, 0.9); + --bg-overlay: rgba(0, 0, 0, 0.05); + --bg-section: rgba(0, 0, 0, 0.02); + --bg-footer: rgba(0, 0, 0, 0.8); + + --text-primary: #1a1a1a; + --text-secondary: #6c757d; + --text-muted: #8e9297; + + --border-color: rgba(0, 0, 0, 0.1); + --border-hover: rgba(255, 70, 85, 0.3); + + --shadow: rgba(0, 0, 0, 0.1); + --shadow-hover: rgba(0, 0, 0, 0.15); + + --accent-color: #ff4655; + --accent-hover: #ff6b6b; + --accent-shadow: rgba(255, 70, 85, 0.4); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.5; font-weight: 400; - /* Gaming color scheme */ - --primary-red: #ff4655; - --primary-red-hover: #ff6b6b; - --dark-bg: #0a0a0a; - --dark-bg-secondary: #1a1a2e; - --dark-bg-tertiary: #16213e; - --text-primary: #ffffff; - --text-secondary: #b8c5d1; - --text-muted: #888; - --border-color: rgba(255, 70, 85, 0.3); - - color: var(--text-primary); - background: linear-gradient(135deg, var(--dark-bg) 0%, var(--dark-bg-secondary) 50%, var(--dark-bg-tertiary) 100%); - font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + + /* Smooth theme transitions */ + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; +} + +/* Dark theme variables */ +.dark { + --bg-primary: #0a0a0a; + --bg-secondary: #1a1a2e; + --bg-tertiary: #16213e; + --bg-navbar: rgba(0, 0, 0, 0.9); + --bg-card: rgba(0, 0, 0, 0.5); + --bg-overlay: rgba(255, 255, 255, 0.02); + --bg-section: rgba(255, 255, 255, 0.02); + --bg-footer: rgba(0, 0, 0, 0.8); + + --text-primary: #ffffff; + --text-secondary: #b8c5d1; + --text-muted: #888; + + --border-color: rgba(255, 70, 85, 0.3); + --border-hover: rgba(255, 70, 85, 0.5); + + --shadow: rgba(0, 0, 0, 0.3); + --shadow-hover: rgba(255, 70, 85, 0.2); + + --accent-color: #ff4655; + --accent-hover: #ff6b6b; + --accent-shadow: rgba(255, 70, 85, 0.4); +} + +/* Light theme variables (explicit) */ +.light { + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-tertiary: #e9ecef; + --bg-navbar: rgba(255, 255, 255, 0.95); + --bg-card: rgba(255, 255, 255, 0.9); + --bg-overlay: rgba(0, 0, 0, 0.05); + + --text-primary: #1a1a1a; + --text-secondary: #6c757d; + --text-muted: #8e9297; + + --border-color: rgba(0, 0, 0, 0.1); + --border-hover: rgba(255, 70, 85, 0.3); + + --shadow: rgba(0, 0, 0, 0.1); + --shadow-hover: rgba(0, 0, 0, 0.15); } * { diff --git a/src/main.tsx b/src/main.tsx index cd81cee..41fb950 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,10 +2,13 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import './i18n' +import { ThemeProvider } from './contexts/ThemeContext' import App from './App.tsx' createRoot(document.getElementById('root')!).render( - + + + , )