diff --git a/.gitignore b/.gitignore index 2427d71..ee73c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ Thumbs.db # ---- Obsidian ---- .obsidian/ -.claudian/sessions/conv-1781943335504-q62bzosye.meta.json +.claudian/ +修改过程笔记/ +学习复盘/ diff --git a/frontend/src/App.css b/frontend/src/App.css index e4e9938..9afbf9a 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -495,7 +495,10 @@ body { display: flex; flex-direction: column; padding: 16px 20px; - gap: 10px; + gap: 12px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--color-border) transparent; } .video-container { @@ -652,7 +655,7 @@ body { flex-direction: column; align-items: center; gap: 10px; - padding: 8px 0; + padding: 10px 0 4px; } .video-controls__row { @@ -1387,11 +1390,258 @@ body { .video-controls__toolbar { display: flex; - gap: 6px; + gap: 8px; flex-wrap: wrap; justify-content: center; + align-items: center; } +/* ---- Round Control Buttons (Camera / Mic) ---- */ + +.btn-ctrl-round { + width: 44px; + height: 44px; + border-radius: 50%; + border: 2px solid var(--color-border); + background: var(--color-surface-2); + color: var(--color-text-muted); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); + flex-shrink: 0; + position: relative; +} + +.btn-ctrl-round:hover { + background: var(--color-surface-3); + border-color: var(--color-surface-3); + color: var(--color-text); + transform: scale(1.06); +} + +.btn-ctrl-round:active { + transform: scale(0.95); +} + +.btn-ctrl-round--on { + background: rgba(52, 211, 153, 0.1); + border-color: rgba(52, 211, 153, 0.3); + color: var(--color-success); +} + +.btn-ctrl-round--on:hover { + background: rgba(52, 211, 153, 0.18); + border-color: rgba(52, 211, 153, 0.45); +} + +.btn-ctrl-round--off { + background: rgba(248, 113, 113, 0.08); + border-color: rgba(248, 113, 113, 0.2); + color: var(--color-error); +} + +.btn-ctrl-round--off:hover { + background: rgba(248, 113, 113, 0.15); + border-color: rgba(248, 113, 113, 0.35); +} + +.btn-ctrl-round--speaking { + animation: micPulse 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite; + box-shadow: 0 0 14px rgba(52, 211, 153, 0.45); +} + +/* ---- Pill Control Buttons (Recognize / Interrupt / Stop) ---- */ + +.btn-ctrl-pill { + padding: 8px 16px; + border-radius: 22px; + font-size: 0.78rem; + font-weight: 500; + cursor: pointer; + transition: all var(--transition-fast); + display: inline-flex; + align-items: center; + gap: 5px; + border: 1px solid var(--color-border); + background: var(--color-surface-2); + color: var(--color-text-muted); + white-space: nowrap; +} + +.btn-ctrl-pill:hover:not(:disabled) { + background: var(--color-surface-3); + color: var(--color-text); + border-color: var(--color-surface-3); +} + +.btn-ctrl-pill:active:not(:disabled) { + transform: translateY(1px); +} + +.btn-ctrl-pill:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-ctrl-pill--recognize { + background: rgba(59, 130, 246, 0.1); + color: var(--color-primary); + border-color: rgba(59, 130, 246, 0.25); +} + +.btn-ctrl-pill--recognize:hover:not(:disabled) { + background: rgba(59, 130, 246, 0.2); + border-color: rgba(59, 130, 246, 0.4); +} + +.btn-ctrl-pill--interrupt { + background: rgba(251, 191, 36, 0.1); + color: var(--color-warning); + border-color: rgba(251, 191, 36, 0.25); +} + +.btn-ctrl-pill--interrupt:hover:not(:disabled) { + background: rgba(251, 191, 36, 0.2); + border-color: rgba(251, 191, 36, 0.4); +} + +.btn-ctrl-pill--stop { + background: rgba(248, 113, 113, 0.1); + color: var(--color-error); + border-color: rgba(248, 113, 113, 0.2); +} + +.btn-ctrl-pill--stop:hover:not(:disabled) { + background: rgba(248, 113, 113, 0.18); + border-color: rgba(248, 113, 113, 0.35); +} + +/* ---- Scenario Chip Strip (horizontal scroll, video panel) ---- */ + +.scenario-strip { + display: flex; + flex-direction: column; + gap: 8px; + flex-shrink: 0; +} + +.scenario-strip__label { + font-size: 0.65rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--color-text-muted); + opacity: 0.7; + padding: 0 2px; +} + +.scenario-strip__scroll { + display: flex; + gap: 6px; + overflow-x: auto; + padding-bottom: 4px; + /* Thin scrollbar - visible but unobtrusive */ + scrollbar-width: thin; + scrollbar-color: var(--color-border) transparent; +} + +.scenario-strip__scroll::-webkit-scrollbar { + height: 3px; +} + +.scenario-strip__scroll::-webkit-scrollbar-track { + background: transparent; +} + +.scenario-strip__scroll::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 2px; +} + +.scenario-strip__scroll:hover::-webkit-scrollbar-thumb { + background: var(--color-text-muted); +} + +/* Individual scenario chip */ +.scenario-chip { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 7px 12px; + border-radius: 20px; + border: 1px solid var(--color-border); + background: var(--color-surface-2); + color: var(--color-text-muted); + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; + flex-shrink: 0; + font-size: 0.74rem; + font-weight: 500; + line-height: 1; +} + +.scenario-chip:hover { + background: var(--color-surface-3); + border-color: var(--color-surface-3); + color: var(--color-text); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(10, 10, 15, 0.12); +} + +.scenario-chip:active { + transform: translateY(0); +} + +/* Active chip */ +.scenario-chip--active { + background: rgba(59, 130, 246, 0.1); + border-color: rgba(59, 130, 246, 0.35); + color: var(--color-primary); +} + +.scenario-chip--active:hover { + background: rgba(59, 130, 246, 0.16); + border-color: rgba(59, 130, 246, 0.45); + color: var(--color-primary); +} + +/* "+" create new scenario chip */ +.scenario-chip--add { + border-style: dashed; + opacity: 0.7; +} + +.scenario-chip--add:hover { + opacity: 1; + border-style: solid; + border-color: var(--color-primary); + color: var(--color-primary); + background: rgba(59, 130, 246, 0.06); +} + +.scenario-chip--add .scenario-chip__icon { + display: inline-flex; + align-items: center; +} + +.scenario-chip__icon { + font-size: 0.9rem; + line-height: 1; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.scenario-chip__name { + letter-spacing: 0.01em; +} + +/* ---- Legacy button styles kept for backward compat ---- */ + .btn--recognize { background: rgba(59, 130, 246, 0.1); color: var(--color-primary); @@ -1416,35 +1666,40 @@ body { /* Device selectors */ .video-controls__devices { display: flex; - gap: 12px; + gap: 8px; justify-content: center; + width: 100%; } .device-select-wrapper { display: flex; - flex-direction: column; - gap: 4px; align-items: center; + gap: 6px; + flex: 1; + min-width: 0; } .device-select-label { - font-size: 0.65rem; + font-size: 0.72rem; color: var(--color-text-muted); - font-weight: 500; - letter-spacing: 0.01em; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; } .device-select { - padding: 5px 10px; - font-size: 0.72rem; - border-radius: var(--radius-sm); + flex: 1; + min-width: 0; + padding: 5px 8px; + font-size: 0.7rem; + border-radius: 6px; border: 1px solid var(--color-border); background: var(--color-surface-2); color: var(--color-text-muted); cursor: pointer; transition: all var(--transition-fast); - min-width: 120px; - text-align: center; + text-overflow: ellipsis; } .device-select:hover { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 89e84d6..df95d42 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,11 +19,34 @@ import { EditScenarioModal } from "./components/EditScenarioModal"; import { AuthProvider, useAuth } from "./lib/auth"; import { loadConfig, loadTheme, saveTheme } from "./lib/storage"; import { I18nContext, parseLocale, t } from "./lib/i18n"; +import { renderScenarioIconById } from "./lib/scenarioIcons"; import type { Locale } from "./lib/i18n"; import type { Theme } from "./types"; import type { UserScenario } from "./lib/api/scenarios"; import "./App.css"; +// ---- 统一 SVG 图标系统(Feather-style,stroke-based) ---- + +const SVG_PROPS = { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" as const, strokeLinejoin: "round" as const }; + +/** 系统情景 SVG 图标映射 */ +function scenarioSvgIcon(id: string) { + switch (id) { + case "free_chat": + return (); + case "interviewer": + return (); + case "english_teacher": + return (); + case "debate": + return (); + case "interpreter": + return (); + default: + return null; + } +} + /** 内部组件,确保在 I18nContext.Provider 内部使用 hooks */ function AppContent() { const { isAuthenticated, isLoading, user, logout, accessToken } = useAuth(); @@ -378,7 +401,7 @@ function AppContent() {