diff --git a/frontend/auth/admin/main.js b/frontend/auth/admin/main.js index f032833..9b0d05a 100644 --- a/frontend/auth/admin/main.js +++ b/frontend/auth/admin/main.js @@ -1,3 +1,6 @@ +import { initThemeFromCache } from '@/utils/theme' +initThemeFromCache() + import '@/assets/style.css' import { createApp } from 'vue' diff --git a/frontend/auth/main.js b/frontend/auth/main.js index e9f238f..ccd8cf3 100644 --- a/frontend/auth/main.js +++ b/frontend/auth/main.js @@ -1,3 +1,6 @@ +import { initThemeFromCache } from '@/utils/theme' +initThemeFromCache() + import '@/assets/style.css' import { createApp } from 'vue' diff --git a/frontend/auth/restricted/theme.js b/frontend/auth/restricted/theme.js index c2a97ff..7308ce1 100644 --- a/frontend/auth/restricted/theme.js +++ b/frontend/auth/restricted/theme.js @@ -1,72 +1,11 @@ -// Early theme override script - runs before Vue to prevent flicker -// Parses ?theme=light or ?theme=dark from URL hash and injects CSS overrides +// Early theme for restricted app - first URL param wins, then localStorage +import { themeColors, applyTheme, getCachedTheme } from '@/utils/theme.js' -const themeColors = { - light: { - 'color-canvas': '#ffffff', - 'color-surface': '#eff6ff', - 'color-surface-subtle': '#dbeafe', - 'color-border': '#2563eb', - 'color-border-strong': '#1e40af', - 'color-heading': '#1e3a8a', - 'color-text': '#1e293b', - 'color-text-muted': '#475569', - 'color-link': '#1d4ed8', - 'color-link-hover': '#1e40af', - 'color-accent': '#2563eb', - 'color-accent-strong': '#1e40af', - 'color-accent-contrast': '#ffffff', - 'color-success-text': '#166534', - 'color-success-bg': '#dcfce7', - 'color-error-text': '#b91c1c', - 'color-error-bg': '#fee2e2', - 'color-info-text': '#1e40af', - 'color-info-bg': '#dbeafe', - 'color-danger': '#dc2626', - 'shadow-soft': '0 10px 30px rgba(30, 64, 175, 0.15)', - }, - dark: { - 'color-canvas': '#0f172a', - 'color-surface': '#141b2f', - 'color-surface-subtle': '#1b243b', - 'color-border': '#25304a', - 'color-border-strong': '#3d4d6b', - 'color-heading': '#fff', - 'color-text': '#e2e8f0', - 'color-text-muted': '#94a3b8', - 'color-link': '#60a5fa', - 'color-link-hover': '#93c5fd', - 'color-accent': '#60a5fa', - 'color-accent-strong': '#3b82f6', - 'color-accent-contrast': '#0b1120', - 'color-success-text': '#34d399', - 'color-success-bg': '#1a4d2e', - 'color-error-text': '#fca5a5', - 'color-error-bg': '#4a1f1f', - 'color-info-text': '#bae6fd', - 'color-info-bg': '#1e3a5f', - 'color-danger': '#f87171', - 'shadow-soft': '0 0 0 #000000', - } -} - -const STYLE_ID = 'theme-override' - -function applyTheme() { +function getTheme() { const params = new URLSearchParams(location.hash.slice(1)) - const theme = params.get('theme') - - // Remove existing override - document.getElementById(STYLE_ID)?.remove() - - if (theme && themeColors[theme]) { - const css = `.surface { ${Object.entries(themeColors[theme]).map(([k, v]) => `--${k}: ${v}`).join('; ')}; }` - const style = document.createElement('style') - style.id = STYLE_ID - style.textContent = css - document.head.appendChild(style) - } + return params.get('theme') || getCachedTheme() || '' } -applyTheme() -addEventListener('hashchange', applyTheme) +// Use .surface selector to preserve transparent background +applyTheme(getTheme(), '.surface') +addEventListener('hashchange', () => applyTheme(getTheme(), '.surface')) diff --git a/frontend/src/assets/style.css b/frontend/src/assets/style.css index e383f83..f387ea1 100644 --- a/frontend/src/assets/style.css +++ b/frontend/src/assets/style.css @@ -131,6 +131,7 @@ a:focus-visible { } .view-root { + position: relative; flex: 1; width: 100%; display: flex; diff --git a/frontend/src/components/ProfileView.vue b/frontend/src/components/ProfileView.vue index d1d4d9e..ee936f5 100644 --- a/frontend/src/components/ProfileView.vue +++ b/frontend/src/components/ProfileView.vue @@ -1,5 +1,15 @@