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 @@
+
{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
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
+
Collaborate in real time with your team