Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState } from 'react';
import { NAV_LINKS, BRAND } from '../data/navigation';
import { useTheme } from '../context/ThemeContext';

function Navbar() {
const [menuOpen, setMenuOpen] = useState(false);
const { theme, toggleTheme } = useTheme();

return (
<header className="navbar" role="banner">
Expand Down Expand Up @@ -39,6 +41,15 @@ function Navbar() {
</li>
))}
</ul>
<button
type="button"
className="navbar__theme-toggle"
onClick={toggleTheme}
aria-label={theme === 'dark' ? '切換為淺色主題' : '切換為深色主題'}
title={theme === 'dark' ? '淺色模式' : '深色模式'}
>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
<a href="#demo" className="btn btn--primary btn--sm navbar__cta">
預約 Demo
</a>
Expand Down
31 changes: 31 additions & 0 deletions src/context/ThemeContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createContext, useContext, useEffect, useState } from 'react';

const STORAGE_KEY = 'salespilot-theme';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
const [theme, setThemeState] = useState(() => {
if (typeof window === 'undefined') return 'dark';
return window.localStorage.getItem(STORAGE_KEY) || 'dark';
});

useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
window.localStorage.setItem(STORAGE_KEY, theme);
}, [theme]);

const toggleTheme = () => setThemeState((t) => (t === 'dark' ? 'light' : 'dark'));

return (
<ThemeContext.Provider value={{ theme, setTheme: setThemeState, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}
56 changes: 55 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,38 @@
/* Layout */
--container-max: 1200px;
--navbar-height: 72px;

/* Navbar overlay (for theme) */
--navbar-bg: rgba(10, 14, 26, 0.8);
}

/* --- 淺色主題 --- */
[data-theme="light"] {
--color-bg: #f8fafc;
--color-bg-elevated: #ffffff;
--color-bg-card: #ffffff;
--color-bg-card-hover: #f1f5f9;
--color-surface: #e2e8f0;

--color-primary: #4f46e5;
--color-primary-light: #6366f1;
--color-primary-dark: #4338ca;
--color-primary-glow: rgba(79, 70, 229, 0.2);

--color-text: #0f172a;
--color-text-secondary: #475569;
--color-text-muted: #64748b;
--color-text-inverse: #f1f5f9;

--color-border: rgba(15, 23, 42, 0.12);
--color-border-hover: rgba(15, 23, 42, 0.25);

--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1);
--shadow-glow: 0 0 40px var(--color-primary-glow);

--navbar-bg: rgba(248, 250, 252, 0.9);
}

/* --- Reset & Base --- */
Expand Down Expand Up @@ -256,7 +288,7 @@ button {
right: 0;
height: var(--navbar-height);
z-index: 1000;
background: rgba(10, 14, 26, 0.8);
background: var(--navbar-bg);
backdrop-filter: blur(16px);
border-bottom: 1px solid var(--color-border);
}
Expand Down Expand Up @@ -292,6 +324,24 @@ button {
gap: var(--space-6);
}

.navbar__theme-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: var(--radius-md);
background: var(--color-surface);
color: var(--color-text);
font-size: 1.25rem;
transition: background var(--transition-fast), transform var(--transition-fast);
}

.navbar__theme-toggle:hover {
background: var(--color-border-hover);
transform: scale(1.05);
}

.navbar__link {
font-size: var(--text-sm);
font-weight: 500;
Expand Down Expand Up @@ -367,6 +417,10 @@ button {
font-size: var(--text-lg);
}

.navbar__theme-toggle {
margin-top: var(--space-4);
}

.navbar__cta {
width: 100%;
margin-top: var(--space-4);
Expand Down
5 changes: 4 additions & 1 deletion src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ThemeProvider } from './context/ThemeContext';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
);