diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 4866f5007..8f5ace7b2 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -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`。 diff --git a/docs/【项目基线】当前产品与工程约束-2026-05-15.md b/docs/【项目基线】当前产品与工程约束-2026-05-15.md index 8665e27cf..36ff0a5d0 100644 --- a/docs/【项目基线】当前产品与工程约束-2026-05-15.md +++ b/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` 的同系色值,不再引入粉红、蓝绿等独立主色方案;具体全局字体选择器保留在消费端原有层叠位置,避免共享样式导入改变既有视觉。 ## 文案与编码 diff --git a/packages/shared/src/theme.css b/packages/shared/src/theme.css new file mode 100644 index 000000000..08fc0a5b5 --- /dev/null +++ b/packages/shared/src/theme.css @@ -0,0 +1,534 @@ +.platform-theme { + --platform-font-family: 'Inter', 'Noto Sans CJK SC', 'WenQuanYi Zen Hei', + 'PingFang SC', 'Microsoft YaHei', ui-sans-serif, system-ui, sans-serif; + color: var(--platform-text-strong); +} + +.platform-theme--light { + color-scheme: light; + --platform-accent: #c7653d; + --platform-body-fill: radial-gradient( + circle at 8% 0%, + rgba(240, 203, 169, 0.24), + transparent 26% + ), + radial-gradient(circle at 92% 8%, rgba(226, 171, 134, 0.2), transparent 22%), + radial-gradient( + circle at 54% 100%, + rgba(204, 117, 76, 0.08), + transparent 30% + ), + linear-gradient(180deg, #fffdf9 0%, #fdf9f5 54%, #f8efe7 100%); + --platform-panel-shadow: 0 22px 60px rgba(112, 57, 30, 0.1), + 0 8px 22px rgba(255, 255, 255, 0.82); + --platform-panel-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.985), + rgba(253, 248, 243, 0.96) + ); + --platform-panel-fill-soft: linear-gradient( + 180deg, + rgba(255, 254, 252, 0.96), + rgba(250, 243, 236, 0.9) + ); + --platform-hero-fill: linear-gradient( + 135deg, + rgba(244, 226, 211, 0.96), + rgba(238, 208, 183, 0.9) + ); + --platform-hero-glow-a: rgba(255, 255, 255, 0.42); + --platform-hero-glow-b: rgba(228, 149, 91, 0.18); + --platform-hero-overlay-strong: linear-gradient( + 135deg, + rgba(238, 208, 183, 0.66), + rgba(204, 117, 76, 0.18) + ); + --platform-hero-overlay-soft: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.34), + rgba(248, 233, 219, 0.22) + ); + --platform-surface-border: rgba(226, 203, 184, 0.88); + --platform-surface-hover-border: rgba(204, 117, 76, 0.42); + --platform-shell-glow-1: rgba(255, 255, 255, 0.34); + --platform-shell-glow-2: rgba(238, 208, 183, 0.22); + --platform-shell-glow-3: rgba(210, 132, 93, 0.12); + --platform-surface-glow-a: rgba(240, 203, 169, 0.18); + --platform-surface-glow-b: rgba(204, 117, 76, 0.1); + --platform-text-strong: #3d1f10; + --platform-text-base: #6f5848; + --platform-text-soft: #988476; + --platform-text-muted: #a38f80; + --platform-brand-logo-title: #4a220f; + --platform-brand-logo-subtitle: #c7653d; + --platform-brand-logo-shadow: #caa48b; + --platform-line-soft: rgba(226, 203, 184, 0.72); + --platform-subpanel-fill: linear-gradient( + 180deg, + rgba(255, 254, 252, 0.94), + rgba(250, 243, 236, 0.9) + ); + --platform-subpanel-border: rgba(225, 204, 187, 0.82); + --platform-warm-border: rgba(204, 117, 76, 0.28); + --platform-warm-bg: rgba(234, 204, 179, 0.32); + --platform-warm-text: #b6623f; + --platform-cool-border: rgba(199, 117, 76, 0.24); + --platform-cool-bg: rgba(238, 208, 183, 0.26); + --platform-cool-text: #b76038; + --platform-neutral-border: rgba(226, 203, 184, 0.44); + --platform-neutral-bg: rgba(255, 253, 250, 0.7); + --platform-neutral-text: #7b6150; + --platform-button-primary-border: rgba(182, 98, 63, 0.32); + --platform-button-primary-solid: #c7653d; + --platform-button-primary-fill: linear-gradient(135deg, #df7f40, #b95d3a); + --platform-button-primary-text: #fffaf5; + --platform-button-secondary-fill: rgba(255, 253, 250, 0.78); + --platform-button-secondary-text: #4b2412; + --platform-button-ghost-fill: rgba(255, 253, 250, 0.56); + --platform-button-ghost-text: #755a49; + --platform-button-danger-border: rgba(185, 75, 58, 0.22); + --platform-button-danger-fill: rgba(255, 237, 229, 0.94); + --platform-button-danger-text: #a6402f; + --platform-unread-dot-fill: #8b5a3d; + --platform-unread-dot-glow: rgba(139, 90, 61, 0.34); + --platform-success-border: rgba(73, 144, 96, 0.24); + --platform-success-bg: rgba(237, 248, 239, 0.92); + --platform-success-text: #2f7b46; + --platform-icon-fill: rgba(255, 253, 250, 0.68); + --platform-icon-border: rgba(226, 203, 184, 0.5); + --platform-icon-text: #7b5c49; + --platform-nav-fill: linear-gradient( + 180deg, + rgba(255, 254, 252, 0.96), + rgba(250, 243, 236, 0.92) + ); + --platform-nav-active-fill: linear-gradient( + 180deg, + rgba(238, 208, 183, 0.48), + rgba(204, 117, 76, 0.16) + ); + --platform-nav-active-border: rgba(204, 117, 76, 0.34); + --platform-nav-active-shadow: 0 10px 24px rgba(182, 98, 63, 0.12); + --platform-modal-fill: linear-gradient( + 180deg, + rgba(255, 254, 252, 0.97), + rgba(250, 243, 236, 0.95) + ); + --platform-modal-border: rgba(255, 255, 255, 0.58); + --platform-desktop-shell-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.99), + rgba(253, 249, 245, 0.985) + ); + --platform-desktop-shell-border: rgba(230, 213, 199, 0.94); + --platform-desktop-shell-inner-border: rgba(233, 218, 205, 0.92); + --platform-desktop-topbar-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.95), + rgba(253, 249, 245, 0.92) + ); + --platform-desktop-panel-fill: linear-gradient( + 180deg, + rgba(255, 254, 252, 0.95), + rgba(250, 243, 236, 0.91) + ); + --platform-desktop-panel-border: rgba(226, 203, 184, 0.88); + --platform-desktop-hover-shadow: 0 16px 30px rgba(112, 57, 30, 0.12); + --platform-overlay-fill: linear-gradient( + 180deg, + rgba(240, 203, 169, 0.14), + rgba(255, 255, 255, 0.58) + ); + --platform-track-border: rgba(226, 203, 184, 0.48); + --platform-track-fill: rgba(255, 253, 250, 0.9); + --platform-page-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.9), + rgba(250, 243, 236, 0.8) + ); + --platform-page-border: rgba(233, 218, 205, 0.88); + --platform-input-fill: rgba(255, 253, 250, 0.94); + --platform-input-fill-focus: rgba(255, 254, 252, 0.96); + --platform-input-highlight: rgba(255, 255, 255, 0.9); + --platform-input-focus-ring: rgba(204, 117, 76, 0.15); + --platform-nav-item-text: #80695a; + --platform-nav-item-text-active: #3d1f10; + --platform-nav-item-hover-fill: rgba(253, 248, 243, 0.94); + --platform-nav-item-icon-fill: rgba(250, 243, 236, 1); + --platform-nav-item-icon-text: #7b5c49; + --platform-nav-item-icon-active-fill: rgba(255, 254, 252, 0.98); + --platform-nav-item-icon-active-text: #c7653d; + --platform-nav-icon-active-shadow: 0 12px 24px rgba(182, 98, 63, 0.16); + --platform-bottom-nav-fill: linear-gradient(180deg, #fffefa, #fff9ef); + --platform-bottom-nav-border: rgba(224, 181, 139, 0.58); + --platform-bottom-nav-divider: rgba(225, 185, 145, 0.52); + --platform-bottom-nav-text: #5b2b19; + --platform-bottom-nav-muted-text: #7a4b37; + --platform-bottom-nav-active-text: #f15a17; + --platform-bottom-nav-primary-fill: linear-gradient(180deg, #ff7a23, #f04d12); + --platform-bottom-nav-primary-shadow: 0 12px 24px rgba(240, 77, 18, 0.28); + --platform-bottom-nav-primary-text: #fffaf2; + --platform-profile-hero-fill: linear-gradient( + 180deg, + rgba(255, 254, 252, 0.96), + rgba(250, 239, 229, 0.9) + ); + --platform-profile-hero-border: rgba(255, 255, 255, 0.56); + --platform-profile-hero-shadow: 0 20px 56px rgba(112, 57, 30, 0.16); + --platform-profile-avatar-fill: linear-gradient( + 135deg, + rgba(223, 127, 64, 0.96), + rgba(181, 91, 56, 0.92) + ); + --platform-profile-avatar-shadow: 0 14px 30px rgba(182, 98, 63, 0.22); + --platform-profile-chip-fill: rgba(255, 253, 250, 0.88); + --platform-profile-chip-hover-fill: rgba(255, 254, 252, 0.96); + --platform-profile-chip-text: #755a49; + --platform-profile-action-fill: linear-gradient(135deg, #df7f40, #b95d3a); + --platform-profile-action-solid: #c7653d; + --platform-profile-action-text: #fffaf5; + --platform-profile-action-shadow: 0 14px 30px rgba(182, 98, 63, 0.22); + --platform-card-overlay-soft: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.08), + rgba(250, 243, 236, 0.82) + ); + --platform-card-overlay-strong: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.16), + rgba(248, 239, 231, 0.92) + ); + --platform-card-overlay-deep: radial-gradient( + circle at top left, + rgba(255, 255, 255, 0.2), + transparent 30% + ), + radial-gradient(circle at right, rgba(228, 149, 91, 0.14), transparent 28%), + linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(248, 239, 231, 0.9)); + --platform-recommend-runtime-fill: var(--platform-panel-fill); + --platform-recommend-runtime-border: rgba(226, 203, 184, 0.42); + --platform-recommend-runtime-shadow: 0 18px 44px rgba(112, 57, 30, 0.12), + inset 0 0 0 1px rgba(255, 255, 255, 0.58); + --platform-recommend-runtime-state-fill: radial-gradient( + circle at 50% 18%, + rgba(204, 117, 76, 0.12), + transparent 34% + ), + linear-gradient( + 180deg, + rgba(255, 255, 255, 0.98), + rgba(250, 243, 236, 0.94) + ); + --platform-recommend-runtime-state-text: var(--platform-text-strong); + --puzzle-runtime-shell-fill: var(--platform-body-fill); + --puzzle-runtime-stage-fill: radial-gradient( + circle at 50% 18%, + rgba(204, 117, 76, 0.13), + transparent 30% + ), + radial-gradient( + circle at 18% 82%, + rgba(240, 203, 169, 0.18), + transparent 28% + ), + linear-gradient(180deg, #fffdf9 0%, #fbf5ed 58%, #f4e5d7 100%); + --puzzle-runtime-grid-line: rgba(112, 57, 30, 0.06); + --puzzle-runtime-text-strong: var(--platform-text-strong); + --puzzle-runtime-text-base: var(--platform-text-base); + --puzzle-runtime-text-soft: var(--platform-text-soft); + --puzzle-runtime-surface-fill: rgba(255, 253, 250, 0.76); + --puzzle-runtime-surface-fill-strong: rgba(255, 253, 250, 0.9); + --puzzle-runtime-surface-border: rgba(226, 203, 184, 0.48); + --puzzle-runtime-board-fill: rgba(255, 253, 250, 0.68); + --puzzle-runtime-board-border: rgba(204, 117, 76, 0.28); + --puzzle-runtime-board-shadow: 0 30px 80px rgba(112, 57, 30, 0.14); + --puzzle-runtime-piece-fill: rgba(255, 253, 250, 0.74); + --puzzle-runtime-piece-border: transparent; + --puzzle-runtime-piece-empty-fill: rgba(238, 208, 183, 0.34); + --puzzle-runtime-piece-empty-text: rgba(111, 88, 72, 0.4); + --puzzle-runtime-piece-selected-fill: linear-gradient( + 135deg, + #df7f40, + #b95d3a + ); + --puzzle-runtime-piece-selected-text: #fffaf5; + --puzzle-runtime-piece-selected-border: transparent; + --puzzle-runtime-next-card-overlay: rgba(74, 34, 15, 0.06); + --puzzle-runtime-control-fill: rgba(255, 253, 250, 0.72); + --puzzle-runtime-control-hover-fill: rgba(204, 117, 76, 0.1); + --puzzle-runtime-primary-fill: var(--platform-button-primary-fill); + --puzzle-runtime-primary-text: var(--platform-button-primary-text); + --puzzle-runtime-primary-shadow: var(--platform-profile-action-shadow); + --puzzle-runtime-accent-text: var(--platform-cool-text); + --puzzle-runtime-cool-text: #6e8d42; + --puzzle-runtime-danger-fill: rgba(255, 237, 229, 0.9); + --puzzle-runtime-danger-text: #a6402f; + --puzzle-runtime-backdrop-fill: rgba(61, 31, 16, 0.34); + --puzzle-runtime-dialog-fill: radial-gradient( + circle at 12% 0%, + rgba(204, 117, 76, 0.18), + transparent 36% + ), + linear-gradient( + 180deg, + rgba(255, 254, 252, 0.98), + rgba(250, 243, 236, 0.95) + ); + --puzzle-runtime-dialog-border: rgba(204, 117, 76, 0.3); + --puzzle-runtime-table-fill: rgba(255, 253, 250, 0.62); + --puzzle-runtime-table-row-fill: rgba(204, 117, 76, 0.12); + --puzzle-runtime-next-card-fill: rgba(255, 253, 250, 0.66); + --puzzle-runtime-next-card-hover-fill: rgba(204, 117, 76, 0.1); +} + +.platform-theme--dark { + color-scheme: dark; + --platform-accent: #5b6cff; + --platform-body-fill: radial-gradient( + circle at top, + rgba(129, 140, 248, 0.2), + transparent 30% + ), + radial-gradient( + circle at top right, + rgba(59, 130, 246, 0.12), + transparent 24% + ), + radial-gradient( + circle at bottom left, + rgba(34, 211, 238, 0.08), + transparent 26% + ), + linear-gradient(180deg, #13151c 0%, #090b11 100%); + --platform-panel-shadow: 0 28px 88px rgba(5, 8, 28, 0.5); + --platform-panel-fill: linear-gradient( + 180deg, + rgba(22, 20, 52, 0.95), + rgba(7, 9, 21, 0.98) + ); + --platform-panel-fill-soft: linear-gradient( + 180deg, + rgba(122, 92, 255, 0.12), + rgba(255, 255, 255, 0.03) + ); + --platform-hero-fill: radial-gradient( + circle at top left, + rgba(129, 140, 248, 0.24), + transparent 34% + ), + radial-gradient(circle at right, rgba(34, 211, 238, 0.12), transparent 32%), + linear-gradient(180deg, rgba(17, 22, 66, 0.95), rgba(8, 10, 24, 0.98)); + --platform-hero-glow-a: rgba(129, 140, 248, 0.18); + --platform-hero-glow-b: rgba(34, 211, 238, 0.1); + --platform-surface-border: rgba(160, 169, 255, 0.12); + --platform-surface-hover-border: rgba(255, 255, 255, 0.16); + --platform-shell-glow-1: rgba(129, 140, 248, 0.2); + --platform-shell-glow-2: rgba(59, 130, 246, 0.12); + --platform-shell-glow-3: rgba(34, 211, 238, 0.08); + --platform-surface-glow-a: rgba(129, 140, 248, 0.18); + --platform-surface-glow-b: rgba(34, 211, 238, 0.1); + --platform-text-strong: #ffffff; + --platform-text-base: rgb(228 228 231); + --platform-text-soft: rgb(161 161 170); + --platform-text-muted: rgb(161 161 170); + --platform-brand-logo-title: #fff7dc; + --platform-brand-logo-subtitle: #9fe7ff; + --platform-brand-logo-shadow: #040814; + --platform-line-soft: rgba(255, 255, 255, 0.1); + --platform-subpanel-fill: rgba(255, 255, 255, 0.05); + --platform-subpanel-border: rgba(255, 255, 255, 0.1); + --platform-warm-border: rgba(167, 139, 250, 0.24); + --platform-warm-bg: rgba(109, 40, 217, 0.18); + --platform-warm-text: rgb(237 233 254); + --platform-cool-border: rgba(103, 232, 249, 0.22); + --platform-cool-bg: rgba(8, 145, 178, 0.14); + --platform-cool-text: rgb(236 254 255); + --platform-neutral-border: rgba(255, 255, 255, 0.08); + --platform-neutral-bg: rgba(255, 255, 255, 0.05); + --platform-neutral-text: rgb(228 228 231); + --platform-button-primary-border: rgba(129, 140, 248, 0.3); + --platform-button-primary-solid: #5b6cff; + --platform-button-primary-fill: linear-gradient(135deg, #5b6cff, #3dd9ff); + --platform-button-primary-text: rgb(238 248 255); + --platform-button-secondary-fill: rgba(255, 255, 255, 0.05); + --platform-button-secondary-text: rgb(244 244 245); + --platform-button-ghost-fill: rgba(255, 255, 255, 0.03); + --platform-button-ghost-text: rgb(212 212 216); + --platform-button-danger-border: rgba(251, 113, 133, 0.2); + --platform-button-danger-fill: rgba(244, 63, 94, 0.1); + --platform-button-danger-text: rgb(255 228 230); + --platform-unread-dot-fill: #d6a27c; + --platform-unread-dot-glow: rgba(214, 162, 124, 0.24); + --platform-success-border: rgba(52, 211, 153, 0.24); + --platform-success-bg: rgba(16, 185, 129, 0.1); + --platform-success-text: rgb(220 252 231); + --platform-icon-fill: rgba(255, 255, 255, 0.05); + --platform-icon-border: rgba(255, 255, 255, 0.1); + --platform-icon-text: rgb(212 212 216); + --platform-nav-fill: linear-gradient( + 180deg, + rgba(109, 40, 217, 0.12), + rgba(255, 255, 255, 0.03) + ); + --platform-nav-active-fill: linear-gradient( + 180deg, + rgba(91, 108, 255, 0.2), + rgba(61, 217, 255, 0.08) + ); + --platform-nav-active-border: rgba(160, 169, 255, 0.24); + --platform-nav-active-shadow: 0 12px 28px rgba(8, 14, 42, 0.4); + --platform-modal-fill: linear-gradient( + 180deg, + rgba(16, 18, 46, 0.98), + rgba(7, 8, 19, 0.98) + ); + --platform-modal-border: rgba(160, 169, 255, 0.12); + --platform-desktop-shell-fill: linear-gradient( + 180deg, + rgba(8, 8, 30, 0.98), + rgba(5, 6, 18, 0.99) + ); + --platform-desktop-shell-border: rgba(160, 169, 255, 0.14); + --platform-desktop-shell-inner-border: rgba(255, 255, 255, 0.03); + --platform-desktop-topbar-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.05), + rgba(255, 255, 255, 0.03) + ); + --platform-desktop-panel-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.05), + rgba(255, 255, 255, 0.02) + ); + --platform-desktop-panel-border: rgba(160, 169, 255, 0.12); + --platform-desktop-hover-shadow: 0 16px 28px rgba(8, 11, 38, 0.2); + --platform-overlay-fill: rgba(5, 8, 28, 0.72); + --platform-track-border: rgba(255, 255, 255, 0.12); + --platform-track-fill: rgba(255, 255, 255, 0.08); + --platform-page-fill: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.04), + rgba(255, 255, 255, 0.02) + ); + --platform-page-border: rgba(255, 255, 255, 0.06); + --platform-input-fill: rgba(255, 255, 255, 0.05); + --platform-input-fill-focus: rgba(255, 255, 255, 0.08); + --platform-input-highlight: rgba(255, 255, 255, 0.06); + --platform-input-focus-ring: rgba(91, 108, 255, 0.22); + --platform-nav-item-text: rgb(161 161 170); + --platform-nav-item-text-active: rgb(238 248 255); + --platform-nav-item-hover-fill: rgba(91, 108, 255, 0.08); + --platform-nav-item-icon-fill: rgba(255, 255, 255, 0.06); + --platform-nav-item-icon-text: rgb(161 161 170); + --platform-nav-item-icon-active-fill: linear-gradient( + 180deg, + rgba(91, 108, 255, 0.24), + rgba(61, 217, 255, 0.12) + ); + --platform-nav-item-icon-active-text: rgb(238 248 255); + --platform-nav-icon-active-shadow: 0 12px 24px rgba(8, 14, 42, 0.42); + --platform-bottom-nav-fill: linear-gradient(180deg, #fffefa, #fff9ef); + --platform-bottom-nav-border: rgba(224, 181, 139, 0.64); + --platform-bottom-nav-divider: rgba(225, 185, 145, 0.56); + --platform-bottom-nav-text: #5b2b19; + --platform-bottom-nav-muted-text: #7a4b37; + --platform-bottom-nav-active-text: #f15a17; + --platform-bottom-nav-primary-fill: linear-gradient(180deg, #ff7a23, #f04d12); + --platform-bottom-nav-primary-shadow: 0 12px 24px rgba(240, 77, 18, 0.3); + --platform-bottom-nav-primary-text: #fffaf2; + --platform-profile-hero-fill: linear-gradient( + 180deg, + rgba(20, 24, 58, 0.96), + rgba(8, 10, 24, 0.98) + ); + --platform-profile-hero-border: rgba(160, 169, 255, 0.14); + --platform-profile-hero-shadow: 0 24px 70px rgba(5, 8, 28, 0.42); + --platform-profile-avatar-fill: linear-gradient( + 135deg, + rgba(91, 108, 255, 0.94), + rgba(61, 217, 255, 0.78) + ); + --platform-profile-avatar-shadow: 0 14px 32px rgba(61, 217, 255, 0.16); + --platform-profile-chip-fill: rgba(255, 255, 255, 0.08); + --platform-profile-chip-hover-fill: rgba(255, 255, 255, 0.14); + --platform-profile-chip-text: rgb(228 228 231); + --platform-profile-action-fill: linear-gradient(135deg, #5b6cff, #3dd9ff); + --platform-profile-action-solid: #5b6cff; + --platform-profile-action-text: rgb(238 248 255); + --platform-profile-action-shadow: 0 14px 32px rgba(91, 108, 255, 0.22); + --platform-card-overlay-soft: linear-gradient( + 180deg, + rgba(8, 10, 14, 0.06), + rgba(8, 10, 14, 0.74) + ); + --platform-card-overlay-strong: linear-gradient( + 180deg, + rgba(8, 10, 14, 0.14), + rgba(8, 10, 14, 0.9) + ); + --platform-card-overlay-deep: radial-gradient( + circle at top left, + rgba(255, 255, 255, 0.14), + transparent 30% + ), + radial-gradient(circle at right, rgba(255, 205, 178, 0.14), transparent 28%), + linear-gradient(180deg, rgba(8, 10, 14, 0.22), rgba(8, 10, 14, 0.9)); + --platform-recommend-runtime-fill: #030303; + --platform-recommend-runtime-border: rgba(255, 255, 255, 0.08); + --platform-recommend-runtime-shadow: inset 0 0 0 1px + rgba(255, 255, 255, 0.025), + 0 18px 44px rgba(0, 0, 0, 0.18); + --platform-recommend-runtime-state-fill: #030303; + --platform-recommend-runtime-state-text: rgba(255, 255, 255, 0.92); + --puzzle-runtime-shell-fill: #020617; + --puzzle-runtime-stage-fill: radial-gradient( + circle at 50% 20%, + rgba(251, 191, 36, 0.18), + transparent 28% + ), + radial-gradient( + circle at 20% 80%, + rgba(249, 115, 22, 0.16), + transparent 26% + ), + linear-gradient(180deg, #2d160e, #020617); + --puzzle-runtime-grid-line: rgba(255, 255, 255, 0.04); + --puzzle-runtime-text-strong: #ffffff; + --puzzle-runtime-text-base: rgba(255, 255, 255, 0.82); + --puzzle-runtime-text-soft: rgba(255, 255, 255, 0.58); + --puzzle-runtime-surface-fill: rgba(0, 0, 0, 0.34); + --puzzle-runtime-surface-fill-strong: rgba(0, 0, 0, 0.5); + --puzzle-runtime-surface-border: rgba(255, 255, 255, 0.12); + --puzzle-runtime-board-fill: rgba(255, 255, 255, 0.08); + --puzzle-runtime-board-border: rgba(255, 255, 255, 0.16); + --puzzle-runtime-board-shadow: 0 30px 80px rgba(0, 0, 0, 0.35); + --puzzle-runtime-piece-fill: rgba(255, 255, 255, 0.12); + --puzzle-runtime-piece-border: transparent; + --puzzle-runtime-piece-empty-fill: rgba(0, 0, 0, 0.18); + --puzzle-runtime-piece-empty-text: rgba(255, 255, 255, 0.2); + --puzzle-runtime-piece-selected-fill: rgba(251, 191, 36, 0.84); + --puzzle-runtime-piece-selected-text: #020617; + --puzzle-runtime-piece-selected-border: transparent; + --puzzle-runtime-next-card-overlay: rgba(0, 0, 0, 0.1); + --puzzle-runtime-control-fill: rgba(0, 0, 0, 0.36); + --puzzle-runtime-control-hover-fill: rgba(255, 255, 255, 0.1); + --puzzle-runtime-primary-fill: #fde68a; + --puzzle-runtime-primary-text: #020617; + --puzzle-runtime-primary-shadow: 0 14px 36px rgba(251, 191, 36, 0.26); + --puzzle-runtime-accent-text: #fde68a; + --puzzle-runtime-cool-text: #cffafe; + --puzzle-runtime-danger-fill: rgba(239, 68, 68, 0.2); + --puzzle-runtime-danger-text: #fee2e2; + --puzzle-runtime-backdrop-fill: rgba(2, 6, 23, 0.68); + --puzzle-runtime-dialog-fill: radial-gradient( + circle at 14% 0%, + rgba(251, 191, 36, 0.16), + transparent 36% + ), + linear-gradient(180deg, rgba(29, 17, 12, 0.96), rgba(2, 6, 23, 0.96)); + --puzzle-runtime-dialog-border: rgba(251, 191, 36, 0.22); + --puzzle-runtime-table-fill: rgba(255, 255, 255, 0.06); + --puzzle-runtime-table-row-fill: rgba(251, 191, 36, 0.14); + --puzzle-runtime-next-card-fill: rgba(255, 255, 255, 0.06); + --puzzle-runtime-next-card-hover-fill: rgba(251, 191, 36, 0.1); +} diff --git a/packages/shared/src/theme.test.ts b/packages/shared/src/theme.test.ts new file mode 100644 index 000000000..0fe6c9c9a --- /dev/null +++ b/packages/shared/src/theme.test.ts @@ -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;'); + }); +}); diff --git a/src/index.css b/src/index.css index d8c13ac58..463ba71b3 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,6 @@ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Serif+SC:wght@400;700&display=swap'); @import 'tailwindcss'; +@import '@/packages/shared/src/theme.css'; @source not "../dist"; @source not "../dist_check"; @source not "../dist_check_final"; @@ -959,542 +960,9 @@ html[data-mobile-keyboard-open='true'] #root { .platform-theme, .platform-theme * { - font-family: 'Inter', 'Noto Sans CJK SC', 'WenQuanYi Zen Hei', 'PingFang SC', - 'Microsoft YaHei', ui-sans-serif, system-ui, sans-serif !important; + font-family: var(--platform-font-family) !important; } -.platform-theme { - color: var(--platform-text-strong); -} - -.platform-theme--light { - color-scheme: light; - --platform-accent: #c7653d; - --platform-body-fill: radial-gradient( - circle at 8% 0%, - rgba(240, 203, 169, 0.24), - transparent 26% - ), - radial-gradient(circle at 92% 8%, rgba(226, 171, 134, 0.2), transparent 22%), - radial-gradient( - circle at 54% 100%, - rgba(204, 117, 76, 0.08), - transparent 30% - ), - linear-gradient(180deg, #fffdf9 0%, #fdf9f5 54%, #f8efe7 100%); - --platform-panel-shadow: 0 22px 60px rgba(112, 57, 30, 0.1), - 0 8px 22px rgba(255, 255, 255, 0.82); - --platform-panel-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.985), - rgba(253, 248, 243, 0.96) - ); - --platform-panel-fill-soft: linear-gradient( - 180deg, - rgba(255, 254, 252, 0.96), - rgba(250, 243, 236, 0.9) - ); - --platform-hero-fill: linear-gradient( - 135deg, - rgba(244, 226, 211, 0.96), - rgba(238, 208, 183, 0.9) - ); - --platform-hero-glow-a: rgba(255, 255, 255, 0.42); - --platform-hero-glow-b: rgba(228, 149, 91, 0.18); - --platform-hero-overlay-strong: linear-gradient( - 135deg, - rgba(238, 208, 183, 0.66), - rgba(204, 117, 76, 0.18) - ); - --platform-hero-overlay-soft: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.34), - rgba(248, 233, 219, 0.22) - ); - --platform-surface-border: rgba(226, 203, 184, 0.88); - --platform-surface-hover-border: rgba(204, 117, 76, 0.42); - --platform-shell-glow-1: rgba(255, 255, 255, 0.34); - --platform-shell-glow-2: rgba(238, 208, 183, 0.22); - --platform-shell-glow-3: rgba(210, 132, 93, 0.12); - --platform-surface-glow-a: rgba(240, 203, 169, 0.18); - --platform-surface-glow-b: rgba(204, 117, 76, 0.1); - --platform-text-strong: #3d1f10; - --platform-text-base: #6f5848; - --platform-text-soft: #988476; - --platform-text-muted: #a38f80; - --platform-brand-logo-title: #4a220f; - --platform-brand-logo-subtitle: #c7653d; - --platform-brand-logo-shadow: #caa48b; - --platform-line-soft: rgba(226, 203, 184, 0.72); - --platform-subpanel-fill: linear-gradient( - 180deg, - rgba(255, 254, 252, 0.94), - rgba(250, 243, 236, 0.9) - ); - --platform-subpanel-border: rgba(225, 204, 187, 0.82); - --platform-warm-border: rgba(204, 117, 76, 0.28); - --platform-warm-bg: rgba(234, 204, 179, 0.32); - --platform-warm-text: #b6623f; - --platform-cool-border: rgba(199, 117, 76, 0.24); - --platform-cool-bg: rgba(238, 208, 183, 0.26); - --platform-cool-text: #b76038; - --platform-neutral-border: rgba(226, 203, 184, 0.44); - --platform-neutral-bg: rgba(255, 253, 250, 0.7); - --platform-neutral-text: #7b6150; - --platform-button-primary-border: rgba(182, 98, 63, 0.32); - --platform-button-primary-solid: #c7653d; - --platform-button-primary-fill: linear-gradient(135deg, #df7f40, #b95d3a); - --platform-button-primary-text: #fffaf5; - --platform-button-secondary-fill: rgba(255, 253, 250, 0.78); - --platform-button-secondary-text: #4b2412; - --platform-button-ghost-fill: rgba(255, 253, 250, 0.56); - --platform-button-ghost-text: #755a49; - --platform-button-danger-border: rgba(185, 75, 58, 0.22); - --platform-button-danger-fill: rgba(255, 237, 229, 0.94); - --platform-button-danger-text: #a6402f; - --platform-unread-dot-fill: #8b5a3d; - --platform-unread-dot-glow: rgba(139, 90, 61, 0.34); - --platform-success-border: rgba(73, 144, 96, 0.24); - --platform-success-bg: rgba(237, 248, 239, 0.92); - --platform-success-text: #2f7b46; - --platform-icon-fill: rgba(255, 253, 250, 0.68); - --platform-icon-border: rgba(226, 203, 184, 0.5); - --platform-icon-text: #7b5c49; - --platform-nav-fill: linear-gradient( - 180deg, - rgba(255, 254, 252, 0.96), - rgba(250, 243, 236, 0.92) - ); - --platform-nav-active-fill: linear-gradient( - 180deg, - rgba(238, 208, 183, 0.48), - rgba(204, 117, 76, 0.16) - ); - --platform-nav-active-border: rgba(204, 117, 76, 0.34); - --platform-nav-active-shadow: 0 10px 24px rgba(182, 98, 63, 0.12); - --platform-modal-fill: linear-gradient( - 180deg, - rgba(255, 254, 252, 0.97), - rgba(250, 243, 236, 0.95) - ); - --platform-modal-border: rgba(255, 255, 255, 0.58); - --platform-desktop-shell-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.99), - rgba(253, 249, 245, 0.985) - ); - --platform-desktop-shell-border: rgba(230, 213, 199, 0.94); - --platform-desktop-shell-inner-border: rgba(233, 218, 205, 0.92); - --platform-desktop-topbar-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.95), - rgba(253, 249, 245, 0.92) - ); - --platform-desktop-panel-fill: linear-gradient( - 180deg, - rgba(255, 254, 252, 0.95), - rgba(250, 243, 236, 0.91) - ); - --platform-desktop-panel-border: rgba(226, 203, 184, 0.88); - --platform-desktop-hover-shadow: 0 16px 30px rgba(112, 57, 30, 0.12); - --platform-overlay-fill: linear-gradient( - 180deg, - rgba(240, 203, 169, 0.14), - rgba(255, 255, 255, 0.58) - ); - --platform-track-border: rgba(226, 203, 184, 0.48); - --platform-track-fill: rgba(255, 253, 250, 0.9); - --platform-page-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.9), - rgba(250, 243, 236, 0.8) - ); - --platform-page-border: rgba(233, 218, 205, 0.88); - --platform-input-fill: rgba(255, 253, 250, 0.94); - --platform-input-fill-focus: rgba(255, 254, 252, 0.96); - --platform-input-highlight: rgba(255, 255, 255, 0.9); - --platform-input-focus-ring: rgba(204, 117, 76, 0.15); - --platform-nav-item-text: #80695a; - --platform-nav-item-text-active: #3d1f10; - --platform-nav-item-hover-fill: rgba(253, 248, 243, 0.94); - --platform-nav-item-icon-fill: rgba(250, 243, 236, 1); - --platform-nav-item-icon-text: #7b5c49; - --platform-nav-item-icon-active-fill: rgba(255, 254, 252, 0.98); - --platform-nav-item-icon-active-text: #c7653d; - --platform-nav-icon-active-shadow: 0 12px 24px rgba(182, 98, 63, 0.16); - --platform-bottom-nav-fill: linear-gradient(180deg, #fffefa, #fff9ef); - --platform-bottom-nav-border: rgba(224, 181, 139, 0.58); - --platform-bottom-nav-divider: rgba(225, 185, 145, 0.52); - --platform-bottom-nav-text: #5b2b19; - --platform-bottom-nav-muted-text: #7a4b37; - --platform-bottom-nav-active-text: #f15a17; - --platform-bottom-nav-primary-fill: linear-gradient(180deg, #ff7a23, #f04d12); - --platform-bottom-nav-primary-shadow: 0 12px 24px rgba(240, 77, 18, 0.28); - --platform-bottom-nav-primary-text: #fffaf2; - --platform-profile-hero-fill: linear-gradient( - 180deg, - rgba(255, 254, 252, 0.96), - rgba(250, 239, 229, 0.9) - ); - --platform-profile-hero-border: rgba(255, 255, 255, 0.56); - --platform-profile-hero-shadow: 0 20px 56px rgba(112, 57, 30, 0.16); - --platform-profile-avatar-fill: linear-gradient( - 135deg, - rgba(223, 127, 64, 0.96), - rgba(181, 91, 56, 0.92) - ); - --platform-profile-avatar-shadow: 0 14px 30px rgba(182, 98, 63, 0.22); - --platform-profile-chip-fill: rgba(255, 253, 250, 0.88); - --platform-profile-chip-hover-fill: rgba(255, 254, 252, 0.96); - --platform-profile-chip-text: #755a49; - --platform-profile-action-fill: linear-gradient(135deg, #df7f40, #b95d3a); - --platform-profile-action-solid: #c7653d; - --platform-profile-action-text: #fffaf5; - --platform-profile-action-shadow: 0 14px 30px rgba(182, 98, 63, 0.22); - --platform-card-overlay-soft: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.08), - rgba(250, 243, 236, 0.82) - ); - --platform-card-overlay-strong: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.16), - rgba(248, 239, 231, 0.92) - ); - --platform-card-overlay-deep: radial-gradient( - circle at top left, - rgba(255, 255, 255, 0.2), - transparent 30% - ), - radial-gradient(circle at right, rgba(228, 149, 91, 0.14), transparent 28%), - linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(248, 239, 231, 0.9)); - --platform-recommend-runtime-fill: var(--platform-panel-fill); - --platform-recommend-runtime-border: rgba(226, 203, 184, 0.42); - --platform-recommend-runtime-shadow: 0 18px 44px rgba(112, 57, 30, 0.12), - inset 0 0 0 1px rgba(255, 255, 255, 0.58); - --platform-recommend-runtime-state-fill: radial-gradient( - circle at 50% 18%, - rgba(204, 117, 76, 0.12), - transparent 34% - ), - linear-gradient( - 180deg, - rgba(255, 255, 255, 0.98), - rgba(250, 243, 236, 0.94) - ); - --platform-recommend-runtime-state-text: var(--platform-text-strong); - --puzzle-runtime-shell-fill: var(--platform-body-fill); - --puzzle-runtime-stage-fill: radial-gradient( - circle at 50% 18%, - rgba(204, 117, 76, 0.13), - transparent 30% - ), - radial-gradient( - circle at 18% 82%, - rgba(240, 203, 169, 0.18), - transparent 28% - ), - linear-gradient(180deg, #fffdf9 0%, #fbf5ed 58%, #f4e5d7 100%); - --puzzle-runtime-grid-line: rgba(112, 57, 30, 0.06); - --puzzle-runtime-text-strong: var(--platform-text-strong); - --puzzle-runtime-text-base: var(--platform-text-base); - --puzzle-runtime-text-soft: var(--platform-text-soft); - --puzzle-runtime-surface-fill: rgba(255, 253, 250, 0.76); - --puzzle-runtime-surface-fill-strong: rgba(255, 253, 250, 0.9); - --puzzle-runtime-surface-border: rgba(226, 203, 184, 0.48); - --puzzle-runtime-board-fill: rgba(255, 253, 250, 0.68); - --puzzle-runtime-board-border: rgba(204, 117, 76, 0.28); - --puzzle-runtime-board-shadow: 0 30px 80px rgba(112, 57, 30, 0.14); - --puzzle-runtime-piece-fill: rgba(255, 253, 250, 0.74); - --puzzle-runtime-piece-border: transparent; - --puzzle-runtime-piece-empty-fill: rgba(238, 208, 183, 0.34); - --puzzle-runtime-piece-empty-text: rgba(111, 88, 72, 0.4); - --puzzle-runtime-piece-selected-fill: linear-gradient( - 135deg, - #df7f40, - #b95d3a - ); - --puzzle-runtime-piece-selected-text: #fffaf5; - --puzzle-runtime-piece-selected-border: transparent; - --puzzle-runtime-next-card-overlay: rgba(74, 34, 15, 0.06); - --puzzle-runtime-control-fill: rgba(255, 253, 250, 0.72); - --puzzle-runtime-control-hover-fill: rgba(204, 117, 76, 0.1); - --puzzle-runtime-primary-fill: var(--platform-button-primary-fill); - --puzzle-runtime-primary-text: var(--platform-button-primary-text); - --puzzle-runtime-primary-shadow: var(--platform-profile-action-shadow); - --puzzle-runtime-accent-text: var(--platform-cool-text); - --puzzle-runtime-cool-text: #6e8d42; - --puzzle-runtime-danger-fill: rgba(255, 237, 229, 0.9); - --puzzle-runtime-danger-text: #a6402f; - --puzzle-runtime-backdrop-fill: rgba(61, 31, 16, 0.34); - --puzzle-runtime-dialog-fill: radial-gradient( - circle at 12% 0%, - rgba(204, 117, 76, 0.18), - transparent 36% - ), - linear-gradient( - 180deg, - rgba(255, 254, 252, 0.98), - rgba(250, 243, 236, 0.95) - ); - --puzzle-runtime-dialog-border: rgba(204, 117, 76, 0.3); - --puzzle-runtime-table-fill: rgba(255, 253, 250, 0.62); - --puzzle-runtime-table-row-fill: rgba(204, 117, 76, 0.12); - --puzzle-runtime-next-card-fill: rgba(255, 253, 250, 0.66); - --puzzle-runtime-next-card-hover-fill: rgba(204, 117, 76, 0.1); -} - -.platform-theme--dark { - color-scheme: dark; - --platform-accent: #5b6cff; - --platform-body-fill: radial-gradient( - circle at top, - rgba(129, 140, 248, 0.2), - transparent 30% - ), - radial-gradient( - circle at top right, - rgba(59, 130, 246, 0.12), - transparent 24% - ), - radial-gradient( - circle at bottom left, - rgba(34, 211, 238, 0.08), - transparent 26% - ), - linear-gradient(180deg, #13151c 0%, #090b11 100%); - --platform-panel-shadow: 0 28px 88px rgba(5, 8, 28, 0.5); - --platform-panel-fill: linear-gradient( - 180deg, - rgba(22, 20, 52, 0.95), - rgba(7, 9, 21, 0.98) - ); - --platform-panel-fill-soft: linear-gradient( - 180deg, - rgba(122, 92, 255, 0.12), - rgba(255, 255, 255, 0.03) - ); - --platform-hero-fill: radial-gradient( - circle at top left, - rgba(129, 140, 248, 0.24), - transparent 34% - ), - radial-gradient(circle at right, rgba(34, 211, 238, 0.12), transparent 32%), - linear-gradient(180deg, rgba(17, 22, 66, 0.95), rgba(8, 10, 24, 0.98)); - --platform-hero-glow-a: rgba(129, 140, 248, 0.18); - --platform-hero-glow-b: rgba(34, 211, 238, 0.1); - --platform-surface-border: rgba(160, 169, 255, 0.12); - --platform-surface-hover-border: rgba(255, 255, 255, 0.16); - --platform-shell-glow-1: rgba(129, 140, 248, 0.2); - --platform-shell-glow-2: rgba(59, 130, 246, 0.12); - --platform-shell-glow-3: rgba(34, 211, 238, 0.08); - --platform-surface-glow-a: rgba(129, 140, 248, 0.18); - --platform-surface-glow-b: rgba(34, 211, 238, 0.1); - --platform-text-strong: #ffffff; - --platform-text-base: rgb(228 228 231); - --platform-text-soft: rgb(161 161 170); - --platform-text-muted: rgb(161 161 170); - --platform-brand-logo-title: #fff7dc; - --platform-brand-logo-subtitle: #9fe7ff; - --platform-brand-logo-shadow: #040814; - --platform-line-soft: rgba(255, 255, 255, 0.1); - --platform-subpanel-fill: rgba(255, 255, 255, 0.05); - --platform-subpanel-border: rgba(255, 255, 255, 0.1); - --platform-warm-border: rgba(167, 139, 250, 0.24); - --platform-warm-bg: rgba(109, 40, 217, 0.18); - --platform-warm-text: rgb(237 233 254); - --platform-cool-border: rgba(103, 232, 249, 0.22); - --platform-cool-bg: rgba(8, 145, 178, 0.14); - --platform-cool-text: rgb(236 254 255); - --platform-neutral-border: rgba(255, 255, 255, 0.08); - --platform-neutral-bg: rgba(255, 255, 255, 0.05); - --platform-neutral-text: rgb(228 228 231); - --platform-button-primary-border: rgba(129, 140, 248, 0.3); - --platform-button-primary-solid: #5b6cff; - --platform-button-primary-fill: linear-gradient(135deg, #5b6cff, #3dd9ff); - --platform-button-primary-text: rgb(238 248 255); - --platform-button-secondary-fill: rgba(255, 255, 255, 0.05); - --platform-button-secondary-text: rgb(244 244 245); - --platform-button-ghost-fill: rgba(255, 255, 255, 0.03); - --platform-button-ghost-text: rgb(212 212 216); - --platform-button-danger-border: rgba(251, 113, 133, 0.2); - --platform-button-danger-fill: rgba(244, 63, 94, 0.1); - --platform-button-danger-text: rgb(255 228 230); - --platform-unread-dot-fill: #d6a27c; - --platform-unread-dot-glow: rgba(214, 162, 124, 0.24); - --platform-success-border: rgba(52, 211, 153, 0.24); - --platform-success-bg: rgba(16, 185, 129, 0.1); - --platform-success-text: rgb(220 252 231); - --platform-icon-fill: rgba(255, 255, 255, 0.05); - --platform-icon-border: rgba(255, 255, 255, 0.1); - --platform-icon-text: rgb(212 212 216); - --platform-nav-fill: linear-gradient( - 180deg, - rgba(109, 40, 217, 0.12), - rgba(255, 255, 255, 0.03) - ); - --platform-nav-active-fill: linear-gradient( - 180deg, - rgba(91, 108, 255, 0.2), - rgba(61, 217, 255, 0.08) - ); - --platform-nav-active-border: rgba(160, 169, 255, 0.24); - --platform-nav-active-shadow: 0 12px 28px rgba(8, 14, 42, 0.4); - --platform-modal-fill: linear-gradient( - 180deg, - rgba(16, 18, 46, 0.98), - rgba(7, 8, 19, 0.98) - ); - --platform-modal-border: rgba(160, 169, 255, 0.12); - --platform-desktop-shell-fill: linear-gradient( - 180deg, - rgba(8, 8, 30, 0.98), - rgba(5, 6, 18, 0.99) - ); - --platform-desktop-shell-border: rgba(160, 169, 255, 0.14); - --platform-desktop-shell-inner-border: rgba(255, 255, 255, 0.03); - --platform-desktop-topbar-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.05), - rgba(255, 255, 255, 0.03) - ); - --platform-desktop-panel-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.05), - rgba(255, 255, 255, 0.02) - ); - --platform-desktop-panel-border: rgba(160, 169, 255, 0.12); - --platform-desktop-hover-shadow: 0 16px 28px rgba(8, 11, 38, 0.2); - --platform-overlay-fill: rgba(5, 8, 28, 0.72); - --platform-track-border: rgba(255, 255, 255, 0.12); - --platform-track-fill: rgba(255, 255, 255, 0.08); - --platform-page-fill: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.04), - rgba(255, 255, 255, 0.02) - ); - --platform-page-border: rgba(255, 255, 255, 0.06); - --platform-input-fill: rgba(255, 255, 255, 0.05); - --platform-input-fill-focus: rgba(255, 255, 255, 0.08); - --platform-input-highlight: rgba(255, 255, 255, 0.06); - --platform-input-focus-ring: rgba(91, 108, 255, 0.22); - --platform-nav-item-text: rgb(161 161 170); - --platform-nav-item-text-active: rgb(238 248 255); - --platform-nav-item-hover-fill: rgba(91, 108, 255, 0.08); - --platform-nav-item-icon-fill: rgba(255, 255, 255, 0.06); - --platform-nav-item-icon-text: rgb(161 161 170); - --platform-nav-item-icon-active-fill: linear-gradient( - 180deg, - rgba(91, 108, 255, 0.24), - rgba(61, 217, 255, 0.12) - ); - --platform-nav-item-icon-active-text: rgb(238 248 255); - --platform-nav-icon-active-shadow: 0 12px 24px rgba(8, 14, 42, 0.42); - --platform-bottom-nav-fill: linear-gradient(180deg, #fffefa, #fff9ef); - --platform-bottom-nav-border: rgba(224, 181, 139, 0.64); - --platform-bottom-nav-divider: rgba(225, 185, 145, 0.56); - --platform-bottom-nav-text: #5b2b19; - --platform-bottom-nav-muted-text: #7a4b37; - --platform-bottom-nav-active-text: #f15a17; - --platform-bottom-nav-primary-fill: linear-gradient(180deg, #ff7a23, #f04d12); - --platform-bottom-nav-primary-shadow: 0 12px 24px rgba(240, 77, 18, 0.3); - --platform-bottom-nav-primary-text: #fffaf2; - --platform-profile-hero-fill: linear-gradient( - 180deg, - rgba(20, 24, 58, 0.96), - rgba(8, 10, 24, 0.98) - ); - --platform-profile-hero-border: rgba(160, 169, 255, 0.14); - --platform-profile-hero-shadow: 0 24px 70px rgba(5, 8, 28, 0.42); - --platform-profile-avatar-fill: linear-gradient( - 135deg, - rgba(91, 108, 255, 0.94), - rgba(61, 217, 255, 0.78) - ); - --platform-profile-avatar-shadow: 0 14px 32px rgba(61, 217, 255, 0.16); - --platform-profile-chip-fill: rgba(255, 255, 255, 0.08); - --platform-profile-chip-hover-fill: rgba(255, 255, 255, 0.14); - --platform-profile-chip-text: rgb(228 228 231); - --platform-profile-action-fill: linear-gradient(135deg, #5b6cff, #3dd9ff); - --platform-profile-action-solid: #5b6cff; - --platform-profile-action-text: rgb(238 248 255); - --platform-profile-action-shadow: 0 14px 32px rgba(91, 108, 255, 0.22); - --platform-card-overlay-soft: linear-gradient( - 180deg, - rgba(8, 10, 14, 0.06), - rgba(8, 10, 14, 0.74) - ); - --platform-card-overlay-strong: linear-gradient( - 180deg, - rgba(8, 10, 14, 0.14), - rgba(8, 10, 14, 0.9) - ); - --platform-card-overlay-deep: radial-gradient( - circle at top left, - rgba(255, 255, 255, 0.14), - transparent 30% - ), - radial-gradient(circle at right, rgba(255, 205, 178, 0.14), transparent 28%), - linear-gradient(180deg, rgba(8, 10, 14, 0.22), rgba(8, 10, 14, 0.9)); - --platform-recommend-runtime-fill: #030303; - --platform-recommend-runtime-border: rgba(255, 255, 255, 0.08); - --platform-recommend-runtime-shadow: inset 0 0 0 1px - rgba(255, 255, 255, 0.025), - 0 18px 44px rgba(0, 0, 0, 0.18); - --platform-recommend-runtime-state-fill: #030303; - --platform-recommend-runtime-state-text: rgba(255, 255, 255, 0.92); - --puzzle-runtime-shell-fill: #020617; - --puzzle-runtime-stage-fill: radial-gradient( - circle at 50% 20%, - rgba(251, 191, 36, 0.18), - transparent 28% - ), - radial-gradient( - circle at 20% 80%, - rgba(249, 115, 22, 0.16), - transparent 26% - ), - linear-gradient(180deg, #2d160e, #020617); - --puzzle-runtime-grid-line: rgba(255, 255, 255, 0.04); - --puzzle-runtime-text-strong: #ffffff; - --puzzle-runtime-text-base: rgba(255, 255, 255, 0.82); - --puzzle-runtime-text-soft: rgba(255, 255, 255, 0.58); - --puzzle-runtime-surface-fill: rgba(0, 0, 0, 0.34); - --puzzle-runtime-surface-fill-strong: rgba(0, 0, 0, 0.5); - --puzzle-runtime-surface-border: rgba(255, 255, 255, 0.12); - --puzzle-runtime-board-fill: rgba(255, 255, 255, 0.08); - --puzzle-runtime-board-border: rgba(255, 255, 255, 0.16); - --puzzle-runtime-board-shadow: 0 30px 80px rgba(0, 0, 0, 0.35); - --puzzle-runtime-piece-fill: rgba(255, 255, 255, 0.12); - --puzzle-runtime-piece-border: transparent; - --puzzle-runtime-piece-empty-fill: rgba(0, 0, 0, 0.18); - --puzzle-runtime-piece-empty-text: rgba(255, 255, 255, 0.2); - --puzzle-runtime-piece-selected-fill: rgba(251, 191, 36, 0.84); - --puzzle-runtime-piece-selected-text: #020617; - --puzzle-runtime-piece-selected-border: transparent; - --puzzle-runtime-next-card-overlay: rgba(0, 0, 0, 0.1); - --puzzle-runtime-control-fill: rgba(0, 0, 0, 0.36); - --puzzle-runtime-control-hover-fill: rgba(255, 255, 255, 0.1); - --puzzle-runtime-primary-fill: #fde68a; - --puzzle-runtime-primary-text: #020617; - --puzzle-runtime-primary-shadow: 0 14px 36px rgba(251, 191, 36, 0.26); - --puzzle-runtime-accent-text: #fde68a; - --puzzle-runtime-cool-text: #cffafe; - --puzzle-runtime-danger-fill: rgba(239, 68, 68, 0.2); - --puzzle-runtime-danger-text: #fee2e2; - --puzzle-runtime-backdrop-fill: rgba(2, 6, 23, 0.68); - --puzzle-runtime-dialog-fill: radial-gradient( - circle at 14% 0%, - rgba(251, 191, 36, 0.16), - transparent 36% - ), - linear-gradient(180deg, rgba(29, 17, 12, 0.96), rgba(2, 6, 23, 0.96)); - --puzzle-runtime-dialog-border: rgba(251, 191, 36, 0.22); - --puzzle-runtime-table-fill: rgba(255, 255, 255, 0.06); - --puzzle-runtime-table-row-fill: rgba(251, 191, 36, 0.14); - --puzzle-runtime-next-card-fill: rgba(255, 255, 255, 0.06); - --puzzle-runtime-next-card-hover-fill: rgba(251, 191, 36, 0.1); -} .platform-brand-logo { display: inline-flex; diff --git a/src/index.test.ts b/src/index.test.ts index 9584033c9..412a98df0 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -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');