From 0849f38629c08122b3056fff1dad0d7c6b7c95f8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 01:28:05 -0500 Subject: [PATCH] update frontend --- backups/AuthContext.jsx.20251219-150345.bak | 244 ---------- src/auth/AuthContext.jsx.bak.1766217948 | 425 ------------------ .../Layout/TopBar.jsx.bak.1766214623 | 287 ------------ .../Layout/TopBar.jsx.bak.1766217948 | 392 ---------------- .../Layout/TopBar.jsx.bak.20251219-163055 | 275 ------------ .../Layout/TopBar.jsx.bak.20251219-164233 | 390 ---------------- .../Layout/TopBar.jsx.bak.fix.1766214994 | 332 -------------- src/main.jsx.bak.1766292419 | 27 -- src/styles/auth-modal.css.bak.1766217948 | 139 ------ src/styles/topbar.css.bak.1766214715 | 251 ----------- src/styles/topbar.css.bak.1766217948 | 425 ------------------ src/styles/topbar.css.bak.20251219-163055 | 61 --- src/styles/topbar.css.bak.20251219-164233 | 174 ------- 13 files changed, 3422 deletions(-) delete mode 100644 backups/AuthContext.jsx.20251219-150345.bak delete mode 100644 src/auth/AuthContext.jsx.bak.1766217948 delete mode 100644 src/components/Layout/TopBar.jsx.bak.1766214623 delete mode 100644 src/components/Layout/TopBar.jsx.bak.1766217948 delete mode 100644 src/components/Layout/TopBar.jsx.bak.20251219-163055 delete mode 100644 src/components/Layout/TopBar.jsx.bak.20251219-164233 delete mode 100644 src/components/Layout/TopBar.jsx.bak.fix.1766214994 delete mode 100644 src/main.jsx.bak.1766292419 delete mode 100644 src/styles/auth-modal.css.bak.1766217948 delete mode 100644 src/styles/topbar.css.bak.1766214715 delete mode 100644 src/styles/topbar.css.bak.1766217948 delete mode 100644 src/styles/topbar.css.bak.20251219-163055 delete mode 100644 src/styles/topbar.css.bak.20251219-164233 diff --git a/backups/AuthContext.jsx.20251219-150345.bak b/backups/AuthContext.jsx.20251219-150345.bak deleted file mode 100644 index 883d3a6..0000000 --- a/backups/AuthContext.jsx.20251219-150345.bak +++ /dev/null @@ -1,244 +0,0 @@ -import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { clearAuth, guessUsernameFromToken, jwtExpSeconds, loadAuth, saveAuth } from "./authStorage"; - -const AuthCtx = createContext(null); - -function apiBase() { - // même domaine par défaut - return (import.meta?.env?.VITE_API_BASE_URL || "").replace(/\/+$/, ""); -} - -async function apiFetch(path, opts = {}) { - const url = apiBase() + path; - const res = await fetch(url, { - ...opts, - headers: { - "Content-Type": "application/json", - ...(opts.headers || {}), - }, - }); - return res; -} - -async function whoami(accessToken) { - const res = await apiFetch("/api/whoami", { - method: "GET", - headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {}, - }); - const j = await res.json().catch(() => null); - return { ok: res.ok, status: res.status, json: j }; -} - -async function refreshTokens(refreshToken) { - const res = await apiFetch("/api/refresh", { - method: "POST", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - const j = await res.json().catch(() => null); - return { ok: res.ok, status: res.status, json: j }; -} - -function nowSec() { - return Math.floor(Date.now() / 1000); -} - -export function AuthProvider({ children }) { - const [status, setStatus] = useState("loading"); // loading | anon | auth - const [user, setUser] = useState(null); // { username } - const [tokens, setTokens] = useState(null); // { access_token, refresh_token, ... } - const [error, setError] = useState(""); - const refreshTimer = useRef(null); - - function setAnon(msg = "") { - setStatus("anon"); - setUser(null); - setTokens(null); - setError(msg || ""); - clearAuth(); - } - - function setAuthFromTokens(t, usernameGuess = "") { - setTokens(t); - saveAuth({ ...t, obtained_at: Date.now() }); - const u = usernameGuess || guessUsernameFromToken(t?.access_token) || ""; - setUser(u ? { username: u } : null); - setStatus("auth"); - setError(""); - } - - async function ensureFreshSession(existing) { - if (!existing?.access_token) return { ok: false, reason: "no_access_token" }; - - const exp = jwtExpSeconds(existing.access_token); - const skew = 60; // seconds - const isExpiredOrSoon = !exp || exp <= (nowSec() + skew); - - // 1) refresh si nécessaire - if (isExpiredOrSoon) { - if (!existing.refresh_token) return { ok: false, reason: "expired_no_refresh" }; - - const rr = await refreshTokens(existing.refresh_token); - if (!rr.ok || !rr.json?.access_token) { - return { ok: false, reason: `refresh_failed_${rr.status}` }; - } - - const merged = { - ...existing, - ...rr.json, - }; - - // certains serveurs renvoient pas refresh_token à chaque fois - if (!merged.refresh_token) merged.refresh_token = existing.refresh_token; - - setAuthFromTokens(merged); - existing = merged; - } else { - // access token encore bon - setAuthFromTokens(existing); - } - - // 2) confirmer avec backend (whoami) - const w = await whoami(existing.access_token); - if (w.ok && w.json?.authenticated) { - const uname = w.json.username || guessUsernameFromToken(existing.access_token) || ""; - setUser(uname ? { username: uname } : null); - setStatus("auth"); - setError(""); - return { ok: true }; - } - - // Si whoami fail, on considère désync (token invalide pour backend) - return { ok: false, reason: `whoami_${w.status}` }; - } - - function scheduleAutoRefresh(t) { - if (refreshTimer.current) { - clearInterval(refreshTimer.current); - refreshTimer.current = null; - } - // check aux 30s, refresh quand exp < 90s - refreshTimer.current = setInterval(async () => { - const cur = loadAuth(); - if (!cur?.access_token) return; - - const exp = jwtExpSeconds(cur.access_token); - if (!exp) return; - - if (exp <= nowSec() + 90) { - if (!cur.refresh_token) { - setAnon("Session expirée (pas de refresh_token)."); - return; - } - const rr = await refreshTokens(cur.refresh_token); - if (!rr.ok || !rr.json?.access_token) { - setAnon("Session expirée. Please login again."); - return; - } - const merged = { ...cur, ...rr.json }; - if (!merged.refresh_token) merged.refresh_token = cur.refresh_token; - setAuthFromTokens(merged); - } - }, 30000); - } - - async function restore() { - setStatus("loading"); - setError(""); - const saved = loadAuth(); - if (!saved?.access_token) { - setAnon(""); - return; - } - - const r = await ensureFreshSession(saved); - if (!r.ok) { - setAnon("Login desynced. Please login again."); - return; - } - - scheduleAutoRefresh(saved); - } - - async function login(username, password) { - setError(""); - setStatus("loading"); - - const res = await apiFetch("/api/login", { - method: "POST", - body: JSON.stringify({ username, password }), - }); - - const j = await res.json().catch(() => null); - if (!res.ok || !j?.access_token) { - setAnon(j?.error_description || j?.error || "Login failed"); - return { ok: false, status: res.status, json: j }; - } - - const t = { - ...j, - obtained_at: Date.now(), - }; - - setAuthFromTokens(t); - scheduleAutoRefresh(t); - - // confirm whoami (si ça fail -> desync) - const w = await whoami(t.access_token); - if (!w.ok || !w.json?.authenticated) { - setAnon("Login desynced. Please login again."); - return { ok: false, status: w.status, json: w.json }; - } - - const uname = w.json.username || guessUsernameFromToken(t.access_token) || ""; - setUser(uname ? { username: uname } : null); - setStatus("auth"); - return { ok: true }; - } - - function logout() { - if (refreshTimer.current) clearInterval(refreshTimer.current); - refreshTimer.current = null; - setAnon(""); - } - - useEffect(() => { - restore(); - - const onStorage = (e) => { - if (e.key === "sociowire:auth") restore(); - }; - window.addEventListener("storage", onStorage); - return () => window.removeEventListener("storage", onStorage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (status === "auth" && tokens?.access_token) { - scheduleAutoRefresh(tokens); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [status]); - - const value = useMemo(() => { - return { - status, - user, - tokens, - error, - - isAuthed: status === "auth", - accessToken: tokens?.access_token || "", - - login, - logout, - restore, - setError, - }; - }, [status, user, tokens, error]); - - return {children}; -} - -export function useAuth() { - return useContext(AuthCtx); -} diff --git a/src/auth/AuthContext.jsx.bak.1766217948 b/src/auth/AuthContext.jsx.bak.1766217948 deleted file mode 100644 index 38e3ace..0000000 --- a/src/auth/AuthContext.jsx.bak.1766217948 +++ /dev/null @@ -1,425 +0,0 @@ -import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { - clearAuth, - guessUsernameFromToken, - jwtExpSeconds, - loadAuth, - saveAuth, - decodeJwtPayload, -} from "./authStorage"; -import { parseAuthError } from "./authErrors"; - -const AuthCtx = createContext(null); - -function apiBase() { - return (import.meta?.env?.VITE_API_BASE_URL || "").replace(/\/+$/, ""); -} - -async function apiFetch(path, opts = {}) { - const url = apiBase() + path; - const res = await fetch(url, { - credentials: "include", - ...opts, - headers: { - ...(opts.headers || {}), - }, - }); - return res; -} - -function tokenEmailVerified(accessToken) { - try { - const p = decodeJwtPayload(accessToken); - if (!p) return null; - if (typeof p.email_verified === "boolean") return p.email_verified; - if (typeof p.emailVerified === "boolean") return p.emailVerified; - return null; - } catch { - return null; - } -} - -// tolerant whoami: accepts {authenticated:true} OR {username:""} OR {user:""} OR just ok+token -async function whoami(accessToken) { - const res = await apiFetch("/api/whoami", { - method: "GET", - headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {}, - }); - - const text = await res.text().catch(() => ""); - let json = null; - try { - json = text ? JSON.parse(text) : null; - } catch { - json = null; - } - - const uname = - (json && (json.username || json.user || json.preferred_username)) || - guessUsernameFromToken(accessToken) || - ""; - - const authed = - !!res.ok && - (json?.authenticated === true || - json?.ok === true || - !!json?.username || - !!json?.user || - !!uname); - - return { ok: !!res.ok, status: res.status, json, text, authenticated: authed, username: uname }; -} - -// NEW: /api/me is the source of truth for email_verified -async function me(accessToken) { - const res = await apiFetch("/api/me", { - method: "GET", - headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {}, - }); - - const text = await res.text().catch(() => ""); - let json = null; - try { - json = text ? JSON.parse(text) : null; - } catch { - json = null; - } - - // Try multiple shapes - const u = (json && (json.user || json.profile || json.me)) || json || null; - - const emailVerified = - u && typeof u.email_verified === "boolean" - ? u.email_verified - : u && typeof u.emailVerified === "boolean" - ? u.emailVerified - : null; - - return { - ok: !!res.ok, - status: res.status, - json, - text, - emailVerified, - }; -} - -async function refreshTokens(refreshToken) { - const res = await apiFetch("/api/refresh", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text().catch(() => ""); - let json = null; - try { - json = text ? JSON.parse(text) : null; - } catch { - json = null; - } - - return { ok: res.ok, status: res.status, json, text }; -} - -async function resendVerification(accessToken) { - const res = await apiFetch("/api/resend-verification", { - method: "POST", - headers: { - "Content-Type": "application/json", - ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), - }, - body: JSON.stringify({}), - }); - - const text = await res.text().catch(() => ""); - let json = null; - try { - json = text ? JSON.parse(text) : null; - } catch { - json = null; - } - - return { ok: res.ok, status: res.status, json, text }; -} - -function nowSec() { - return Math.floor(Date.now() / 1000); -} - -export function AuthProvider({ children }) { - const [status, setStatus] = useState("loading"); // loading | anon | auth - const [user, setUser] = useState(null); // { username } - const [tokens, setTokens] = useState(null); // { access_token, refresh_token, ... } - - const [error, setError] = useState(""); - const [lastInfo, setLastInfo] = useState(""); - const [needsEmailVerification, setNeedsEmailVerification] = useState(false); - - const refreshTimer = useRef(null); - - function setAnon(msg = "") { - setStatus("anon"); - setUser(null); - setTokens(null); - setError(msg || ""); - setLastInfo(""); - setNeedsEmailVerification(false); - clearAuth(); - } - - function setAuthFromTokens(t, usernameGuess = "") { - setTokens(t); - saveAuth({ ...t, obtained_at: Date.now() }); - - const u = usernameGuess || guessUsernameFromToken(t?.access_token) || ""; - setUser(u ? { username: u } : null); - - setStatus("auth"); - setError(""); - } - - async function refreshProfile(accessTokenOverride) { - const at = accessTokenOverride || tokens?.access_token; - if (!at) return { ok: False }; - - // /api/me is the truth; token claim is fallback only - const mr = await me(at); - let verified = mr.emailVerified - - if (typeof verified !== "boolean") { - const tv = tokenEmailVerified(at); - if (typeof tv === "boolean") verified = tv; - } - - if (typeof verified === "boolean") { - setNeedsEmailVerification(!verified); - return { ok: true, verified }; - } - - // If cannot determine, do not block UI aggressively - setNeedsEmailVerification(false); - return { ok: mr.ok, verified: null }; - } - - async function ensureFreshSession(existing) { - if (!existing?.access_token) return { ok: false, reason: "no_access_token" }; - - const exp = jwtExpSeconds(existing.access_token); - const skew = 60; - const isExpiredOrSoon = !exp || exp <= nowSec() + skew; - - if (isExpiredOrSoon) { - if (!existing.refresh_token) return { ok: false, reason: "expired_no_refresh" }; - - const rr = await refreshTokens(existing.refresh_token); - if (!rr.ok || !rr.json?.access_token) { - return { ok: false, reason: `refresh_failed_${rr.status}`, detail: rr.text }; - } - - const merged = { ...existing, ...rr.json }; - if (!merged.refresh_token) merged.refresh_token = existing.refresh_token; - - setAuthFromTokens(merged); - existing = merged; - } else { - setAuthFromTokens(existing); - } - - const w = await whoami(existing.access_token); - if (w.ok && w.authenticated) { - setUser(w.username ? { username: w.username } : null); - setStatus("auth"); - setError(""); - - // ✅ Update email verification state from backend - await refreshProfile(existing.access_token); - - return { ok: true }; - } - - return { ok: false, reason: `whoami_${w.status}`, detail: w.text || "" }; - } - - function scheduleAutoRefresh() { - if (refreshTimer.current) { - clearInterval(refreshTimer.current); - refreshTimer.current = null; - } - - refreshTimer.current = setInterval(async () => { - const cur = loadAuth(); - if (!cur?.access_token) return; - - const exp = jwtExpSeconds(cur.access_token); - if (!exp) return; - - if (exp <= nowSec() + 90) { - if (!cur.refresh_token) { - setAnon("Session expirée (pas de refresh_token)."); - return; - } - - const rr = await refreshTokens(cur.refresh_token); - if (!rr.ok || !rr.json?.access_token) { - setAnon("Session expirée. Please login again."); - return; - } - - const merged = { ...cur, ...rr.json }; - if (!merged.refresh_token) merged.refresh_token = cur.refresh_token; - - setAuthFromTokens(merged); - - // keep verification fresh too - await refreshProfile(merged.access_token); - } else { - // lightweight: refresh verification occasionally without spamming - // (every 30s interval already runs; but this call is only parsing token unless needed) - const tv = tokenEmailVerified(cur.access_token); - if (typeof tv === "boolean") setNeedsEmailVerification(!tv); - } - }, 30000); - } - - async function restore() { - setStatus("loading"); - setError(""); - setLastInfo(""); - const saved = loadAuth(); - if (!saved?.access_token) { - setAnon(""); - return; - } - - const r = await ensureFreshSession(saved); - if (!r.ok) { - setAnon("Login desynced. Please login again."); - return; - } - - scheduleAutoRefresh(); - } - - async function login(username, password) { - setError(""); - setLastInfo(""); - setStatus("loading"); - - const res = await apiFetch("/api/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username, password }), - }); - - const text = await res.text().catch(() => ""); - let j = null; - try { - j = text ? JSON.parse(text) : null; - } catch { - j = null; - } - - if (!res.ok || !j?.access_token) { - const parsed = parseAuthError(text || "", res.status); - setNeedsEmailVerification(parsed.code === "email_verification_required"); - setAnon(parsed.message || j?.error_description || j?.error || text || "Login failed"); - return { ok: false, status: res.status, json: j, text }; - } - - const t = { ...j, obtained_at: Date.now() }; - setAuthFromTokens(t); - scheduleAutoRefresh(); - - const w = await whoami(t.access_token); - if (!w.ok || !w.authenticated) { - setAnon("Login desynced. Please login again."); - return { ok: false, status: w.status, json: w.json, text: w.text }; - } - - setUser(w.username ? { username: w.username } : null); - setStatus("auth"); - - // ✅ update verified state from backend - await refreshProfile(t.access_token); - - return { ok: true }; - } - - function logout() { - if (refreshTimer.current) clearInterval(refreshTimer.current); - refreshTimer.current = null; - setAnon(""); - } - - async function resendVerificationEmail() { - const at = tokens?.access_token || ""; - if (!at) { - setError("Not logged in."); - return { ok: false }; - } - - setError(""); - setLastInfo(""); - - const r = await resendVerification(at); - if (!r.ok) { - setError(r.json?.error || r.json?.message || r.text || `Resend failed (${r.status})`); - return { ok: false, status: r.status }; - } - - setLastInfo("Verification email sent. Check inbox/spam, then refresh session (logout/login)."); - return { ok: true }; - } - - useEffect(() => { - restore(); - const onStorage = (e) => { - if (e.key === "sociowire:auth") restore(); - }; - window.addEventListener("storage", onStorage); - return () => window.removeEventListener("storage", onStorage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Backward-compatible fields expected by TopBar/MapView - const value = useMemo(() => { - const loading = status === "loading"; - const authenticated = status === "auth"; - const username = user?.username || ""; - const token = tokens?.access_token || ""; - - return { - // internal - status, - user, - tokens, - error, - - // expected by UI - loading, - authenticated, - username, - token, - - lastError: error || "", - lastInfo: lastInfo || "", - needsEmailVerification: !!needsEmailVerification, - resendVerificationEmail, - - login, - logout, - restore, - refreshProfile, - setError, - setLastInfo, - setNeedsEmailVerification, - }; - }, [status, user, tokens, error, lastInfo, needsEmailVerification]); - - return {children}; -} - -export function useAuth() { - return useContext(AuthCtx); -} diff --git a/src/components/Layout/TopBar.jsx.bak.1766214623 b/src/components/Layout/TopBar.jsx.bak.1766214623 deleted file mode 100644 index 5a0e45b..0000000 --- a/src/components/Layout/TopBar.jsx.bak.1766214623 +++ /dev/null @@ -1,287 +0,0 @@ -import React, { useEffect, useMemo, useState } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; -import AuthToast from "../Auth/AuthToast"; - -const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" }; - -function initialLetter(name = "") { - const s = (name || "").trim(); - return s ? s[0].toUpperCase() : "G"; -} - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { - loading, - authenticated, - username, - login, - logout, - needsEmailVerification, - openKeycloak, - lastError, - lastErrorCode, - lastInfo, - clearError, - clearInfo, - } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [regUser, setRegUser] = useState(""); - const [regEmail, setRegEmail] = useState(""); - const [regPass, setRegPass] = useState(""); - const [signupError, setSignupError] = useState(""); - const [signupInfo, setSignupInfo] = useState(""); - const [signupLoading, setSignupLoading] = useState(false); - - const openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - setSignupInfo(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - setSignupInfo(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - setSignupInfo(""); - - if (!firstName.trim() || !lastName.trim() || !regUser.trim() || !regEmail.trim() || !regPass) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - - try { - setSignupLoading(true); - - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - - setSignupInfo("Account created. Check your inbox (spam) to verify your email, then login."); - setMode("login"); - setLoginUser(regUser.trim()); - setLoginPass(""); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - const toast = useMemo(() => { - if (lastError) { - const t = needsEmailVerification ? "warn" : "error"; - const title = needsEmailVerification ? "Vérification email requise" : "Connexion impossible"; - return { type: t, title, message: lastError }; - } - if (lastInfo) { - return { type: "success", title: "", message: lastInfo }; - } - return null; - }, [lastError, lastInfo, needsEmailVerification]); - - return ( - <> - {/* Toast area (under topbar) */} - {(toast?.message) && ( -
- { - if (lastError) clearError(); - if (lastInfo) clearInfo(); - }} - /> - {needsEmailVerification && ( -
- -
- )} -
- )} - -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
-
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
-
-
- {initialLetter(username)} -
-
-
Connecté
-
@{username || "user"}
-
-
- -
- ) : ( -
-
-
- -
-
-
{loading ? "Loading…" : "Guest"}
-
anon
-
-
- - - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {mode === "login" ? ( -
- - - -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - {signupInfo && ( -
- {signupInfo} -
- )} - - -
- )} -
-
- )} - - ); -} diff --git a/src/components/Layout/TopBar.jsx.bak.1766217948 b/src/components/Layout/TopBar.jsx.bak.1766217948 deleted file mode 100644 index 4fc365e..0000000 --- a/src/components/Layout/TopBar.jsx.bak.1766217948 +++ /dev/null @@ -1,392 +0,0 @@ -import React, { useEffect, useMemo, useState } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; -import AuthToast from "../Auth/AuthToast"; - -const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" }; -const LAST_EMAIL_KEY = "sociowire:lastEmail"; - -function initialLetter(name = "") { - const s = (name || "").trim(); - return s ? s[0].toUpperCase() : "G"; -} - -function looksLikeEmail(s = "") { - return typeof s === "string" && /.+@.+\..+/.test(s.trim()); -} - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { - loading, - authenticated, - username, - login, - logout, - needsEmailVerification, - resendVerificationEmail, - lastError, - lastInfo, - setError, - setLastInfo, - } = useAuth(); - - const clearError = () => setError && setError(""); - const clearInfo = () => setLastInfo && setLastInfo(""); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [regUser, setRegUser] = useState(""); - const [regEmail, setRegEmail] = useState(""); - const [regPass, setRegPass] = useState(""); - const [signupError, setSignupError] = useState(""); - const [signupInfo, setSignupInfo] = useState(""); - const [signupLoading, setSignupLoading] = useState(false); - - const openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - setSignupInfo(""); - - // Prefill login with last known email (nice for email-verified return) - if (nextMode === "login") { - try { - const last = localStorage.getItem(LAST_EMAIL_KEY) || ""; - if (!loginUser.trim() && last.trim()) setLoginUser(last.trim()); - } catch {} - } - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - setSignupInfo(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - /* SW_URL_NOTICE:BEGIN */ - useEffect(() => { - try { - const qs = new URLSearchParams(window.location.search || ""); - const verified = qs.get("verified") || qs.get("email_verified"); - const signup = qs.get("signup"); - const notice = qs.get("notice"); - const email = (qs.get("email") || qs.get("user") || qs.get("username") || "").trim(); - - if (email) { - try { localStorage.setItem(LAST_EMAIL_KEY, email); } catch {} - // If user uses email to login, prefill it - if (!loginUser.trim()) setLoginUser(email); - if (!regEmail.trim()) setRegEmail(email); - } - - if (verified === "1" || verified === "true") { - setSignupInfo(`✅ Email confirmé${email ? ` (${email})` : ""}. Tu peux te connecter.`); - setMode("login"); - setShowAuth(true); - } else if (signup === "1" || signup === "true") { - setSignupInfo("✅ Compte créé. Vérifie tes emails (et le spam), puis connecte-toi."); - setMode("login"); - setShowAuth(true); - } else if (notice) { - const msg = decodeURIComponent(notice); - if (msg && msg.trim()) { - setSignupInfo(msg.trim()); - setMode("login"); - setShowAuth(true); - } - } - - // clean URL (remove params without reload) - if (verified || signup || notice || email) { - const clean = window.location.pathname + (window.location.hash || ""); - window.history.replaceState({}, "", clean); - } - } catch (e) { - // ignore - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - /* SW_URL_NOTICE:END */ - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - // store email-like login for next time - try { - if (looksLikeEmail(loginUser)) localStorage.setItem(LAST_EMAIL_KEY, loginUser.trim()); - } catch {} - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - setSignupInfo(""); - - if (!firstName.trim() || !lastName.trim() || !regUser.trim() || !regEmail.trim() || !regPass) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - - try { - setSignupLoading(true); - - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - - try { localStorage.setItem(LAST_EMAIL_KEY, regEmail.trim()); } catch {} - - setSignupInfo("✅ Account created. Check your inbox (spam) to verify your email, then login."); - setMode("login"); - - // Some people login with email, some with username => prefer email if present - setLoginUser((regEmail.trim() || regUser.trim())); - setLoginPass(""); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - const toast = useMemo(() => { - if (lastError) { - const t = needsEmailVerification ? "warn" : "error"; - const title = needsEmailVerification ? "Vérification email requise" : "Connexion impossible"; - return { type: t, title, message: lastError }; - } - if (lastInfo) return { type: "success", title: "", message: lastInfo }; - return null; - }, [lastError, lastInfo, needsEmailVerification]); - - const authComboLabel = authenticated ? "Log off" : (loading ? "..." : "Log on"); - const authLine = authenticated ? "Online" : "Guest"; - const authHandle = authenticated ? `@${username || "user"}` : "anon"; - - return ( - <> - {/* Toast area (under topbar) */} - {(toast?.message) && ( -
- { - if (lastError) clearError(); - if (lastInfo) clearInfo(); - }} - /> - - {needsEmailVerification && ( -
- - - -
- )} -
- )} - -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
-
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {/* ONE unified auth button (guest online / log on / log off) */} - - - {!authenticated && ( - - )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {/* Nice notices (modal) */} - {signupInfo ?
{signupInfo}
: null} - - {mode === "login" && needsEmailVerification ? ( -
- ⚠️ Ton compte doit confirmer l’email avant de se connecter. Vérifie ta boîte mail (et le spam), - clique le lien, puis reviens ici. -
- ) : null} - - {mode === "login" && lastError ? ( -
{lastError}
- ) : null} - - {mode === "login" && lastInfo ? ( -
{lastInfo}
- ) : null} - - {mode === "login" ? ( -
- - - - - -
- ) : ( -
-
- - -
- - - - - - - - {signupError ?
{signupError}
: null} - {signupInfo ?
{signupInfo}
: null} - - -
- )} -
-
- )} - - ); -} diff --git a/src/components/Layout/TopBar.jsx.bak.20251219-163055 b/src/components/Layout/TopBar.jsx.bak.20251219-163055 deleted file mode 100644 index e562d76..0000000 --- a/src/components/Layout/TopBar.jsx.bak.20251219-163055 +++ /dev/null @@ -1,275 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; - -const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" }; - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { - loading, - authenticated, - username, - login, - logout, - needsEmailVerification, - resendVerificationEmail, - lastError, - lastInfo, - } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [regUser, setRegUser] = useState(""); - const [regEmail, setRegEmail] = useState(""); - const [regPass, setRegPass] = useState(""); - const [signupError, setSignupError] = useState(""); - const [signupInfo, setSignupInfo] = useState(""); - const [signupLoading, setSignupLoading] = useState(false); - - let authLabel = "AUTH: anon"; - if (loading) authLabel = "AUTH: loading…"; - else if (authenticated) authLabel = `AUTH: user=${username || "?"}`; - - const openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - setSignupInfo(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - setSignupInfo(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - setSignupInfo(""); - - if (!firstName.trim() || !lastName.trim() || !regUser.trim() || !regEmail.trim() || !regPass) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - - try { - setSignupLoading(true); - - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - - // IMPORTANT: si Verify Email est activé côté Keycloak, on ne tente PAS de login auto. - setSignupInfo("Account created. Check your inbox (spam) to verify your email, then login."); - setMode("login"); - setLoginUser(regUser.trim()); - setLoginPass(""); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - return ( - <> -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
- -
- {authLabel} - {lastError && ( - - ({lastError}) - - )} -
- - {authenticated && needsEmailVerification && ( -
- Verify email required. - Posting is locked until verification. - -
- )} - - {!needsEmailVerification && lastInfo && ( -
- {lastInfo} -
- )} -
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
- {username} - -
- ) : ( -
- - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {mode === "login" ? ( -
- - - - {lastError &&
{lastError}
} -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - {signupInfo && ( -
- {signupInfo} -
- )} - - -
- )} -
-
- )} - - ); -} diff --git a/src/components/Layout/TopBar.jsx.bak.20251219-164233 b/src/components/Layout/TopBar.jsx.bak.20251219-164233 deleted file mode 100644 index 3938917..0000000 --- a/src/components/Layout/TopBar.jsx.bak.20251219-164233 +++ /dev/null @@ -1,390 +0,0 @@ -import React, { useState, useEffect, useMemo } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; - -const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" }; -const LAST_SIGNUP_USER_KEY = "sociowire:lastSignupUser"; - -function normalizeAuthMessage(raw) { - const msg = String(raw || "").trim(); - if (!msg) return ""; - - const lower = msg.toLowerCase(); - - // Keycloak classic when email verification required - if ( - lower.includes("not fully set up") || - lower.includes("verify") || - lower.includes("verification") || - lower.includes("required action") || - (lower.includes("email") && lower.includes("verified")) - ) { - return "Tu dois confirmer ton email avant de te connecter. Vérifie ta boîte mail (et le spam), puis réessaie."; - } - - // Bad credentials - if ( - lower.includes("invalid user credentials") || - lower.includes("bad credentials") || - (lower.includes("invalid_grant") && !lower.includes("not fully set up")) - ) { - return "Mauvais username ou mot de passe."; - } - - // Fallback (shorten very long raw blobs) - if (msg.length > 240) return msg.slice(0, 240) + "…"; - return msg; -} - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { - loading, - authenticated, - username, - login, - logout, - needsEmailVerification, - resendVerificationEmail, - lastError, - lastInfo, - } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [regUser, setRegUser] = useState(""); - const [regEmail, setRegEmail] = useState(""); - const [regPass, setRegPass] = useState(""); - const [signupError, setSignupError] = useState(""); - const [signupInfo, setSignupInfo] = useState(""); - const [signupLoading, setSignupLoading] = useState(false); - - const isVerifiedPage = - typeof window !== "undefined" && window.location?.pathname === "/auth/verified"; - - const prettyError = useMemo(() => normalizeAuthMessage(lastError), [lastError]); - - const openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - setSignupInfo(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - setSignupInfo(""); - }; - - // close modal automatically on auth - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - // ✅ If user lands on /auth/verified (after clicking email link), - // open login modal + prefill username from last signup. - useEffect(() => { - if (!isVerifiedPage) return; - - setMode("login"); - setShowAuth(true); - - try { - const lastU = localStorage.getItem(LAST_SIGNUP_USER_KEY) || ""; - if (lastU && !loginUser) setLoginUser(lastU); - } catch {} - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isVerifiedPage]); - - // ✅ After successful login, if still on /auth/verified, cleanly go back home - useEffect(() => { - if (!authenticated) return; - if (typeof window === "undefined") return; - const path = window.location?.pathname || ""; - if (path === "/auth/verified") { - try { - window.history.replaceState({}, "", "/"); - } catch {} - } - }, [authenticated]); - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - setSignupInfo(""); - - if (!firstName.trim() || !lastName.trim() || !regUser.trim() || !regEmail.trim() || !regPass) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - - try { - setSignupLoading(true); - - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - - // Store last signup username so /auth/verified can prefill login. - try { - localStorage.setItem(LAST_SIGNUP_USER_KEY, regUser.trim()); - } catch {} - - // IMPORTANT: si Verify Email est activé côté Keycloak, on ne tente PAS de login auto. - setSignupInfo("Compte créé ✅ Vérifie ton email (spam inclus), puis reviens te connecter."); - setMode("login"); - setLoginUser(regUser.trim()); - setLoginPass(""); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - const AuthPill = () => { - if (loading) { - return ( - - - Loading… - - ); - } - if (authenticated) { - return ( - - - Connecté - - ); - } - return ( - - - Guest - - ); - }; - - return ( - <> -
-
-
SW
- -
-
SOCIOWIRE.com
-
Wired to life
- - {/* ✅ Clean auth row */} -
- - {authenticated && username && ( - - - {username} - - )} -
- - {/* ✅ Verified page hint */} - {isVerifiedPage && !authenticated && ( -
- ✅ Email confirmé. - Connecte-toi pour continuer. -
- )} - - {/* Email verification banner (if you later wire it from backend) */} - {authenticated && needsEmailVerification && ( -
- - - Verify email required. - - Posting is locked until verification. - -
- )} - - {/* ✅ Nice error banner */} - {prettyError && ( -
- - {prettyError} -
- )} - - {/* Optional lastInfo from AuthContext */} - {!needsEmailVerification && lastInfo && ( -
- - {lastInfo} -
- )} -
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
-
- - - - {username || "user"} -
- - -
- ) : ( -
- - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {mode === "login" ? ( -
- - - -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - {signupInfo && ( -
- {signupInfo} -
- )} - - -
- )} -
-
- )} - - ); -} diff --git a/src/components/Layout/TopBar.jsx.bak.fix.1766214994 b/src/components/Layout/TopBar.jsx.bak.fix.1766214994 deleted file mode 100644 index 3883b85..0000000 --- a/src/components/Layout/TopBar.jsx.bak.fix.1766214994 +++ /dev/null @@ -1,332 +0,0 @@ -import React, { useEffect, useMemo, useState } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; -import AuthToast from "../Auth/AuthToast"; - -const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" }; - -function initialLetter(name = "") { - const s = (name || "").trim(); - return s ? s[0].toUpperCase() : "G"; -} - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { - loading, - authenticated, - username, - login, - logout, - needsEmailVerification, - openKeycloak, - lastError, - lastErrorCode, - lastInfo, - clearError, - clearInfo, - } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [regUser, setRegUser] = useState(""); - const [regEmail, setRegEmail] = useState(""); - const [regPass, setRegPass] = useState(""); - const [signupError, setSignupError] = useState(""); - const [signupInfo, setSignupInfo] = useState(""); - const [signupLoading, setSignupLoading] = useState(false); - - const openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - setSignupInfo(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - setSignupInfo(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - /* SW_URL_NOTICE:BEGIN */ - useEffect(() => { - try { - const qs = new URLSearchParams(window.location.search || ""); - const verified = qs.get("verified") || qs.get("email_verified"); - const signup = qs.get("signup"); - const notice = qs.get("notice"); - - if (verified === "1" || verified === "true") { - setSignupInfo("✅ Email confirmé. Tu peux te connecter."); - setMode("login"); - setShowAuth(true); - } else if (signup === "1" || signup === "true") { - setSignupInfo("✅ Compte créé. Vérifie tes emails (et le spam), puis connecte-toi."); - setMode("login"); - setShowAuth(true); - } else if (notice) { - // allow custom notice (URL-encoded) - const msg = decodeURIComponent(notice); - if (msg && msg.trim()) { - setSignupInfo(msg.trim()); - setMode("login"); - setShowAuth(true); - } - } - - // clean URL (remove params without reload) - if (verified or signup or notice): - const clean = window.location.pathname + (window.location.hash || ""); - window.history.replaceState({}, "", clean); - } catch { - // ignore - } - }, []); - /* SW_URL_NOTICE:END */ - - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - setSignupInfo(""); - - if (!firstName.trim() || !lastName.trim() || !regUser.trim() || !regEmail.trim() || !regPass) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - - try { - setSignupLoading(true); - - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - - setSignupInfo("Account created. Check your inbox (spam) to verify your email, then login."); - setMode("login"); - setLoginUser(regUser.trim()); - setLoginPass(""); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - const toast = useMemo(() => { - if (lastError) { - const t = needsEmailVerification ? "warn" : "error"; - const title = needsEmailVerification ? "Vérification email requise" : "Connexion impossible"; - return { type: t, title, message: lastError }; - } - if (lastInfo) { - return { type: "success", title: "", message: lastInfo }; - } - return null; - }, [lastError, lastInfo, needsEmailVerification]); - - return ( - <> - {/* Toast area (under topbar) */} - {(toast?.message) && ( -
- { - if (lastError) clearError(); - if (lastInfo) clearInfo(); - }} - /> - {needsEmailVerification && ( -
- -
- )} -
- )} - -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
-
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
-
-
- {initialLetter(username)} -
-
-
Connecté
-
@{username || "user"}
-
-
- -
- ) : ( -
-
-
- -
-
-
{loading ? "Loading…" : "Guest"}
-
anon
-
-
- - - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {/* SW_MODAL_NOTICE:BEGIN */} - {signupInfo ? ( -
- {signupInfo} -
- ) : null} - {/* SW_MODAL_NOTICE:END */} - - {mode === "login" ? ( -
- - - -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - {signupInfo && ( -
- {signupInfo} -
- )} - - -
- )} -
-
- )} - - ); -} diff --git a/src/main.jsx.bak.1766292419 b/src/main.jsx.bak.1766292419 deleted file mode 100644 index 8295cfc..0000000 --- a/src/main.jsx.bak.1766292419 +++ /dev/null @@ -1,27 +0,0 @@ -import "@fortawesome/fontawesome-free/css/all.min.css"; // local via npm (no CDN) - -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App.jsx"; - -import "./styles/base.css"; -import "./styles/layout.css"; -import "./styles/topbar.css"; -import "./styles/map.css"; -import "./styles/overlays.css"; -import "./styles/posts.css"; -import "./styles/theme.css"; -import "./styles/auth-modal.css"; -import "./styles/auth-toast.css"; -import "./styles/filters.css"; -import "./styles/mapMarkers.css"; - -import { AuthProvider } from "./auth/AuthContext.jsx"; - -ReactDOM.createRoot(document.getElementById("root")).render( - - - - - -); diff --git a/src/styles/auth-modal.css.bak.1766217948 b/src/styles/auth-modal.css.bak.1766217948 deleted file mode 100644 index 7b18c9a..0000000 --- a/src/styles/auth-modal.css.bak.1766217948 +++ /dev/null @@ -1,139 +0,0 @@ -.auth-modal-backdrop { - position: fixed; - inset: 0; - z-index: 60; - display: flex; - align-items: center; - justify-content: center; - background: rgba(2, 6, 23, 0.65); - backdrop-filter: blur(8px); -} - -.auth-modal { - width: min(640px, 95vw); - border-radius: 22px; - padding: 1.1rem 1.25rem 1.35rem; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.85); - border: 1px solid rgba(148, 163, 184, 0.7); -} - -body[data-theme="dark"] .auth-modal { - background: radial-gradient(circle at top left, #0f172a, #020617 70%); -} -body[data-theme="blue"] .auth-modal { - background: radial-gradient(circle at top left, #0ea5e9, #020617 65%); - border-color: rgba(96, 165, 250, 0.9); -} -body[data-theme="light"] .auth-modal { - background: radial-gradient(circle at top left, #ffffff, #e5e7eb 70%); - color: #020617; - border-color: rgba(148, 163, 184, 0.7); -} - -.auth-modal-header { display:flex; align-items:center; gap:.5rem; margin-bottom:.9rem; } - -.auth-tab { - padding: .45rem .95rem; - border-radius: 999px; - border: 1px solid transparent; - background: transparent; - font-size: .85rem; - color: #e5e7eb; - cursor: pointer; -} -.auth-tab-active { - background: linear-gradient(135deg, #1d4ed8, #38bdf8); - border-color: rgba(191, 219, 254, 0.9); - color: #f9fafb; -} -body[data-theme="light"] .auth-tab { color:#0f172a; } -body[data-theme="light"] .auth-tab-active { color:#f9fafb; } - -.auth-close { - margin-left:auto; - width:32px; height:32px; - border-radius:999px; - border:1px solid rgba(148, 163, 184, 0.6); - background: rgba(15, 23, 42, 0.85); - color:#e5e7eb; - font-size:.8rem; - cursor:pointer; -} -body[data-theme="light"] .auth-close { background:#e5e7eb; color:#020617; } - -.auth-form { display:flex; flex-direction:column; gap:.7rem; } -.auth-row { display:flex; gap:.6rem; } -.auth-row label { flex:1; } -.auth-form label { display:flex; flex-direction:column; font-size:.8rem; gap:.25rem; } - -.auth-form input { - border-radius: 999px; - border: 1px solid rgba(51, 65, 85, 0.9); - background: rgba(15, 23, 42, 0.96); - color: #e5e7eb; - padding: .55rem .85rem; - font-size: .85rem; -} -body[data-theme="light"] .auth-form input { - background:#f9fafb; - color:#020617; - border-color: rgba(148, 163, 184, 0.9); -} - -.btn-full { width:100%; margin-top:.4rem; } - -.auth-error { - margin-top:.25rem; - font-size:.8rem; - color:#fecaca; - background: rgba(127, 29, 29, 0.55); - border-radius:.5rem; - padding:.4rem .6rem; - border: 1px solid rgba(248, 113, 113, 0.7); -} - -@media (max-width:480px){ - .auth-modal { padding:.95rem .9rem 1.1rem; } - .auth-row { flex-direction:column; } -} - -/* SW_AUTH_NOTICE:BEGIN */ -.auth-notice{ - margin: .2rem 0 .65rem; - padding: .55rem .7rem; - border-radius: 14px; - font-size: .82rem; - font-weight: 800; - line-height: 1.2; - border: 1px solid rgba(148,163,184,.35); - background: rgba(2,6,23,.22); - color: #e5e7eb; -} - -.auth-notice-success{ - background: rgba(34,197,94,0.14); - border-color: rgba(34,197,94,0.55); - color: #d1fae5; -} - -.auth-notice-error{ - background: rgba(248,113,113,0.14); - border-color: rgba(248,113,113,0.55); - color: #ffe4e6; -} - -.auth-notice-warn{ - background: rgba(251,191,36,0.12); - border-color: rgba(251,191,36,0.40); - color: #fffbeb; -} - -body[data-theme="light"] .auth-notice{ - background: rgba(255,255,255,0.92); - border-color: rgba(148,163,184,0.75); - color: #0b1220; -} -body[data-theme="light"] .auth-notice-success{ color:#0b1220; } -body[data-theme="light"] .auth-notice-error{ color:#0b1220; } -body[data-theme="light"] .auth-notice-warn{ color:#0b1220; } -/* SW_AUTH_NOTICE:END */ diff --git a/src/styles/topbar.css.bak.1766214715 b/src/styles/topbar.css.bak.1766214715 deleted file mode 100644 index ddb2af1..0000000 --- a/src/styles/topbar.css.bak.1766214715 +++ /dev/null @@ -1,251 +0,0 @@ -.topbar{ - position: sticky; top:0; z-index:100; - width:100%; - padding: .55rem .9rem; - display:flex; align-items:center; justify-content:space-between; gap:.6rem; - background: linear-gradient(135deg,#071225 0%,#0b2b5f 38%,#0e65c0 72%,#27a8ff 100%); - border-bottom-left-radius:18px; border-bottom-right-radius:18px; - box-shadow: 0 10px 24px rgba(0,0,0,.55), 0 0 18px rgba(56,189,248,.45); - overflow:hidden; -} - -.logo-circle{ - width:40px; height:40px; border-radius:50%; flex-shrink:0; - background-image:url("/icons/logo-master.png"); - background-size:cover; background-position:center; background-repeat:no-repeat; - box-shadow: 0 0 10px rgba(37,99,235,.8), 0 0 18px rgba(56,189,248,.6); - color:transparent; -} - -.title-block{ display:flex; flex-direction:column; line-height:1.05; min-width:0; } -.main-title{ font-size:1.02rem; font-weight:800; letter-spacing:.04em; color:#f9fafb; } -.sub-title{ font-size:.74rem; color:#dbeafe; opacity:.95; } - -.topbar-right{ display:flex; align-items:center; gap:.5rem; margin-left:auto; flex-wrap:wrap; } - -.theme-switch{ display:flex; gap:.28rem; } -.theme-dot{ - width:22px; height:22px; border-radius:999px; - border:1px solid rgba(148,163,184,.85); - background:rgba(15,23,42,.9); color:#e5e7eb; font-size:.65rem; - display:flex; align-items:center; justify-content:center; padding:0; cursor:pointer; - box-shadow:0 0 6px rgba(15,23,42,.9); transition:transform 0.36s ease; -} -.theme-dot:hover{ transform:scale(1.06); } -.theme-dot-active{ border-color:#facc15; box-shadow:0 0 10px rgba(250,204,21,.9),0 0 14px rgba(56,189,248,.55); } - -.btn-primary, .btn-secondary{ - padding:.32rem .85rem; font-size:.78rem; border-radius:999px; font-weight:700; cursor:pointer; - white-space:nowrap; line-height:1; -} -.btn-primary{ - border:1px solid #22c55e; - background:radial-gradient(circle at 30% 30%,#22c55e,#16a34a); - color:#f9fafb; - box-shadow:0 4px 10px rgba(22,163,74,.5),0 0 8px rgba(22,163,74,.6); -} -.btn-primary:disabled{ opacity:.6; cursor:default; } -.btn-secondary{ - border:1px solid #bfdbfe; - background:rgba(15,23,42,.25); - color:#e0f2fe; - box-shadow:0 3px 10px rgba(15,23,42,.6); -} -.btn-secondary:hover{ background:rgba(15,23,42,.45); } - -@media (max-width:640px){ - .topbar{ padding:.5rem .7rem; border-bottom-left-radius:16px; border-bottom-right-radius:16px; } - .main-title{ font-size:1rem; } - .sub-title{ font-size:.7rem; } - .topbar-right{ width:100%; justify-content:flex-start; gap:.45rem; } -} - -/* SW_AUTH_UI:BEGIN */ -.auth-strip{ - margin-top: 6px; - display:flex; - align-items:center; - gap: .45rem; - flex-wrap: wrap; -} - -.auth-pill{ - display:inline-flex; - align-items:center; - gap: .35rem; - padding: .18rem .55rem; - border-radius: 999px; - font-size: .70rem; - font-weight: 900; - letter-spacing: .02em; - border: 1px solid rgba(148,163,184,.55); - background: rgba(2,6,23,.25); - color: #e5e7eb; -} -.auth-pill i{ font-size: .85rem; opacity:.95; } - -.auth-pill--ok{ - border-color: rgba(34,197,94,.65); - background: rgba(34,197,94,.16); -} -.auth-pill--loading{ - border-color: rgba(56,189,248,.65); - background: rgba(56,189,248,.14); -} -.auth-pill--anon{ - border-color: rgba(148,163,184,.55); - background: rgba(2,6,23,.20); -} - -.auth-banner{ - margin-top: 6px; - padding: 6px 10px; - border-radius: 14px; - font-size: .72rem; - font-weight: 800; - display:flex; - gap: 8px; - align-items:center; - flex-wrap:wrap; -} -.auth-banner--error{ - background: rgba(248,113,113,0.14); - border: 1px solid rgba(248,113,113,0.55); - color: #ffe4e6; -} -.auth-banner--info{ - background: rgba(56,189,248,0.14); - border: 1px solid rgba(56,189,248,0.55); - color: #e0f2fe; -} -.auth-banner--success{ - background: rgba(34,197,94,0.14); - border: 1px solid rgba(34,197,94,0.55); - color: #d1fae5; -} - -.user-chip{ - display:inline-flex; - align-items:center; - gap: .45rem; - padding: .22rem .55rem; - border-radius: 999px; - background: rgba(2,6,23,.25); - border: 1px solid rgba(148,163,184,.55); -} -.user-avatar{ - width: 26px; - height: 26px; - border-radius: 999px; - display:flex; - align-items:center; - justify-content:center; - background: rgba(56,189,248,0.18); - border: 1px solid rgba(56,189,248,0.35); -} -.user-avatar i{ font-size: 14px; } - -.user-name{ - font-size: .80rem; - font-weight: 950; - color: #f9fafb; - max-width: 220px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* Light theme readability */ -body[data-theme="light"] .auth-pill{ - background: rgba(255,255,255,0.82); - border-color: rgba(148,163,184,0.85); - color: #0b1220; -} -body[data-theme="light"] .user-chip{ - background: rgba(255,255,255,0.82); - border-color: rgba(148,163,184,0.85); -} -body[data-theme="light"] .user-name{ color:#0b1220; } -body[data-theme="light"] .user-avatar{ - background: rgba(30,107,255,0.12); - border-color: rgba(30,107,255,0.35); - color:#0b1220; -} -/* SW_AUTH_UI:END */ - -/* SW_AUTH_UI2:BEGIN */ -.sw-notice{ - margin: 10px 14px 0 14px; - padding: 10px 12px; - border-radius: 16px; - font-size: .78rem; - font-weight: 800; - line-height: 1.2; - border: 1px solid rgba(148,163,184,.35); - background: rgba(2,6,23,.22); - color: #e5e7eb; -} -.sw-notice--error{ - background: rgba(248,113,113,0.14); - border-color: rgba(248,113,113,0.55); - color: #ffe4e6; -} -.sw-notice--info{ - background: rgba(56,189,248,0.14); - border-color: rgba(56,189,248,0.55); - color: #e0f2fe; -} -.sw-notice--success{ - background: rgba(34,197,94,0.14); - border-color: rgba(34,197,94,0.55); - color: #d1fae5; -} -.sw-notice--warn{ - background: rgba(251,191,36,0.12); - border-color: rgba(251,191,36,0.40); - color: #fffbeb; -} - -/* single user chip (no duplicates) */ -.user-chip{ - display:inline-flex; - align-items:center; - gap: .45rem; - padding: .22rem .55rem; - border-radius: 999px; - background: rgba(2,6,23,.25); - border: 1px solid rgba(148,163,184,.55); -} -.user-avatar{ - width: 26px; - height: 26px; - border-radius: 999px; - display:flex; - align-items:center; - justify-content:center; - background: rgba(56,189,248,0.18); - border: 1px solid rgba(56,189,248,0.35); -} -.user-avatar i{ font-size: 14px; } -.user-name{ - font-size: .82rem; - font-weight: 950; - color: #f9fafb; - max-width: 180px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.user-badge{ - display:inline-flex; - align-items:center; - gap: .35rem; - padding: .10rem .45rem; - border-radius: 999px; - font-size: .70rem; - font-weight: 950; - border: 1px solid rgba(34,197,94,.55); - background: rgba(34,197,94,.14); - color: #d1fae5; -} -/* SW_AUTH_UI2:END */ diff --git a/src/styles/topbar.css.bak.1766217948 b/src/styles/topbar.css.bak.1766217948 deleted file mode 100644 index af2ccea..0000000 --- a/src/styles/topbar.css.bak.1766217948 +++ /dev/null @@ -1,425 +0,0 @@ -.topbar{ - position: sticky; top:0; z-index:100; - width:100%; - padding: .55rem .9rem; - display:flex; align-items:center; justify-content:space-between; gap:.6rem; - background: linear-gradient(135deg,#071225 0%,#0b2b5f 38%,#0e65c0 72%,#27a8ff 100%); - border-bottom-left-radius:18px; border-bottom-right-radius:18px; - box-shadow: 0 10px 24px rgba(0,0,0,.55), 0 0 18px rgba(56,189,248,.45); - overflow:hidden; -} - -.logo-circle{ - width:40px; height:40px; border-radius:50%; flex-shrink:0; - background-image:url("/icons/logo-master.png"); - background-size:cover; background-position:center; background-repeat:no-repeat; - box-shadow: 0 0 10px rgba(37,99,235,.8), 0 0 18px rgba(56,189,248,.6); - color:transparent; -} - -.title-block{ display:flex; flex-direction:column; line-height:1.05; min-width:0; } -.main-title{ font-size:1.02rem; font-weight:800; letter-spacing:.04em; color:#f9fafb; } -.sub-title{ font-size:.74rem; color:#dbeafe; opacity:.95; } - -.topbar-right{ display:flex; align-items:center; gap:.5rem; margin-left:auto; flex-wrap:wrap; } - -.theme-switch{ display:flex; gap:.28rem; } -.theme-dot{ - width:22px; height:22px; border-radius:999px; - border:1px solid rgba(148,163,184,.85); - background:rgba(15,23,42,.9); color:#e5e7eb; font-size:.65rem; - display:flex; align-items:center; justify-content:center; padding:0; cursor:pointer; - box-shadow:0 0 6px rgba(15,23,42,.9); transition:transform 0.36s ease; -} -.theme-dot:hover{ transform:scale(1.06); } -.theme-dot-active{ border-color:#facc15; box-shadow:0 0 10px rgba(250,204,21,.9),0 0 14px rgba(56,189,248,.55); } - -.btn-primary, .btn-secondary{ - padding:.32rem .85rem; font-size:.78rem; border-radius:999px; font-weight:700; cursor:pointer; - white-space:nowrap; line-height:1; -} -.btn-primary{ - border:1px solid #22c55e; - background:radial-gradient(circle at 30% 30%,#22c55e,#16a34a); - color:#f9fafb; - box-shadow:0 4px 10px rgba(22,163,74,.5),0 0 8px rgba(22,163,74,.6); -} -.btn-primary:disabled{ opacity:.6; cursor:default; } -.btn-secondary{ - border:1px solid #bfdbfe; - background:rgba(15,23,42,.25); - color:#e0f2fe; - box-shadow:0 3px 10px rgba(15,23,42,.6); -} -.btn-secondary:hover{ background:rgba(15,23,42,.45); } - -@media (max-width:640px){ - .topbar{ padding:.5rem .7rem; border-bottom-left-radius:16px; border-bottom-right-radius:16px; } - .main-title{ font-size:1rem; } - .sub-title{ font-size:.7rem; } - .topbar-right{ width:100%; justify-content:flex-start; gap:.45rem; } -} - -/* SW_AUTH_UI:BEGIN */ -.auth-strip{ - margin-top: 6px; - display:flex; - align-items:center; - gap: .45rem; - flex-wrap: wrap; -} - -.auth-pill{ - display:inline-flex; - align-items:center; - gap: .35rem; - padding: .18rem .55rem; - border-radius: 999px; - font-size: .70rem; - font-weight: 900; - letter-spacing: .02em; - border: 1px solid rgba(148,163,184,.55); - background: rgba(2,6,23,.25); - color: #e5e7eb; -} -.auth-pill i{ font-size: .85rem; opacity:.95; } - -.auth-pill--ok{ - border-color: rgba(34,197,94,.65); - background: rgba(34,197,94,.16); -} -.auth-pill--loading{ - border-color: rgba(56,189,248,.65); - background: rgba(56,189,248,.14); -} -.auth-pill--anon{ - border-color: rgba(148,163,184,.55); - background: rgba(2,6,23,.20); -} - -.auth-banner{ - margin-top: 6px; - padding: 6px 10px; - border-radius: 14px; - font-size: .72rem; - font-weight: 800; - display:flex; - gap: 8px; - align-items:center; - flex-wrap:wrap; -} -.auth-banner--error{ - background: rgba(248,113,113,0.14); - border: 1px solid rgba(248,113,113,0.55); - color: #ffe4e6; -} -.auth-banner--info{ - background: rgba(56,189,248,0.14); - border: 1px solid rgba(56,189,248,0.55); - color: #e0f2fe; -} -.auth-banner--success{ - background: rgba(34,197,94,0.14); - border: 1px solid rgba(34,197,94,0.55); - color: #d1fae5; -} - -.user-chip{ - display:inline-flex; - align-items:center; - gap: .45rem; - padding: .22rem .55rem; - border-radius: 999px; - background: rgba(2,6,23,.25); - border: 1px solid rgba(148,163,184,.55); -} -.user-avatar{ - width: 26px; - height: 26px; - border-radius: 999px; - display:flex; - align-items:center; - justify-content:center; - background: rgba(56,189,248,0.18); - border: 1px solid rgba(56,189,248,0.35); -} -.user-avatar i{ font-size: 14px; } - -.user-name{ - font-size: .80rem; - font-weight: 950; - color: #f9fafb; - max-width: 220px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* Light theme readability */ -body[data-theme="light"] .auth-pill{ - background: rgba(255,255,255,0.82); - border-color: rgba(148,163,184,0.85); - color: #0b1220; -} -body[data-theme="light"] .user-chip{ - background: rgba(255,255,255,0.82); - border-color: rgba(148,163,184,0.85); -} -body[data-theme="light"] .user-name{ color:#0b1220; } -body[data-theme="light"] .user-avatar{ - background: rgba(30,107,255,0.12); - border-color: rgba(30,107,255,0.35); - color:#0b1220; -} -/* SW_AUTH_UI:END */ - -/* SW_AUTH_UI2:BEGIN */ -.sw-notice{ - margin: 10px 14px 0 14px; - padding: 10px 12px; - border-radius: 16px; - font-size: .78rem; - font-weight: 800; - line-height: 1.2; - border: 1px solid rgba(148,163,184,.35); - background: rgba(2,6,23,.22); - color: #e5e7eb; -} -.sw-notice--error{ - background: rgba(248,113,113,0.14); - border-color: rgba(248,113,113,0.55); - color: #ffe4e6; -} -.sw-notice--info{ - background: rgba(56,189,248,0.14); - border-color: rgba(56,189,248,0.55); - color: #e0f2fe; -} -.sw-notice--success{ - background: rgba(34,197,94,0.14); - border-color: rgba(34,197,94,0.55); - color: #d1fae5; -} -.sw-notice--warn{ - background: rgba(251,191,36,0.12); - border-color: rgba(251,191,36,0.40); - color: #fffbeb; -} - -/* single user chip (no duplicates) */ -.user-chip{ - display:inline-flex; - align-items:center; - gap: .45rem; - padding: .22rem .55rem; - border-radius: 999px; - background: rgba(2,6,23,.25); - border: 1px solid rgba(148,163,184,.55); -} -.user-avatar{ - width: 26px; - height: 26px; - border-radius: 999px; - display:flex; - align-items:center; - justify-content:center; - background: rgba(56,189,248,0.18); - border: 1px solid rgba(56,189,248,0.35); -} -.user-avatar i{ font-size: 14px; } -.user-name{ - font-size: .82rem; - font-weight: 950; - color: #f9fafb; - max-width: 180px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.user-badge{ - display:inline-flex; - align-items:center; - gap: .35rem; - padding: .10rem .45rem; - border-radius: 999px; - font-size: .70rem; - font-weight: 950; - border: 1px solid rgba(34,197,94,.55); - background: rgba(34,197,94,.14); - color: #d1fae5; -} -/* SW_AUTH_UI2:END */ - - -/* SW_TOPBAR_POLISH:BEGIN */ - -/* Make right side look like a clean “glass dock” */ -.topbar-right{ - padding: .28rem .35rem; - border-radius: 999px; - background: rgba(2,6,23,.18); - border: 1px solid rgba(255,255,255,.14); - backdrop-filter: blur(10px); -} - -/* User chip used by TopBar.jsx (missing styles before) */ -.sw-userchip{ - display:flex; - align-items:center; - gap:.5rem; - padding:.28rem .55rem; - border-radius: 999px; - background: rgba(2,6,23,.22); - border: 1px solid rgba(148,163,184,.45); - box-shadow: 0 4px 14px rgba(0,0,0,.25); -} - -.sw-avatar{ - width: 30px; - height: 30px; - border-radius: 999px; - display:grid; - place-items:center; - background: rgba(56,189,248,0.18); - border: 1px solid rgba(56,189,248,0.35); - color: #e5e7eb; - font-weight: 950; - overflow:hidden; -} -.sw-avatar i{ font-size: 14px; } - -.sw-usertext{ - display:flex; - flex-direction:column; - line-height: 1.05; - min-width: 0; -} -.sw-userline{ - font-size: .72rem; - font-weight: 950; - color: #f9fafb; -} -.sw-userhandle{ - font-size: .68rem; - font-weight: 800; - color: #dbeafe; - opacity: .92; - max-width: 160px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* Mobile: dock becomes full width and nice */ -@media (max-width:640px){ - .topbar-right{ - width:100%; - border-radius: 16px; - justify-content: flex-start; - } -} - -/* Light theme readability */ -body[data-theme="light"] .topbar-right{ - background: rgba(255,255,255,.55); - border-color: rgba(148,163,184,.55); -} -body[data-theme="light"] .sw-userchip{ - background: rgba(255,255,255,.80); - border-color: rgba(148,163,184,.70); - color: #0b1220; -} -body[data-theme="light"] .sw-userline, -body[data-theme="light"] .sw-userhandle{ - color:#0b1220; -} -body[data-theme="light"] .sw-avatar{ - background: rgba(30,107,255,0.10); - border-color: rgba(30,107,255,0.35); - color:#0b1220; -} - -/* SW_TOPBAR_POLISH:END */ - -/* SW_TOPBAR_OVERFLOW_FIX:BEGIN */ -.topbar{ - flex-wrap: wrap; /* ✅ allow wrap instead of overflowing right */ - row-gap: .45rem; -} - -.topbar-left{ - display:flex; - align-items:center; - gap: .65rem; - min-width: 0; /* ✅ allow shrinking */ -} - -.title-block{ min-width:0; } -.main-title, .sub-title{ - max-width: 58vw; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.topbar-right{ - min-width: 0; - max-width: 100%; - flex: 1 1 auto; - justify-content: flex-end; - flex-wrap: wrap; /* ✅ wrap buttons instead of clipping */ - gap: .45rem; -} - -@media (max-width:640px){ - .main-title, .sub-title{ max-width: 86vw; } - .topbar-right{ - justify-content: flex-start; - width: 100%; - } -} - -/* Unified auth button (guest online / log on / log off) */ -.sw-auth-combo{ - display:flex; - align-items:center; - gap:.5rem; - padding:.25rem .55rem; - border-radius: 999px; - border: 1px solid rgba(148,163,184,.55); - background: rgba(2,6,23,.22); - color: #e5e7eb; - cursor:pointer; - max-width: 100%; - box-shadow: 0 4px 14px rgba(0,0,0,.22); -} - -.sw-auth-combo .sw-usertext{ - min-width: 0; -} - -.sw-auth-combo .sw-userhandle{ - max-width: 120px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.sw-auth-action{ - margin-left: .25rem; - padding: .22rem .55rem; - border-radius: 999px; - font-weight: 950; - font-size: .70rem; - border: 1px solid rgba(56,189,248,.35); - background: rgba(56,189,248,.14); - color: #dbeafe; - white-space: nowrap; -} - -.sw-auth-combo--on .sw-auth-action{ - border-color: rgba(34,197,94,.45); - background: rgba(34,197,94,.16); - color: #d1fae5; -} -/* SW_TOPBAR_OVERFLOW_FIX:END */ diff --git a/src/styles/topbar.css.bak.20251219-163055 b/src/styles/topbar.css.bak.20251219-163055 deleted file mode 100644 index aa73bf4..0000000 --- a/src/styles/topbar.css.bak.20251219-163055 +++ /dev/null @@ -1,61 +0,0 @@ -.topbar{ - position: sticky; top:0; z-index:100; - width:100%; - padding: .55rem .9rem; - display:flex; align-items:center; justify-content:space-between; gap:.6rem; - background: linear-gradient(135deg,#071225 0%,#0b2b5f 38%,#0e65c0 72%,#27a8ff 100%); - border-bottom-left-radius:18px; border-bottom-right-radius:18px; - box-shadow: 0 10px 24px rgba(0,0,0,.55), 0 0 18px rgba(56,189,248,.45); - overflow:hidden; -} - -.logo-circle{ - width:40px; height:40px; border-radius:50%; flex-shrink:0; - background-image:url("/icons/logo-master.png"); - background-size:cover; background-position:center; background-repeat:no-repeat; - box-shadow: 0 0 10px rgba(37,99,235,.8), 0 0 18px rgba(56,189,248,.6); - color:transparent; -} - -.title-block{ display:flex; flex-direction:column; line-height:1.05; min-width:0; } -.main-title{ font-size:1.02rem; font-weight:800; letter-spacing:.04em; color:#f9fafb; } -.sub-title{ font-size:.74rem; color:#dbeafe; opacity:.95; } - -.topbar-right{ display:flex; align-items:center; gap:.5rem; margin-left:auto; flex-wrap:wrap; } - -.theme-switch{ display:flex; gap:.28rem; } -.theme-dot{ - width:22px; height:22px; border-radius:999px; - border:1px solid rgba(148,163,184,.85); - background:rgba(15,23,42,.9); color:#e5e7eb; font-size:.65rem; - display:flex; align-items:center; justify-content:center; padding:0; cursor:pointer; - box-shadow:0 0 6px rgba(15,23,42,.9); transition:transform 0.36s ease; -} -.theme-dot:hover{ transform:scale(1.06); } -.theme-dot-active{ border-color:#facc15; box-shadow:0 0 10px rgba(250,204,21,.9),0 0 14px rgba(56,189,248,.55); } - -.btn-primary, .btn-secondary{ - padding:.32rem .85rem; font-size:.78rem; border-radius:999px; font-weight:700; cursor:pointer; - white-space:nowrap; line-height:1; -} -.btn-primary{ - border:1px solid #22c55e; - background:radial-gradient(circle at 30% 30%,#22c55e,#16a34a); - color:#f9fafb; - box-shadow:0 4px 10px rgba(22,163,74,.5),0 0 8px rgba(22,163,74,.6); -} -.btn-primary:disabled{ opacity:.6; cursor:default; } -.btn-secondary{ - border:1px solid #bfdbfe; - background:rgba(15,23,42,.25); - color:#e0f2fe; - box-shadow:0 3px 10px rgba(15,23,42,.6); -} -.btn-secondary:hover{ background:rgba(15,23,42,.45); } - -@media (max-width:640px){ - .topbar{ padding:.5rem .7rem; border-bottom-left-radius:16px; border-bottom-right-radius:16px; } - .main-title{ font-size:1rem; } - .sub-title{ font-size:.7rem; } - .topbar-right{ width:100%; justify-content:flex-start; gap:.45rem; } -} diff --git a/src/styles/topbar.css.bak.20251219-164233 b/src/styles/topbar.css.bak.20251219-164233 deleted file mode 100644 index bb73d50..0000000 --- a/src/styles/topbar.css.bak.20251219-164233 +++ /dev/null @@ -1,174 +0,0 @@ -.topbar{ - position: sticky; top:0; z-index:100; - width:100%; - padding: .55rem .9rem; - display:flex; align-items:center; justify-content:space-between; gap:.6rem; - background: linear-gradient(135deg,#071225 0%,#0b2b5f 38%,#0e65c0 72%,#27a8ff 100%); - border-bottom-left-radius:18px; border-bottom-right-radius:18px; - box-shadow: 0 10px 24px rgba(0,0,0,.55), 0 0 18px rgba(56,189,248,.45); - overflow:hidden; -} - -.logo-circle{ - width:40px; height:40px; border-radius:50%; flex-shrink:0; - background-image:url("/icons/logo-master.png"); - background-size:cover; background-position:center; background-repeat:no-repeat; - box-shadow: 0 0 10px rgba(37,99,235,.8), 0 0 18px rgba(56,189,248,.6); - color:transparent; -} - -.title-block{ display:flex; flex-direction:column; line-height:1.05; min-width:0; } -.main-title{ font-size:1.02rem; font-weight:800; letter-spacing:.04em; color:#f9fafb; } -.sub-title{ font-size:.74rem; color:#dbeafe; opacity:.95; } - -.topbar-right{ display:flex; align-items:center; gap:.5rem; margin-left:auto; flex-wrap:wrap; } - -.theme-switch{ display:flex; gap:.28rem; } -.theme-dot{ - width:22px; height:22px; border-radius:999px; - border:1px solid rgba(148,163,184,.85); - background:rgba(15,23,42,.9); color:#e5e7eb; font-size:.65rem; - display:flex; align-items:center; justify-content:center; padding:0; cursor:pointer; - box-shadow:0 0 6px rgba(15,23,42,.9); transition:transform 0.36s ease; -} -.theme-dot:hover{ transform:scale(1.06); } -.theme-dot-active{ border-color:#facc15; box-shadow:0 0 10px rgba(250,204,21,.9),0 0 14px rgba(56,189,248,.55); } - -.btn-primary, .btn-secondary{ - padding:.32rem .85rem; font-size:.78rem; border-radius:999px; font-weight:700; cursor:pointer; - white-space:nowrap; line-height:1; -} -.btn-primary{ - border:1px solid #22c55e; - background:radial-gradient(circle at 30% 30%,#22c55e,#16a34a); - color:#f9fafb; - box-shadow:0 4px 10px rgba(22,163,74,.5),0 0 8px rgba(22,163,74,.6); -} -.btn-primary:disabled{ opacity:.6; cursor:default; } -.btn-secondary{ - border:1px solid #bfdbfe; - background:rgba(15,23,42,.25); - color:#e0f2fe; - box-shadow:0 3px 10px rgba(15,23,42,.6); -} -.btn-secondary:hover{ background:rgba(15,23,42,.45); } - -@media (max-width:640px){ - .topbar{ padding:.5rem .7rem; border-bottom-left-radius:16px; border-bottom-right-radius:16px; } - .main-title{ font-size:1rem; } - .sub-title{ font-size:.7rem; } - .topbar-right{ width:100%; justify-content:flex-start; gap:.45rem; } -} - -/* SW_AUTH_UI:BEGIN */ -.auth-strip{ - margin-top: 6px; - display:flex; - align-items:center; - gap: .45rem; - flex-wrap: wrap; -} - -.auth-pill{ - display:inline-flex; - align-items:center; - gap: .35rem; - padding: .18rem .55rem; - border-radius: 999px; - font-size: .70rem; - font-weight: 900; - letter-spacing: .02em; - border: 1px solid rgba(148,163,184,.55); - background: rgba(2,6,23,.25); - color: #e5e7eb; -} -.auth-pill i{ font-size: .85rem; opacity:.95; } - -.auth-pill--ok{ - border-color: rgba(34,197,94,.65); - background: rgba(34,197,94,.16); -} -.auth-pill--loading{ - border-color: rgba(56,189,248,.65); - background: rgba(56,189,248,.14); -} -.auth-pill--anon{ - border-color: rgba(148,163,184,.55); - background: rgba(2,6,23,.20); -} - -.auth-banner{ - margin-top: 6px; - padding: 6px 10px; - border-radius: 14px; - font-size: .72rem; - font-weight: 800; - display:flex; - gap: 8px; - align-items:center; - flex-wrap:wrap; -} -.auth-banner--error{ - background: rgba(248,113,113,0.14); - border: 1px solid rgba(248,113,113,0.55); - color: #ffe4e6; -} -.auth-banner--info{ - background: rgba(56,189,248,0.14); - border: 1px solid rgba(56,189,248,0.55); - color: #e0f2fe; -} -.auth-banner--success{ - background: rgba(34,197,94,0.14); - border: 1px solid rgba(34,197,94,0.55); - color: #d1fae5; -} - -.user-chip{ - display:inline-flex; - align-items:center; - gap: .45rem; - padding: .22rem .55rem; - border-radius: 999px; - background: rgba(2,6,23,.25); - border: 1px solid rgba(148,163,184,.55); -} -.user-avatar{ - width: 26px; - height: 26px; - border-radius: 999px; - display:flex; - align-items:center; - justify-content:center; - background: rgba(56,189,248,0.18); - border: 1px solid rgba(56,189,248,0.35); -} -.user-avatar i{ font-size: 14px; } - -.user-name{ - font-size: .80rem; - font-weight: 950; - color: #f9fafb; - max-width: 220px; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* Light theme readability */ -body[data-theme="light"] .auth-pill{ - background: rgba(255,255,255,0.82); - border-color: rgba(148,163,184,0.85); - color: #0b1220; -} -body[data-theme="light"] .user-chip{ - background: rgba(255,255,255,0.82); - border-color: rgba(148,163,184,0.85); -} -body[data-theme="light"] .user-name{ color:#0b1220; } -body[data-theme="light"] .user-avatar{ - background: rgba(30,107,255,0.12); - border-color: rgba(30,107,255,0.35); - color:#0b1220; -} -/* SW_AUTH_UI:END */