Files
TankIC/3DTank/docs/UI_Rebuild_Plan.md

478 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TankiOffline UI 重建计划书(全站 UI)
> 目标:把原客户端(`module_82347` 大厅/车库/商店/军团/结算 UI + `module_21300` 战斗 HUD)
> 还原到 Kotlin/JS 新工程,最终覆盖**全站所有界面**,并与真实状态 / 协议数据打通。
> 本文件用于对齐范围、技术栈、里程碑与验收口径。
## 0. 已确认决策(2026-10-25)
| 议题 | 决策 |
|---|---|
| 技术栈 | **Kotlin/JS + kotlin-react + Emotion**(与现工程同栈) |
| 还原精度 | **组件级还原**;背景/摄像机是动态的,不做像素 diff,由人工验收,尽可能贴近 |
| 数据源 | **本地 mock 服务器**(协议级 mock,稳定产出用户/物品/大厅数据) |
| 原版账号 | 有(`WYT2WYT`),仅用于线上获取必要信息(DOM/CSS/资源/文案),**不做逐屏截图对拍** |
| 3D | **现在接 WebGL**:先做 3D 资源加载器与车库预览,再逐步接 `92980` 渲染管线 |
| 首批屏幕 | **大厅骨架 + 车库** |
| 资源部署 | 开发用本地镜像;生产走 CDN / 反向代理 |
| 混跑策略 | 允许长期保留部分原 JS UI,按屏幕逐步替换为 Kotlin |
---
## 1. 调研结论(先纠正三个认知)
### 1.1 原前端不是手写 React,而是 **Kotlin/JS + React + Emotion**
证据:
| 证据 | 说明 |
|---|---|
| `module_glossary`:`78931` = React 18.3.1、`45555` = React-DOM 18.3.1 | 运行时确实是 React |
| `94090` = “React 封装的内部 UI 构建工具 RBuilder” | Kotlin 侧的 React DSL |
| `82347` 的代码形态:`Sq(d7n, "MainScreenComponentStyle", $q, h$)`、`this.aux([], K2n)`、`Eq("containerPanel", 1, …)` | 这是 Kotlin/JS 编译后的 styled-component 构造,不是手写 JS |
| 类名如 `MainScreenComponentStyle-containerPanel`、`RoundBigButtonComponentStyle-outerCircle` | Emotion 生成的组件样式名 |
| `82347` 依赖 `10032 / 18334 / 61860 / 87072 / 92980 / 61378 / 85196 …` | UI 直接吃状态、服务、渲染、战斗系统 |
**结论**:另起一个 React/TS 工程不是“更原版”,反而会丢掉 Kotlin 数据模型。
推荐在现有 Kotlin/JS 工程里加 **kotlin-react + Emotion**,逐屏移植;纯 React/TS 只作为备选。
### 1.2 原 UI 的体量与耦合
- `82347`:5.50 MB,约 21.3 万行,覆盖大厅 / 车库 / 商店 / 军团 / 好友 / 新闻 / 设置 / 结算 / 弹窗。
- `21300`:战斗内 HUD(血条、准星、道具、计分板、聊天…)。
- 大量组件直接依赖:
- 状态:`RootState` 的 89 个切片(已迁移 89/89);
- 服务:`18334` 的 509 处动作派发、物品目录(已迁移 `ItemCatalog`);
- 渲染:`92980` WebGL(车库 3D 预览、战斗场景);
- 战斗:`87072`(HUD 数据)。
- 因此 UI 必须分层:**布局/样式可先独立还原,数据和 3D 后接**。
### 1.3 资源已经齐了
- 镜像:`3DTank/web/play/static`(1239 文件 / 71.4 MB,含 images / fonts / translations / sound / videos / textdata)。
- 已复制到:`TankiOffline/app/src/jsMain/resources/play/static`,开发服务器直接以 `/play/static/...` 提供。
- 官方翻译:`/play/static/translations/zh-CN.61d6ab60.json`(运行时加载,已接)。
- 可直接引用的资源 id → 路径:`3DTank/src/resources.json` 的 `resourceMap`。
- 可选:`3DTank/Start-LocalAndRelay.ps1` 启动镜像 + 中继(抓包/真实服务器)。
---
## 2. 技术方案
### 2.1 推荐方案 A:Kotlin/JS + kotlin-react + Emotion(与现工程同栈)
```
TankiOffline/
ui/ # 新:React 组件(Kotlin)
src/jsMain/kotlin/Ui/
Theme.kt # 颜色/字体/间距 token(从原 Emotion 样式抽出)
Components/ # Button / Panel / Tabs / Dialog / ItemCard ...
Screens/ # Lobby / Garage / Shop / Battles / Quests ...
Layout/ # MainScreen / Header / Menu / Chat / Footer
app/ # 入口:Entrance → 连接 → Lobby
state/ # 已有 RootState
net/ # 已有协议
```
依赖:
```kotlin
implementation(npm("react", "18.3.1"))
implementation(npm("react-dom", "18.3.1"))
implementation(npm("@emotion/react", "11.x"))
// Kotlin 侧可用 kotlin-react / kotlin-emotion 包装,或直接 external 声明
```
数据流:
```
SessionRouter (Store) ──订阅──> UiStore (Adapter) ──props──> React 组件
ItemCatalog ───────────────────┘
Session 状态(连接/帧/日志)─────┘
```
优点:复用现有状态类;一套 Gradle 构建;后续可直接接 3D/战斗。
缺点:Kotlin React 组件写法与原始编译产物不完全一致,需要逐屏翻译。
### 2.2 备选方案 B:独立 React/TS 工程
```
webui/ (Vite + React + TS + Emotion)
↕ WebSocket / postMessage
TankiOffline (Kotlin/JS 客户端)
```
优点:前端生态成熟、UI 开发快、便于像素级还原。
缺点:状态模型要跨语言复制/序列化;构建两套;长期维护成本高。
### 2.3 备选方案 C:混跑原 JS 模块
直接把 `module_82347` 作为 external 挂到 `window.__modules__`,用现有 Kotlin 状态通过 bridge 驱动。
优点:**保真度最高**、最快看到完整原版 UI。
缺点:不是“重建”,依赖原 bundle;长期难以维护。
可作为 **过渡验收**(视觉基准)而不是最终形态。
### 2.4 保真度分层
| 级别 | 内容 | 验收 |
|---|---|---|
| L0 | 结构骨架(当前) | 能导航、无报错 |
| L1 | 官方资源 / 字体 / 布局比例 | 截图肉眼接近 |
| L2 | 像素级静态还原 | 与原版截图逐屏 diff |
| L3 | 真实数据 + 交互 | 状态变化驱动 UI |
| L4 | 3D 车库 / 战斗场景 | WebGL 管线接入 |
---
## 3. 里程碑
### M0 基础设施(进行中)
- [x] 资源镜像接入(`/play/static`)。
- [x] 登录页 1:1(EntranceView + entrance.css)。
- [ ] `ui` 模块启用 kotlin-react + Emotion,建立 `Theme.kt`。
- [ ] 协议级 mock 服务器(用户属性 / 物品目录 / 大厅列表)。
- [ ] `ScreenRegistry`:屏幕 ID ↔ 组件(对齐原 `ScreenId`)。
- [ ] `UiStore`:订阅 `SessionRouter.Store`,节流重绘。
- [ ] 3D 资源加载器(`.3ds` / 贴图 / 场景描述)+ 车库预览最小场景。
- [ ] 截图 / DOM 基准工具(人工验收辅助,不做像素门禁)。
### M1 大厅骨架(2 个迭代)
- 顶栏:等级/昵称/GS/水晶/金币/VIP/设置/声音。
- 左侧菜单:9 个官方图标 + 文案 + 选中态。
- 底部:大号“开始战斗”按钮、聊天面板、新闻条。
- 公告/活动/每日任务浮层。
- 验收:与官方大厅截图逐项对齐(无 3D 场景时用静态背景占位)。
### M2 车库(3 个迭代)
- 分类页签(12 类,来自 `ItemCategory` 枚举)。
- 物品网格:名称/稀有度/数量/VIP/价格;数据源 `ItemCatalog`。
- 物品详情:属性、升级、装备、皮肤。
- 3D 预览:接 M0 的 3D 加载器,先渲染底盘/炮塔/贴图与旋转,再补光照/摄像机。
### M2.5 3D / 渲染(并行)
- 3DS / 贴图 / 资源清单加载。
- 车库场景:平台、背景、坦克组装、光照、摄像机环绕。
- 与 `92980` 渲染管线的接口对齐;最终替换为原引擎实现。
### M3 商店 / 幸运礼盒(2 个迭代)
- 商店分类、卡片、折扣、支付方式、礼盒开启动画占位。
- 数据:`Shop` 切片 + `ItemCatalog`。
### M4 战斗选择 / 任务 / 军团 / 好友 / 新闻(3 个迭代)
- 战斗模式卡片、战斗列表、创建战场表单。
- 任务:主线/每日/每周/星级挑战/合约。
- 军团/好友:列表、搜索、请求、成员管理。
- 新闻:列表 + 详情。
### M5 设置 / 弹窗体系(2 个迭代)
- 设置 8 个页签(游戏/画面/声音/操控/账户/战斗/安全/支持)。
- 通用 Dialog / Toast / 确认框 / 购买弹窗 / 升级弹窗 / 排名弹窗。
### M6 结算与 HUD(依赖 S5)
- 战斗结算、奖励、排名变化。
- 战斗 HUD(`21300`):血条、道具、准星、计分板、聊天。
---
## 4. 每屏的数据契约(先定,避免返工)
| 屏幕 | 主要数据 | 现有来源 | 缺口 |
|---|---|---|---|
| Entrance | 语言/资源/连接状态 | `Translations` / `LobbyClient` | 真实登录 |
| Lobby Home | User / News / Notifications / Quests 摘要 | RootState 已迁移 | 公告、活动配置 |
| Garage | ItemCatalog / Garage 切片 / GaragePreview | 已有 | 3D 预览、装备操作 |
| Shop | Shop 切片 / ItemCatalog | 已有 | 支付流程 |
| Battles | BattlesSelect / Maps / Matchmaking | 已有 | 创建/匹配 |
| Quests | Quests / Challenges / ContractState | 已有 | 领奖链路 |
| Clans | Clan / ClanStorage / ClanChat | 已有 | 社交操作 |
| Friends | Friends 切片 | 已有 | 搜索/请求 |
| News | News 切片 | 已有 | 详情页 |
| Settings | 8 个设置切片 | 已有 | 持久化 port |
| HUD | BattleStatistics / BattleUsers / Supplies | 部分 | S5 战斗系统 |
---
## 5. 验收方式
1. **视觉基准**:原版客户端逐屏截图(需要可登录环境);新 UI 同分辨率截图,人工/像素 diff。
2. **DOM 基准**:同一数据下对比关键元素的尺寸、间距、颜色 token。
3. **交互基准**:点击路径一致(菜单、返回、弹窗层级)。
4. **数据基准**:同一抓包回放后,界面显示与状态断言一致(可自动化)。
---
## 6. 风险与待决事项
| 风险 | 影响 | 对策 |
|---|---|---|
| 没有可登录账号,拿不到原版大厅截图/DOM | 无法做像素级对比 | 用测试账号 / 本地 mock 服务器 / 仅按源码还原 |
| 3D 车库与战斗场景 | 大厅“灵魂”缺失 | 先用静态背景 + 官方素材,S4 后替换 |
| `82347` 21 万行,逐屏翻译量大 | 周期长 | 按 M1–M5 切片,先布局后逻辑 |
| 原样式是 Emotion 动态类名 | 难以直接复用 CSS | 抽 token + 按截图复写;或运行时抓取 CSS |
| 资源 71 MB 进包 | 首屏体积 | 开发用镜像;生产走 CDN/代理 |
---
## 7. 当前进度(本文件创建时)
- 登录页:1:1 完成(官方背景、logo、圆形登录按钮、健康忠告、年龄提示、ClientInfo)。
- 大厅:L0/L1 骨架(官方图标、真实翻译、物品目录、连接状态)。
- 资源:本地镜像已接入。
- 已确认:技术栈 Kotlin/React、组件级还原、本地 mock、3D 现在做、首批大厅+车库、允许混跑。
## 9. 原版 DOM 实测映射(2026-10-25,登录 WYT2WYT 后)
视口 1296×1006,根字号 12.96px(`.ksc-37` 的 `max(min(1.48148vh,1vw),3px)`)。
所有尺寸换算:1em ≈ 12.96px。
### 9.1 大厅首页
| 原 DOM 类 | 位置 / 尺寸 | 含义 |
|---|---|---|
| `MainScreenComponentStyle-containerPanel` | 0,0 1296×78 | 顶栏(6em,底边 1px 白 25%) |
| `UserInfoContainerStyle-blockLeftPanel` | 0,0 408×78 | 等级图标 / 组队图标 / 昵称 / XP |
| `UserInfoContainerStyle-rankIconContainerClickable` | 155,0 78×78 | 等级图标(`static/images/<rank>.<hash>.webp`) |
| `UserInfoContainerStyle-containerProgressMainScreen` | 246,15 162×49 | 昵称 + XP 条 + 数值 |
| `UserScoreComponentStyle-blockRightPanel` | 1017,0 279×78 | 两行货币(红宝石 / 水晶)+ 全屏 + 退出 |
| `MainScreenComponentStyle-containerForMenuGradient` | 0,0 648×1006 | 左侧 50%,黑→透明 0.5 |
| `MainScreenComponentStyle-playButtonContainer` | 57,130 284×139 | “开始战斗”,背景 `videoplay.79570900.gif` |
| `MainScreenComponentStyle-blockMainMenu` | 57,283 324×645 | 菜单容器 |
| `PrimaryMenuItemComponentStyle-menuItemContainer` | 65,283 350×58 | 菜单项:礼盒 / 任务 / 车库 / 商店 / 设置 |
| `PrimaryMenuItemComponentStyle-itemCommonLi` | 73,290 45×45 | 图标(mask + `#bfd5ff`) |
| `PrimaryMenuItemComponentStyle-itemName` | 133,293 | 2em 大写文字 |
| `FooterComponentStyle-footer` | 0,928 648×78 | 新闻 / 聊天 / 好友 / 军团(65×65) |
| `MainScreenComponentStyle-gradientBlockRight` | 936,0 360×1006 | 右侧渐变(27.75em,0.75,旋转 180°) |
| `BattlePassLobbyComponentStyle-menuBattlePass` | 972,110 292×130 | 星级挑战面板(22.5em×10em,right 2.5em) |
| `AnnouncementHomeScreenComponentStyle-mainContainer` | 971,778 293×196 | 公告卡(22.5em 宽,right/bottom 2.5em) |
| `ClientInfoComponentStyle-main` | 0,989 1296×17 | UID / 版本 / 电池 |
### 9.2 车库
| 原 DOM 类 | 位置 / 尺寸 | 含义 |
|---|---|---|
| `BreadcrumbsComponentStyle-headerContainer` | 0,0 1296×78 | 车库顶栏(返回 / 标题 / 货币 / 退出) |
| `BreadcrumbsComponentStyle-backButton` | 0,0 78×78 | 返回 |
| `BreadcrumbsComponentStyle-rootTitle` | 585,2 126×73 | 当前屏幕标题 |
| `GarageCommonStyle-garageContainer` | 0,78 1296×928 | 车库主体 |
| `GarageCommonStyle-positionContent` | 0,130 1296×876 | 内容区 |
| `GarageMainScreenStyle-blockParameters` | 78,130 1140×685 | 中央 3D 预览 + 左右参数 |
| `TankParametersStyle-leftParamsContainer` | 78,130 293×209 | 左上:GS + 参数 |
| `MountedItemsStyle-container` | 920,130 298×492 | 右上:无人机 / 手榴弹 / 炮塔 / 底盘 |
| `MountedItemsStyle-resistanceContainer` | 78,740 52×65 ×4 | 左下:防御模块 |
| `SuppliesComponentStyle-commonBlockSupplies` | 78,862 1140×125 | 底部道具栏 |
| `TankPreviewComponentStyle-visible GarageComponentStyle-tankPreview` | 0,0 1296×1006 | 3D 坦克画布 |
### 9.3 对应关系(JS → DOM)
- `82347` 里 `Sq(Class, "XxxComponentStyle", ...)` 注册的样式类,就是 DOM 上的 `XxxComponentStyle-*`。
- `Eq("containerPanel", 1, ...)` 返回的类名拼接为 `MainScreenComponentStyle-containerPanel`。
- 样式值直接写在 `jq(Class).xxx = function(){ return this.<slot>.dk(this, Eq("...")) }` 的样式对象里;
运行时可从 `document.styleSheets` 按类名前缀抓取(已用于本表)。
- 组件层级:`MainScreen` → `Header` / `PrimaryMenu` / `Footer` / `BattlePass` / `Announcement`;
车库:`GarageCommon` → `TankParameters` / `MountedItems` / `Supplies` / `TankPreview`。
### 9.4 当前实现状态
- 大厅首页已按上表重建(6em 顶栏、21.875em Play、27em 菜单、5em 底栏、22.5em 右栏)。
- 车库正在按 9.2 重建:左侧参数 / 中央 3D / 右侧已装配件 / 底部道具栏;
物品选择列表作为抽屉保留,后续替换为原版的分类轮盘。
- [x] Kotlin/React 工具链接通(kotlin-react / kotlin-react-dom / kotlin-emotion,React 19.1)。
- [x] 物品目录 fixture:`SessionCheck` 从抓包导出 `resources/fixtures/catalog.json`(1399 物品)。
- [x] 车库第一版 React 界面:分类页签(12 类)、搜索、物品网格(120 张卡片)、右侧详情位。
- [ ] 车库按 9.2 的原版布局重建 + 3D 预览加载器。
- [ ] 协议级 mock 服务器(用户 / 大厅列表 / 任务)。
- [ ] 大厅其余屏幕迁移到 React。
---
## 10. 原版 UI 还原方法(已验证,2026-10-26 设置页)
> 这一节记录的是实际跑通的方法。后续所有页面都按这个流程做,不再先写通用样式再“看起来像”。
### 10.1 原则
1. **不自己设计 UI**:布局、尺寸、颜色、类名、DOM 层级都以混淆版源码/原版 DOM 为唯一来源。
2. **先找定义,再做映射**:找不到原版定义就标注“未展开”,不靠截图猜。
3. **能用原版资源就不画近似**:图标、勾、knob、箭头、背景图优先用 `play/static/images` 里的原版资源。
4. **文案走 i18n**:使用 `Translations.T("lobby....")`,不硬编码中文。
5. **颜色走配色表**:使用 `Palette.kt`(来源 `C9i`),不手写近似色。
6. **每页完成后删除旧近似样式和死代码**,避免两套样式并存。
### 10.2 定位组件定义
1. 在 `module_82347.js` 搜索:
```js
Sq(Class, "XxxComponentStyle", ...)
```
找到样式类。
2. 看类构造器:
```js
this.xxx_1 = this.aux([], styleFn)
```
找到字段与对应 `styleFn`。
3. 看访问器:
```js
jq(Class).accessor = function () { ... Eq("fieldName", ...) ... }
```
确认字段名和生成的语义类名。
4. 找渲染函数/组件:`jq(Component).tap` / `render`,确认 DOM 层级和子组件。
5. 把类名、字段、行号记录到对应的 `*StyleTable.md`。
示例(设置页):
| 组件 | 类 / 函数 | 位置 |
|---|---|---|
| `SettingsComponentStyle` | `kNi` | `module_82347.js:89525` |
| `SettingsMenuComponentStyle` | `EMi` | `90329` |
| `SettingsButtonsComponentStyle` | `UPi` | `89326` |
| `DropDownStyle` | `J7t` | `34073` |
| `ContextMenuStyle` | `u7t` | `33638` |
| `CheckBoxComponentStyle` | `QBi` | `92742` |
| `CheckBoxStyle` | `Z0t` | `33272` |
| `InputRangeComponentStyle` | `EUi` | `92980` |
### 10.3 提取真实 CSS
1. 在无头页面加载 `module_37036`(Kotlin 运行时)+ `module_82347`。
2. 实现最小 webpack require:`i(id)`、`i.p`、`i.d`、`i.r`、`i.n`、`i.o`、`i.hmd`。
3. patch `module_37036` 的 `ld`,捕获 `Sq(Class, Name)` 注册表。
4. 实例化目标样式类,遍历 `kuv_1.a1_1`,执行每个 `entry.duv_1[0](recorder)`。
5. recorder 记录 `rpf` / `gp8` / `jp8` / `lp7` / `qp7`,输出 base/nested CSS。
6. 动态方法直接调用,例如:
- `d1mg(recorder, important)`:下拉选项
- `m1md` / `n1md`:复选框 / Toggle Switch
- `hUi.tap`:滑杆进度渐变
7. 提取结果写入 `*StyleTable.md`,作为实现依据。
### 10.4 还原真实 DOM 和类名
1. 按原版 DOM 写结构,不替换成自创的 `button` / `div`。
2. 保留语义类名:
- `InputRangeComponentStyle-blockSoundVolume`
- `InputRangeComponentStyle-soundVolume`
- `InputRangeComponentStyle-range`
- `SettingsComponentStyle-slider`
- `CheckBoxComponentStyle-oneSetting`
- `CheckBoxStyle-checkbox`
- `DropDownStyle` / `ContextMenuStyle` 相关类
3. 保留原版 `ksc-*` 标记用于核对,例如 `ksc-56`(百分比)、`ksc-57`(滑杆)、`ksc-62`(Switch)、`ksc-63`(标签容器)。
4. 原版 DOM 示例:
滑杆:
```html
<div class="InputRangeComponentStyle-blockSoundVolume SettingsComponentStyle-slider">
<p>主音量<span class="ksc-56">(80%)</span></p>
<div class="InputRangeComponentStyle-soundVolume">
<input class="ksc-57 InputRangeComponentStyle-range" type="range" ...>
</div>
</div>
```
Switch:
```html
<div class="ksc-46">
<div class="CheckBoxComponentStyle-oneSetting">
<div class="CheckBoxStyle-checkbox">
<label><input class="ksc-47" type="checkbox"><span class="ksc-62 -displayFlex"></span></label>
</div>
<div class="ksc-63 -flexStartAlignCenter"><label>显示帧数和延迟</label></div>
</div>
</div>
```
### 10.5 提取原版资源
1. 资源模块通常长这样:
```js
67116:(t,n,i)=>{ t.exports = i.p + "static/images/correct.afad1b22.svg" }
```
2. 通过 IIFE 参数表与调用参数对齐,找到变量对应的 module id:
- `Vn` → module `67116` → `correct.afad1b22.svg`(Switch 开)
- `Kn` → module `13357` → `incorrectCheck.1918884a.svg`(Switch 关)
- `$n` → module `74120` → `checkMark.fb44274a.svg`(方形复选框勾)
3. 资源放到 `/play/static/images/`,直接引用,不再用 CSS 画近似图形。
### 10.6 配色 / i18n / 尺寸
- 颜色:`C9i` / `E9i()` 主题对象 → `Palette.kt` → `ColorPalette.md`。
- 文案:`Translations.T("lobby....")`,语言切换走 `Translations.LoadLocale`。
- 尺寸:直接使用 style 函数里的 em 值;`1em ≈ 12.96px`(1296×1006)。
- 细节:百分比位置、颜色、间距、圆角都按原 DOM / style 函数,不凭感觉调。
### 10.7 验收
1. 无头探针打开页面,记录 DOM、rect、computed style、截图。
2. 对比项:
- 语义类名 / `ksc-*` 标记是否存在
- 尺寸、间距、圆角、边框
- 颜色(背景、文字、强调色、进度、Switch)
- 交互(下拉展开/滚动、滑杆拖动、Switch 切换)
3. 与 `*StyleTable.md` 对齐;不一致就回到 10.2 重新提取。
4. 完成后删除旧近似样式、未使用函数、死代码。
### 10.8 设置页验证记录
设置页已按本方法完成:
- HEAD 复用主页顶栏,只替换 VIP 为退出键。
- 菜单顺序:游戏显示 / 战斗 / 画面 / 音效 / 操控 / 账户 / 安全。
- 颜色全部走 `Palette`。
- 文案全部走 i18n。
- 滑杆:原版 DOM + `InputRangeComponentStyle` + 进度渐变。
- Switch:原版 `CheckBoxComponentStyle-oneSetting` + `ksc-62` + 原版圆形 knob。
- 下拉:`DropDownStyle` + `ContextMenuStyle` + 固定选项高度 + 按需滚动。
详细映射见:
- `3DTank/docs/SettingsStyleTable.md` 第 6/7/8 节
- `3DTank/docs/ColorPalette.md`
---
## 11. 桩代码标记规范
> 目的:不允许“看起来像原版”的自创组件混进最终 UI。找不到原版定义时,只能留明确标记的桩。
### 11.1 强制标记
任何非原版实现、占位、模拟数据、临时组件必须同时具备:
1. 源码注释:
```kotlin
// STUB: 原版 <组件名/样式类> 尚未展开
```
2. DOM 标记:
```kotlin
Ui("stub.<screen>.<part>")
```
3. 文档记录:在 `UiTranslationStatus.md` 的“待翻译/未展开”中列出。
### 11.2 禁止事项
- 禁止用自创的 `button` / `div` / 卡片替代原版组件而不标记。
- 禁止用近似色、近似尺寸冒充原版值。
- 禁止把 mock 数据写成真实数据;mock 必须标 `STUB` 或 `offline demo`。
- 禁止在未找到原版定义时直接写“完成”。
### 11.3 删除规则
- 原版定义补齐后,删除对应 `STUB` 注释、`data-ui="stub.*"` 和文档条目。
- 删除旧近似样式时,同步删除未使用的 style 函数/常量,避免两套实现并存。
### 11.4 HEAD 验证记录
HEAD 已按第 10 节方法重写:
- 样式类:`MainScreenComponentStyle-containerPanel` / `UserInfoContainerStyle-blockLeftPanel` / `UserScoreComponentStyle-blockRightPanel`
- Logo:`iconTankiOnline.2a9c236f.svg`(192×60px)
- 等级块:`5.9375em × 6em`,仅右边框
- XP 容器:`.875em` 高,`margin-top:.55em`,`opacity:.8`
- XP 条:`12.5em × .25em`,绿色 50% 背景
- 货币列:`9.2em` 宽,`margin-right:2em`
- VIP 块(`x6n.tap`):`6em × 6em`,`box-shadow:1px 0 0 0 rgba(255,255,255,.25)`,未开通 VIP 时金红径向渐变,`:hover` 5% 白;图标 `iconButtonPremium.bc17a592.svg`,`2em × 2em`
- 组队块(`L4.tap`):`5.9375em × 6em`,右边框,`:hover` 5% 白;2 人组 `commonUserIconsBlockGroup` `3.25em × 1.9em` + 两个 `q4` 图标 `1.5em`(`groupUser.67c9ec61.svg`,空位色 `rgba(191,212,255,.5)`)
- 全屏块(`uKt`):`6em × 6em`,左右各 1px 白 25% 边框,`:hover` 5% 白 + 图标变白;图标 `fullscreen.0b4547f5.svg`,`1.5em`
- 退出块(`BreadcrumbsComponentStyle-logout`):`6em × 6em`,无边框,`:hover` 5% 白 + 图标变白;图标 `iconBack.62869951.svg`,`1.5em`
- 货币列(`nti`):`display:flex; flex-direction:column; position:relative`;红宝石/水晶行按 `l1qd`/`n1qd`,`min-width:7.9375em`、`height:3em`、`padding-right:.75em`、红宝石行 `border-bottom`;不再套用 `coinsContainer` 的 `9.2em` / `margin-right:2em` / 右边框
- 实测 rect:左块 `408.58×77.75`、VIP `77.75×77.75`、组队/等级 `77.94×77.75`、用户 `162×48.59`、货币列 `102.86×77.75`(x=1035.64)、右块从 `1035.64` 开始。