diff --git a/libs/app/src/application.cpp b/libs/app/src/application.cpp index e23c849a..ea313c79 100644 --- a/libs/app/src/application.cpp +++ b/libs/app/src/application.cpp @@ -508,7 +508,11 @@ void Application::init_pipeline() { auto primary = exe_dir / "pipelines"; if (!exe_dir.empty() && std::filesystem::exists(primary)) return primary; - return exe_dir / ".." / "config" / "pipelines"; + // From build/cli/ or $PREFIX/bin/: try ../config/pipelines first, + // then ../../config/pipelines (project-root layout during dev). + auto fallback1 = exe_dir / ".." / "config" / "pipelines"; + if (std::filesystem::exists(fallback1)) return fallback1; + return exe_dir / ".." / ".." / "config" / "pipelines"; }(), .worlds_base_dir = options_.home_dir / "worldbuilding", .condition_evaluator = condition_evaluator, diff --git a/libs/llm/src/anthropic_provider.cpp b/libs/llm/src/anthropic_provider.cpp index 0d8b52ec..c5b03911 100644 --- a/libs/llm/src/anthropic_provider.cpp +++ b/libs/llm/src/anthropic_provider.cpp @@ -51,8 +51,9 @@ nlohmann::json AnthropicProvider::build_request_body(const ChatRequest& request) nlohmann::json item; item["role"] = m.role; - // assistant 消息可能包含 tool_use content blocks - if (m.role == "assistant" && !m.tool_calls.empty()) { + // assistant 消息可能包含 thinking / tool_use content blocks + if (m.role == "assistant" && + (!m.tool_calls.empty() || !m.provider_content_blocks_json.empty())) { nlohmann::json content_arr = nlohmann::json::array(); if (!m.provider_content_blocks_json.empty()) { auto preserved_blocks = nlohmann::json::parse( @@ -134,6 +135,7 @@ std::future AnthropicProvider::chat( // SSE 累积状态 std::string response_text; + std::string error_body; // raw response captured for error diagnostics int input_tokens = 0, output_tokens = 0; int cache_read_tokens = 0, cache_write_tokens = 0; bool has_usage = false; @@ -237,6 +239,7 @@ std::future AnthropicProvider::chat( }; auto write_callback = [&](const std::string& data) { + error_body += data; line_buffer += data; size_t newline = 0; while ((newline = line_buffer.find('\n')) != std::string::npos) { @@ -267,6 +270,7 @@ std::future AnthropicProvider::chat( for (int attempt = 0; attempt <= retry.max_retries; attempt++) { response_text.clear(); + error_body.clear(); input_tokens = 0; output_tokens = 0; has_usage = false; @@ -341,8 +345,9 @@ std::future AnthropicProvider::chat( // Not retryable: other 4xx if (http_code >= 400 && http_code < 500 && http_code != 429) { + spdlog::error("Anthropic API error body: {}", error_body); throw AgentError(ErrorType::LLM_ERROR, - "LLM API error (HTTP " + std::to_string(http_code) + ")"); + "LLM API error (HTTP " + std::to_string(http_code) + "): " + error_body); } // Exhausted retries @@ -351,7 +356,7 @@ std::future AnthropicProvider::chat( throw AgentError(ErrorType::LLM_ERROR, curl_easy_strerror(res)); } throw AgentError(ErrorType::LLM_ERROR, - "LLM API error after retries (HTTP " + std::to_string(http_code) + ")"); + "LLM API error after retries (HTTP " + std::to_string(http_code) + "): " + error_body); } // Backoff and retry diff --git a/libs/storage/include/merak/session_store.hpp b/libs/storage/include/merak/session_store.hpp index 5c93b2bd..277e1997 100644 --- a/libs/storage/include/merak/session_store.hpp +++ b/libs/storage/include/merak/session_store.hpp @@ -119,7 +119,7 @@ class SessionStore { private: std::shared_ptr conn_; std::filesystem::path root_; - mutable std::mutex mutex_; + mutable std::recursive_mutex mutex_; mutable std::mutex plan_mutex_; std::string plan_text_; void exec(const std::string& sql); diff --git a/webui/docs/superpowers/plans/2026-06-22-ui-visual-alignment.md b/webui/docs/superpowers/plans/2026-06-22-ui-visual-alignment.md new file mode 100644 index 00000000..7c5f08de --- /dev/null +++ b/webui/docs/superpowers/plans/2026-06-22-ui-visual-alignment.md @@ -0,0 +1,742 @@ +# UI Visual Alignment Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Align WebUI visual implementation with DESIGN.md — unify DesktopShell colors, eliminate hardcoded values, clean up misleading variable names, and add animation tokens. + +**Architecture:** 4-layer incremental plan. Layer 1 cleans global.css (add `--border-subtle`, deprecated aliases, animation tokens). Layer 2 rewrites DesktopShell from hardcoded blue to glass/blur warm system. Layer 3 replaces 30+ hardcoded `rgba()` values and PipelineNavigator colors with CSS variables. Layer 4 adds page/agent/tab transition animations. + +**Tech Stack:** React 19, TypeScript, CSS Modules, Vite, vitest + +--- + +## File Structure Map + +``` +src/styles/global.css — :root tokens (Layer 1) +src/shell/DesktopShell.module.css — shell styling (Layer 2) +src/shell/DesktopShell.tsx — shell component (Layer 2, optional) +src/App.tsx — page transition (Layer 4) +src/App.module.css — border-subtle + editor exit (Layer 3+4) +src/components/MainPanel.tsx — agent switch animation (Layer 4) +src/components/MainPanel.module.css — border-subtle (Layer 3) +src/components/InspectorPanel.tsx — tab crossfade (Layer 4) +src/components/InspectorPanel.module.css — border-subtle (Layer 3) +src/components/Composer.module.css — border-subtle (Layer 3) +src/components/ChatTimeline.module.css — border-subtle (Layer 3) +src/components/WorldSidebar.module.css — border-subtle (Layer 3) +src/components/WorldDashboard.module.css — border-subtle (Layer 3) +src/components/Skeleton.module.css — border-subtle (Layer 3) +src/components/cells/Cells.module.css — border-subtle (Layer 3) +src/components/Sidebar/PipelineNavigator.module.css — hardcoded colors (Layer 3) +src/components/ChapterEditor.tsx — exit animation (Layer 4) +src/components/ChapterEditor.module.css — --hover → hardcoded (Layer 3) +src/components/HelpDrawer.module.css — --brand-wash (Layer 3) +``` + +--- + +## Layer 1: Global CSS Cleanup + +### Task 1.1: Add `--border-subtle` and animation tokens, mark deprecated aliases + +**Files:** +- Modify: `src/styles/global.css` (:root block) + +- [ ] **Step 1: Add new tokens and deprecated comments** + +```css +/* In :root block, after existing tokens: */ + +/* ---- Subtle border (replaces hardcoded rgba(224,223,220,0.92) in components) ---- */ +--border-subtle: rgba(224, 223, 220, 0.92); + +/* ---- Animation tokens ---- */ +--ease-out: cubic-bezier(0.4, 0, 0.2, 1); +--anim-lift-sm: translateY(-1px); +--anim-lift-md: translateY(-3px); +--anim-press: scale(0.98); +--anim-fade-in: 200ms var(--ease) both; +``` + +- [ ] **Step 2: Add deprecated comments on aliases (do not remove — components still reference them)** + +```css +/* In :root block, add comment above --teal/--indigo/--violet/--purple/--brand-wash group: */ + +/* ⚠️ DEPRECATED aliases — kept for backward compat. New code use --ink / --muted-strong / --surface-muted directly. */ +--teal: #171717; +--teal-strong: #171717; +--teal-soft: #ededeb; +--teal-wash: #f7f7f5; +--brand-wash: #f7f7f5; +--indigo: #4a4a4a; +--indigo-soft: #eeeeec; +--violet: #62605b; +--violet-soft: #f0efed; +--purple: #62605b; +--purple-soft: #f0efed; +``` + +- [ ] **Step 3: Verify build passes** + +Run: `cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5` +Expected: no errors + +- [ ] **Step 4: Commit** + +```bash +git add src/styles/global.css +git commit -m "style: add --border-subtle and animation tokens, mark deprecated aliases" +``` + +--- + +## Layer 2: DesktopShell Visual Unification + +### Task 2.1: Rewrite DesktopShell CSS — hardcoded blue → CSS variables + glass + +**Files:** +- Modify: `src/shell/DesktopShell.module.css` + +- [ ] **Step 1: Replace sidebar background and border** + +In `.sidebar` (around line 23), replace: +```css +.sidebar { + /* ... keep grid/flex/sizing ... */ + border-right: 1px solid #e2e8f0; + background: #fff; +} +``` +With: +```css +.sidebar { + /* ... keep grid/flex/sizing ... */ + border-right: 1px solid var(--border); + background: rgba(255, 255, 255, 0.82); + backdrop-filter: blur(18px); +} +``` + +- [ ] **Step 2: Replace worldCard, search, worldSelector border/background colors** + +```css +.worldCard, +.search, +.worldSelector { + display: flex; + align-items: center; + gap: 9px; + border: 1px solid var(--border-subtle); + border-radius: 7px; + background: var(--surface); + color: var(--ink); +} +``` + +Remove the old hardcoded declarations: +```css +/* DELETE these old lines: */ +/* border: 1px solid #dce3ed; */ +/* background: #fff; */ +/* color: #17213b; */ +``` + +- [ ] **Step 3: Replace nav button colors** + +In `.navButton`: +```css +.navButton { + /* ... keep existing layout ... */ + background: transparent; + color: var(--muted-strong); + /* ... keep transitions ... */ +} +``` + +In `.navButton:hover`: +```css +.navButton:hover { + background: var(--surface); + color: var(--ink); +} +``` + +In `.navButton[aria-current='page']`: +```css +.navButton[aria-current='page'] { + background: linear-gradient(90deg, var(--brand-soft), var(--surface)); + color: var(--ink); + font-weight: 700; + box-shadow: inset 2px 0 0 var(--ink); +} +``` + +- [ ] **Step 4: Replace topbar colors** + +In `.topbar`: +```css +.topbar { + /* ... keep layout ... */ + border-bottom: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.94); + backdrop-filter: blur(14px); +} +``` + +- [ ] **Step 5: Replace search and kbd colors** + +In `.search`: +```css +.search { + /* ... keep layout ... */ + color: var(--muted); + /* REMOVE: color: #667085; */ +} +``` + +In `.search kbd`: +```css +.search kbd { + /* ... keep layout ... */ + border: 1px solid var(--border); + border-radius: 4px; + background: var(--surface-muted); + /* REMOVE: border: 1px solid #e3e8ef; */ + /* REMOVE: background: #f8fafc; */ +} +``` + +- [ ] **Step 6: Replace worldSelector border** + +In `.worldSelector`: +```css +.worldSelector { + /* ... keep layout ... */ + border: 1px solid var(--border-subtle); + /* REMOVE: border: 1px solid #dce3ed; */ +} +``` + +- [ ] **Step 7: Replace mobile nav backdrop color** + +In `.navBackdrop`: +```css +.navBackdrop { + /* ... keep layout ... */ + background: rgba(23, 23, 23, 0.38); + /* REMOVE: background: rgb(15 23 42 / 38%); */ +} +``` + +- [ ] **Step 8: Replace mobile menu/close button colors** + +In `.menuButton` and `.navClose`: +```css +.menuButton, +.navClose { + /* ... keep layout ... */ + border: 1px solid var(--border); + background: var(--surface); + color: var(--muted-strong); + /* REMOVE: color: var(--brand); */ +} +``` + +- [ ] **Step 9: Remove `--shell-navy` declaration** + +In `.shell`: +```css +.shell { + /* REMOVE: --shell-navy: var(--brand); */ + display: grid; + grid-template-columns: 244px minmax(0, 1fr); + /* ... keep rest ... */ +} +``` + +- [ ] **Step 10: Verify build passes** + +Run: `cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5` +Expected: no errors + +- [ ] **Step 11: Commit** + +```bash +git add src/shell/DesktopShell.module.css +git commit -m "style: unify DesktopShell with warm monochrome glass/blur language" +``` + +--- + +## Layer 3: Component Hardcoded Color Replacement + +### Task 3.1: Bulk replace `rgba(224, 223, 220, 0.92)` with `var(--border-subtle)` + +**Files:** 10 files, ~30 occurrences + +- [ ] **Step 1: Run search to confirm exact count** + +```bash +grep -rn 'rgba(224, 223, 220' src/ --include='*.css' | wc -l +``` + +- [ ] **Step 2: Bulk replace using sed** + +```bash +cd /home/icepop/Merak/webui +find src/ -name '*.css' -exec sed -i 's/rgba(224, 223, 220, 0\.92)/var(--border-subtle)/g' {} + +find src/ -name '*.css' -exec sed -i 's/rgba(224, 223, 220, 0\.9)/var(--border-subtle)/g' {} + +find src/ -name '*.css' -exec sed -i 's/rgba(224, 223, 220, 0\.88)/var(--border-subtle)/g' {} + +``` + +- [ ] **Step 3: Verify no remaining occurrences** + +```bash +grep -rn 'rgba(224, 223, 220' src/ --include='*.css' +``` +Expected: no output + +- [ ] **Step 4: Verify build passes** + +Run: `cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5` +Expected: no errors + +- [ ] **Step 5: Commit** + +```bash +git add -u src/ +git commit -m "style: replace hardcoded border-subtle rgba() with CSS variable" +``` + +### Task 3.2: Fix PipelineNavigator hardcoded colors + +**Files:** +- Modify: `src/components/Sidebar/PipelineNavigator.module.css` + +- [ ] **Step 1: Replace error banner colors** + +In `.errorBanner`: +```css +.errorBanner { + background: var(--ruby-soft); + border: 1px solid var(--ruby); + /* ... keep rest ... */ + color: var(--ruby); +} +``` +Remove: `background: #fff0f0; border: 1px solid #e00; color: #c00;` + +In `.errorBanner button`: +```css +.errorBanner button { + background: none; + border: none; + color: var(--ruby); + /* ... keep rest ... */ +} +``` +Remove: `color: #c00;` + +- [ ] **Step 2: Replace complete banner colors** + +In `.completeBanner`: +```css +.completeBanner { + background: var(--green-soft); + border: 1px solid var(--green); + /* ... keep rest ... */ + color: var(--green); +} +``` +Remove: `background: #f0fff0; border: 1px solid #4caf50; color: #2e7d32;` + +In `.completeBanner button`: +```css +.completeBanner button { + background: none; + border: none; + color: var(--green); + /* ... keep rest ... */ +} +``` +Remove: `color: #2e7d32;` + +- [ ] **Step 3: Replace confirm dialog button colors** + +In `.confirmBtn`: +```css +.confirmBtn { + /* ... keep layout ... */ + background: var(--ink); + color: #fff; +} +``` +Remove: `background: #4a90d9;` + +In `.confirmDialog`: +```css +.confirmDialog { + background: var(--surface); + /* ... keep rest ... */ +} +``` +Remove: `background: #fff;` + +- [ ] **Step 4: Replace condMet dot color** + +In `.condMet`: +```css +.condMet { + background: var(--green); +} +``` +Remove: `background: var(--teal-soft, #4a8);` + +- [ ] **Step 5: Verify build passes** + +Run: `cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5` +Expected: no errors + +- [ ] **Step 6: Commit** + +```bash +git add src/components/Sidebar/PipelineNavigator.module.css +git commit -m "style: replace PipelineNavigator hardcoded colors with CSS variables" +``` + +### Task 3.3: Fix ChapterEditor `--hover` and `--violet` references + +**Files:** +- Modify: `src/components/ChapterEditor.module.css` + +- [ ] **Step 1: Replace `var(--hover)` with hardcoded neutral hover** + +In `.contextBtn:hover, .reviewBtn:hover` and `.entityTag:hover`: +```css +.contextBtn:hover, +.reviewBtn:hover { + background: rgba(23, 23, 23, 0.055); + /* ... keep rest ... */ +} +``` +Remove: `background: var(--hover);` + +In `.entityTag:hover`: +```css +.entityTag:hover { + background: rgba(23, 23, 23, 0.055); + /* ... keep rest ... */ +} +``` +Remove: `background: var(--hover);` + +- [ ] **Step 2: Replace `var(--violet)` with `var(--muted-strong)`** + +In `.entityTag.secret`: +```css +.entityTag.secret { + border-color: var(--muted-strong); + color: var(--muted-strong); +} +``` +Remove: `border-color: var(--violet); color: var(--violet);` + +- [ ] **Step 3: Verify build passes** + +Run: `cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5` +Expected: no errors + +- [ ] **Step 4: Commit** + +```bash +git add src/components/ChapterEditor.module.css +git commit -m "style: replace --hover and --violet with explicit values in ChapterEditor" +``` + +### Task 3.4: Fix `--brand-wash` reference in HelpDrawer + +**Files:** +- Modify: `src/components/HelpDrawer.module.css` + +- [ ] **Step 1: Replace `var(--brand-wash)` with `var(--surface-muted)`** + +```css +/* In .someClass, line ~131: */ +/* background: var(--brand-wash); → background: var(--surface-muted); */ +``` + +- [ ] **Step 2: Verify build and commit** + +```bash +cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5 +git add src/components/HelpDrawer.module.css +git commit -m "style: replace --brand-wash with --surface-muted in HelpDrawer" +``` + +--- + +## Layer 4: Interaction Transitions & Animations + +### Task 4.1: Add page transition animation to App.tsx + +**Files:** +- Modify: `src/App.module.css` + +- [ ] **Step 1: Add page transition keyframes** + +Append to `src/App.module.css`: +```css +@keyframes pageFadeIn { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pageFadeOut { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-6px); + } +} + +.editorExit { + animation: pageFadeOut 280ms var(--ease-spring) forwards; +} + +.pageEnter { + animation: pageFadeIn 280ms var(--ease-spring) both; +} +``` + +- [ ] **Step 2: Add `pageEnter` class to workspace element in App.tsx** + +In `src/App.tsx`, the three-column workbench section (around line 458), add `className={styles.pageEnter}` to the workspace div: + +```tsx +// OLD: +
+ +// NEW: +
+``` + +- [ ] **Step 3: Add editor exit animation to App.tsx** + +In the editor overlay section (around line 312), wrap the exit in a stateful animation. At the top of `AppInner`, add: + +```tsx +const [exitingEditor, setExitingEditor] = useState(false); +``` + +Modify the editor overlay div to support exit animation: + +```tsx +// OLD at around line 312: +
+ +// NEW: +
+``` + +Wrap the `safeNavigate('overview')` call in editor's back button: + +The back button already calls `safeNavigate('overview')`. To add animation, we'd need to intercept. Skip this — the overlay is replaced instantly when state changes. The exit animation can be added later if needed. + +Instead, just add the keyframe definitions. The `editorOverlay` already has `animation: pageIn 280ms` for entry. + +- [ ] **Step 4: Verify build passes** + +Run: `cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5` +Expected: no errors + +- [ ] **Step 5: Commit** + +```bash +git add src/App.module.css src/App.tsx +git commit -m "feat: add page transition animation keyframes" +``` + +### Task 4.2: Add Inspector tab crossfade transition + +**Files:** +- Modify: `src/components/InspectorPanel.module.css` + +- [ ] **Step 1: Add crossfade to content area** + +In `.content`: +```css +.content { + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 10px; + padding-right: 2px; + animation: tabFadeIn 180ms var(--ease) both; +} +``` + +Add keyframe: +```css +@keyframes tabFadeIn { + from { + opacity: 0; + transform: translateX(4px); + } + to { + opacity: 1; + transform: translateX(0); + } +} +``` + +Also add reduced-motion override: +```css +@media (prefers-reduced-motion: reduce) { + .content { + animation: none; + } +} +``` + +- [ ] **Step 2: Verify build and commit** + +```bash +cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5 +git add src/components/InspectorPanel.module.css +git commit -m "feat: add Inspector tab content crossfade transition" +``` + +### Task 4.3: Add Agent switch crossfade to MainPanel + +**Files:** +- Modify: `src/components/ChatTimeline.module.css` + +- [ ] **Step 1: Add agent-switch animation** + +Append to `src/components/ChatTimeline.module.css`: +```css +.agentSwitch { + animation: agentFadeIn 200ms var(--ease) both; +} + +@keyframes agentFadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (prefers-reduced-motion: reduce) { + .agentSwitch { + animation: none; + } +} +``` + +- [ ] **Step 2: Apply class when agentId changes in MainPanel.tsx** + +In `src/components/MainPanel.tsx`, add a key prop to ChatTimeline based on agentId: + +```tsx +// OLD around line 169: + + +// NEW: + +``` + +Actually CSS modules don't pass className through like that directly. Instead, wrap in a div: + +```tsx +
+ +
+``` + +But we need to import the style. Actually MainPanel already imports `styles from './MainPanel.module.css'`. Let's add the keyframe to MainPanel.module.css instead: + +Append to `src/components/MainPanel.module.css`: +```css +.agentSwitch { + animation: agentFadeIn 200ms var(--ease) both; +} + +@keyframes agentFadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (prefers-reduced-motion: reduce) { + .agentSwitch { + animation: none; + } +} +``` + +Then in MainPanel.tsx, wrap ChatTimeline: +```tsx +
+ +
+``` + +- [ ] **Step 3: Verify build and commit** + +```bash +cd /home/icepop/Merak/webui && npm run build 2>&1 | tail -5 +git add src/components/MainPanel.module.css src/components/MainPanel.tsx +git commit -m "feat: add agent switch crossfade animation in MainPanel" +``` + +### Task 4.4: Run full test suite + +- [ ] **Step 1: Run tests** + +```bash +cd /home/icepop/Merak/webui && npm test 2>&1 +``` + +Expected: All tests pass (no failures introduced by CSS/style changes) + +--- + +## Summary + +**Total Tasks:** 8 +**Total Files Modified:** ~15 +**Delivery Order:** + +| Commit | Content | +|--------|---------| +| 1 | `style: add --border-subtle and animation tokens, mark deprecated aliases` | +| 2 | `style: unify DesktopShell with warm monochrome glass/blur language` | +| 3 | `style: replace hardcoded border-subtle rgba() with CSS variable` | +| 4 | `style: replace PipelineNavigator hardcoded colors with CSS variables` | +| 5 | `style: replace --hover and --violet with explicit values in ChapterEditor` | +| 6 | `style: replace --brand-wash with --surface-muted in HelpDrawer` | +| 7 | `feat: add page transition animation keyframes` | +| 8 | `feat: add Inspector tab content crossfade transition` | +| 9 | `feat: add agent switch crossfade animation in MainPanel` | + +The 30+ hardcoded `rgba(224,223,220,0.92)` replacements across multiple files are grouped into a single commit (3) as they are purely mechanical. diff --git a/webui/docs/superpowers/specs/2026-06-22-ui-visual-alignment-design.md b/webui/docs/superpowers/specs/2026-06-22-ui-visual-alignment-design.md new file mode 100644 index 00000000..2b416735 --- /dev/null +++ b/webui/docs/superpowers/specs/2026-06-22-ui-visual-alignment-design.md @@ -0,0 +1,205 @@ +# UI Visual Alignment Design + +## Summary + +将 WebUI 的视觉实现与 DESIGN.md 设计规范对齐。当前存在三套配色系统并存的问题:DESIGN.md 定义暖灰单色品牌,global.css 使用 Tailwind Slate/Blue 色板,DesktopShell 硬编码独立蓝色调。此方案以 DESIGN.md 为唯一真相源,统一颜色系统、消除硬编码、规范化动效 Token。 + +## Scope + +4 个 Layer,按顺序交付: + +### Layer 1: 全局 CSS 变量重写 + +**文件:** `src/styles/global.css` (:root 块) + +将所有 Color Token 从 Tailwind Slate 色板替换为 DESIGN.md 的暖灰单色系统: + +| 变量 | 当前值 | 目标值 (DESIGN.md) | +|------|--------|-------------------| +| --ink | #0f172a | #171717 | +| --ink-soft | #1e293b | #333333 | +| --page | #ffffff | #f7f7f5 | +| --page-warm | #f1f5f9 | #eeeeeb | +| --surface-muted | #f1f5f9 | #f3f3f1 | +| --surface-strong | #f8fafc | #fafafa | +| --border | #e2e8f0 | #e0dfdc | +| --border-strong | #cbd5e1 | #c8c7c3 | +| --muted | #64748b | #77756f | +| --muted-strong | #475569 | #55524c | +| --brand | #06266f | #343434 | +| --brand-soft | #dbe7ff | #e8e7e4 | +| --brand-strong | #041c55 | #55524c | + +**新增变量:** +- `--border-subtle: rgba(224, 223, 220, 0.92)` — 柔和卡片边框,用于与 --border (solid) 互补 + +**移除的冗余变量:** +- `--teal, --teal-strong, --teal-soft, --teal-wash` — 语义混乱,统一为 --ink / --surface-muted / --page +- `--indigo, --violet, --purple` — 三个同义变量,统一为 --muted-strong +- `--hover: rgba(6, 38, 111, 0.06)` — 蓝色残留,改为 `rgba(23, 23, 23, 0.06)` +- `--brand-wash` — 非 DESIGN.md 变量,引用替换为 --surface-muted + +**语义色:** amber (#8a5a12)、ruby (#be123c)、green (#16a34a) 已与 DESIGN.md 对齐,保持不变。 + +**新建动效 Token (global.css 中新增):** +- `--ease-out: cubic-bezier(0.4, 0, 0.2, 1)` — 退出/消失动画 +- `--anim-lift-sm: translateY(-1px)` — hover 微升 +- `--anim-lift-md: translateY(-3px)` — hover 明显升 +- `--anim-press: scale(0.98)` — 点击下沉 +- `--anim-fade-in: 200ms var(--ease) both` — 页面切换/列表入场淡入 + +**Type Token 更新 (对齐 DESIGN.md typography):** +- body fontSize: 13px (已一致) +- title fontSize: 16px, fontWeight: 780 (需检查) +- label fontSize: 10px, fontWeight: 720, letterSpacing: 0.04em, uppercase (需统一) + +### Layer 2: DesktopShell 视觉统一 + +**文件:** `src/shell/DesktopShell.module.css` + +将所有硬编码颜色替换为 CSS 变量引用: + +| 硬编码值 | 替换为 | +|----------|--------| +| #fff (sidebar, worldCard, search) | var(--surface) | +| #e2e8f0 (border) | var(--border) | +| #dce3ed | rgba(224,223,220,0.92) | +| #17213b | var(--ink) | +| #263653 | var(--muted-strong) | +| #667085 | var(--muted) | +| #f8fafc | var(--surface-muted) | +| #f1f5fb (hover) | var(--surface) | +| #06266f / --shell-navy | var(--brand) | +| rgb(15 23 42 / 38%) (backdrop) | rgba(23,23,23,0.38) | + +**视觉特性对齐:** +- Sidebar: `backdrop-filter: blur(18px)` + 半透明背景 (与 WorldSidebar 一致) +- 导航激活态: 蓝色 solid → `linear-gradient(90deg, var(--brand-soft), var(--surface))` + inset left rail +- 顶栏: `backdrop-filter: blur(14px)` + 半透明背景 (与 MainPanel header 一致) +- 保留现有布局结构,只改风格层 + +### Layer 3: 组件颜色审计 + +**批量替换 (全局搜索替换):** +- 所有 `rgba(224, 223, 220, 0.92)` → `var(--border-subtle)` (约 30 处) + +**页面级 --brand 替换:** +- `OverviewPage.module.css`: 所有 `var(--brand)` 文字色 → `var(--ink)`,brand 背景 → `var(--brand-soft)` 或 `var(--surface-muted)` +- `ChaptersPage.module.css`: 进度条 track 色 → `var(--muted-strong)` +- `ScenesPage.module.css`: 创建按钮 → 接入主按钮样式 +- `SecretsPage.module.css`: 同上 +- `FilesPage.module.css`: 同上 + +**PipelineNavigator 硬编码修复:** +- 错误色 #c00 / background #fff0f0 → `var(--ruby)` / `var(--ruby-soft)` +- 完成色 #2e7d32 / background #f0fff0 → `var(--green)` / `var(--green-soft)` +- 确认按钮 #4a90d9 → `var(--ink)` +- 对话框背景 #fff → `var(--surface)` + +**AgentCard / AgentStatusBar:** +- 独立颜色系统 → 使用 DESIGN.md 语义色 + 中性色变量 + +**ChapterEditor:** +- 编辑器面板色调 → 对齐 InspectorPanel 的 editor panel 样式 + +### Layer 4: 交互反馈补充 + +**新增过渡动画:** +1. **页面切换:** pageContent 加 200ms opacity + translateY(6px) fade-in +2. **Agent 切换:** ChatTimeline 内容 crossfade (200ms),Agent 名称 slide-in +3. **编辑器退出:** EditorOverlay 加 pageOut 动画 (280ms, reverse spring) +4. **列表操作:** 新增项 messageIn 入场,删除项 shrink-out (200ms) + +**Run 状态流可视化:** +- PipelineNavigator phase 切换加 height morph + 颜色渐变过渡 +- RunInspector 加 indeterminate 进度指示器 + +**文件保存状态:** +- saving 状态: 微 spinner +- saved 状态: checkmark 弹入 → 1200ms 淡出 + +**Inspector Tab 切换:** +- 内容区 180ms crossfade (opacity 过渡) + +**影响文件:** App.tsx, MainPanel.tsx, InspectorPanel.tsx, DesktopShell.tsx, ChapterEditor.tsx, PipelineNavigator.tsx, RunInspector.tsx + +### Out of Scope + +- 不改变任何组件的 DOM 结构或 props 接口 +- 不改变路由逻辑 +- 不改变状态管理 (AppState) +- 不新增第三方动画库 +- 不改动后端/API +- 不改变 Tauri 集成 + +## Component Breakdown + +### 受影响文件清单 + +**CSS 变量 (1 file):** +- `src/styles/global.css` — :root 块重写 + +**Shell (1 file):** +- `src/shell/DesktopShell.module.css` — 全量颜色替换 + glass 语言 + +**Components (15 files):** +- `src/components/InspectorPanel.module.css` — border-subtle 替换, tab 过渡 +- `src/components/MainPanel.module.css` — border-subtle 替换 +- `src/components/Composer.module.css` — border-subtle 替换 +- `src/components/ChatTimeline.module.css` — border-subtle 替换, crossfade +- `src/components/WorldSidebar.module.css` — border-subtle 替换 +- `src/components/WorldDashboard.module.css` — border-subtle 替换 +- `src/components/WorldOnboarding.module.css` — brand-wash 替换 +- `src/components/ChapterEditor.module.css` — 对齐 editor panel 样式 +- `src/components/Sidebar.module.css` — 变量引用检查 +- `src/components/Sidebar/PipelineNavigator.module.css` — 硬编码修复 +- `src/components/Inspector/AgentCardEdit.module.css` — 变量引用检查 +- `src/components/Inspector/AgentCardView.module.css` — 变量引用检查 +- `src/components/Inspector/AgentPromptViewer.module.css` — 变量引用检查 +- `src/components/Inspector/CreationDashboard.module.css` — 变量引用检查 +- `src/components/cells/Cells.module.css` — border-subtle 替换 + +**Pages (9 files):** +- `src/pages/OverviewPage.module.css` — --brand → --ink +- `src/pages/ChaptersPage.module.css` — --brand 替换 +- `src/pages/ScenesPage.module.css` — --brand 替换 +- `src/pages/SecretsPage.module.css` — --brand 替换 +- `src/pages/FilesPage.module.css` — --brand 替换 +- `src/pages/CharactersPage.module.css` — 变量引用检查 +- `src/pages/ForeshadowingPage.module.css` — 变量引用检查 +- `src/pages/WorldPage.module.css` — 变量引用检查 +- `src/pages/SessionsPage.module.css` — 变量引用检查 + +**TSX 逻辑修改 (5 files):** +- `src/shell/DesktopShell.tsx` — Shell 侧边栏可接入 BrandMark (可选) +- `src/App.tsx` — 页面切换动画逻辑 +- `src/components/MainPanel.tsx` — Agent 切换过渡逻辑 +- `src/components/InspectorPanel.tsx` — Tab 切换 crossfade +- `src/components/ChapterEditor.tsx` — 退出动画逻辑 + +## Testing Strategy + +**视觉回归测试:** +- 在亮色主题下逐页截图对比(Overview → Workbench → Editor → Settings) +- 检查所有 hover/press/focus 状态 +- 验证移动端响应式断点 + +**组件测试:** +- 已有 vitest 测试套件,不需新增测试文件 +- 确保 CSS 变量引用在 JSDOM 环境下不报错 + +**手动检查:** +- 所有使用 `--brand` 的页面确认颜色语义正确 +- DesktopShell 导航激活/悬停状态 +- WorldSidebar agent list 激活态 +- Composer 发送按钮/取消按钮 hover +- PipelineNavigator error/complete banner + +## Delivery + +分 4 个 Commit 按 Layer 顺序交付: + +1. `style: align global CSS variables with DESIGN.md` — Layer 1 +2. `style: unify DesktopShell with workbench glass language` — Layer 2 +3. `style: replace hardcoded colors with CSS variables across components` — Layer 3 +4. `feat: add interaction transitions and animation tokens` — Layer 4 diff --git a/webui/src/App.module.css b/webui/src/App.module.css index dbbe064b..24e16c7c 100644 --- a/webui/src/App.module.css +++ b/webui/src/App.module.css @@ -43,7 +43,7 @@ display: flex; flex-direction: column; overflow: hidden; - border: 1px solid rgba(224, 223, 220, 0.88); + border: 1px solid var(--border-subtle); border-radius: 8px; background: rgba(255, 255, 255, 0.82); box-shadow: var(--shadow-lg); diff --git a/webui/src/components/ChapterEditor.module.css b/webui/src/components/ChapterEditor.module.css index d8280ed9..2a48ee63 100644 --- a/webui/src/components/ChapterEditor.module.css +++ b/webui/src/components/ChapterEditor.module.css @@ -66,7 +66,7 @@ .contextBtn:hover, .reviewBtn:hover { - background: var(--hover); + background: rgba(23, 23, 23, 0.055); border-color: var(--border-strong); transform: translateY(-1px); } @@ -206,7 +206,7 @@ } .entityTag:hover { - background: var(--hover); + background: rgba(23, 23, 23, 0.055); border-color: var(--border-strong); transform: translateY(-1px); } @@ -222,8 +222,8 @@ } .entityTag.secret { - border-color: var(--violet); - color: var(--violet); + border-color: var(--muted-strong); + color: var(--muted-strong); } .location { diff --git a/webui/src/components/ChatTimeline.module.css b/webui/src/components/ChatTimeline.module.css index 70b9af63..18aaa1e7 100644 --- a/webui/src/components/ChatTimeline.module.css +++ b/webui/src/components/ChatTimeline.module.css @@ -26,7 +26,7 @@ max-width: 100%; text-align: left; color: var(--muted); - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(250, 250, 250, 0.86)), var(--surface); border-radius: 7px; diff --git a/webui/src/components/Composer.module.css b/webui/src/components/Composer.module.css index 707da5bc..029fd9b8 100644 --- a/webui/src/components/Composer.module.css +++ b/webui/src/components/Composer.module.css @@ -1,6 +1,6 @@ .area { min-width: 0; - border-top: 1px solid rgba(224, 223, 220, 0.9); + border-top: 1px solid var(--border-subtle); padding: 11px 14px 13px; background: linear-gradient(180deg, rgba(250, 250, 250, 0.72), rgba(255, 255, 255, 0.92)), var(--surface); @@ -20,7 +20,7 @@ align-items: center; gap: 2px; padding: 2px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; background: rgba(245, 242, 236, 0.8); } @@ -75,7 +75,7 @@ display: inline-flex; align-items: center; gap: 6px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; background: rgba(255, 255, 255, 0.72); color: var(--muted-strong); @@ -116,7 +116,7 @@ grid-template-columns: minmax(0, 1.6fr) 150px; gap: 8px; margin-bottom: 8px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; background: linear-gradient(135deg, rgba(255, 255, 255, 0.86), rgba(247, 247, 245, 0.74)), @@ -214,7 +214,7 @@ display: flex; gap: 10px; align-items: flex-end; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(250, 250, 250, 0.9)), var(--surface); border-radius: 7px; diff --git a/webui/src/components/HelpDrawer.module.css b/webui/src/components/HelpDrawer.module.css index 08309efb..59f82308 100644 --- a/webui/src/components/HelpDrawer.module.css +++ b/webui/src/components/HelpDrawer.module.css @@ -128,7 +128,7 @@ margin-top: 18px; border: 1px solid var(--border); border-radius: 8px; - background: var(--brand-wash); + background: var(--surface-muted); padding: 14px; } diff --git a/webui/src/components/Inspector/EndSceneModal.module.css b/webui/src/components/Inspector/EndSceneModal.module.css index 66b7f27d..d92ad557 100644 --- a/webui/src/components/Inspector/EndSceneModal.module.css +++ b/webui/src/components/Inspector/EndSceneModal.module.css @@ -81,7 +81,7 @@ .foreshadowItem { border: 1px solid var(--border); border-radius: 10px; - background: linear-gradient(135deg, var(--brand-wash), #fff); + background: linear-gradient(135deg, var(--surface-muted), #fff); padding: 10px 12px; font-size: 12px; line-height: 1.45; diff --git a/webui/src/components/InspectorPanel.module.css b/webui/src/components/InspectorPanel.module.css index 49be486d..9f4bd217 100644 --- a/webui/src/components/InspectorPanel.module.css +++ b/webui/src/components/InspectorPanel.module.css @@ -1,7 +1,7 @@ .panel { width: 360px; min-width: 360px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; background: linear-gradient(180deg, rgba(255, 255, 255, 0.84), rgba(242, 242, 240, 0.82)), @@ -51,7 +51,7 @@ .closeBtn { display: grid; place-items: center; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: rgba(255, 255, 255, 0.78); color: var(--muted); width: 28px; @@ -88,7 +88,7 @@ grid-template-columns: repeat(5, minmax(0, 1fr)); padding: 3px; background: rgba(233, 227, 216, 0.78); - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; } @@ -164,11 +164,12 @@ flex-direction: column; gap: 10px; padding-right: 2px; + animation: tabFadeIn 180ms var(--ease) both; } .empty { margin-top: 32px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: rgba(255, 255, 255, 0.76); color: var(--muted); border-radius: 6px; @@ -182,7 +183,7 @@ .runCard { background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(250, 250, 250, 0.84)), var(--surface); - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; padding: 12px; box-shadow: 0 8px 22px rgba(23, 23, 23, 0.035); @@ -325,7 +326,7 @@ .storyStats span, .contextGrid div { min-width: 0; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: rgba(255, 255, 255, 0.66); border-radius: 6px; padding: 8px; @@ -458,7 +459,7 @@ .fileItem { width: 100%; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: rgba(255, 255, 255, 0.72); border-radius: 6px; padding: 9px; @@ -578,7 +579,7 @@ min-height: 340px; background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(250, 250, 250, 0.84)), var(--surface); - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; padding: 12px; display: flex; @@ -689,7 +690,7 @@ align-items: center; justify-content: center; gap: 7px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; background: var(--surface-strong); color: var(--muted-strong); @@ -955,7 +956,7 @@ } .timelineItem { - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; background: rgba(255, 255, 255, 0.68); padding: 10px; @@ -1099,7 +1100,8 @@ .section, .runCard, .outputCard, - .editorPanel { + .editorPanel, + .content { animation: none; } } @@ -1243,3 +1245,14 @@ .timeInput:disabled { opacity: 0.5; } + +@keyframes tabFadeIn { + from { + opacity: 0; + transform: translateX(4px); + } + to { + opacity: 1; + transform: translateX(0); + } +} diff --git a/webui/src/components/MainPanel.module.css b/webui/src/components/MainPanel.module.css index bee6f3ef..6d043bef 100644 --- a/webui/src/components/MainPanel.module.css +++ b/webui/src/components/MainPanel.module.css @@ -12,7 +12,7 @@ align-items: center; gap: 10px; padding: 10px 13px; - border-bottom: 1px solid rgba(224, 223, 220, 0.9); + border-bottom: 1px solid var(--border-subtle); background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(250, 250, 250, 0.78)), var(--surface); backdrop-filter: blur(14px); @@ -22,7 +22,7 @@ .iconBtn { width: 34px; height: 34px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; background: rgba(255, 255, 255, 0.78); color: var(--ink-soft); @@ -113,7 +113,7 @@ display: inline-flex; align-items: center; gap: 6px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); background: rgba(245, 242, 236, 0.86); color: var(--muted); border-radius: 999px; @@ -189,7 +189,7 @@ .guide { pointer-events: auto; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; background: rgba(255, 255, 255, 0.94); box-shadow: var(--shadow-lg); @@ -230,7 +230,7 @@ .guideGrid article { display: grid; gap: 4px; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 7px; background: rgba(255, 255, 255, 0.68); padding: 10px; @@ -273,7 +273,8 @@ @media (prefers-reduced-motion: reduce) { .header, .liveChipOn, - .guide { + .guide, + .agentSwitch { animation: none; } } @@ -300,3 +301,22 @@ width: calc(100% - 16px); } } + +.agentSwitch { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + animation: agentFadeIn 200ms var(--ease) both; +} + +@keyframes agentFadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/webui/src/components/MainPanel.tsx b/webui/src/components/MainPanel.tsx index f83645c4..c0a92348 100644 --- a/webui/src/components/MainPanel.tsx +++ b/webui/src/components/MainPanel.tsx @@ -166,7 +166,9 @@ export default function MainPanel({
)} - +
+ +
); diff --git a/webui/src/components/Sidebar.module.css b/webui/src/components/Sidebar.module.css index cba37162..09a7b093 100644 --- a/webui/src/components/Sidebar.module.css +++ b/webui/src/components/Sidebar.module.css @@ -138,8 +138,8 @@ .sessionItemActive { background: linear-gradient(90deg, var(--brand-soft), var(--surface)); - border-color: rgba(122, 79, 19, 0.22); - color: var(--brand); + border-color: var(--border-strong); + color: var(--ink); box-shadow: var(--shadow-sm); } diff --git a/webui/src/components/Sidebar/PipelineNavigator.module.css b/webui/src/components/Sidebar/PipelineNavigator.module.css index 21c1e22b..a97fe533 100644 --- a/webui/src/components/Sidebar/PipelineNavigator.module.css +++ b/webui/src/components/Sidebar/PipelineNavigator.module.css @@ -167,7 +167,7 @@ } .condMet { - background: var(--teal-soft, #4a8); + background: var(--green); } .condPending { @@ -175,8 +175,8 @@ } .errorBanner { - background: #fff0f0; - border: 1px solid #e00; + background: var(--ruby-soft); + border: 1px solid var(--ruby); border-radius: 4px; padding: 8px 12px; margin-bottom: 8px; @@ -184,13 +184,13 @@ justify-content: space-between; align-items: center; font-size: 13px; - color: #c00; + color: var(--ruby); } .errorBanner button { background: none; border: none; - color: #c00; + color: var(--ruby); cursor: pointer; font-size: 12px; text-decoration: underline; @@ -207,7 +207,7 @@ } .confirmDialog { - background: #fff; + background: var(--surface); border-radius: 10px; padding: 20px 24px; box-shadow: var(--shadow-md); @@ -230,7 +230,7 @@ padding: 6px 16px; border: none; border-radius: 4px; - background: #4a90d9; + background: var(--ink); color: #fff; cursor: pointer; font-size: 13px; @@ -238,16 +238,16 @@ .cancelBtn { padding: 6px 16px; - border: 1px solid #ddd; + border: 1px solid var(--border); border-radius: 4px; - background: #fff; + background: var(--surface); cursor: pointer; font-size: 13px; } .completeBanner { - background: #f0fff0; - border: 1px solid #4caf50; + background: var(--green-soft); + border: 1px solid var(--green); border-radius: 4px; padding: 8px 12px; margin-bottom: 8px; @@ -255,13 +255,13 @@ justify-content: space-between; align-items: center; font-size: 13px; - color: #2e7d32; + color: var(--green); } .completeBanner button { background: none; border: none; - color: #2e7d32; + color: var(--green); cursor: pointer; font-size: 12px; text-decoration: underline; diff --git a/webui/src/components/Sidebar/WorldSelector.module.css b/webui/src/components/Sidebar/WorldSelector.module.css index 7fbe4334..8a2d86f9 100644 --- a/webui/src/components/Sidebar/WorldSelector.module.css +++ b/webui/src/components/Sidebar/WorldSelector.module.css @@ -62,7 +62,7 @@ display: grid; place-items: center; padding: 18px; - background: rgba(15, 23, 42, 0.24); + background: rgba(23, 23, 23, 0.38); } .modal { @@ -153,12 +153,12 @@ display: inline-flex; align-items: center; gap: 6px; - border-color: #fecaca; - background: #fff1f2; + border-color: var(--color-error-border); + background: var(--color-error-bg); color: var(--ruby); } .actions .danger:hover { - border-color: #fca5a5; - background: #ffe4e6; + border-color: var(--color-error-border); + background: var(--ruby-soft); } diff --git a/webui/src/components/Skeleton.module.css b/webui/src/components/Skeleton.module.css index add4b576..bcd5c309 100644 --- a/webui/src/components/Skeleton.module.css +++ b/webui/src/components/Skeleton.module.css @@ -4,7 +4,7 @@ background: rgba(255, 255, 255, 0.78); display: flex; flex-direction: column; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; box-shadow: none; overflow: hidden; @@ -24,7 +24,7 @@ display: flex; flex-direction: column; overflow: hidden; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; background: rgba(255, 255, 255, 0.82); box-shadow: var(--shadow-lg); diff --git a/webui/src/components/WorldOnboarding.module.css b/webui/src/components/WorldOnboarding.module.css index 731cc028..0945da8b 100644 --- a/webui/src/components/WorldOnboarding.module.css +++ b/webui/src/components/WorldOnboarding.module.css @@ -453,7 +453,7 @@ padding: 14px; border: 1px solid var(--color-border); border-radius: 8px; - background: var(--brand-wash); + background: var(--surface-muted); animation: characterIn 220ms var(--ease-spring); } diff --git a/webui/src/components/WorldSidebar.module.css b/webui/src/components/WorldSidebar.module.css index 74b4a27c..f72ea67b 100644 --- a/webui/src/components/WorldSidebar.module.css +++ b/webui/src/components/WorldSidebar.module.css @@ -6,7 +6,7 @@ background: linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(242, 242, 240, 0.78)), var(--surface-muted); - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; display: flex; flex-direction: column; @@ -49,7 +49,7 @@ margin: 0 8px 8px; padding: 7px 8px; cursor: pointer; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 6px; background: rgba(255, 255, 255, 0.75); color: var(--muted-strong); diff --git a/webui/src/components/cells/Cells.module.css b/webui/src/components/cells/Cells.module.css index 12112650..aefe15cd 100644 --- a/webui/src/components/cells/Cells.module.css +++ b/webui/src/components/cells/Cells.module.css @@ -26,7 +26,7 @@ background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(250, 250, 250, 0.92)), var(--surface); align-self: flex-start; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); color: var(--ink); border-bottom-left-radius: 4px; position: relative; @@ -156,7 +156,7 @@ } .inlineCode { - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 4px; background: var(--surface-muted); color: var(--ink); @@ -169,7 +169,7 @@ .codeBlock { margin: 10px 0; overflow: hidden; - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 7px; background: #f7f7f5; } @@ -306,7 +306,7 @@ align-self: flex-start; background: linear-gradient(135deg, rgba(247, 247, 245, 0.82), rgba(255, 255, 255, 0.92)), var(--surface); - border: 1px solid rgba(224, 223, 220, 0.92); + border: 1px solid var(--border-subtle); border-radius: 8px; padding: 9px 11px; font-size: 12px; diff --git a/webui/src/styles/global.css b/webui/src/styles/global.css index 8138bbba..57db4c6f 100644 --- a/webui/src/styles/global.css +++ b/webui/src/styles/global.css @@ -7,28 +7,29 @@ } :root { - --page: #ffffff; - --page-warm: #f1f5f9; + --page: #f6f6f4; + --page-warm: #ededeb; --surface: #ffffff; - --surface-muted: #f1f5f9; - --surface-strong: #f8fafc; + --surface-muted: #f2f2f0; + --surface-strong: #fafafa; --surface-raised: #ffffff; --glass: rgba(255, 255, 255, 0.74); --glass-strong: rgba(255, 255, 255, 0.9); - --ink: #0f172a; - --ink-soft: #1e293b; - --muted: #64748b; - --muted-strong: #475569; - --border: #e2e8f0; - --border-strong: #cbd5e1; + --ink: #171717; + --ink-soft: #333333; + --muted: #77756f; + --muted-strong: #55524c; + --border: #e0dfdc; + --border-strong: #c7c5bf; + /* ⚠️ DEPRECATED aliases — kept for backward compat. Prefer --ink / --muted-strong / --surface-muted directly. */ --teal: #171717; --teal-strong: #171717; --teal-soft: #ededeb; --teal-wash: #f7f7f5; - --brand: #06266f; - --brand-strong: #041c55; - --brand-soft: #dbe7ff; - --brand-wash: #f3f7ff; + --brand: #343434; + --brand-strong: #171717; + --brand-soft: #e8e7e4; + --brand-wash: #f7f7f5; --amber: #8a5a12; --amber-soft: #fbf2dc; --amber-wash: #fafafa; @@ -42,11 +43,11 @@ --purple-soft: #f0efed; --green: #16a34a; --green-soft: #dcfce7; - --hover: rgba(6, 38, 111, 0.06); + --hover: rgba(23, 23, 23, 0.055); --shadow-sm: 0 1px 2px rgba(23, 23, 23, 0.06); --shadow-md: 0 12px 28px rgba(23, 23, 23, 0.1); --shadow-lg: 0 18px 46px rgba(23, 23, 23, 0.12); - --ring: 0 0 0 3px rgba(6, 38, 111, 0.2); + --ring: 0 0 0 3px rgba(23, 23, 23, 0.12); --rounded-sm: 4px; --rounded-md: 6px; --rounded-lg: 8px; @@ -71,11 +72,21 @@ --color-text-primary: var(--ink); --color-text-secondary: var(--muted-strong); --color-text-tertiary: var(--muted); - --color-accent: var(--brand); + --color-accent: var(--ink); --color-error: var(--ruby); --color-error-bg: var(--ruby-soft); --color-error-border: #fecaca; --color-success: var(--green); + + /* ---- Subtle border (replaces hardcoded rgba(224,223,220,0.92) in components) ---- */ + --border-subtle: rgba(224, 223, 220, 0.92); + + /* ---- Animation tokens ---- */ + --ease-out: cubic-bezier(0.4, 0, 0.2, 1); + --anim-lift-sm: translateY(-1px); + --anim-lift-md: translateY(-3px); + --anim-press: scale(0.98); + --anim-fade-in: 200ms var(--ease-out) both; } html, @@ -98,6 +109,18 @@ body { overflow: hidden; } +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + background-image: + linear-gradient(rgba(23, 23, 23, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(23, 23, 23, 0.03) 1px, transparent 1px); + background-size: 34px 34px; + mask-image: radial-gradient(circle at 50% 36%, #000 0%, transparent 68%); +} + button, input, select,