Spaces:
Running
Running
| <script setup lang="ts"> | |
| import { ref, onMounted } from 'vue' | |
| import type { ChatMessage, ModelInfo, StatsResponse, AuthUser } from './types' | |
| import { | |
| getModels, getStats, getAuthConfig, loginWithGoogle, getMe, | |
| setToken, clearToken, getToken, | |
| } from './api' | |
| import { t, getStoredLang, storeLang } from './i18n' | |
| import type { Lang } from './i18n' | |
| import ConfigProvider from './wc3/ConfigProvider' | |
| import Wc3Button from './wc3/base/Button.vue' | |
| import Wc3Cursor from './wc3/base/Cursor.vue' | |
| import { RACE_KEY } from './wc3/consts' | |
| import SidePanel from './components/SidePanel.vue' | |
| import ChatView from './components/ChatView.vue' | |
| import ImageView from './components/ImageView.vue' | |
| import LoginPage from './components/LoginPage.vue' | |
| // Language state | |
| const lang = ref<Lang>(getStoredLang()) | |
| function setLang(newLang: Lang) { | |
| lang.value = newLang | |
| storeLang(newLang) | |
| } | |
| // Auth state | |
| const authLoading = ref(true) | |
| const authEnabled = ref(false) | |
| const googleClientId = ref('') | |
| const currentUser = ref<AuthUser | null>(null) | |
| const isLoggedIn = ref(false) | |
| // App state | |
| const activeTab = ref<'chat' | 'image'>('chat') | |
| const selectedModel = ref('gemini-3-flash-preview') | |
| const showCitations = ref(true) | |
| const models = ref<Record<string, ModelInfo>>({}) | |
| const stats = ref<StatsResponse>({ documents: 0, collection: '' }) | |
| const chatHistory = ref<ChatMessage[]>([]) | |
| const race = ref(RACE_KEY.UNDEAD) | |
| onMounted(async () => { | |
| try { | |
| // 1. Check if auth is enabled | |
| const authCfg = await getAuthConfig() | |
| authEnabled.value = authCfg.auth_enabled | |
| googleClientId.value = authCfg.google_client_id || '' | |
| // 2. If auth is enabled, check existing token | |
| if (authCfg.auth_enabled && getToken()) { | |
| try { | |
| const me = await getMe() | |
| currentUser.value = me | |
| isLoggedIn.value = true | |
| } catch { | |
| clearToken() | |
| } | |
| } | |
| // 3. If no auth required, skip login | |
| if (!authCfg.auth_enabled) { | |
| isLoggedIn.value = true | |
| } | |
| // 4. Load app data if logged in | |
| if (isLoggedIn.value) { | |
| await loadAppData() | |
| } | |
| } catch (e) { | |
| console.error('Init error:', e) | |
| } finally { | |
| authLoading.value = false | |
| } | |
| }) | |
| async function loadAppData() { | |
| const [modelsRes, statsRes] = await Promise.all([getModels(), getStats()]) | |
| models.value = modelsRes.models | |
| selectedModel.value = modelsRes.default | |
| stats.value = statsRes | |
| } | |
| async function handleGoogleLogin(credential: string) { | |
| try { | |
| const res = await loginWithGoogle(credential) | |
| setToken(res.token) | |
| currentUser.value = res.user | |
| isLoggedIn.value = true | |
| await loadAppData() | |
| } catch (e: any) { | |
| console.error('Login error:', e) | |
| } | |
| } | |
| function handleLogout() { | |
| clearToken() | |
| currentUser.value = null | |
| isLoggedIn.value = false | |
| chatHistory.value = [] | |
| } | |
| function clearChat() { | |
| chatHistory.value = [] | |
| } | |
| </script> | |
| <template> | |
| <!-- Loading splash --> | |
| <div v-if="authLoading" class="loading-splash"> | |
| <div class="loading-splash__icon">⚔️</div> | |
| <div class="loading-splash__text">{{ t('app.loading', lang) }}</div> | |
| </div> | |
| <!-- Login page (when auth is required and user not logged in) --> | |
| <LoginPage | |
| v-else-if="authEnabled && !isLoggedIn" | |
| :googleClientId="googleClientId" | |
| :lang="lang" | |
| @google-login="handleGoogleLogin" | |
| @update:lang="setLang" | |
| /> | |
| <!-- Main app --> | |
| <ConfigProvider v-else :theme="race"> | |
| <Wc3Cursor /> | |
| <div class="app-shell"> | |
| <!-- Sidebar --> | |
| <aside class="sidebar"> | |
| <SidePanel | |
| v-model:model="selectedModel" | |
| v-model:showCitations="showCitations" | |
| v-model:race="race" | |
| :models="models" | |
| :stats="stats" | |
| :user="currentUser" | |
| :lang="lang" | |
| @clear-chat="clearChat" | |
| @logout="handleLogout" | |
| @update:lang="setLang" | |
| /> | |
| </aside> | |
| <!-- Main --> | |
| <main class="main-content"> | |
| <!-- Header Bar (styled like WC3 top bar) --> | |
| <header class="app-header"> | |
| <div class="header-frame"> | |
| <h1>{{ t('app.title', lang) }}</h1> | |
| <p class="subtitle">{{ t('app.subtitle', lang) }}</p> | |
| </div> | |
| </header> | |
| <!-- Tab bar --> | |
| <nav class="tab-bar"> | |
| <div class="tab-btn-wrap"> | |
| <Wc3Button | |
| size="s" | |
| :class="['tab-trigger', { active: activeTab === 'chat' }]" | |
| @click="activeTab = 'chat'" | |
| > | |
| {{ t('tab.chat', lang) }} | |
| </Wc3Button> | |
| </div> | |
| <div class="tab-btn-wrap"> | |
| <Wc3Button | |
| size="s" | |
| :class="['tab-trigger', { active: activeTab === 'image' }]" | |
| @click="activeTab = 'image'" | |
| > | |
| {{ t('tab.image', lang) }} | |
| </Wc3Button> | |
| </div> | |
| </nav> | |
| <!-- Content --> | |
| <ChatView | |
| v-if="activeTab === 'chat'" | |
| v-model:history="chatHistory" | |
| :model="selectedModel" | |
| :showCitations="showCitations" | |
| :lang="lang" | |
| /> | |
| <ImageView | |
| v-else | |
| :model="selectedModel" | |
| :showCitations="showCitations" | |
| :lang="lang" | |
| /> | |
| </main> | |
| </div> | |
| </ConfigProvider> | |
| </template> | |
| <style lang="scss"> | |
| /* ============================================================ | |
| GLOBAL STYLES — WC3 THEME (uses real textures from the lib) | |
| ============================================================ */ | |
| :root { | |
| --wc3-bg: #0a0a0f; | |
| --wc3-bg-panel: #0f0f1a; | |
| --wc3-gold: #c8aa6e; | |
| --wc3-gold-bright: #f0d060; | |
| --wc3-gold-dim: #8b7b4f; | |
| --wc3-text: #d4c4a0; | |
| --wc3-text-light: #f0e6d0; | |
| --wc3-text-dim: #7a6e5a; | |
| --wc3-text-muted: #555060; | |
| --wc3-border: #5a4a2a; | |
| --wc3-border-dim: #3a2e1e; | |
| --wc3-red-soft: rgba(140, 40, 40, 0.25); | |
| --wc3-blue-soft: rgba(30, 50, 90, 0.35); | |
| --font-heading: 'Cinzel', serif; | |
| --font-display: 'Cinzel Decorative', 'Cinzel', serif; | |
| --font-body: 'Crimson Text', serif; | |
| --sidebar-w: 270px; | |
| } | |
| *, | |
| *::before, | |
| *::after { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| html, body { | |
| height: 100%; | |
| overflow: hidden; | |
| background: var(--wc3-bg); | |
| color: var(--wc3-text); | |
| font-family: var(--font-body); | |
| line-height: 1.6; | |
| } | |
| #app { | |
| height: 100vh; | |
| } | |
| /* ---------- Scrollbar ---------- */ | |
| ::-webkit-scrollbar { width: 8px; } | |
| ::-webkit-scrollbar-track { background: rgba(0,0,0,0.3); } | |
| ::-webkit-scrollbar-thumb { | |
| background: linear-gradient(180deg, var(--wc3-gold-dim), #4a3a20); | |
| border-radius: 2px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { background: var(--wc3-gold); } | |
| /* ---------- Layout Shell ---------- */ | |
| .app-shell { | |
| display: flex; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| /* ---------- Sidebar ---------- */ | |
| .sidebar { | |
| width: var(--sidebar-w); | |
| min-width: var(--sidebar-w); | |
| background: linear-gradient(180deg, #0f0f22 0%, #0a0a18 100%); | |
| border-right: 2px solid var(--wc3-border); | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| box-shadow: 2px 0 20px rgba(0,0,0,0.5); | |
| } | |
| /* ---------- Main ---------- */ | |
| .main-content { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| background: radial-gradient(ellipse at top center, #12122a 0%, #0a0a16 60%, #070710 100%); | |
| } | |
| /* ---------- Header ---------- */ | |
| .app-header { | |
| text-align: center; | |
| padding: 1.25rem 1rem 0.5rem; | |
| flex-shrink: 0; | |
| } | |
| .header-frame h1 { | |
| font-family: var(--font-display); | |
| font-size: 2rem; | |
| font-weight: 900; | |
| background: linear-gradient(180deg, #f0d060 0%, #c8aa6e 50%, #8b7b4f 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| letter-spacing: 3px; | |
| filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8)); | |
| } | |
| .header-frame .subtitle { | |
| font-family: var(--font-body); | |
| color: var(--wc3-text-dim); | |
| font-style: italic; | |
| font-size: 0.95rem; | |
| margin-top: 0.15rem; | |
| } | |
| /* ---------- Divider ---------- */ | |
| .gold-divider { | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--wc3-gold-dim), transparent); | |
| margin: 0.75rem 0; | |
| border: none; | |
| } | |
| /* ---------- Tab Bar (using real WC3 buttons) ---------- */ | |
| .tab-bar { | |
| display: flex; | |
| gap: 0.5rem; | |
| padding: 0.35rem 1.5rem; | |
| border-bottom: 1px solid var(--wc3-border-dim); | |
| flex-shrink: 0; | |
| } | |
| .tab-btn-wrap { | |
| height: 32px; | |
| min-width: 120px; | |
| } | |
| .tab-trigger.active { | |
| filter: brightness(1.3); | |
| } | |
| /* ---------- Chat bubble (shared) ---------- */ | |
| .msg { | |
| max-width: 85%; | |
| padding: 0.85rem 1.15rem; | |
| border-radius: 6px; | |
| line-height: 1.65; | |
| font-size: 1rem; | |
| border: 1px solid transparent; | |
| animation: fadeSlide 0.25s ease; | |
| } | |
| @keyframes fadeSlide { | |
| from { opacity: 0; transform: translateY(8px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .msg.user { | |
| align-self: flex-end; | |
| background: linear-gradient(135deg, rgba(140,40,40,0.35), rgba(90,25,25,0.25)); | |
| border-color: rgba(200,80,80,0.3); | |
| color: var(--wc3-text-light); | |
| } | |
| .msg.assistant { | |
| align-self: flex-start; | |
| background: linear-gradient(135deg, rgba(20,30,65,0.45), rgba(15,20,50,0.3)); | |
| border-color: rgba(100,130,200,0.25); | |
| color: var(--wc3-text-light); | |
| } | |
| /* Markdown */ | |
| .msg h1, .msg h2, .msg h3, .msg h4 { | |
| font-family: var(--font-heading); | |
| color: var(--wc3-gold); | |
| margin: 0.75rem 0 0.35rem; | |
| } | |
| .msg h1 { font-size: 1.3rem; } | |
| .msg h2 { font-size: 1.15rem; } | |
| .msg h3 { font-size: 1.05rem; } | |
| .msg p { margin: 0.4rem 0; } | |
| .msg strong { color: var(--wc3-gold-bright); } | |
| .msg em { font-style: italic; } | |
| .msg ul, .msg ol { padding-left: 1.25rem; margin: 0.4rem 0; } | |
| .msg li { margin: 0.2rem 0; } | |
| .msg code { | |
| background: rgba(200,170,110,0.08); | |
| border: 1px solid var(--wc3-border-dim); | |
| padding: 0.1rem 0.4rem; | |
| border-radius: 3px; | |
| font-size: 0.9em; | |
| color: var(--wc3-gold); | |
| } | |
| .msg pre { | |
| background: rgba(0,0,0,0.3); | |
| border: 1px solid var(--wc3-border-dim); | |
| border-radius: 4px; | |
| padding: 0.75rem; | |
| overflow-x: auto; | |
| margin: 0.5rem 0; | |
| } | |
| .msg pre code { background: none; border: none; padding: 0; } | |
| .msg blockquote { | |
| border-left: 3px solid var(--wc3-gold-dim); | |
| padding-left: 0.75rem; | |
| margin: 0.5rem 0; | |
| color: var(--wc3-text); | |
| font-style: italic; | |
| } | |
| .msg hr { | |
| border: none; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--wc3-gold-dim), transparent); | |
| margin: 0.75rem 0; | |
| } | |
| .msg-meta { | |
| font-size: 0.8rem; | |
| color: var(--wc3-text-dim); | |
| font-style: italic; | |
| margin-top: 0.4rem; | |
| } | |
| /* ---------- Form Inputs ---------- */ | |
| .wc3-select { | |
| width: 100%; | |
| padding: 0.5rem 0.75rem; | |
| font-family: var(--font-body); | |
| font-size: 0.95rem; | |
| color: var(--wc3-text-light); | |
| background: linear-gradient(180deg, #1a1a32, #13132a); | |
| border: 1px solid var(--wc3-gold-dim); | |
| border-radius: 2px; | |
| outline: none; | |
| cursor: pointer; | |
| appearance: none; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23c8aa6e' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); | |
| background-repeat: no-repeat; | |
| background-position: right 0.75rem center; | |
| } | |
| .wc3-select:hover { border-color: var(--wc3-gold); } | |
| .wc3-select:focus { | |
| border-color: var(--wc3-gold); | |
| box-shadow: 0 0 8px rgba(200,170,110,0.2); | |
| } | |
| .wc3-select option { | |
| background: var(--wc3-bg-panel); | |
| color: var(--wc3-text); | |
| } | |
| .wc3-input { | |
| width: 100%; | |
| padding: 0.6rem 0.85rem; | |
| font-family: var(--font-body); | |
| font-size: 1rem; | |
| color: var(--wc3-text-light); | |
| background: linear-gradient(180deg, #12122a, #0f0f24); | |
| border: 1px solid var(--wc3-border); | |
| border-radius: 2px; | |
| outline: none; | |
| box-shadow: inset 0 2px 6px rgba(0,0,0,0.3); | |
| transition: border-color 0.15s; | |
| } | |
| .wc3-input:focus { | |
| border-color: var(--wc3-gold); | |
| box-shadow: inset 0 2px 6px rgba(0,0,0,0.3), 0 0 8px rgba(200,170,110,0.15); | |
| } | |
| .wc3-input::placeholder { color: var(--wc3-text-muted); } | |
| .wc3-checkbox { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| cursor: pointer; | |
| font-family: var(--font-body); | |
| color: var(--wc3-text); | |
| font-size: 0.95rem; | |
| } | |
| .wc3-checkbox input[type="checkbox"] { | |
| appearance: none; | |
| width: 18px; | |
| height: 18px; | |
| background: #13132a; | |
| border: 1px solid var(--wc3-gold-dim); | |
| border-radius: 2px; | |
| cursor: pointer; | |
| position: relative; | |
| } | |
| .wc3-checkbox input[type="checkbox"]:checked { | |
| border-color: var(--wc3-gold); | |
| } | |
| .wc3-checkbox input[type="checkbox"]:checked::after { | |
| content: '✓'; | |
| position: absolute; | |
| top: -1px; left: 2px; | |
| color: var(--wc3-gold-bright); | |
| font-size: 14px; | |
| font-weight: bold; | |
| } | |
| /* ---------- Loading ---------- */ | |
| .loading { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| padding: 0.75rem 1rem; | |
| color: var(--wc3-gold); | |
| font-family: var(--font-heading); | |
| font-size: 0.85rem; | |
| font-style: italic; | |
| animation: fadeSlide 0.3s ease; | |
| } | |
| .dot-pulse { | |
| display: inline-flex; | |
| gap: 4px; | |
| } | |
| .dot-pulse span { | |
| width: 6px; height: 6px; | |
| border-radius: 50%; | |
| background: var(--wc3-gold); | |
| animation: pulse 1.2s infinite ease-in-out; | |
| } | |
| .dot-pulse span:nth-child(2) { animation-delay: 0.2s; } | |
| .dot-pulse span:nth-child(3) { animation-delay: 0.4s; } | |
| @keyframes pulse { | |
| 0%, 80%, 100% { opacity: 0.2; transform: scale(0.8); } | |
| 40% { opacity: 1; transform: scale(1.1); } | |
| } | |
| /* ---------- Error ---------- */ | |
| .error-msg { | |
| background: rgba(180,40,40,0.15); | |
| border: 1px solid #cc3333; | |
| color: #ff8888; | |
| padding: 0.6rem 0.85rem; | |
| border-radius: 4px; | |
| font-size: 0.9rem; | |
| } | |
| /* ---------- Resource bar ---------- */ | |
| .resource-bar { | |
| background: linear-gradient(180deg, #1a1a30, #111122); | |
| border: 1px solid var(--wc3-border); | |
| border-radius: 3px; | |
| padding: 0.45rem 0.75rem; | |
| text-align: center; | |
| font-family: var(--font-heading); | |
| font-size: 0.8rem; | |
| color: var(--wc3-gold); | |
| letter-spacing: 1px; | |
| } | |
| /* ---------- Sidebar headings ---------- */ | |
| .sidebar h2 { | |
| font-family: var(--font-heading); | |
| color: var(--wc3-gold); | |
| font-size: 0.85rem; | |
| letter-spacing: 2px; | |
| text-transform: uppercase; | |
| text-shadow: 0 0 8px rgba(200,170,110,0.2); | |
| padding-bottom: 0.4rem; | |
| border-bottom: 1px solid var(--wc3-border-dim); | |
| margin-top: 0.75rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .sidebar h2:first-child { margin-top: 0; } | |
| .model-caption { | |
| font-size: 0.85rem; | |
| color: var(--wc3-text-dim); | |
| font-style: italic; | |
| margin-top: 0.25rem; | |
| } | |
| /* ---------- Sidebar footer ---------- */ | |
| .sidebar-footer { | |
| margin-top: auto; | |
| padding-top: 0.75rem; | |
| text-align: center; | |
| font-size: 0.75rem; | |
| color: var(--wc3-text-muted); | |
| font-style: italic; | |
| line-height: 1.6; | |
| } | |
| /* ---------- Chat input bar ---------- */ | |
| .chat-input-bar { | |
| display: flex; | |
| gap: 0.5rem; | |
| padding: 0.75rem 1.5rem; | |
| background: linear-gradient(180deg, var(--wc3-bg-panel), var(--wc3-bg)); | |
| border-top: 1px solid var(--wc3-border); | |
| flex-shrink: 0; | |
| align-items: center; | |
| } | |
| .chat-input-bar .wc3-input { flex: 1; } | |
| .chat-send-btn { | |
| height: 38px; | |
| min-width: 100px; | |
| } | |
| /* ---------- Citations ---------- */ | |
| .citations-toggle { | |
| font-family: var(--font-heading); | |
| font-size: 0.8rem; | |
| color: var(--wc3-gold-dim); | |
| background: none; | |
| border: 1px solid var(--wc3-border-dim); | |
| border-radius: 3px; | |
| padding: 0.35rem 0.75rem; | |
| cursor: pointer; | |
| margin-top: 0.5rem; | |
| transition: all 0.15s; | |
| } | |
| .citations-toggle:hover { | |
| border-color: var(--wc3-gold); | |
| color: var(--wc3-gold); | |
| } | |
| .citations-panel { | |
| margin-top: 0.5rem; | |
| border: 1px solid var(--wc3-border-dim); | |
| border-radius: 4px; | |
| background: rgba(0,0,0,0.2); | |
| padding: 0.75rem; | |
| animation: fadeSlide 0.2s ease; | |
| } | |
| .citation-item { | |
| padding: 0.5rem 0; | |
| border-bottom: 1px solid var(--wc3-border-dim); | |
| } | |
| .citation-item:last-child { border-bottom: none; } | |
| .citation-header { | |
| font-family: var(--font-heading); | |
| font-size: 0.8rem; | |
| color: var(--wc3-gold); | |
| margin-bottom: 0.25rem; | |
| display: flex; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 0.25rem; | |
| } | |
| .citation-text { | |
| font-size: 0.85rem; | |
| color: var(--wc3-text); | |
| font-style: italic; | |
| border-left: 2px solid var(--wc3-gold-dim); | |
| padding-left: 0.6rem; | |
| margin-top: 0.25rem; | |
| line-height: 1.5; | |
| } | |
| /* ---------- Image tab ---------- */ | |
| .image-tab { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 1.5rem; | |
| } | |
| .upload-zone { | |
| border: 2px dashed var(--wc3-border); | |
| border-radius: 6px; | |
| padding: 2rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.15s; | |
| background: rgba(0,0,0,0.15); | |
| margin-bottom: 1rem; | |
| } | |
| .upload-zone:hover { | |
| border-color: var(--wc3-gold); | |
| background: rgba(200,170,110,0.03); | |
| } | |
| .upload-zone input[type="file"] { display: none; } | |
| .upload-zone .icon { font-size: 2.5rem; margin-bottom: 0.5rem; } | |
| .upload-zone p { color: var(--wc3-text-dim); font-size: 0.95rem; } | |
| .image-preview { | |
| max-width: 100%; | |
| max-height: 300px; | |
| border: 1px solid var(--wc3-border); | |
| border-radius: 4px; | |
| margin-bottom: 1rem; | |
| } | |
| .analysis-result { | |
| background: linear-gradient(135deg, rgba(20,30,65,0.45), rgba(15,20,50,0.3)); | |
| border: 1px solid rgba(100,130,200,0.25); | |
| border-radius: 6px; | |
| padding: 1rem 1.25rem; | |
| line-height: 1.65; | |
| } | |
| /* ---------- Loading splash ---------- */ | |
| .loading-splash { | |
| width: 100vw; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| background: radial-gradient(ellipse at center, #12122a 0%, #0a0a16 50%, #050510 100%); | |
| color: var(--wc3-gold, #c8aa6e); | |
| } | |
| .loading-splash__icon { | |
| font-size: 3rem; | |
| margin-bottom: 1rem; | |
| animation: pulse 1.5s ease-in-out infinite; | |
| } | |
| .loading-splash__text { | |
| font-family: var(--font-heading, 'Cinzel', serif); | |
| font-size: 1rem; | |
| letter-spacing: 3px; | |
| text-transform: uppercase; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 0.5; transform: scale(1); } | |
| 50% { opacity: 1; transform: scale(1.1); } | |
| } | |
| /* ---------- User badge (sidebar) ---------- */ | |
| .user-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| padding: 0.4rem 0; | |
| } | |
| .user-avatar { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| border: 1.5px solid var(--wc3-gold-dim, #8b7b4f); | |
| flex-shrink: 0; | |
| } | |
| .user-info { | |
| display: flex; | |
| flex-direction: column; | |
| min-width: 0; | |
| } | |
| .user-name { | |
| font-family: var(--font-heading, 'Cinzel', serif); | |
| color: var(--wc3-gold, #c8aa6e); | |
| font-size: 0.75rem; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .logout-btn { | |
| background: none; | |
| border: none; | |
| color: var(--wc3-text-dim, #7a6e5a); | |
| font-size: 0.65rem; | |
| cursor: pointer; | |
| text-align: left; | |
| padding: 0; | |
| font-family: var(--font-body, 'Crimson Text', serif); | |
| text-decoration: underline; | |
| text-underline-offset: 2px; | |
| } | |
| .logout-btn:hover { color: var(--wc3-gold, #c8aa6e); } | |
| /* ---------- Responsive ---------- */ | |
| @media (max-width: 768px) { | |
| .sidebar { display: none; } | |
| :root { --sidebar-w: 0px; } | |
| .header-frame h1 { font-size: 1.4rem; } | |
| } | |
| </style> | |