From 418fb591303c3260949aa4e42eb896acfa59ef5c Mon Sep 17 00:00:00 2001 From: FaranRaja Date: Fri, 14 Aug 2026 19:00:52 +0500 Subject: [PATCH] feat(web): add functional Dark/Light theme toggle with persistence and complete token support --- web/frontend/src/App.jsx | 13 ++- web/frontend/src/components/AuthPage.css | 26 ++++- web/frontend/src/components/AuthPage.jsx | 10 +- web/frontend/src/components/ChatInterface.css | 82 ++++++++------ web/frontend/src/components/Dashboard.css | 63 ++++++----- web/frontend/src/components/Dashboard.jsx | 53 ++++----- .../src/components/DocumentPreviewModal.css | 60 ++++++---- web/frontend/src/components/LandingPage.css | 34 ++++++ web/frontend/src/components/LandingPage.jsx | 2 + web/frontend/src/components/ProfileView.css | 106 +++++++++--------- web/frontend/src/components/ProfileView.jsx | 13 +++ .../src/components/QuizResultsView.css | 75 ++++++------- web/frontend/src/components/QuizView.css | 57 +++++----- web/frontend/src/components/ThemeToggle.css | 96 ++++++++++++++++ web/frontend/src/components/ThemeToggle.jsx | 65 +++++++++++ web/frontend/src/context/ThemeContext.jsx | 42 +++++++ web/frontend/src/index.css | 94 ++++++++++++++-- 17 files changed, 641 insertions(+), 250 deletions(-) create mode 100644 web/frontend/src/components/ThemeToggle.css create mode 100644 web/frontend/src/components/ThemeToggle.jsx create mode 100644 web/frontend/src/context/ThemeContext.jsx diff --git a/web/frontend/src/App.jsx b/web/frontend/src/App.jsx index d37ddc5..c4e61bf 100644 --- a/web/frontend/src/App.jsx +++ b/web/frontend/src/App.jsx @@ -2,6 +2,7 @@ import { useState } from "react"; import "./App.css"; import { AuthProvider, useAuth } from "./context/AuthContext.jsx"; import { ToastProvider } from "./context/ToastContext.jsx"; +import { ThemeProvider } from "./context/ThemeContext.jsx"; import LandingPage from "./components/LandingPage.jsx"; import Login from "./components/Login.jsx"; import Signup from "./components/Signup.jsx"; @@ -36,11 +37,13 @@ function AppContent() { function App() { return ( - - - - - + + + + + + + ); } diff --git a/web/frontend/src/components/AuthPage.css b/web/frontend/src/components/AuthPage.css index f6d4327..3a85201 100644 --- a/web/frontend/src/components/AuthPage.css +++ b/web/frontend/src/components/AuthPage.css @@ -312,9 +312,33 @@ .auth-social-btn:hover { border-color: var(--color-primary-purple); - background: #1c1c33; + background: var(--color-surface-hover); } @media (max-width: 900px) { .auth-left { display: none; } +} + +/* Light Theme Overrides for Auth Page */ +[data-theme="light"] .auth-container { + background: var(--color-bg-body); +} + +[data-theme="light"] .auth-feature { + background: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03); +} + +[data-theme="light"] .auth-tabs { + background: #e2e8f0; +} + +[data-theme="light"] .auth-social-btn { + background: #ffffff; + border-color: #cbd5e1; +} + +[data-theme="light"] .auth-social-btn:hover { + background: #f8fafc; } \ No newline at end of file diff --git a/web/frontend/src/components/AuthPage.jsx b/web/frontend/src/components/AuthPage.jsx index 11cf37b..b8b6a68 100644 --- a/web/frontend/src/components/AuthPage.jsx +++ b/web/frontend/src/components/AuthPage.jsx @@ -1,7 +1,10 @@ import { useState } from "react"; import { useAuth } from "../context/AuthContext.jsx"; +import ThemeToggle from "./ThemeToggle.jsx"; import "./AuthPage.css"; +const API_BASE = "http://localhost:8000"; + function AuthPage({ initialMode = "login", onLoginSuccess, onBackToHome }) { const [mode, setMode] = useState(initialMode); const [email, setEmail] = useState(""); @@ -18,7 +21,7 @@ function AuthPage({ initialMode = "login", onLoginSuccess, onBackToHome }) { const endpoint = mode === "login" ? "/login" : "/signup"; try { - const response = await fetch(`http://localhost:5000${endpoint}`, { + const response = await fetch(`${API_BASE}${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), @@ -107,7 +110,10 @@ function AuthPage({ initialMode = "login", onLoginSuccess, onBackToHome }) { -
+
+
+ +

{mode === "login" ? "Welcome Back" : "Create Account"}

diff --git a/web/frontend/src/components/ChatInterface.css b/web/frontend/src/components/ChatInterface.css index 5fc79c5..275f252 100644 --- a/web/frontend/src/components/ChatInterface.css +++ b/web/frontend/src/components/ChatInterface.css @@ -2,11 +2,12 @@ display: flex; flex-direction: column; height: 100%; - background: rgba(22, 22, 42, 0.4); + background: var(--color-card-bg); backdrop-filter: blur(10px); - border: 1px solid rgba(42, 42, 62, 0.5); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); overflow: hidden; + box-shadow: 0 4px 20px var(--color-shadow); } .chat-header { @@ -14,8 +15,8 @@ justify-content: space-between; align-items: center; padding: 1rem 1.5rem; - background: rgba(18, 16, 31, 0.8); - border-bottom: 1px solid rgba(42, 42, 62, 0.8); + background: var(--color-bg-topbar); + border-bottom: 1px solid var(--color-card-border); } .chat-header-info h3 { @@ -30,9 +31,9 @@ } .header-doc-select { - background: rgba(30, 30, 50, 0.8); - border: 1px solid rgba(124, 58, 237, 0.4); - color: #e2e8f0; + background: var(--color-input-bg); + border: 1px solid var(--color-input-border); + color: var(--color-text-primary); padding: 6px 12px; border-radius: 8px; font-size: 0.82rem; @@ -41,25 +42,30 @@ } .header-doc-select option { - background: #121222; - color: #e2e8f0; + background: var(--color-card-bg); + color: var(--color-text-primary); } .header-doc-select option:disabled { - color: #64748b; + color: var(--color-text-muted); font-style: italic; } .btn-back-nav { - background: rgba(124, 58, 237, 0.15); - border: 1px solid rgba(124, 58, 237, 0.3); - color: #c084fc; + background: var(--color-surface-hover); + border: 1px solid var(--color-card-border); + color: var(--color-primary-purple); padding: 4px 10px; border-radius: 6px; font-size: 0.8rem; cursor: pointer; margin-bottom: 6px; display: inline-block; + transition: all 0.2s ease; +} + +.btn-back-nav:hover { + background: var(--color-surface-active); } .chat-status { @@ -89,7 +95,7 @@ .btn-clear { background: transparent; border: 1px solid rgba(239, 68, 68, 0.3); - color: #ef4444; + color: var(--color-error); padding: 0.4rem 0.8rem; border-radius: var(--radius-md); font-size: 0.8rem; @@ -99,7 +105,7 @@ .btn-clear:hover { background: rgba(239, 68, 68, 0.1); - border-color: #ef4444; + border-color: var(--color-error); } .chat-messages { @@ -121,12 +127,12 @@ } .chat-messages::-webkit-scrollbar-thumb { - background: rgba(124, 58, 237, 0.2); + background: var(--color-surface-active); border-radius: 3px; } .chat-messages::-webkit-scrollbar-thumb:hover { - background: rgba(124, 58, 237, 0.5); + background: var(--color-primary-purple); } .message-wrapper { @@ -149,7 +155,7 @@ font-size: 0.95rem; line-height: 1.5; position: relative; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 15px var(--color-shadow); animation: fadeIn 0.25s ease-out; } @@ -159,21 +165,21 @@ } .user-bubble { - background: linear-gradient(135deg, var(--color-primary-purple), var(--color-primary-blue)); - color: var(--color-text-primary); + background: var(--gradient-primary); + color: #ffffff; border-bottom-right-radius: 0.2rem; } .assistant-bubble { - background: rgba(30, 30, 50, 0.9); + background: var(--color-chat-bubble-ai); color: var(--color-text-primary); - border: 1px solid rgba(60, 60, 90, 0.4); + border: 1px solid var(--color-card-border); border-bottom-left-radius: 0.2rem; } .error-bubble { border-color: rgba(239, 68, 68, 0.4); - background: rgba(40, 20, 30, 0.9); + background: rgba(239, 68, 68, 0.08); } .message-content { @@ -184,11 +190,15 @@ .message-time { display: block; font-size: 0.7rem; - color: rgba(255, 255, 255, 0.4); + color: var(--color-text-muted); margin-top: 0.5rem; text-align: right; } +.user-bubble .message-time { + color: rgba(255, 255, 255, 0.7); +} + .assistant-bubble .message-time { color: var(--color-text-muted); } @@ -197,7 +207,7 @@ .message-sources { margin-top: 0.75rem; padding-top: 0.75rem; - border-top: 1px solid rgba(255, 255, 255, 0.1); + border-top: 1px solid var(--color-border-subtle); } .sources-title { @@ -216,8 +226,8 @@ .source-tag { font-size: 0.7rem; - background: rgba(96, 165, 250, 0.15); - border: 1px solid rgba(96, 165, 250, 0.3); + background: rgba(96, 165, 250, 0.12); + border: 1px solid rgba(96, 165, 250, 0.25); color: var(--color-accent-blue); padding: 0.2rem 0.5rem; border-radius: 4px; @@ -250,9 +260,9 @@ } .ai-tag { - color: #c084fc; - background: rgba(124, 58, 237, 0.18); - border: 1px solid rgba(192, 132, 252, 0.25); + color: var(--color-accent-purple); + background: var(--color-surface-hover); + border: 1px solid var(--color-border-subtle); padding: 2px 10px; border-radius: 12px; display: inline-flex; @@ -279,7 +289,7 @@ gap: 6px; font-size: 0.76rem; font-weight: 600; - color: var(--color-accent-purple, #a78bfa); + color: var(--color-accent-purple); } .typing-text { @@ -297,7 +307,7 @@ display: block; width: 7px; height: 7px; - background: var(--color-accent-purple, #8b5cf6); + background: var(--color-accent-purple); box-shadow: 0 0 8px rgba(139, 92, 246, 0.6); border-radius: 50%; animation: typingBounce 1.4s ease-in-out infinite; @@ -321,14 +331,14 @@ display: flex; align-items: flex-end; padding: 1rem 1.5rem; - background: rgba(18, 16, 31, 0.8); - border-top: 1px solid rgba(42, 42, 62, 0.8); + background: var(--color-bg-topbar); + border-top: 1px solid var(--color-card-border); gap: 0.75rem; } .chat-input-form textarea { flex: 1; - background: rgba(30, 30, 50, 0.5); + background: var(--color-input-bg); border: 1px solid var(--color-input-border); border-radius: var(--radius-md); color: var(--color-text-primary); @@ -348,7 +358,7 @@ } .chat-send-btn { - background: linear-gradient(135deg, var(--color-primary-purple), var(--color-primary-blue)); + background: var(--gradient-primary); color: white; border: none; width: 44px; diff --git a/web/frontend/src/components/Dashboard.css b/web/frontend/src/components/Dashboard.css index fa2cb75..7c3ed78 100644 --- a/web/frontend/src/components/Dashboard.css +++ b/web/frontend/src/components/Dashboard.css @@ -17,13 +17,14 @@ .sidebar-nav { width: 72px; min-width: 72px; - background: rgba(13, 11, 28, 0.95); - border-right: 1px solid rgba(42, 42, 62, 0.5); + background: var(--color-bg-sidebar); + border-right: 1px solid var(--color-card-border); display: flex; flex-direction: column; align-items: center; padding: 20px 0; position: relative; + transition: background 0.25s ease, border-color 0.25s ease; } .sidebar-logo-area { @@ -167,9 +168,10 @@ align-items: center; justify-content: space-between; padding: 20px 32px; - border-bottom: 1px solid rgba(42, 42, 62, 0.5); - background: rgba(10, 8, 20, 0.6); + border-bottom: 1px solid var(--color-card-border); + background: var(--color-bg-topbar); flex-shrink: 0; + transition: background 0.25s ease, border-color 0.25s ease; } .top-bar-info h1 { @@ -450,12 +452,13 @@ align-items: center; gap: 16px; padding: 14px 20px; - background: rgba(22, 22, 42, 0.4); - border: 1px solid rgba(42, 42, 62, 0.3); + background: var(--color-input-bg); + border: 1px solid var(--color-card-border); border-radius: 10px; transition: all 0.3s ease; animation: fileSlideIn 0.4s ease forwards; opacity: 0; + box-shadow: 0 2px 8px var(--color-shadow); } .file-row:nth-child(1) { animation-delay: 0.05s; } @@ -470,8 +473,8 @@ } .file-row:hover { - background: rgba(30, 30, 55, 0.5); - border-color: rgba(124, 58, 237, 0.25); + background: var(--color-card-bg-hover); + border-color: var(--color-card-border-hover); } .file-row.deleting { @@ -701,12 +704,12 @@ content: "Delete"; position: absolute; right: calc(100% + 10px); - background: #16162a; + background: var(--color-card-bg); padding: 5px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 600; - color: #ef4444; + color: var(--color-error); white-space: nowrap; opacity: 0; pointer-events: none; @@ -848,8 +851,8 @@ align-items: center; justify-content: space-between; padding: 14px 28px; - border-bottom: 1px solid rgba(42, 42, 62, 0.5); - background: rgba(10, 8, 20, 0.4); + border-bottom: 1px solid var(--color-card-border); + background: var(--color-bg-topbar); flex-shrink: 0; } @@ -948,12 +951,12 @@ } .bubble-ai { - background: rgba(20, 20, 36, 0.85); + background: var(--color-chat-bubble-ai); color: var(--color-text-primary); - border: 1px solid rgba(124, 58, 237, 0.22); + border: 1px solid var(--color-card-border); border-radius: 18px 18px 18px 4px; backdrop-filter: blur(12px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); + box-shadow: 0 4px 16px var(--color-shadow); } .bubble-error { @@ -1101,8 +1104,8 @@ align-items: center; gap: 12px; padding: 14px 28px; - border-top: 1px solid rgba(42, 42, 62, 0.5); - background: rgba(10, 8, 20, 0.6); + border-top: 1px solid var(--color-card-border); + background: var(--color-bg-topbar); flex-shrink: 0; } @@ -1202,7 +1205,7 @@ .modal-backdrop { position: fixed; inset: 0; - background: rgba(5, 5, 16, 0.78); + background: var(--color-modal-overlay); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); z-index: 9999; @@ -1219,10 +1222,10 @@ } .modal-card { - background: #141424; - border: 1px solid rgba(124, 58, 237, 0.25); + background: var(--color-modal-bg); + border: 1px solid var(--color-card-border); border-radius: 20px; - box-shadow: 0 25px 60px rgba(0, 0, 0, 0.6), 0 0 30px rgba(124, 58, 237, 0.1); + box-shadow: 0 25px 60px var(--color-shadow); padding: 32px 28px; max-width: 440px; width: 100%; @@ -1251,7 +1254,7 @@ } .modal-close-btn:hover { - color: #fff; + color: var(--color-text-primary); } .modal-icon-wrap { @@ -1274,7 +1277,7 @@ .modal-title { font-size: 1.3rem; font-weight: 700; - color: #ffffff; + color: var(--color-text-primary); margin-bottom: 10px; } @@ -1287,12 +1290,12 @@ } .modal-filename { - color: var(--color-accent-purple, #a78bfa); + color: var(--color-accent-purple); } .modal-subtext { font-size: 0.85rem; - color: #f87171; + color: var(--color-error); margin-bottom: 24px; } @@ -1306,9 +1309,9 @@ flex: 1; padding: 12px 18px; border-radius: 10px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.15); - color: #ffffff; + background: var(--color-surface-hover); + border: 1px solid var(--color-card-border); + color: var(--color-text-primary); font-size: 0.9rem; font-weight: 600; cursor: pointer; @@ -1316,8 +1319,8 @@ } .modal-btn-cancel:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.25); + background: var(--color-surface-active); + border-color: var(--color-primary-purple); } .modal-btn-delete { diff --git a/web/frontend/src/components/Dashboard.jsx b/web/frontend/src/components/Dashboard.jsx index 7f5459b..a46321c 100644 --- a/web/frontend/src/components/Dashboard.jsx +++ b/web/frontend/src/components/Dashboard.jsx @@ -2,9 +2,9 @@ import { useState, useEffect } from "react"; import { useAuth } from "../context/AuthContext.jsx"; import { useToast } from "../context/ToastContext.jsx"; import ProfileView from "./ProfileView.jsx"; -import SettingsView from "./SettingsView.jsx"; import QuizView from "./QuizView.jsx"; import QuizResultsView from "./QuizResultsView.jsx"; +import ThemeToggle from "./ThemeToggle.jsx"; import "./Dashboard.css"; import DocumentPreviewModal from "./DocumentPreviewModal.jsx"; @@ -114,37 +114,40 @@ function TopBar({ activeTab }) {

{title}

{subtitle}

-
+
+
- {initial} +
+ {initial} +
+ + {displayName} +
- - {displayName} -
); diff --git a/web/frontend/src/components/DocumentPreviewModal.css b/web/frontend/src/components/DocumentPreviewModal.css index a376770..ebc2a46 100644 --- a/web/frontend/src/components/DocumentPreviewModal.css +++ b/web/frontend/src/components/DocumentPreviewModal.css @@ -1,7 +1,7 @@ .modal-overlay { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.6); + background: var(--color-modal-overlay, rgba(0, 0, 0, 0.6)); backdrop-filter: blur(6px); display: flex; align-items: center; @@ -11,14 +11,14 @@ } .modal-card { - background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.6); + background: var(--color-modal-bg, var(--color-card-bg)); + border: 1px solid var(--color-card-border); border-radius: 18px; width: 520px; max-width: 100%; max-height: 85vh; overflow-y: auto; - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5); + box-shadow: 0 24px 80px var(--color-shadow); position: relative; } @@ -39,15 +39,17 @@ align-items: center; padding: 22px 24px 14px 24px; } + .modal-header h3 { - color: #fff; + color: var(--color-text-primary); font-size: 1.1rem; margin: 0; } + .modal-close-btn { background: transparent; border: none; - color: #9ca3af; + color: var(--color-text-muted); font-size: 1.1rem; cursor: pointer; width: 32px; @@ -55,9 +57,10 @@ border-radius: 8px; transition: all 0.2s ease; } + .modal-close-btn:hover { - background: rgba(124, 58, 237, 0.12); - color: #fff; + background: var(--color-surface-hover); + color: var(--color-text-primary); } .modal-body { @@ -65,12 +68,13 @@ } .modal-status-text { - color: #9ca3af; + color: var(--color-text-secondary); text-align: center; padding: 30px 0; } + .modal-status-text.error-text { - color: #ef4444; + color: var(--color-error); } .modal-file-title { @@ -78,16 +82,18 @@ align-items: center; gap: 14px; padding: 14px; - background: rgba(124, 58, 237, 0.06); - border: 1px solid rgba(124, 58, 237, 0.15); + background: var(--color-surface-hover); + border: 1px solid var(--color-border-subtle); border-radius: 12px; margin-bottom: 18px; } + .modal-file-icon { font-size: 1.6rem; } + .modal-filename { - color: #fff; + color: var(--color-text-primary); font-weight: 600; margin: 0 0 4px 0; max-width: 380px; @@ -95,9 +101,10 @@ text-overflow: ellipsis; white-space: nowrap; } + .modal-type-badge { font-size: 0.7rem; - color: #a78bfa; + color: var(--color-accent-purple); font-weight: 600; letter-spacing: 0.5px; } @@ -108,24 +115,27 @@ gap: 12px; margin-bottom: 6px; } + .info-item { - background: rgba(22, 22, 42, 0.6); - border: 1px solid rgba(42, 42, 62, 0.4); + background: var(--color-input-bg); + border: 1px solid var(--color-card-border); border-radius: 12px; padding: 14px 16px; } + .info-item-label { display: block; font-size: 0.68rem; - color: #6b7280; + color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } + .info-item-value { font-size: 0.95rem; font-weight: 600; - color: #fff; + color: var(--color-text-primary); display: flex; align-items: center; gap: 6px; @@ -137,11 +147,13 @@ border-radius: 50%; display: inline-block; } -.solid-dot { background: #22c55e; } + +.solid-dot { background: var(--color-success); } .pulse-dot { - background: #f59e0b; + background: var(--color-warning); animation: pulse-glow 1.4s ease-in-out infinite; } + @keyframes pulse-glow { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } @@ -150,17 +162,19 @@ .metadata-section { margin-top: 18px; padding-top: 18px; - border-top: 1px solid rgba(42, 42, 62, 0.4); + border-top: 1px solid var(--color-border-subtle); } + .metadata-section h4 { - color: #fff; + color: var(--color-text-primary); font-size: 0.9rem; margin: 0 0 12px 0; } + .metadata-note { margin-top: 12px; font-size: 0.8rem; - color: #9ca3af; + color: var(--color-text-secondary); font-style: italic; line-height: 1.5; } diff --git a/web/frontend/src/components/LandingPage.css b/web/frontend/src/components/LandingPage.css index d66573d..8d21065 100644 --- a/web/frontend/src/components/LandingPage.css +++ b/web/frontend/src/components/LandingPage.css @@ -211,3 +211,37 @@ html, body { .hero-title { font-size: 2.2rem; } .navbar, .footer { padding-left: 20px; padding-right: 20px; } } + +/* Light Theme Overrides for Landing Page */ +[data-theme="light"] .landing { + --bg-dark: #f8fafc; + --bg-card: #ffffff; + --border-soft: rgba(0, 0, 0, 0.08); + --text-primary: #0f172a; + --text-secondary: #475569; +} + +[data-theme="light"] .navbar.scrolled { + background: rgba(255, 255, 255, 0.92); +} + +[data-theme="light"] .bento-card, +[data-theme="light"] .flashcard-face { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06); + background: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.08); +} + +[data-theme="light"] .hero-badge { + background: rgba(109, 40, 217, 0.08); + border-color: rgba(109, 40, 217, 0.2); + color: var(--primary); +} + +[data-theme="light"] .tag { + background: #ffffff; + border-color: rgba(0, 0, 0, 0.1); + color: #334155; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + diff --git a/web/frontend/src/components/LandingPage.jsx b/web/frontend/src/components/LandingPage.jsx index 5b3eb9b..854920f 100644 --- a/web/frontend/src/components/LandingPage.jsx +++ b/web/frontend/src/components/LandingPage.jsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from "react"; +import ThemeToggle from "./ThemeToggle.jsx"; import "./LandingPage.css"; const FEATURES = [ @@ -184,6 +185,7 @@ export default function LandingPage({ onNavigate = () => {} }) { e.preventDefault()}>How it Works
+
diff --git a/web/frontend/src/components/ProfileView.css b/web/frontend/src/components/ProfileView.css index 71fbc19..ca927ae 100644 --- a/web/frontend/src/components/ProfileView.css +++ b/web/frontend/src/components/ProfileView.css @@ -22,8 +22,8 @@ /* Profile Header Card */ .profile-header-card { - background: var(--color-card-bg, #16162a); - border: 1px solid rgba(42, 42, 62, 0.4); + background: var(--color-card-bg); + border: 1px solid var(--color-card-border); border-radius: 16px; padding: 32px 36px; display: flex; @@ -31,7 +31,8 @@ gap: 28px; position: relative; overflow: hidden; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + box-shadow: 0 10px 30px var(--color-shadow); + transition: all 0.3s ease; } .profile-header-card::before { @@ -41,14 +42,14 @@ left: 0; right: 0; height: 4px; - background: linear-gradient(to right, #7c3aed, #2563eb); + background: var(--gradient-primary); } .profile-avatar { width: 86px; height: 86px; border-radius: 50%; - background: linear-gradient(135deg, #7c3aed, #ec4899); + background: var(--gradient-logo); display: flex; align-items: center; justify-content: center; @@ -75,24 +76,24 @@ .profile-header-info h2 { font-size: 1.5rem; font-weight: 700; - color: #ffffff; + color: var(--color-text-primary); margin-bottom: 4px; } .profile-header-info .profile-email { font-size: 0.95rem; - color: #60a5fa; + color: var(--color-accent-blue); margin-bottom: 6px; } .profile-header-info .profile-join-date { font-size: 0.82rem; - color: #6b7280; + color: var(--color-text-muted); } .profile-edit-btn { padding: 10px 24px; - background: linear-gradient(to right, #7c3aed, #2563eb); + background: var(--gradient-primary); border: none; border-radius: 10px; color: white; @@ -116,16 +117,17 @@ } .stat-card { - background: var(--color-card-bg, #16162a); - border: 1px solid rgba(42, 42, 62, 0.4); + background: var(--color-card-bg); + border: 1px solid var(--color-card-border); border-radius: 14px; padding: 24px; text-align: center; transition: all 0.3s ease; + box-shadow: 0 4px 14px var(--color-shadow); } .stat-card:hover { - border-color: rgba(124, 58, 237, 0.3); + border-color: var(--color-card-border-hover); transform: translateY(-2px); } @@ -137,15 +139,16 @@ .stat-value { font-size: 2rem; font-weight: 700; - background: linear-gradient(to right, #a78bfa, #60a5fa); + background: var(--gradient-heading); -webkit-background-clip: text; + background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 4px; } .stat-label { font-size: 0.82rem; - color: #6b7280; + color: var(--color-text-muted); font-weight: 500; } @@ -157,10 +160,12 @@ } .profile-section-card { - background: var(--color-card-bg, #16162a); - border: 1px solid rgba(42, 42, 62, 0.4); + background: var(--color-card-bg); + border: 1px solid var(--color-card-border); border-radius: 14px; padding: 24px; + box-shadow: 0 4px 14px var(--color-shadow); + transition: all 0.3s ease; } .profile-section-card.full-width { @@ -170,7 +175,7 @@ .profile-section-card h3 { font-size: 1rem; font-weight: 600; - color: #ffffff; + color: var(--color-text-primary); margin-bottom: 16px; display: flex; align-items: center; @@ -183,7 +188,7 @@ justify-content: space-between; align-items: center; padding: 12px 0; - border-bottom: 1px solid rgba(42, 42, 62, 0.3); + border-bottom: 1px solid var(--color-border-subtle); } .info-row:last-child { @@ -192,18 +197,18 @@ .info-label { font-size: 0.85rem; - color: #9ca3af; + color: var(--color-text-secondary); font-weight: 500; } .info-value { font-size: 0.88rem; font-weight: 600; - color: #ffffff; + color: var(--color-text-primary); } .info-value.muted { - color: #6b7280; + color: var(--color-text-muted); } /* Change Password Form */ @@ -222,7 +227,7 @@ .form-label { font-size: 0.82rem; font-weight: 500; - color: #9ca3af; + color: var(--color-text-secondary); } .password-input-wrapper { @@ -234,27 +239,27 @@ .password-input-wrapper .form-input { width: 100%; padding: 11px 44px 11px 14px; - background: #16162a; - border: 1px solid #2a2a3e; - border-radius: 0.5rem; - color: #ffffff; + background: var(--color-input-bg); + border: 1px solid var(--color-input-border); + border-radius: var(--radius-md); + color: var(--color-text-primary); font-size: 0.88rem; outline: none; transition: all 0.2s ease; } .password-input-wrapper .form-input:focus { - border-color: #7c3aed; + border-color: var(--color-primary-purple); box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); } .password-input-wrapper .form-input.input-error { - border-color: #ef4444; + border-color: var(--color-error); box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); } .password-input-wrapper .form-input.input-success { - border-color: #22c55e; + border-color: var(--color-success); box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1); } @@ -275,7 +280,7 @@ .form-error { font-size: 0.75rem; - color: #ef4444; + color: var(--color-error); min-height: 16px; } @@ -283,7 +288,7 @@ .strength-bar-container { width: 100%; height: 4px; - background: rgba(42, 42, 62, 0.8); + background: var(--color-border-subtle); border-radius: 4px; margin-top: 4px; overflow: hidden; @@ -298,22 +303,22 @@ .strength-bar.weak { width: 33%; - background: #ef4444; + background: var(--color-error); } .strength-bar.medium { width: 66%; - background: #f59e0b; + background: var(--color-warning); } .strength-bar.strong { width: 100%; - background: #22c55e; + background: var(--color-success); } .strength-text { font-size: 0.72rem; - color: #6b7280; + color: var(--color-text-muted); margin-top: 4px; display: block; } @@ -328,7 +333,7 @@ .req-item { font-size: 0.72rem; - color: #6b7280; + color: var(--color-text-muted); display: flex; align-items: center; gap: 6px; @@ -340,17 +345,17 @@ } .req-item.met { - color: #22c55e; + color: var(--color-success); } .req-item.met .req-icon { - color: #22c55e; + color: var(--color-success); } /* Form Message Banner */ .form-message { padding: 10px 14px; - border-radius: 0.5rem; + border-radius: var(--radius-md); font-size: 0.82rem; font-weight: 500; } @@ -358,20 +363,20 @@ .form-message.success { background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.25); - color: #22c55e; + color: var(--color-success); } .form-message.error { background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.25); - color: #ef4444; + color: var(--color-error); } .btn-submit-password { padding: 12px 24px; - background: linear-gradient(to right, #7c3aed, #2563eb); + background: var(--gradient-primary); border: none; - border-radius: 0.5rem; + border-radius: var(--radius-md); color: white; font-size: 0.9rem; font-weight: 600; @@ -400,7 +405,7 @@ background: rgba(239, 68, 68, 0.08); border: 1.5px solid rgba(239, 68, 68, 0.3); border-radius: 10px; - color: #ef4444; + color: var(--color-error); font-size: 0.88rem; font-weight: 600; cursor: pointer; @@ -409,16 +414,17 @@ .btn-logout-profile:hover { background: rgba(239, 68, 68, 0.15); - border-color: rgba(239, 68, 68, 0.6); + border-color: var(--color-error); box-shadow: 0 0 14px rgba(239, 68, 68, 0.2); } /* Danger Zone */ .danger-zone { - background: var(--color-card-bg, #16162a); + background: var(--color-card-bg); border: 1px solid rgba(239, 68, 68, 0.2); border-radius: 14px; padding: 24px; + box-shadow: 0 4px 14px var(--color-shadow); } .danger-zone.full-width { @@ -428,7 +434,7 @@ .danger-zone h3 { font-size: 1rem; font-weight: 600; - color: #ef4444; + color: var(--color-error); margin-bottom: 12px; display: flex; align-items: center; @@ -437,7 +443,7 @@ .danger-zone p { font-size: 0.85rem; - color: #9ca3af; + color: var(--color-text-secondary); margin-bottom: 16px; } @@ -446,7 +452,7 @@ background: rgba(239, 68, 68, 0.1); border: 1.5px solid rgba(239, 68, 68, 0.4); border-radius: 10px; - color: #ef4444; + color: var(--color-error); font-size: 0.88rem; font-weight: 600; cursor: pointer; @@ -455,7 +461,7 @@ .btn-delete-account:hover { background: rgba(239, 68, 68, 0.2); - border-color: #ef4444; + border-color: var(--color-error); box-shadow: 0 0 14px rgba(239, 68, 68, 0.2); } diff --git a/web/frontend/src/components/ProfileView.jsx b/web/frontend/src/components/ProfileView.jsx index abf2d8f..7890fec 100644 --- a/web/frontend/src/components/ProfileView.jsx +++ b/web/frontend/src/components/ProfileView.jsx @@ -1,5 +1,6 @@ import { useState, useEffect } from "react"; import { useAuth } from "../context/AuthContext.jsx"; +import ThemeToggle from "./ThemeToggle.jsx"; import "./ProfileView.css"; export default function ProfileView() { @@ -371,6 +372,18 @@ export default function ProfileView() {
+ {/* Appearance & Preferences Card */} +
+

🎨 Appearance & Preferences

+
+
+ Theme + Toggle between Dark (default) and Light theme +
+ +
+
+ {/* Session Card */}

🚪 Session

diff --git a/web/frontend/src/components/QuizResultsView.css b/web/frontend/src/components/QuizResultsView.css index 98450ca..0e944b6 100644 --- a/web/frontend/src/components/QuizResultsView.css +++ b/web/frontend/src/components/QuizResultsView.css @@ -30,17 +30,17 @@ .stat-card { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.4); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 24px; text-align: center; transition: all 0.3s ease; + box-shadow: 0 4px 14px var(--color-shadow); } .stat-card:hover { - border-color: rgba(124, 58, 237, 0.3); + border-color: var(--color-card-border-hover); transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(124, 58, 237, 0.1); } .stat-icon { @@ -106,8 +106,8 @@ } .filter-select option { - background: #16162a; - color: #ffffff; + background: var(--color-card-bg); + color: var(--color-text-primary); } /* ═══════════════════════════════════════════════════════════════════════════ @@ -121,26 +121,28 @@ .no-results-card { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.4); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 32px; text-align: center; color: var(--color-text-secondary); font-size: 0.95rem; + box-shadow: 0 4px 14px var(--color-shadow); } /* Result Group Card */ .result-group-card { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.4); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 24px; + box-shadow: 0 4px 14px var(--color-shadow); transition: all 0.3s ease; } .result-group-card:hover { - border-color: rgba(124, 58, 237, 0.25); - background: rgba(22, 22, 42, 0.6); + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); } .result-header { @@ -183,23 +185,23 @@ justify-content: center; font-weight: 700; position: relative; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + box-shadow: 0 4px 12px var(--color-shadow); } .score-circle.good { - background: linear-gradient(135deg, rgba(34, 197, 150, 0.2), rgba(34, 197, 150, 0.1)); - border: 2px solid rgba(34, 197, 150, 0.4); + background: rgba(34, 197, 94, 0.12); + border: 2px solid rgba(34, 197, 94, 0.4); color: var(--color-success); } .score-circle.fair { - background: linear-gradient(135deg, rgba(245, 158, 11, 0.2), rgba(245, 158, 11, 0.1)); + background: rgba(245, 158, 11, 0.12); border: 2px solid rgba(245, 158, 11, 0.4); - color: #f59e0b; + color: var(--color-warning); } .score-circle.poor { - background: linear-gradient(135deg, rgba(239, 68, 68, 0.2), rgba(239, 68, 68, 0.1)); + background: rgba(239, 68, 68, 0.12); border: 2px solid rgba(239, 68, 68, 0.4); color: var(--color-error); } @@ -214,7 +216,7 @@ gap: 24px; margin-bottom: 16px; padding-bottom: 16px; - border-bottom: 1px solid rgba(42, 42, 62, 0.3); + border-bottom: 1px solid var(--color-border-subtle); } .result-stat { @@ -230,9 +232,9 @@ /* View Details Button */ .btn-view-details { padding: 8px 16px; - background: rgba(124, 58, 237, 0.12); - border: 1px solid rgba(124, 58, 237, 0.3); - color: var(--color-accent-purple); + background: var(--color-surface-hover); + border: 1px solid var(--color-card-border); + color: var(--color-primary-purple); border-radius: var(--radius-md); font-size: 0.85rem; font-weight: 600; @@ -241,7 +243,7 @@ } .btn-view-details:hover { - background: rgba(124, 58, 237, 0.2); + background: var(--color-surface-active); border-color: var(--color-primary-purple); } @@ -249,7 +251,7 @@ .result-details { margin-top: 16px; padding-top: 16px; - border-top: 1px solid rgba(42, 42, 62, 0.3); + border-top: 1px solid var(--color-border-subtle); display: flex; flex-direction: column; gap: 12px; @@ -270,9 +272,10 @@ .detail-row { display: flex; gap: 12px; - padding: 12px; - background: rgba(30, 30, 50, 0.5); + padding: 12px 14px; + background: var(--color-input-bg); border-radius: var(--radius-md); + border: 1px solid var(--color-border-subtle); border-left: 3px solid transparent; } @@ -319,19 +322,13 @@ color: var(--color-success); } -/* ═══════════════════════════════════════════════════════════════════════════ - Topics to Review - ═══════════════════════════════════════════════════════════════════════════ */ +/* Topics to Review */ .topics-to-review-card { background: var(--color-card-bg); - border: 1px solid rgba(124, 58, 237, 0.2); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 24px; - background: linear-gradient( - 135deg, - rgba(124, 58, 237, 0.05), - rgba(124, 58, 237, 0.02) - ); + box-shadow: 0 4px 14px var(--color-shadow); } .topics-to-review-card h3 { @@ -348,16 +345,15 @@ font-style: italic; } -/* ═══════════════════════════════════════════════════════════════════════════ - Empty / Loading / Error States - ═══════════════════════════════════════════════════════════════════════════ */ +/* Empty / Loading / Error States */ .empty-state-card, .error-state-card { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.4); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 60px 40px; text-align: center; + box-shadow: 0 4px 14px var(--color-shadow); } .empty-icon, @@ -404,10 +400,11 @@ .loading-state { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.4); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 60px 40px; text-align: center; + box-shadow: 0 4px 14px var(--color-shadow); } .loading-dots { @@ -445,9 +442,7 @@ margin-bottom: 6px; } -/* ═══════════════════════════════════════════════════════════════════════════ - Responsive - ═══════════════════════════════════════════════════════════════════════════ */ +/* Responsive */ @media (max-width: 768px) { .results-stats { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); diff --git a/web/frontend/src/components/QuizView.css b/web/frontend/src/components/QuizView.css index 5bc6d6e..eabad40 100644 --- a/web/frontend/src/components/QuizView.css +++ b/web/frontend/src/components/QuizView.css @@ -24,11 +24,13 @@ ═══════════════════════════════════════════════════════════════════════════ */ .quiz-request-card { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.5); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 32px 36px; max-width: 700px; margin: 0 auto; + box-shadow: 0 4px 20px var(--color-shadow); + transition: all 0.3s ease; } .quiz-request-card h2 { @@ -88,7 +90,7 @@ } .form-input:disabled { - background: rgba(42, 42, 62, 0.4); + background: var(--color-surface-hover); color: var(--color-text-muted); cursor: not-allowed; } @@ -165,8 +167,8 @@ align-items: center; gap: 8px; padding: 16px 12px; - background: rgba(30, 30, 50, 0.5); - border: 2px solid rgba(42, 42, 62, 0.5); + background: var(--color-input-bg); + border: 1.5px solid var(--color-input-border); border-radius: var(--radius-md); color: var(--color-text-secondary); font-size: 0.85rem; @@ -176,15 +178,15 @@ } .quiz-type-btn:hover:not(:disabled) { - border-color: rgba(124, 58, 237, 0.3); - background: rgba(124, 58, 237, 0.08); - color: var(--color-accent-purple); + border-color: var(--color-primary-purple); + background: var(--color-surface-hover); + color: var(--color-primary-purple); } .quiz-type-btn.active { - background: rgba(124, 58, 237, 0.15); + background: var(--color-surface-active); border-color: var(--color-primary-purple); - color: var(--color-accent-purple); + color: var(--color-primary-purple); box-shadow: 0 0 12px rgba(124, 58, 237, 0.2); } @@ -252,7 +254,7 @@ justify-content: space-between; align-items: center; padding: 20px 0; - border-bottom: 1px solid rgba(42, 42, 62, 0.3); + border-bottom: 1px solid var(--color-border-subtle); } .quiz-header-info h2 { @@ -272,9 +274,9 @@ .btn-back-quiz { padding: 8px 16px; - background: rgba(124, 58, 237, 0.12); - border: 1px solid rgba(124, 58, 237, 0.3); - color: var(--color-accent-purple); + background: var(--color-surface-hover); + border: 1px solid var(--color-card-border); + color: var(--color-primary-purple); border-radius: var(--radius-md); font-size: 0.85rem; font-weight: 600; @@ -283,7 +285,7 @@ } .btn-back-quiz:hover { - background: rgba(124, 58, 237, 0.2); + background: var(--color-surface-active); border-color: var(--color-primary-purple); } @@ -303,15 +305,16 @@ /* Question Card */ .question-card { background: var(--color-card-bg); - border: 1px solid rgba(42, 42, 62, 0.4); + border: 1px solid var(--color-card-border); border-radius: var(--radius-lg); padding: 24px; + box-shadow: 0 4px 14px var(--color-shadow); transition: all 0.3s ease; } .question-card:hover { - border-color: rgba(124, 58, 237, 0.25); - background: rgba(22, 22, 42, 0.6); + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); } .question-header { @@ -327,7 +330,7 @@ text-transform: uppercase; letter-spacing: 0.8px; color: var(--color-accent-purple); - background: rgba(124, 58, 237, 0.12); + background: var(--color-surface-hover); padding: 4px 10px; border-radius: 4px; } @@ -343,13 +346,13 @@ } .question-status.answered { - background: rgba(34, 197, 150, 0.1); + background: rgba(34, 197, 94, 0.1); color: var(--color-success); } .question-status.unanswered { background: rgba(245, 158, 11, 0.1); - color: #f59e0b; + color: var(--color-warning); } .question-text { @@ -373,16 +376,16 @@ align-items: center; gap: 12px; padding: 12px 14px; - background: rgba(30, 30, 50, 0.5); - border: 1px solid rgba(42, 42, 62, 0.4); + background: var(--color-input-bg); + border: 1px solid var(--color-input-border); border-radius: var(--radius-md); cursor: pointer; transition: all 0.2s ease; } .option-label:hover { - background: rgba(124, 58, 237, 0.08); - border-color: rgba(124, 58, 237, 0.2); + background: var(--color-surface-hover); + border-color: var(--color-primary-purple); } .option-input { @@ -402,7 +405,7 @@ .answer-input { width: 100%; padding: 12px 16px; - background: rgba(30, 30, 50, 0.5); + background: var(--color-input-bg); border: 1px solid var(--color-input-border); border-radius: var(--radius-md); color: var(--color-text-primary); @@ -436,7 +439,7 @@ letter-spacing: 0.5px; padding: 3px 8px; border-radius: 4px; - background: rgba(124, 58, 237, 0.1); + background: var(--color-surface-hover); color: var(--color-accent-purple); } @@ -446,7 +449,7 @@ gap: 12px; justify-content: flex-end; padding-top: 20px; - border-top: 1px solid rgba(42, 42, 62, 0.3); + border-top: 1px solid var(--color-border-subtle); } .btn-submit-quiz { diff --git a/web/frontend/src/components/ThemeToggle.css b/web/frontend/src/components/ThemeToggle.css new file mode 100644 index 0000000..0032cd2 --- /dev/null +++ b/web/frontend/src/components/ThemeToggle.css @@ -0,0 +1,96 @@ +/* Theme Toggle Button Styles */ +.theme-toggle-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + background: var(--color-surface-hover, rgba(124, 58, 237, 0.08)); + border: 1px solid var(--color-border-subtle, rgba(124, 58, 237, 0.2)); + border-radius: 10px; + padding: 8px 12px; + cursor: pointer; + color: var(--color-text-primary); + font-family: var(--font-body); + font-size: 0.85rem; + font-weight: 500; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + outline: none; + user-select: none; +} + +.theme-toggle-btn:hover { + background: var(--color-surface-active, rgba(124, 58, 237, 0.16)); + border-color: var(--color-primary-purple); + transform: translateY(-1px); + box-shadow: 0 4px 12px var(--color-shadow); +} + +.theme-toggle-btn:active { + transform: translateY(0); +} + +.theme-toggle-icon-container { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.theme-toggle-btn:hover .theme-toggle-icon-container { + transform: rotate(15deg) scale(1.1); +} + +.theme-svg { + display: block; + transition: all 0.25s ease; +} + +.sun-svg { + filter: drop-shadow(0 0 4px rgba(245, 158, 11, 0.6)); +} + +.moon-svg { + filter: drop-shadow(0 0 4px rgba(99, 102, 241, 0.6)); +} + +.theme-toggle-label { + font-size: 0.84rem; + font-weight: 500; + color: var(--color-text-secondary); +} + +.theme-toggle-btn:hover .theme-toggle-label { + color: var(--color-text-primary); +} + +/* Switch Variant for Profile Settings */ +.theme-switch-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 0; + border-bottom: 1px solid var(--color-border-subtle); +} + +.theme-switch-row:last-child { + border-bottom: none; +} + +.theme-switch-info { + display: flex; + flex-direction: column; + gap: 2px; +} + +.theme-switch-title { + font-size: 0.92rem; + font-weight: 600; + color: var(--color-text-primary); +} + +.theme-switch-desc { + font-size: 0.8rem; + color: var(--color-text-muted); +} diff --git a/web/frontend/src/components/ThemeToggle.jsx b/web/frontend/src/components/ThemeToggle.jsx new file mode 100644 index 0000000..86117d9 --- /dev/null +++ b/web/frontend/src/components/ThemeToggle.jsx @@ -0,0 +1,65 @@ +import { useTheme } from "../context/ThemeContext.jsx"; +import "./ThemeToggle.css"; + +export default function ThemeToggle({ showLabel = false, className = "" }) { + const { theme, toggleTheme, isDark } = useTheme(); + + return ( + + ); +} diff --git a/web/frontend/src/context/ThemeContext.jsx b/web/frontend/src/context/ThemeContext.jsx new file mode 100644 index 0000000..8cb1fa4 --- /dev/null +++ b/web/frontend/src/context/ThemeContext.jsx @@ -0,0 +1,42 @@ +import { createContext, useContext, useState, useEffect } from "react"; + +const ThemeContext = createContext(); + +export function ThemeProvider({ children }) { + const [theme, setTheme] = useState(() => { + try { + const saved = localStorage.getItem("studymind_theme"); + if (saved === "light" || saved === "dark") { + return saved; + } + } catch {} + return "dark"; // Default is Dark Theme + }); + + useEffect(() => { + document.documentElement.setAttribute("data-theme", theme); + try { + localStorage.setItem("studymind_theme", theme); + } catch {} + }, [theme]); + + const toggleTheme = () => { + setTheme((prev) => (prev === "dark" ? "light" : "dark")); + }; + + const isDark = theme === "dark"; + + return ( + + {children} + + ); +} + +export function useTheme() { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + return context; +} diff --git a/web/frontend/src/index.css b/web/frontend/src/index.css index 6d584a4..32ad287 100644 --- a/web/frontend/src/index.css +++ b/web/frontend/src/index.css @@ -1,10 +1,19 @@ :root { - /* Colors */ - --color-bg-left: #12101f; - --color-bg-right: #050508; - --color-card-bg: #16162a; - --color-input-bg: #16162a; - --color-input-border: #2a2a3e; + /* ═══════════════════════════════════════════════════════════════════════════ + Dark Theme (Default) + ═══════════════════════════════════════════════════════════════════════════ */ + --color-bg-left: #0e0c1b; + --color-bg-right: #06050b; + --color-bg-body: #0a0814; + --color-bg-sidebar: rgba(13, 11, 28, 0.96); + --color-bg-topbar: rgba(10, 8, 20, 0.7); + --color-card-bg: #151329; + --color-card-bg-hover: #1c1936; + --color-card-border: rgba(50, 48, 80, 0.6); + --color-card-border-hover: rgba(124, 58, 237, 0.4); + + --color-input-bg: #121024; + --color-input-border: #2e2a4a; --color-input-border-hover: #4c4c6e; --color-text-primary: #ffffff; @@ -19,17 +28,77 @@ --color-error: #ef4444; --color-success: #22c55e; + --color-warning: #f59e0b; - --gradient-primary: linear-gradient(to right, var(--color-primary-purple), var(--color-primary-blue)); + --gradient-primary: linear-gradient(135deg, var(--color-primary-purple), var(--color-primary-blue)); --gradient-logo: linear-gradient(135deg, var(--color-primary-purple), var(--color-accent-pink)); - --gradient-heading: linear-gradient(to right, var(--color-accent-purple), var(--color-accent-blue)); + --gradient-heading: linear-gradient(135deg, var(--color-accent-purple), var(--color-accent-blue)); - /* Fonts */ - --font-body: 'Poppins', sans-serif; + /* UI Surface Overlays */ + --color-surface-translucent: rgba(22, 22, 42, 0.7); + --color-surface-hover: rgba(124, 58, 237, 0.08); + --color-surface-active: rgba(124, 58, 237, 0.16); + --color-modal-bg: #16142e; + --color-modal-overlay: rgba(5, 5, 12, 0.8); + --color-chat-bubble-ai: #1c1936; + --color-chat-bubble-user: linear-gradient(135deg, #7c3aed, #2563eb); + --color-border-subtle: rgba(255, 255, 255, 0.08); + --color-shadow: rgba(0, 0, 0, 0.35); - /* Radius */ + /* Fonts & Layout */ + --font-body: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --radius-sm: 0.375rem; --radius-md: 0.5rem; --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +/* ═══════════════════════════════════════════════════════════════════════════ + Light Theme + ═══════════════════════════════════════════════════════════════════════════ */ +[data-theme="light"] { + --color-bg-left: #f8fafc; + --color-bg-right: #f1f5f9; + --color-bg-body: #f8fafc; + --color-bg-sidebar: #ffffff; + --color-bg-topbar: rgba(255, 255, 255, 0.9); + --color-card-bg: #ffffff; + --color-card-bg-hover: #f8fafc; + --color-card-border: #e2e8f0; + --color-card-border-hover: rgba(109, 40, 217, 0.35); + + --color-input-bg: #ffffff; + --color-input-border: #cbd5e1; + --color-input-border-hover: #94a3b8; + + --color-text-primary: #0f172a; + --color-text-secondary: #475569; + --color-text-muted: #64748b; + + --color-primary-purple: #6d28d9; + --color-primary-blue: #1d4ed8; + --color-accent-blue: #2563eb; + --color-accent-purple: #7c3aed; + --color-accent-pink: #db2777; + + --color-error: #dc2626; + --color-success: #16a34a; + --color-warning: #d97706; + + --gradient-primary: linear-gradient(135deg, #6d28d9, #1d4ed8); + --gradient-logo: linear-gradient(135deg, #7c3aed, #db2777); + --gradient-heading: linear-gradient(135deg, #6d28d9, #2563eb); + + /* UI Surface Overlays */ + --color-surface-translucent: rgba(255, 255, 255, 0.85); + --color-surface-hover: rgba(109, 40, 217, 0.06); + --color-surface-active: rgba(109, 40, 217, 0.12); + --color-modal-bg: #ffffff; + --color-modal-overlay: rgba(15, 23, 42, 0.5); + --color-chat-bubble-ai: #f1f5f9; + --color-chat-bubble-user: linear-gradient(135deg, #6d28d9, #2563eb); + --color-border-subtle: rgba(0, 0, 0, 0.08); + --color-shadow: rgba(15, 23, 42, 0.06); } * { @@ -44,4 +113,7 @@ html, body, #root { body { font-family: var(--font-body); + background: var(--color-bg-body); + color: var(--color-text-primary); + transition: background-color 0.25s ease, color 0.25s ease; } \ No newline at end of file