diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3ad54b9..8af9478 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "jwt-decode": "^4.0.0", "katex": "^0.16.22", "lodash": "^4.17.21", + "lucide-react": "^0.548.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.7.0", @@ -11758,6 +11759,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.548.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.548.0.tgz", + "integrity": "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index d36f312..c3295e8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "jwt-decode": "^4.0.0", "katex": "^0.16.22", "lodash": "^4.17.21", + "lucide-react": "^0.548.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.7.0", diff --git a/frontend/src/App.js b/frontend/src/App.js index bccaba3..0ab27bc 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -9,12 +9,16 @@ import AuthSuccess from './pages/AuthSuccess'; import ProtectedRoute from './components/ProtectedRoute'; import { AuthProvider } from './context/AuthContext'; +import ThemeToggle from './components/ThemeToggle'; function App() { return ( - - } /> + +
+ +
+ } /> } /> +
+

CollabSpace

+
+ {/* place ThemeToggle where appropriate in your toolbar/header */} + +
+
+ +
+
+

Welcome to CollabSpace

+

Your content goes here. The theme toggle above switches light/dark modes.

+
+
+ + ); +} diff --git a/frontend/src/components/ProtectedRoute.jsx b/frontend/src/components/ProtectedRoute.jsx new file mode 100644 index 0000000..765c717 --- /dev/null +++ b/frontend/src/components/ProtectedRoute.jsx @@ -0,0 +1,27 @@ +import React from "react"; +import { Navigate } from "react-router-dom"; + +export default function ProtectedRoute({ children }) { + const isDev = + (typeof import !== "undefined" && + typeof import.meta !== "undefined" && + import.meta.env && + import.meta.env.MODE === "development") || + process.env.NODE_ENV === "development"; + + if (isDev) { + return children; // bypass auth in dev + } + + // existing production check — edit the key to match your app + const token = + localStorage.getItem("token") || + localStorage.getItem("authToken") || + sessionStorage.getItem("token"); + + if (!token) { + return ; + } + + return children; +} diff --git a/frontend/src/components/ThemeToggle.jsx b/frontend/src/components/ThemeToggle.jsx new file mode 100644 index 0000000..39b4995 --- /dev/null +++ b/frontend/src/components/ThemeToggle.jsx @@ -0,0 +1,71 @@ +import React, { useEffect, useState } from "react"; +import { Sun, Moon } from "lucide-react"; + +export default function ThemeToggle() { + const [darkMode, setDarkMode] = useState(() => localStorage.getItem("theme") === "dark"); + + useEffect(() => { + if (darkMode) { + document.documentElement.classList.add("dark"); + localStorage.setItem("theme", "dark"); + } else { + document.documentElement.classList.remove("dark"); + localStorage.setItem("theme", "light"); + } + }, [darkMode]); + +return ( +
+ +
+); + +} \ No newline at end of file diff --git a/frontend/src/hooks/useTheme.js b/frontend/src/hooks/useTheme.js new file mode 100644 index 0000000..628089f --- /dev/null +++ b/frontend/src/hooks/useTheme.js @@ -0,0 +1,71 @@ +// src/hooks/useTheme.js +import { useEffect, useState, useCallback } from "react"; + +const STORAGE_KEY = "collabspace:theme"; // persist key + +function getPreferredTheme() { + // 1) explicit saved value + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved === "dark" || saved === "light") return saved; + } catch (e) { + // ignore + } + + // 2) system preference + if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { + return "dark"; + } + + // default + return "light"; +} + +export default function useTheme() { + const [theme, setThemeState] = useState(() => getPreferredTheme()); + + const applyTheme = useCallback((t) => { + const root = document.documentElement; + if (!root) return; + if (t === "dark") root.classList.add("dark"); + else root.classList.remove("dark"); + }, []); + + useEffect(() => { + applyTheme(theme); + try { + localStorage.setItem(STORAGE_KEY, theme); + } catch (e) {} + }, [theme, applyTheme]); + + // observe system preference changes so the app can respond + useEffect(() => { + const mq = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)"); + if (!mq) return; + const handle = (e) => { + // only change if user hasn't explicitly saved a preference + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved !== null) return; + } catch (err) {} + setThemeState(e.matches ? "dark" : "light"); + }; + if (mq.addEventListener) mq.addEventListener("change", handle); + else mq.addListener(handle); + return () => { + if (mq.removeEventListener) mq.removeEventListener("change", handle); + else mq.removeListener(handle); + }; + }, []); + + const setTheme = useCallback((t) => { + if (t !== "dark" && t !== "light") return; + setThemeState(t); + }, []); + + const toggle = useCallback(() => { + setThemeState((prev) => (prev === "dark" ? "light" : "dark")); + }, []); + + return { theme, setTheme, toggle }; +} diff --git a/frontend/src/index.css b/frontend/src/index.css index ec2585e..6500fd6 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -11,3 +11,44 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } +/* src/index.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Define light theme variables on :root and dark overrides under .dark */ +:root { + --bg: #ffffff; + --panel: #f8fafc; + --muted: #6b7280; + --text: #0f172a; + --primary: #4f46e5; +} + +/* When ".dark" is applied to or , override */ +.dark { + --bg: #0b1220; + --panel: #071127; + --muted: #94a3b8; + --text: #e6eef8; + --primary: #7c5cff; +} + +/* Example utility classes using variables */ +body { + background-color: var(--bg); + color: var(--text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/*make panels/cards automatically switch */ +.card { + background-color: var(--panel); + color: var(--text); +} + +/* small helper for the theme toggle button */ +.theme-toggle { + @apply inline-flex items-center justify-center rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-offset-1; +} diff --git a/frontend/src/index.js b/frontend/src/index.js index 0ddc68d..e3130f0 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -2,7 +2,7 @@ // import ReactDOM from 'react-dom/client'; // import './index.css'; // import App from './App'; -import reportWebVitals from './reportWebVitals'; + // const root = ReactDOM.createRoot(document.getElementById('root')); // root.render( @@ -13,10 +13,37 @@ import reportWebVitals from './reportWebVitals'; // src/main.jsx + import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; +import './index.css'; import App from './App'; +import reportWebVitals from './reportWebVitals'; + +// Dark-mode initialization (runs before React renders) --- +(function initTheme() { + try { + const STORAGE_KEY = 'collabspace:theme'; + const saved = localStorage.getItem(STORAGE_KEY); + const prefersDark = + window.matchMedia && + window.matchMedia('(prefers-color-scheme: dark)').matches; + const theme = + saved === 'dark' || saved === 'light' + ? saved + : prefersDark + ? 'dark' + : 'light'; + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + } catch (e) { + console.error('Theme init error', e); + } +})(); ReactDOM.createRoot(document.getElementById('root')).render( @@ -26,7 +53,10 @@ ReactDOM.createRoot(document.getElementById('root')).render( ); +// CRA performance helper (optional) +reportWebVitals(); + // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); + diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx new file mode 100644 index 0000000..70a6840 --- /dev/null +++ b/frontend/src/index.jsx @@ -0,0 +1,23 @@ + +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; +import "./index.css"; + +// Immediately apply the saved or preferred theme before React paints +(function initTheme() { + try { + const STORAGE_KEY = "collabspace:theme"; + const saved = localStorage.getItem(STORAGE_KEY); + const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = saved === "dark" || saved === "light" ? saved : (prefersDark ? "dark" : "light"); + if (theme === "dark") document.documentElement.classList.add("dark"); + else document.documentElement.classList.remove("dark"); + } catch (e) { + // ignore + } +})(); + +const container = document.getElementById("root"); +const root = createRoot(container); +root.render(); diff --git a/frontend/src/pages/Dashboard.css b/frontend/src/pages/Dashboard.css index 8634a8a..d6b415e 100644 --- a/frontend/src/pages/Dashboard.css +++ b/frontend/src/pages/Dashboard.css @@ -1,253 +1,121 @@ -/* === Base Layout === */ +/* Dashboard.css */ +:root { + --bg-color: #f9f9f9; + --text-color: #222; + --card-bg: #fff; + --border-color: #ddd; + --button-bg: #007bff; + --button-text: #fff; +} + +body.dark-mode { + --bg-color: #1e1e1e; + --text-color: #eaeaea; + --card-bg: #2b2b2b; + --border-color: #444; + --button-bg: #4a90e2; + --button-text: #f9f9f9; +} + .dashboard-container { - font-family: 'Segoe UI', sans-serif; - background-color: #f1f3f4; - min-height: 100vh; display: flex; flex-direction: column; + background: var(--bg-color); + color: var(--text-color); + min-height: 100vh; + transition: background 0.3s, color 0.3s; } -/* === Header === */ .dashboard-header { - background-color: #fff; - padding: 16px 32px; display: flex; justify-content: space-between; align-items: center; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); - position: sticky; - top: 0; - z-index: 10; -} - -.dashboard-header h1 { - font-size: 20px; - color: #202124; - font-weight: 500; + padding: 16px; + border-bottom: 1px solid var(--border-color); + background: var(--card-bg); } -.user-info { +.user-controls { display: flex; align-items: center; - gap: 16px; -} - -.user-info span { - font-size: 14px; - color: #5f6368; + gap: 1rem; } -.user-info button { - background-color: #d93025; - color: #fff; - padding: 6px 14px; - font-size: 13px; - border: none; - border-radius: 20px; +.theme-toggle { + background: transparent; + border: 1px solid var(--border-color); + color: var(--text-color); + border-radius: 6px; + padding: 6px 10px; cursor: pointer; - transition: background 0.2s ease; + transition: all 0.3s; +} +.theme-toggle:hover { + background: var(--border-color); } -.user-info button:hover { - background-color: #c5221f; +.dashboard-sidebar { + padding: 16px; + background: var(--card-bg); + border-bottom: 1px solid var(--border-color); } -/* === Main Content === */ .dashboard-main { - padding: 32px; flex: 1; + padding: 20px; } -/* === Dashboard Actions === */ -.dashboard-actions { - display: flex; - gap: 12px; - margin-bottom: 24px; - flex-wrap: wrap; -} - -/* === Create Button === */ .create-button { - background-color: #1a73e8; - color: #fff; - padding: 10px 20px; + background: var(--button-bg); + color: var(--button-text); + padding: 10px 16px; border: none; - font-size: 15px; - border-radius: 24px; + border-radius: 6px; cursor: pointer; - box-shadow: 0 1px 3px rgba(60, 64, 67, 0.3); - transition: background 0.2s ease, box-shadow 0.2s ease; + transition: opacity 0.3s; } - .create-button:hover { - background-color: #185abc; - box-shadow: 0 2px 6px rgba(60, 64, 67, 0.3); -} - - - -/* === Search Bar === */ -.doc-search-bar { - width: 100%; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 24px; -} - -.search-input { - width: 100%; - max-width: 320px; - padding: 10px 16px; - border: 1.5px solid #cbd5e1; - border-radius: 24px; - font-size: 15px; - background: #f8fafc; - color: #22223b; - outline: none; - transition: border 0.2s, box-shadow 0.2s; - box-shadow: 0 1px 3px rgba(60, 64, 67, 0.06); -} - -.search-input:focus { - border-color: #2563eb; - box-shadow: 0 2px 8px rgba(37, 99, 235, 0.10); - background: #fff; + opacity: 0.85; } -/* === Document Grid === */ .doc-list { - display: grid; - gap: 24px; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); list-style: none; padding: 0; - margin: 0; + margin: 20px 0; + display: grid; + gap: 12px; } -/* === Document Card === */ .doc-card { - background-color: #fff; - border-radius: 12px; - padding: 16px; - box-shadow: 0 1px 3px rgba(60, 64, 67, 0.15); - transition: box-shadow 0.2s ease, transform 0.1s ease; - cursor: pointer; display: flex; + justify-content: space-between; align-items: center; - gap: 12px; - position: relative; -} - -.doc-icon { - font-size: 2rem; - flex-shrink: 0; -} - -.doc-info { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 4px; + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 12px; + cursor: pointer; + transition: transform 0.2s, background 0.3s; } - .doc-card:hover { - box-shadow: 0 4px 10px rgba(60, 64, 67, 0.2); - transform: translateY(-1px) scale(1.03); + transform: scale(1.02); + background: var(--border-color); } -.doc-card h3 { +.doc-info h3 { margin: 0; - font-size: 16px; - color: #202124; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.item-type { - font-size: 12px; - color: #6c757d; - background-color: #f8f9fa; - padding: 2px 8px; - border-radius: 12px; - align-self: flex-start; + font-size: 1.1em; } -/* === Delete Button === */ .delete-button { - position: absolute; - top: 8px; - right: 8px; background: none; border: none; - font-size: 16px; cursor: pointer; - opacity: 0.6; - transition: opacity 0.2s ease; - padding: 4px; - border-radius: 4px; -} - -.delete-button:hover { - opacity: 1; - background-color: #f8f9fa; -} - -/* === Label / Status (optional) === */ -.open-label { - display: inline-block; - margin-top: 6px; - background-color: #34a853; - color: #fff; - padding: 2px 8px; - border-radius: 20px; - font-size: 12px; + font-size: 1.1em; } -/* === No Documents Message === */ .no-docs { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-top: 48px; - color: #5f6368; - gap: 12px; -} - -.no-docs-illustration { - font-size: 4rem; - margin-bottom: 8px; - opacity: 0.7; -} - -/* === Responsive Design === */ -@media (max-width: 768px) { - .dashboard-main { - padding: 16px; - } - - .doc-list { - grid-template-columns: 1fr; - gap: 16px; - } - - .doc-card { - padding: 12px; - } - - .doc-icon { - font-size: 1.5rem; - } - - .doc-card h3 { - font-size: 14px; - } - - .item-type { - font-size: 11px; - } + text-align: center; + margin-top: 40px; + color: var(--text-color); } diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 1ea98ae..bd304e8 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -11,14 +11,20 @@ export default function Dashboard() { const { user, logout, refreshToken } = useAuth(); const [documents, setDocuments] = useState([]); const [query, setQuery] = useState(""); + const [darkMode, setDarkMode] = useState( + () => localStorage.getItem('theme') === 'dark'); const navigate = useNavigate(); const handleSearchResultClick = (docId, pageIndex) => { navigate(`/editor/${docId}`); setTimeout(() => { - switchPage(pageIndex); // ← from your useEditor hook - }, 300); // wait for editor to mount + switchPage(pageIndex); + }, 300); }; + useEffect(() => { + document.body.classList.toggle('dark-mode', darkMode); + localStorage.setItem('theme', darkMode ? 'dark' : 'light'); + }, [darkMode]); useEffect(() => { const fetchData = async () => { @@ -135,79 +141,99 @@ export default function Dashboard() { ) : []; - return ( -
-
-

📄 CollabSpace

-
- {user?.email} - -
-
- - - -
-
- -
- - {filteredDocuments.length > 0 ? ( -
    - {filteredDocuments.map(doc => ( -
  • navigate(`/editor/${doc.id}`)}> -
    - -
    -

    {doc.title}

    - Document -
    - -
    -
  • - ))} -
- ) : ( -
- -

No documents found.

- -
- )} -
-
- ); -} + return ( +
+
+

📄 CollabSpace

+
+ +
+ {user?.email} + +
+
+
+ + + +
+
+ +
+ + {filteredDocuments.length > 0 ? ( +
    + {filteredDocuments.map((doc) => ( +
  • navigate(`/editor/${doc.id}`)}> +
    + +
    +

    {doc.title}

    + Document +
    + +
    +
  • + ))} +
+ ) : ( +
+ +

No documents found.

+ +
+ )} +
+
+ ); + } \ No newline at end of file diff --git a/frontend/src/pages/Login.css b/frontend/src/pages/Login.css index 0471555..2b2b539 100644 --- a/frontend/src/pages/Login.css +++ b/frontend/src/pages/Login.css @@ -1,4 +1,136 @@ .login-container { + min-height: 100vh; + background: linear-gradient(to right, #f8fafc, #e2e8f0); + display: flex; + justify-content: center; + align-items: center; + font-family: 'Segoe UI', sans-serif; +} + +.login-card { + background: #ffffff; + padding: 3rem 2rem; + border-radius: 12px; + width: 100%; + max-width: 400px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + text-align: center; + border: 1px solid transparent; +} + +.app-title { + font-size: 2rem; + font-weight: 800; + color: #2563eb; + margin-bottom: 0.5rem; +} + +.login-title { + font-size: 1.2rem; + margin-bottom: 1.5rem; + color: #333333; +} + +.login-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.login-input { + padding: 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 1rem; + background-color: #ffffff; + color: #0f172a; +} + +.login-input::placeholder { + color: #64748b; +} + +.login-button { + background-color: #2563eb; + color: #ffffff; + padding: 0.75rem; + border: none; + border-radius: 6px; + font-weight: bold; + cursor: pointer; + transition: background 0.3s ease; +} + +.login-button:hover { + background-color: #1d4ed8; +} + +.login-button:disabled { + background-color: #94a3b8; + cursor: not-allowed; +} + +.login-error { + background-color: #fee2e2; + color: #b91c1c; + padding: 0.75rem; + border-radius: 6px; + margin-bottom: 1rem; +} + +.login-footer { + margin-top: 1rem; + font-size: 0.9rem; +} + +.signup-link { + color: #2563eb; + text-decoration: none; + font-weight: 500; +} + +.signup-link:hover { + text-decoration: underline; +} + +/* Dark mode */ +html.dark .login-container { + background: #0b0f19; + color: #e5e7eb; +} + +html.dark .login-card { + background-color: #1e293b; + border-color: #334155; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4); +} + +html.dark .login-title { + color: #e5e7eb; +} + +html.dark .login-input { + background-color: #0f172a; + border-color: #334155; + color: #e5e7eb; +} + +html.dark .login-input::placeholder { + color: #94a3b8; +} + +html.dark .login-button { + background-color: #2563eb; + color: #ffffff; +} + +html.dark .login-button:hover { + background-color: #1d4ed8; +} + +html.dark .login-error { + background-color: #451a1a; + color: #fecaca; min-height: 100vh; background: linear-gradient(to right, #f8fafc, #e2e8f0); display: flex; diff --git a/frontend/src/pages/Signup.css b/frontend/src/pages/Signup.css index 6956da2..9aa8315 100644 --- a/frontend/src/pages/Signup.css +++ b/frontend/src/pages/Signup.css @@ -1,141 +1,146 @@ - - - @keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(40px); - } - to { - opacity: 1; - transform: translateY(0); - } + from { + opacity: 0; + transform: translateY(40px); } - + to { + opacity: 1; + transform: translateY(0); + } +} .signup-container { - min-height: 100vh; - background: linear-gradient(to right, #f8fafc, #e2e8f0); - display: flex; - justify-content: center; - align-items: center; - font-family: 'Segoe UI', sans-serif; - } - - .signup-card { - background: white; - padding: 3rem 2rem; - border-radius: 12px; - width: 100%; - max-width: 400px; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); - text-align: center; - animation: fadeInUp 0.6s ease-out both; - } - - .signup-input { - padding: 0.75rem; - border: 1px solid #cbd5e1; - border-radius: 6px; - font-size: 1rem; - transition: transform 0.2s ease, box-shadow 0.2s ease; - } - - .signup-input:focus { - outline: none; - transform: scale(1.02); - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4); - } - - .signup-button { - background-color: #2563eb; - color: white; - padding: 0.75rem; - border: none; - border-radius: 6px; - font-weight: bold; - cursor: pointer; - transition: background 0.3s ease, transform 0.2s ease; - } - - .signup-button:hover { - background-color: #1d4ed8; - transform: translateY(-1px); - } - - .signup-button:disabled { - background-color: #94a3b8; - cursor: not-allowed; - transform: none; - } - - - .app-title { - font-size: 2rem; - font-weight: 800; - color: #2563eb; - margin-bottom: 0.5rem; - } - - .signup-title { - font-size: 1.2rem; - margin-bottom: 1.5rem; - color: #333; - } - - .signup-form { - display: flex; - flex-direction: column; - gap: 1rem; - } - - .signup-input { - padding: 0.75rem; - border: 1px solid #cbd5e1; - border-radius: 6px; - font-size: 1rem; - } - - .signup-button { - background-color: #2563eb; - color: white; - padding: 0.75rem; - border: none; - border-radius: 6px; - font-weight: bold; - cursor: pointer; - transition: background 0.3s ease; - } - - .signup-button:hover { - background-color: #1d4ed8; - } - - .signup-button:disabled { - background-color: #94a3b8; - cursor: not-allowed; - } - - .signup-error { - background-color: #fee2e2; - color: #b91c1c; - padding: 0.75rem; - border-radius: 6px; - margin-bottom: 1rem; - } - - .signup-footer { - margin-top: 1rem; - font-size: 0.9rem; - } - - .login-link { - color: #2563eb; - text-decoration: none; - font-weight: 500; - } - - .login-link:hover { - text-decoration: underline; - } - \ No newline at end of file + min-height: 100vh; + background: linear-gradient(to right, #f8fafc, #e2e8f0); + display: flex; + justify-content: center; + align-items: center; + font-family: 'Segoe UI', sans-serif; +} + +.signup-card { + background: #ffffff; + padding: 3rem 2rem; + border-radius: 12px; + width: 100%; + max-width: 400px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + text-align: center; + animation: fadeInUp 0.6s ease-out both; + border: 1px solid transparent; +} + +.signup-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.signup-input { + padding: 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 1rem; + background-color: #ffffff; + color: #0f172a; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.signup-input:focus { + outline: none; + transform: scale(1.02); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4); +} + +.signup-button { + background-color: #2563eb; + color: #ffffff; + padding: 0.75rem; + border: none; + border-radius: 6px; + font-weight: bold; + cursor: pointer; + transition: background 0.3s ease, transform 0.2s ease; +} + +.signup-button:hover { + background-color: #1d4ed8; + transform: translateY(-1px); +} + +.signup-button:disabled { + background-color: #94a3b8; + cursor: not-allowed; + transform: none; +} + +.app-title { + font-size: 2rem; + font-weight: 800; + color: #2563eb; + margin-bottom: 0.5rem; +} + +.signup-title { + font-size: 1.2rem; + margin-bottom: 1.5rem; + color: #333333; +} + +.signup-error { + background-color: #fee2e2; + color: #b91c1c; + padding: 0.75rem; + border-radius: 6px; + margin-bottom: 1rem; +} + +.signup-footer { + margin-top: 1rem; + font-size: 0.9rem; +} + +.login-link { + color: #2563eb; + text-decoration: none; + font-weight: 500; +} + +.login-link:hover { + text-decoration: underline; +} + +/* Dark mode */ +html.dark .signup-container { + background: #0b0f19; + color: #e5e7eb; +} + +html.dark .signup-card { + background-color: #1e293b; + border-color: #334155; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4); +} + +html.dark .signup-title { + color: #e5e7eb; +} + +html.dark .signup-input { + background-color: #0f172a; + border-color: #334155; + color: #e5e7eb; +} + +html.dark .signup-input::placeholder { + color: #94a3b8; +} + +html.dark .signup-button { + background-color: #2563eb; + color: #ffffff; +} + +html.dark .signup-button:hover { + background-color: #1d4ed8; +}