diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 00000000..08b8b9f2 --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,70 @@ +# Product + + + +## Platform + +web + +## Users + +- 主要用户(依据仓库功能推断,待产品负责人确认):需要比较 DNF 装备搭配、打造方案与技能输出的玩家。 +- 核心使用场景:在角色、装备和技能配置之间反复调整,快速比较伤害、冷却与装备效果结果。 + +## Product Purpose + +DCalc 是免费的 DNF 装备搭配与伤害计算工具。它把装备选择、技能设置、打造设置和结果计算集中在同一工作流中,减少玩家手工换算和跨页面比对的成本。 + +成功意味着用户可以完整配置一个角色方案,理解关键计算结果,并可靠地比较不同搭配,而不是只得到缺少上下文的单一总分。 + +## Positioning + +DCalc 将 DNF 的装备、技能、打造和效果数据组织为可交互的角色计算工作台,并保留接近游戏客户端的视觉与数据语义。该定位来自当前仓库实现;对外传播口径仍待产品负责人确认。 + +## Operating Context + +- 用户围绕角色进行装备、技能与打造配置,并在结果区查看技能伤害、技能等级、技能冷却、装备特效及效果点数。 +- `frontend` 是既有业务行为基线;`frontend-ui` 是独立、渐进式的 Vue 3 UI 重构目标。 +- `/components` 是组件库的可执行目录页,承担交互状态、双主题、窄屏与 Portal 回归验证。 +- 产品包含大量 DNF 原始像素素材、品质颜色和紧凑数据表,桌面密集操作是当前明确的主场景;移动 Web 需要可访问和无页面级横向溢出,但不是把所有组件放大为通用移动应用风格。 + +## Capabilities and Constraints + +- 已实现:武器、套装、贴膜、通宝、称号宠物、秘宝选择。 +- 已实现:技能等级、觉醒绑定和技能次数设置;技能次数自动计算与 Buff 数值设置仍未完成。 +- 已实现:增幅、强化、锻造、徽章、附魔、调适、时装、皮肤、光环、宠物装备、辟邪玉、勋章及杂项设置。 +- 已实现:技能伤害、技能等级、技能冷却、装备特效、装备效果/点数和角色基础信息计算。 +- `frontend-ui` 使用 Vue 3、TypeScript、Vue Router、Pinia、Vite、Reka UI、Tailwind CSS 4 与语义 CSS Variables。 +- 通用 UI 组件不得承载 API、Store、路由或 DNF 业务规则;这些逻辑留在页面、领域组件与 composable 中。 +- 迁移必须保留既有业务行为,并将 `design` 目录中的结构、状态、动效与真实素材作为视觉事实来源。 + +## Brand Commitments + +- 产品名称为 DCalc / DNF计算器。 +- 当前界面保留 DNF 客户端式的暗色、黄铜、蓝黑面板与像素素材语言。 +- 品质颜色是数据语义,不得当作普通装饰色随意替换。 +- 第二套 `midnight` 主题用于验证组件没有耦合到经典 DCalc 皮肤,而不是另起一套组件实现。 + +## Evidence on Hand + +- 产品能力与未完成项:`README.md`。 +- 新旧前端边界、技术栈、主题模型与组件目录:`frontend-ui/README.md`。 +- 迁移和验收规则:`frontend-ui/docs/ui-migration-standard.md`。 +- 视觉实现基线与素材:`design/src-web`。 +- 当前组件实现与语义 Token:`frontend-ui/src/components/ui`、`frontend-ui/src/style`。 +- 仓库内没有可用于宣传的真实用户数据、性能基准、合作方背书或商业承诺;后续界面不得虚构这些内容。 + +## Product Principles + +1. 计算可信度优先:配置、边界和结果必须可追踪、可复核。 +2. 保留业务完整性:UI 迁移不能丢失既有操作、快捷方式或数据语义。 +3. 密集但可扫描:为高频比较保留紧凑信息密度,同时建立清晰层级和稳定状态反馈。 +4. 组件与业务解耦:交互基础能力进入组件库,DNF 规则留在领域层。 +5. 真实素材优先:存在可靠原始素材时使用原素材,不用近似图标或通用模板替代。 + +## Accessibility & Inclusion + +- 所有交互组件需要可读名称、可见焦点和键盘路径。 +- 普通文本与背景对比度至少达到 WCAG AA;不可用状态仍承载信息时,不使用不可辨认的低对比文本。 +- 触屏窄视口上的独立操作目标至少为 24 × 24 CSS px;标签包裹的选择控件可以由标签提供等效点击面积。 +- 动效必须尊重 `prefers-reduced-motion`,Portal 内容必须保持可见、可关闭并正确恢复焦点。 diff --git a/design.md b/design.md new file mode 100644 index 00000000..88686454 --- /dev/null +++ b/design.md @@ -0,0 +1,217 @@ +--- +name: DCalc frontend-ui +description: DNF 客户端质感的紧凑型双主题 Vue 组件系统 +colors: + canvas-deep: "#050505" + panel-charcoal: "#131515" + row-graphite: "#1f1f1e" + title-steel: "#415272" + control-navy: "#1a2b4e" + border-brass: "#504029" + text-primary: "#f6f8f4" + text-secondary: "#d6d3c6" + text-muted: "#a7a396" + selected-gold: "#daa41d" + focus-cyan: "#4ba7d3" + danger-readable: "#ef8883" + midnight-canvas: "#080b12" + midnight-panel: "#111827" + midnight-accent: "#315c97" +typography: + body: + fontFamily: "dcalc, SimSun, serif" + fontSize: "12px" + fontWeight: 400 + lineHeight: 1.4 + control: + fontFamily: "dcalc, SimSun, serif" + fontSize: "13px" + fontWeight: 400 + lineHeight: 1 + catalog-title: + fontFamily: "Segoe UI, Microsoft YaHei, sans-serif" + fontSize: "clamp(24px, 2vw, 30px)" + fontWeight: 650 + lineHeight: 1.1 +rounded: + sm: "2px" + md: "2px" + lg: "2px" +spacing: + compact: "4px" + sm: "7px" + md: "10px" + lg: "14px" +components: + control-default: + backgroundColor: "{colors.control-navy}" + textColor: "{colors.text-primary}" + rounded: "{rounded.sm}" + height: "30px" + padding: "0 14px" + control-compact: + backgroundColor: "{colors.control-navy}" + textColor: "{colors.text-secondary}" + rounded: "{rounded.sm}" + height: "24px" + padding: "0 10px" + data-row: + backgroundColor: "{colors.row-graphite}" + textColor: "{colors.text-secondary}" + rounded: "{rounded.sm}" + height: "38px" +--- + +# Design System: DCalc frontend-ui + +## Overview + +**Creative North Star: "游戏内的精密计算台"** + +当前系统不是通用 SaaS 皮肤,而是一套面向 DNF 密集配置与结果比对的操作界面。视觉语言来自游戏客户端:深黑工作区、蓝黑控件、黄铜边缘、像素素材和高密度数据。装饰必须服务于状态识别,不能遮盖数值、品质和选择关系。 + +`dcalc` 与 `midnight` 共用同一组件和 `--ui-*` 语义契约。经典主题保留黄铜与金色强调,Midnight 主题转为冷蓝层级;组件禁止按主题名分支。 + +**Key Characteristics:** + +- 紧凑、像素对齐、数据优先。 +- 近方形轮廓与一像素分层边缘。 +- 品质色、选择色、焦点色和状态色职责分离。 +- 原始位图素材与 CSS 构造图形并存,但不使用近似 emoji 或随意替代图标。 +- 组件目录是可交互回归面,不是静态样例墙。 + +## Colors + +经典 DCalc 使用暖金和黄铜在近黑表面上建立层级,Midnight 使用冷蓝映射相同语义。 + +### Primary + +- **控制台深蓝:**用于主控件静止态与可操作区域,保持沉稳,不扩散成大面积装饰。 +- **选中金:**只表达已选内容和关键数值,不兼任键盘焦点。 +- **焦点青:**只表达 focus-visible、输入聚焦和可访问性定位。 + +### Secondary + +- **黄铜边缘:**用于经典主题中的边框、分隔和像素层次。 +- **午夜蓝:**用于第二主题的激活面、浮层和控件状态,保持与经典主题相同的信息结构。 + +### Tertiary + +- **可读危险色:**用于深色表面上的失败与危险文本;破坏性按钮表面继续使用独立的 destructive Token。 + +### Neutral + +- **深黑画布:**页面最底层背景。 +- **炭黑面板:**承载组件组、表格和对话框内容。 +- **主文本 / 次文本 / 弱文本:**形成三级阅读层级;弱文本仍需达到普通文本对比度要求。 + +### Named Rules + +**The Semantic Boundary Rule.** 组件只消费 `--ui-*` 语义 Token;`--dcalc-*` 只保留物理色和品质数据语义。 + +**The Rarity Is Data Rule.** 品质颜色代表物品数据,不得用于普通按钮、装饰线或营销强调。 + +## Typography + +**Display Font:** Segoe UI(Microsoft YaHei 回退,仅用于组件目录标题) +**Body Font:** dcalc(SimSun 回退) +**Label/Mono Font:** Consolas(仅用于编号、索引和测量信息) + +**Character:** 业务界面以 DCalc 像素字体保持游戏内质感;目录页标题使用清晰的系统无衬线字体承担文档层级。等宽字体只服务于技术索引与数值,不作为“科技感”装饰。 + +### Hierarchy + +- **Catalog title:**中高字重、流式 24–30px,只用于目录页主标题。 +- **Panel heading:**由 46px 标题栏承载,文本层级高于内容但不追求超大字号。 +- **Control:**13px/1,适用于默认高度控件和密集输入。 +- **Body:**12px/1.4,适用于说明、数据标签和表格内容。 +- **Micro label:**原则上不低于 10px;低于该值只允许用于已有像素图形内部且同时提供可访问名称的装饰性字形。 + +### Named Rules + +**The Readable Density Rule.** 紧凑来自间距和布局,不以牺牲正文可读性换取密度。 + +## Layout + +页面采用流式网格承载组件组合,像素素材、1px 边框、控件高度和纹理对齐保留物理尺寸。默认控件高度分为 24px、30px 和 44px;页面 gutter、列宽和面板间距使用 `clamp()` 与 `minmax(0, 1fr)` 响应变化。 + +桌面允许多列高密度工作区;900px 以下将主要双列组合收为单列;520px 以下优先保证独立操作目标和可读顺序。宽表格与状态矩阵使用组件内部横向滚动,页面本身不得出现横向溢出。 + +应用根到路由页的 Flex/Grid 高度链必须保留 `min-height: 0`,每个页面只有一个主要纵向滚动所有者。Portal 内容不能进入会裁剪它的局部容器。 + +### Calculation Workspace + +角色计算页遵循“配置 → 结果”的连续任务路径。901px 及以上使用两列工作区:左列承载装备、技能或打造配置,右列结果占满剩余空间;角色背景只作为氛围层,不得用空白结构列把输入和结果分隔到视口两端。全局计算开关与导入操作放在顶部导航工具区,避免为少量操作保留整条底栏。 + +900px 及以下改为单一纵向滚动流:配置在前、结果在后,配置面板按路由使用有界宽度并居中。结果概览与技能表依次排列且都保留独立内部滚动;页面宽度不得超过视口。伤害表使用容器宽度而非浏览器宽度决定信息密度:容器达到 620px 时直接展示技能、CD、次数、秒伤、总伤害和占比,窄容器保留技能、次数和占比三项核心列。 + +## Elevation & Depth + +系统以色调分层、内描边和一像素边缘为主,阴影只用于浮层、选中反馈和必要的空间分离。普通面板不叠加“边框加大面积柔光”的通用卡片效果。 + +### Shadow Vocabulary + +- **Control inset:**一像素内高光,表达游戏控件的凹凸边缘。 +- **Popup lift:**带向下偏移的柔和阴影,仅用于 Select、Popover、Menu 与 Tooltip 等浮层。 +- **Selected glow:**低范围辉光,只跟随选中或焦点状态,不作为静态装饰。 + +### Named Rules + +**The State Earns Depth Rule.** 深度必须对应浮层、焦点、悬停或选择状态;静止内容默认依靠色调和边缘分层。 + +## Shapes + +组件轮廓以 2px 近方角为默认,配合一像素边框、内描边和少量 `clip-path` 斜切形成游戏客户端的机械感。圆形仅用于单选点、滑块拇指和评分胶囊等具有明确交互语义的部件;普通卡片与面板不使用大圆角或药丸轮廓。 + +## Components + +### Buttons + +- **Shape:** 近方角、30px 默认高度,紧凑与大型尺寸分别为 24px 和 44px。 +- **Primary:** 蓝黑渐变表面、黄铜边缘和高对比文本;变体由 CVA 管理。 +- **Hover / Focus:** 悬停提升边缘与表面亮度;focus-visible 使用语义焦点色;按下只产生 1px 位移。 +- **Disabled:** 同时移除可操作暗示并保留可辨识标签,不使用不可读文字伪装禁用。 + +### Cards / Containers + +- **Corner Style:** 2px 近方角或像素斜切。 +- **Background:** 画布、面板、行三层深色表面。 +- **Shadow Strategy:** 默认平面分层,浮层和状态反馈才使用阴影。 +- **Border:** 1px 语义边缘;避免与宽柔光重复表达同一层级。 +- **Internal Padding:** 以 4、7、10、14px 为主要密度节奏,具体像素素材可保留源尺寸。 + +### Inputs / Fields + +- **Style:** 30px 默认高度、深色内凹表面、1px 边框与紧凑内边距。 +- **Focus:** 边缘切换到 focus Token,并增加一像素可见外沿。 +- **Error / Disabled:** 错误和不可用分别使用独立语义前景;不可用信息仍需可读。 + +### Navigation + +导航使用与内容一致的像素字体和表面层级。当前项使用 selected 语义,键盘位置使用 ring 语义;两者不可合并。窄屏允许重新编排,但不改变路由与业务层级。 + +### Data and Selection Components + +表格、树、级联、商店行和物品槽是系统的签名组件。它们保留品质色、像素素材、紧凑行高与明确的 selected/disabled 状态。含有次级操作的可选行必须把“选择”和“查看”实现为两个同级可聚焦操作,禁止在 `option` 内嵌套按钮。 + +### Portal Components + +Select、Autocomplete、Tooltip、DropdownMenu、Popover 与 Dialog 通过 Reka UI 保留键盘和焦点语义。普通浮层使用受控层级 80,Tooltip 使用更高但有界的层级;必须验证四边碰撞、Escape 关闭和焦点恢复。 + +## Do's and Don'ts + +### Do: + +- **Do** 使用 Primitive → Semantic 两层 Token,并用同一组件代码验证 DCalc 与 Midnight。 +- **Do** 保留真实 PNG、字体、光标、品质色和像素对齐尺寸。 +- **Do** 在组件目录展示默认、悬停、按下、焦点、禁用、选中、打开和 reduced-motion 状态。 +- **Do** 为图形按钮、评分项和复合行操作提供独立的可读名称与焦点位置。 +- **Do** 把连续几何变化交给 CSS Variable 或 transform,把有限变体交给 CVA。 + +### Don't: + +- **Don't** 在通用组件中请求接口、读取业务 Store、绑定路由或加入单页面兼容逻辑。 +- **Don't** 按主题名分支组件样式,也不要在组件中直接使用主题专属装饰色。 +- **Don't** 用 emoji、近似图标、大圆角卡片或通用 SaaS 模板替代 DNF 视觉事实。 +- **Don't** 通过缩小正文、隐藏焦点或使用低对比禁用色来换取紧凑感。 +- **Don't** 用构建成功代替浏览器验证;Portal、滚动、键盘、主题和窄屏都必须实际操作。 diff --git a/design/index-web.html b/design/index-web.html index 260ea4b0..a19aa2fe 100644 --- a/design/index-web.html +++ b/design/index-web.html @@ -221,9 +221,11 @@ const groupPadding = parseFloat(tabsStyle.paddingLeft) + parseFloat(tabsStyle.paddingRight); const itemPadding = parseFloat(buttonStyle.paddingLeft) + parseFloat(buttonStyle.paddingRight); const contentWidth = Math.ceil(widestLabel + itemPadding); - const availableWidth = Math.max(0, tabs.clientWidth - groupPadding); + const parentWidth = tabs.parentElement?.clientWidth ?? tabs.clientWidth; + const availableWidth = Math.max(0, parentWidth - groupPadding); const itemWidth = Math.min(contentWidth, availableWidth / labels.length); tabs.style.setProperty('--tab-item-width', `${itemWidth}px`); + tabs.style.setProperty('--tab-group-width', `${itemWidth * labels.length + groupPadding}px`); }; const positionMarker = (tab, animate = true) => { if (!marker || !tab) return; @@ -251,10 +253,12 @@ }); }); if ('ResizeObserver' in window) { - new ResizeObserver(() => { + const resizeObserver = new ResizeObserver(() => { equalizeItems(); positionMarker(tabs.querySelector('.game-tab.selected'), false); - }).observe(tabs); + }); + resizeObserver.observe(tabs); + if (tabs.parentElement) resizeObserver.observe(tabs.parentElement); } }); diff --git a/design/src-web/tabs/component.js b/design/src-web/tabs/component.js index c832738b..f68d5afc 100644 --- a/design/src-web/tabs/component.js +++ b/design/src-web/tabs/component.js @@ -1,4 +1,4 @@ (() => { window.componentRegistry ||= {}; - window.componentRegistry["src-web/tabs/index.html"] = {"showcase":"
\n
Tabs / Pagination
CSS 126 hover base + CSS 667 selected overlay · CSS 127/128/180
\n
\n
\n
\n
Tabs · Marker = Arrow · first tab selected
\n
\n
\n
\n
Tabs · Marker = Line
\n
\n
\n
\n
\n
◀1 / 15▶搜索出 142 个
\n
ITEM WIDTH = EQUALIZED MAX CONTENTDEFAULT / HOVER / CLICK CAPTURED
\n
\n
\n
"}; + window.componentRegistry["src-web/tabs/index.html"] = {"showcase":"
\n
Tabs / Pagination
CSS 126 hover base + CSS 667 selected overlay · CSS 127/128/180
\n
\n
\n
\n
Tabs · Compact 32/24 · Marker = Arrow
\n
\n
\n
\n
Tabs · Marker = Line
\n
\n
\n
\n
\n
◀1 / 15▶搜索出 142 个
\n
GROUP = CONTENT WIDTH · ITEMS = EQUALIZED MAX CONTENTLIST 32 · ITEM 24 · TEXT 14
\n
\n
\n
"}; })(); diff --git a/design/src-web/tabs/index.html b/design/src-web/tabs/index.html index 6d87e0dd..e7cd2f91 100644 --- a/design/src-web/tabs/index.html +++ b/design/src-web/tabs/index.html @@ -15,7 +15,7 @@
-
Tabs · Marker = Arrow · first tab selected
+
Tabs · Compact 32/24 · Marker = Arrow
@@ -25,7 +25,7 @@
◀1 / 15▶搜索出 142 个
-
ITEM WIDTH = EQUALIZED MAX CONTENTDEFAULT / HOVER / CLICK CAPTURED
+
GROUP = CONTENT WIDTH · ITEMS = EQUALIZED MAX CONTENTLIST 32 · ITEM 24 · TEXT 14
diff --git a/design/src-web/tabs/style/index.css b/design/src-web/tabs/style/index.css index d7d7f717..09ac5e75 100644 --- a/design/src-web/tabs/style/index.css +++ b/design/src-web/tabs/style/index.css @@ -8,12 +8,13 @@ .tabs-showcase { display:grid;grid-template-columns:1fr 1fr;gap:18px; } .game-tabs { position:relative; - width:100%; - height:40px; + width:var(--tab-group-width,max-content); + max-width:100%; + height:32px; display:flex; justify-content:flex-start; align-items:flex-start; - padding:5px 7px; + padding:4px 6px; overflow:visible; border:0; border-radius:0; @@ -24,9 +25,9 @@ content:""; position:absolute; z-index:3; - left:7px; - right:7px; - top:35px; + left:6px; + right:6px; + top:28px; height:1px; background:#584840; pointer-events:none; @@ -54,12 +55,12 @@ align-items:center; justify-content:center; z-index:auto; - height:30px; - padding:0 12px; + height:24px; + padding:0 10px; border:0; background:#080808; color:#9c815a; - font:16px/1 "DCalc",SimSun,sans-serif; + font:14px/1 "DCalc",SimSun,sans-serif; text-shadow:1px 1px 0 #000,0 0 2px #000; white-space:nowrap; cursor:pointer; @@ -67,10 +68,10 @@ .game-tab:not(:last-of-type)::after { content:""; position:absolute; - top:5px; + top:4px; right:0; width:1px; - height:19px; + height:15px; background:linear-gradient(180deg, rgba(52,47,42,.153) 0%, rgba(52,47,42,.376) 5.56%, @@ -97,7 +98,7 @@ .game-tab:active { transform:none; } .game-tab:active .tab-label, .game-tab.is-pressed .tab-label { transform:translateY(1px); } -.game-tabs > .tab-marker { position:absolute;z-index:4;left:0;bottom:0;width:39px;height:5px;opacity:0;transform:translateX(-50%);pointer-events:none; } +.game-tabs > .tab-marker { position:absolute;z-index:4;left:0;bottom:0;width:33px;height:4px;opacity:0;transform:translateX(-50%);pointer-events:none; } .game-tabs > .tab-marker::before { content:""; position:absolute; @@ -120,8 +121,8 @@ position:absolute; left:50%; top:1px; - width:9px; - height:4px; + width:7px; + height:3px; background:linear-gradient(180deg,#9d7a35 0%,#a7823f 46%,#d0a56a 100%); clip-path:polygon(0 0,100% 0,50% 100%); transform:translateX(-50%); diff --git a/frontend-ui/design-qa.md b/frontend-ui/design-qa.md index 13383845..ac9df83b 100644 --- a/frontend-ui/design-qa.md +++ b/frontend-ui/design-qa.md @@ -8,6 +8,16 @@ - Density normalization: devicePixelRatio 1; no resampling required - State: default catalog with Button state matrix; interactive Dialog state captured separately at `catalog-audit-dialog-animated.png` +## Calculator workbench QA (2026-08-19) + +- Design reference: `/Users/xigua/.codex/visualizations/2026/08/19/01a01882-9a2a-7d63-ade8-34a8104c8a6d/calculator-workbench-concept.html`. +- Equipment evidence: `frontend-ui-implemented-equipment-1440.png`; the 1440 px workspace keeps the equipment dictionary, character overview and damage table visible without document-level horizontal overflow. +- Skill evidence: `frontend-ui-implemented-skill-detail-1440.png`; the 778 px configuration rail resolves to a 556 px skill-tree panel and a 210 px resident detail panel, while the 642 px result rail remains independently readable. The standard tree no longer creates a horizontal scrollbar at 1440 px. +- Forge evidence: `frontend-ui-implemented-forge-1440.png`; the existing forge editors remain the source of truth and the new read-only summary rail does not mutate calculation data. Multi-control Sundry rows span the editor width and no longer overflow horizontally. +- Special-job evidence: `frontend-ui-implemented-creator-skill-1440.png` and `frontend-ui-implemented-dark-knight-skill-1440.png`. Creator keeps its original coordinate-based skill-tree structure and extra awakening; Dark Knight keeps its coordinate-based tree plus six shortcuts and six five-stage combo columns. The skill picker is captured at `frontend-ui-implemented-dark-knight-picker-1440.png` and remains inside the 210 px context rail. +- Responsive evidence: document overflow measured 0 px at 1440, 1280 and 390 widths. At 1280 the standard skill tree uses an intentional internal horizontal scroll surface (528 px content in a 471 px viewport); at 390 the context panel is hidden, both main rails measure 371 px, and document width remains 390 px. +- Compact collapsibles (2026-08-19): equipment catalog groups and equipment detail sections now use the shared Reka-based DataSection at a measured 24 px header height. Equipment groups collapse independently while retaining the Forge-style state label. Skill-detail sections use the same compact anatomy with the 13 px indicator on the right and no expanded/collapsed copy; `基本信息` was collapsed independently without hiding `技能描述` or `技能属性`. Browser evidence is captured at `frontend-ui-compact-collapsibles-1440.png`. + ## Full-view comparison The implementation keeps the source board's full-width dark surface, 46 px panel headings, brass dividers, DCalc typography, square corners, compact 30 px controls, and dense state matrices. The catalog intentionally adds a component index, theme controls, and implementation-specific copy; these are catalog navigation rather than component anatomy changes. @@ -16,7 +26,12 @@ The implementation keeps the source board's full-width dark surface, 46 px panel - Button: three five-column state rows now match the source's Rest, Hover, Pressed, Disabled, and tone-family organization. Panel height is 281.5 px versus the 292 px source module target; the difference is the catalog's smaller bottom spacing and is visually non-material. - NumberField: 30 px field, 23 px spinner, visible up/down arrows, and two 14 px `+10`/`-10` fast-step buttons match the source anatomy. -- Tabs: 40 px list, 30 px triggers, glow layers, divider line, animated 39 × 5 px arrow marker, and line-marker variant are present. Marker movement follows Reka selection state and respects reduced motion. +- Tabs: the current compact-density implementation uses a 32 px list, 24 px triggers, 14 px text, + glow layers, divider line, and an animated line marker without a downward triangle by default. + The arrow marker remains available only as an explicit compatibility variant. Each list sizes + itself from its longest label and gives every trigger the same width; marker movement still follows + Reka selection state and respects reduced motion. The cited full-page image predates this compact-density + refresh. - Dialog: the titlebar uses the exact 45 raster frames from `windowheadereffect.img`, advances every 66 ms while focused, and hides the effect in the unfocused/reduced-motion state. - TreeSelect: the Reka tree now opens the selected path, uses the source's flush 2 px row rhythm, eight rarity colors, pixel branch glyph, selected border, and distinct hover/active/focus-visible @@ -39,6 +54,12 @@ The implementation keeps the source board's full-width dark surface, 46 px panel - NumberField fast step: 1 → 11. - Tabs: line marker moved to `来源`; selected state became active. +- Tabs compact refresh (2026-08-19): the variable-label catalog group measured 4 × 68 px inside a + 284 px list instead of filling its 1155 px parent; the line-marker group measured 3 × 44 px inside + 144 px. At 390 px both groups retained equal item widths, 32/24 px heights and zero document-level + horizontal overflow. The character navigation measured 3 × 68 px inside a 216 px list. DCalc and + Midnight themes retained the same geometry. The default marker now uses the line treatment, so + business tabs no longer render a downward triangle unless a caller explicitly requests `arrow`. - Checkbox: unchecked → checked. - Switch: checked → unchecked. - Rating: 1 → 3. diff --git a/frontend-ui/src/api/info/type.ts b/frontend-ui/src/api/info/type.ts index 0659c95b..ca0df3c3 100644 --- a/frontend-ui/src/api/info/type.ts +++ b/frontend-ui/src/api/info/type.ts @@ -108,6 +108,8 @@ export interface ISkill { name: string desc: string }[] + /** 后端下发的技能树坐标:[列, 行]。缔造者与黑暗武士使用扩展网格。 */ + cellPosition: number[] uuid: string upType: 'damage' | 'heal' } diff --git a/frontend-ui/src/components/ui/data-section/UiDataRow.vue b/frontend-ui/src/components/ui/data-section/UiDataRow.vue index 9922bd38..20eb50ed 100644 --- a/frontend-ui/src/components/ui/data-section/UiDataRow.vue +++ b/frontend-ui/src/components/ui/data-section/UiDataRow.vue @@ -47,6 +47,6 @@ const props = withDefaults( } .ui-data-row.is-disabled { - color: var(--ui-control-disabled-foreground); + color: var(--ui-unavailable-foreground); } diff --git a/frontend-ui/src/components/ui/data-section/UiDataSection.vue b/frontend-ui/src/components/ui/data-section/UiDataSection.vue index b1b0c632..165facc0 100644 --- a/frontend-ui/src/components/ui/data-section/UiDataSection.vue +++ b/frontend-ui/src/components/ui/data-section/UiDataSection.vue @@ -12,8 +12,17 @@ const props = withDefaults( disabled?: boolean expandedLabel?: string collapsedLabel?: string + compact?: boolean + showState?: boolean + indicatorPosition?: 'start' | 'end' }>(), - { expandedLabel: '已展开', collapsedLabel: '未展开' }, + { + expandedLabel: '已展开', + collapsedLabel: '未展开', + compact: false, + showState: true, + indicatorPosition: 'start', + }, ) const open = defineModel('open', { default: true }) @@ -23,12 +32,21 @@ const open = defineModel('open', { default: true }) {{ title }} - {{ open ? expandedLabel : collapsedLabel }} + + {{ open ? expandedLabel : collapsedLabel }} + @@ -99,4 +117,12 @@ const open = defineModel('open', { default: true }) .ui-data-section__content { padding: var(--ui-disclosure-content-padding-block) var(--ui-field-padding-inline); } + +.ui-data-section--compact .ui-data-section__trigger { + min-height: var(--ui-control-height-compact); +} + +.ui-data-section--indicator-end .ui-data-section__trigger > img { + order: 3; +} diff --git a/frontend-ui/src/components/ui/number-field/UiNumberField.vue b/frontend-ui/src/components/ui/number-field/UiNumberField.vue index 4f25dceb..061797b7 100644 --- a/frontend-ui/src/components/ui/number-field/UiNumberField.vue +++ b/frontend-ui/src/components/ui/number-field/UiNumberField.vue @@ -207,4 +207,11 @@ function fastChange(direction: 1 | -1) { .ui-number-field__fast-step button + button span { top: 2px; } + +@media (max-width: 520px) { + .ui-number-field { + height: 50px; + grid-template-columns: minmax(0, 1fr) 25px; + } +} diff --git a/frontend-ui/src/components/ui/pagination/UiPagination.vue b/frontend-ui/src/components/ui/pagination/UiPagination.vue index db163afd..75efde11 100644 --- a/frontend-ui/src/components/ui/pagination/UiPagination.vue +++ b/frontend-ui/src/components/ui/pagination/UiPagination.vue @@ -66,7 +66,7 @@ const page = defineModel('page', { default: 1 }) border: 1px solid var(--ui-pagination-border); outline: none; background: var(--ui-pagination-background); - color: var(--ui-control-disabled-foreground); + color: var(--ui-muted-foreground); cursor: pointer; } @@ -82,6 +82,7 @@ const page = defineModel('page', { default: 1 }) } .ui-pagination__button:disabled { + color: var(--ui-control-disabled-foreground); cursor: not-allowed; opacity: 0.45; } diff --git a/frontend-ui/src/components/ui/progress/UiProgress.vue b/frontend-ui/src/components/ui/progress/UiProgress.vue index 6fb7935e..89e5a2a1 100644 --- a/frontend-ui/src/components/ui/progress/UiProgress.vue +++ b/frontend-ui/src/components/ui/progress/UiProgress.vue @@ -31,7 +31,10 @@ const visibleMarkers = computed(() => {