抽取配色css (#79)
Co-authored-by: 段舒康 <kdletters@qq.com> Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/79 Reviewed-by: 段舒康 <kdletters@qq.com> Co-authored-by: kvtodev <kvtodev@outlook.com> Co-committed-by: kvtodev <kvtodev@outlook.com>
This commit was merged in pull request #79.
This commit is contained in:
@@ -2649,7 +2649,7 @@
|
||||
# 2026-05-20 陶泥儿主视觉配色回收为暖白/陶土橙
|
||||
|
||||
- 背景:用户要求只替换产品各界面的 UI 颜色,不改布局,并以两张陶泥儿主视觉图作为配色依据。
|
||||
- 决策:平台亮色主题的主色回收到暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;后台管理也同步切换到同一暖橙体系。主题变量优先通过 `src/index.css` 的 `--platform-*` token 统一控制,零散组件只做必要的局部替换。
|
||||
- 决策:平台亮色主题的主色回收到暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;后台管理也同步切换到同一暖橙体系。主题变量和平台字体栈优先通过 `packages/shared/src/theme.css` 的 `--platform-*` token 统一控制,具体全局字体选择器留在消费端原有层叠位置,零散组件只做必要的局部替换。
|
||||
- 影响范围:主站平台壳层、常用表单 / 按钮 / 卡片 / 背景、后台管理 UI、业务进度条和小游戏结果条的通用强调色。
|
||||
- 验证方式:优先检查 `src/index.css` 与 `apps/admin-web/src/styles/admin.css` 是否还存在旧粉色主色;再用编码检查和可执行的本地 typecheck / build 验证。
|
||||
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
|
||||
@@ -108,7 +108,7 @@ server-rs + Axum + SpacetimeDB
|
||||
12. “我的”页泥点余额、累计游玩、已玩游戏三张统计卡只展示各自标签和值,三个统计 icon 使用小尺寸普通 UI 档位,内容不换行,不在统计区底部展示“更新于”时间;移动端昵称、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。
|
||||
13. 移动端“我的”页需要兼容窄屏:头像 / 昵称 / 陶泥号、三张统计卡、五项常用功能和法律信息都必须能在底部固定 TabBar 上方完整滚动露出,不得与底部 dock、刘海 safe-area 或相邻 UI 元素遮挡重叠。
|
||||
14. RPG 等运行态的战斗飘字、血量变化和即时反馈必须在暗色、噪声高的场景背景上保持可读:使用高亮文字、深色描边、强阴影或小面积半透明底,不只依赖红/绿文字本身表达伤害或治疗。
|
||||
15. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `src/index.css` 的 `--platform-*` 主题变量和 `apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案。
|
||||
15. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `packages/shared/src/theme.css` 的 `--platform-*` 主题变量和平台字体栈、`apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案;具体全局字体选择器保留在消费端原有层叠位置,避免共享样式导入改变既有视觉。
|
||||
|
||||
## 文案与编码
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const themePath = path.resolve(
|
||||
process.cwd(),
|
||||
'packages/shared/src/theme.css',
|
||||
);
|
||||
|
||||
function getCssBlock(source: string, selector: string) {
|
||||
const selectorIndex = source.indexOf(selector);
|
||||
expect(selectorIndex, `${selector} should exist`).toBeGreaterThanOrEqual(0);
|
||||
|
||||
const openBraceIndex = source.indexOf('{', selectorIndex);
|
||||
let depth = 0;
|
||||
for (let index = openBraceIndex; index < source.length; index += 1) {
|
||||
const char = source[index];
|
||||
if (char === '{') {
|
||||
depth += 1;
|
||||
} else if (char === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
return source.slice(openBraceIndex + 1, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`${selector} block is not closed`);
|
||||
}
|
||||
|
||||
describe('shared platform theme', () => {
|
||||
it('exposes the shared platform font stack without fixing its cascade position', () => {
|
||||
const css = fs.readFileSync(themePath, 'utf8');
|
||||
const platformTheme = getCssBlock(css, '.platform-theme');
|
||||
|
||||
expect(platformTheme).toContain("--platform-font-family: 'Inter'");
|
||||
expect(css).not.toContain('.platform-theme,\n.platform-theme *');
|
||||
});
|
||||
|
||||
it('keeps the light and dark semantic color contracts', () => {
|
||||
const css = fs.readFileSync(themePath, 'utf8');
|
||||
const light = getCssBlock(css, '.platform-theme--light');
|
||||
const dark = getCssBlock(css, '.platform-theme--dark');
|
||||
|
||||
expect(light).toContain('--platform-accent: #c7653d;');
|
||||
expect(light).toContain('linear-gradient(135deg, #df7f40, #b95d3a)');
|
||||
expect(light).toContain('--platform-text-strong: #3d1f10;');
|
||||
expect(dark).toContain('--platform-accent: #5b6cff;');
|
||||
expect(dark).toContain('linear-gradient(135deg, #5b6cff, #3dd9ff)');
|
||||
expect(dark).toContain('--platform-text-strong: #ffffff;');
|
||||
});
|
||||
});
|
||||
+2
-534
File diff suppressed because it is too large
Load Diff
+30
-4
@@ -7,6 +7,13 @@ function readIndexCss() {
|
||||
return fs.readFileSync(path.resolve(process.cwd(), 'src/index.css'), 'utf8');
|
||||
}
|
||||
|
||||
function readPlatformThemeCss() {
|
||||
return fs.readFileSync(
|
||||
path.resolve(process.cwd(), 'packages/shared/src/theme.css'),
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
|
||||
function getCssBlock(source: string, selector: string) {
|
||||
const selectorIndex = source.indexOf(selector);
|
||||
expect(selectorIndex, `${selector} should exist in src/index.css`).toBeGreaterThanOrEqual(0);
|
||||
@@ -194,11 +201,16 @@ describe('index stylesheet unread dots', () => {
|
||||
|
||||
it('uses warm brown tokens for draft unread markers instead of red literals', () => {
|
||||
const css = readIndexCss();
|
||||
const themeCss = readPlatformThemeCss();
|
||||
|
||||
expect(css).toContain('--platform-unread-dot-fill: #8b5a3d;');
|
||||
expect(css).toContain('--platform-unread-dot-glow: rgba(139, 90, 61, 0.34);');
|
||||
expect(css).toContain('--platform-unread-dot-fill: #d6a27c;');
|
||||
expect(css).toContain('--platform-unread-dot-glow: rgba(214, 162, 124, 0.24);');
|
||||
expect(themeCss).toContain('--platform-unread-dot-fill: #8b5a3d;');
|
||||
expect(themeCss).toContain(
|
||||
'--platform-unread-dot-glow: rgba(139, 90, 61, 0.34);',
|
||||
);
|
||||
expect(themeCss).toContain('--platform-unread-dot-fill: #d6a27c;');
|
||||
expect(themeCss).toContain(
|
||||
'--platform-unread-dot-glow: rgba(214, 162, 124, 0.24);',
|
||||
);
|
||||
|
||||
for (const selector of [
|
||||
'.creation-work-card__unread-dot',
|
||||
@@ -213,6 +225,20 @@ describe('index stylesheet unread dots', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform font overrides after branded title typography', () => {
|
||||
const css = readIndexCss();
|
||||
const themeCss = readPlatformThemeCss();
|
||||
const titleRuleIndex = css.indexOf('.selection-hero-brand__title');
|
||||
const platformThemeRuleIndex = css.indexOf('.platform-theme,\n.platform-theme *');
|
||||
|
||||
expect(titleRuleIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(platformThemeRuleIndex).toBeGreaterThan(titleRuleIndex);
|
||||
expect(getCssBlock(css, '.platform-theme,\n.platform-theme *')).toContain(
|
||||
'font-family: var(--platform-font-family) !important;',
|
||||
);
|
||||
expect(themeCss).not.toContain('.platform-theme,\n.platform-theme *');
|
||||
});
|
||||
|
||||
it('keeps the creation shelf share button on a visible surface', () => {
|
||||
const css = readIndexCss();
|
||||
const block = getCssBlock(css, '.creation-work-card__quick-action-button');
|
||||
|
||||
Reference in New Issue
Block a user