diff --git a/frontend/.gitignore b/frontend/.gitignore index a0f88c9..3b64204 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -24,3 +24,5 @@ dist-ssr *.sw? .env .env.local + +/src/assets diff --git a/frontend/index.html b/frontend/index.html index 5af9ea3..6c8b4bd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,9 @@ - + + + Realtime Collab diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..cc02814 Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/docnest-icon-32.png b/frontend/public/docnest-icon-32.png new file mode 100644 index 0000000..3d736b0 Binary files /dev/null and b/frontend/public/docnest-icon-32.png differ diff --git a/frontend/public/docnest-icon-64.png b/frontend/public/docnest-icon-64.png new file mode 100644 index 0000000..5923089 Binary files /dev/null and b/frontend/public/docnest-icon-64.png differ diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 0827af6..6a392c6 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,7 +1,8 @@ import { useAuth } from '@/contexts/AuthContext'; import { Button } from '@/components/ui/button'; -import PixelIcon from '@/components/PixelIcon/PixelIcon'; import { LogOut } from 'lucide-react'; +import DocNestLogo from '@/assets/docnest/docnest-icon-128.png'; +import ThemeToggle from '@/components/ThemeToggle'; function Navbar() { const { user, logout } = useAuth(); @@ -35,12 +36,18 @@ function Navbar() { gap-2 " > - + DocNest DocNest {/* User Section */}
+ {user.avatar_url && ( (() => getPreferredTheme()); + + useEffect(() => { + applyTheme(theme); + }, [theme]); + + const nextTheme: ThemeMode = theme === "dark" ? "light" : "dark"; + + return ( + + ); +} + +export default ThemeToggle; diff --git a/frontend/src/index.css b/frontend/src/index.css index 8c5fa47..4180645 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -27,6 +27,28 @@ --ring: 214 89% 52%; --radius: 0.75rem; } + + .dark { + --background: 215 26% 7%; + --foreground: 0 0% 98%; + --card: 215 21% 11%; + --card-foreground: 0 0% 98%; + --popover: 215 21% 11%; + --popover-foreground: 0 0% 98%; + --primary: 213 93% 60%; + --primary-foreground: 0 0% 100%; + --secondary: 173 70% 42%; + --secondary-foreground: 0 0% 100%; + --muted: 215 15% 15%; + --muted-foreground: 215 10% 58%; + --accent: 197 100% 68%; + --accent-foreground: 215 26% 7%; + --destructive: 0 70% 52%; + --destructive-foreground: 0 0% 100%; + --border: 215 12% 21%; + --input: 215 12% 21%; + --ring: 213 93% 60%; + } } * { @@ -81,6 +103,47 @@ --pixel-text-muted: #64748B; } + .dark { + --surface: 215 21% 11%; + --surface-muted: 215 15% 15%; + --text-primary: 0 0% 98%; + --text-secondary: 215 15% 82%; + --text-muted: 215 10% 70%; + --brand: 213 93% 60%; + --brand-dark: 213 90% 52%; + --brand-teal: 173 70% 42%; + --brand-teal-dark: 173 68% 34%; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45); + --shadow-md: 0 12px 30px rgba(0, 0, 0, 0.55); + --shadow-lg: 0 20px 50px rgba(0, 0, 0, 0.65); + --focus-ring: 0 0 0 3px rgba(88, 166, 255, 0.35); + --gradient-hero: linear-gradient(120deg, #0d1117 0%, #111827 55%, #161b22 100%); + --gradient-accent: linear-gradient(120deg, #2f81f7 0%, #14b8a6 100%); + + --pixel-purple-deep: #0b1f4b; + --pixel-purple-bright: #2f81f7; + --pixel-pink-vibrant: #58a6ff; + --pixel-cyan-bright: #14b8a6; + --pixel-orange-warm: #f59e0b; + --pixel-yellow-gold: #fbbf24; + --pixel-green-lime: #22c55e; + --pixel-green-forest: #16a34a; + + --pixel-bg-dark: #0d1117; + --pixel-bg-medium: #161b22; + --pixel-bg-light: #1f2937; + --pixel-panel: #0f172a; + --pixel-white: #e5e7eb; + + --pixel-shadow-dark: rgba(0, 0, 0, 0.5); + --pixel-outline: #30363d; + + --pixel-text-primary: #e5e7eb; + --pixel-text-secondary: #c9d1d9; + --pixel-text-muted: #8b949e; + } + body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; diff --git a/frontend/src/lib/theme.ts b/frontend/src/lib/theme.ts new file mode 100644 index 0000000..c1e2afc --- /dev/null +++ b/frontend/src/lib/theme.ts @@ -0,0 +1,37 @@ +export type ThemeMode = "light" | "dark"; + +export const getStoredTheme = (): ThemeMode | null => { + try { + const value = localStorage.getItem("theme"); + if (value === "light" || value === "dark") { + return value; + } + } catch { + // Ignore storage access errors + } + return null; +}; + +export const getPreferredTheme = (): ThemeMode => { + const stored = getStoredTheme(); + if (stored) return stored; + + if (typeof window !== "undefined" && window.matchMedia) { + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + } + + return "light"; +}; + +export const applyTheme = (theme: ThemeMode) => { + if (typeof document === "undefined") return; + document.documentElement.classList.toggle("dark", theme === "dark"); + + try { + localStorage.setItem("theme", theme); + } catch { + // Ignore storage access errors + } +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 966f17a..12f2b2d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,8 +1,11 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; +import { applyTheme, getPreferredTheme } from "./lib/theme"; import "./index.css"; +applyTheme(getPreferredTheme()); + ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/pages/LandingPage.css b/frontend/src/pages/LandingPage.css index ef76fc9..5d49099 100644 --- a/frontend/src/pages/LandingPage.css +++ b/frontend/src/pages/LandingPage.css @@ -6,6 +6,13 @@ background: hsl(var(--background)); } +.landing-theme-toggle { + position: fixed; + top: 24px; + right: 24px; + z-index: 20; +} + /* ======================================== Hero Section ======================================== */ @@ -50,6 +57,12 @@ margin-bottom: 1.5rem; } +.hero-logo-icon { + width: 28px; + height: 28px; + image-rendering: pixelated; +} + .hero-brand { font-size: 0.95rem; font-weight: 700; diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index ff69c5f..3f7a8ff 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1,5 +1,7 @@ import FloatingGem from '../components/PixelSprites/FloatingGem'; import PixelIcon from '../components/PixelIcon/PixelIcon'; +import DocNestLogo from '../assets/docnest/docnest-icon-128.png'; +import ThemeToggle from '../components/ThemeToggle'; import { API_BASE_URL } from '../config'; import './LandingPage.css'; @@ -14,6 +16,9 @@ function LandingPage() { return (
+
+ +
{/* Hero Section */}
@@ -26,7 +31,11 @@ function LandingPage() {
- + DocNest DocNest
diff --git a/frontend/src/pages/LoginPage.css b/frontend/src/pages/LoginPage.css index f65d818..c239326 100644 --- a/frontend/src/pages/LoginPage.css +++ b/frontend/src/pages/LoginPage.css @@ -5,6 +5,7 @@ justify-content: center; background: var(--gradient-hero); padding: 24px; + position: relative; } .login-container { @@ -18,11 +19,32 @@ text-align: center; } +.login-theme-toggle { + position: fixed; + top: 24px; + right: 24px; + z-index: 20; +} + +.login-brand { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-bottom: 8px; +} + +.login-logo { + width: 32px; + height: 32px; + image-rendering: pixelated; +} + .login-title { font-size: 32px; font-weight: 700; color: hsl(var(--text-primary)); - margin: 0 0 8px 0; + margin: 0; } .login-subtitle { diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 228561b..80225b8 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -2,6 +2,8 @@ import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { API_BASE_URL } from '../config'; +import DocNestLogo from '../assets/docnest/docnest-icon-128.png'; +import ThemeToggle from '../components/ThemeToggle'; import './LoginPage.css'; function LoginPage() { @@ -34,8 +36,14 @@ function LoginPage() { return (
+
+ +
-

DocNest

+
+ DocNest +

DocNest

+

Collaborate in real time with your team