178 lines
7.8 KiB
Markdown
178 lines
7.8 KiB
Markdown
# UI Coding Standard
|
||
|
||
> **会话交接 / 改动总览**:见 `docs/AGENT_UI_CHANGELOG.md`(文件映射、9-slice 架构、已知坑、未收尾项)。
|
||
|
||
## Goal
|
||
|
||
This project should treat `public/UI` and `public/Icons` as the single source of truth for fantasy UI chrome and item icon semantics. New UI code should match asset naming first, then map product meaning onto those assets through a small semantic layer in code.
|
||
|
||
## Asset Folders
|
||
|
||
- `public/UI`: UI chrome, tabs, buttons, frames, arrows, HUD, equipment-slot markers, map controls.
|
||
- `public/Icons`: item and ability icons. Most files are semantic item glyphs with numeric prefixes.
|
||
|
||
## Naming Rules Observed
|
||
|
||
### `public/UI`
|
||
|
||
- `1_*`: base interaction assets and compact function glyphs.
|
||
- `11_*`: directional arrows and small action markers.
|
||
- `_s`: small size.
|
||
- `_xs`: extra-small size.
|
||
- `_pick` / `_picked`: selected, active, or pressed state.
|
||
- `_d`: disabled, dimmed, or inactive state.
|
||
- `_on` / `_off`: explicit toggle state.
|
||
- `Hud_icon_*`: HUD/tab-friendly icons.
|
||
- `Icon_Eq_*`: equipment-slot semantics.
|
||
- `Map_*`: map-only controls or chrome.
|
||
- `Frame_*`, `Popup_*`, `Dialogue_*`, `Inventory_*`, `Quest_*`, `Skill_*`: domain-specific container assets.
|
||
|
||
### `public/Icons`
|
||
|
||
- Pattern is usually `NN_name.png`.
|
||
- The numeric prefix is an atlas/catalog index, not UI meaning.
|
||
- Semantic matching should be based on the name segment, not the number.
|
||
- Prefer item-like meanings such as `sword`, `magic`, `potion`, `relic`, `treasure`, `crystal`, `shield`.
|
||
|
||
## Required Coding Pattern
|
||
|
||
- Do not hardcode random asset filenames directly in feature components.
|
||
- Put semantic mappings in `src/uiAssets.ts`.
|
||
- Render pixel UI assets through `src/components/PixelIcon.tsx`.
|
||
- Pick icons by UI meaning, not by whichever file "looks close enough" in one screen.
|
||
- If a state exists in art, wire both active and inactive assets instead of tinting one image in CSS.
|
||
- Major UI chrome should use authored textures from `public/UI`, not only plain Tailwind borders.
|
||
|
||
## Layout Rules For Icon UI
|
||
|
||
- Navigation/tab icons should be visually larger than body text.
|
||
- Preferred structure is `icon above + label below`, centered, instead of inline icon-text rows for compact nav.
|
||
- Tab labels should stay readable on mobile with `clamp()`-based sizing.
|
||
- Touch targets should remain comfortable on phones; do not ship tiny icon buttons.
|
||
|
||
## Responsive Rules For UI Images
|
||
|
||
- Do not stretch framed pixel UI with `background-size: 100% 100%`.
|
||
- Framed buttons, panels, tabs, modal shells, and title plates must use 9-slice scaling.
|
||
- Corners stay fixed, edges only stretch or repeat on one axis, and the center fills independently.
|
||
- Use `clamp()` for padding, min-height, icon size, and text size when the same control appears on desktop and mobile.
|
||
- Avoid fixed pixel-only widths for primary panels and buttons unless the element is purely decorative.
|
||
- For modal or framed panels, allow internal scrolling on small screens instead of overflowing the viewport.
|
||
- Decorative UI should never block content readability; add a dark tint layer when needed.
|
||
- Avoid non-integer `scale()` hover effects on pixel-framed controls; prefer `translateY`, brightness, or shadow shifts.
|
||
|
||
## Pixel Rendering Rules
|
||
|
||
- Pixel icons must use point-sampled rendering such as `image-rendering: pixelated`.
|
||
- Pixel-framed UI should keep consistent slice thickness on both axes; never uniformly squash the whole frame texture.
|
||
- Any large information plate inside a popup must also be skinned with UI art, not a plain dark rectangle.
|
||
|
||
## Semantic Mapping Used Now
|
||
|
||
### Tabs
|
||
|
||
- `character` -> `1_armor` / `1_armor_d`
|
||
- `adventure` -> `1_weapon` / `1_weapon_d`
|
||
- `inventory` -> `Hud_icon_inventory` / `Hud_icon_inventory_d`
|
||
|
||
### World Select
|
||
|
||
- `wuxia` -> `38_sword`
|
||
- `xianxia` -> `72_magic`
|
||
|
||
### Shared Chrome
|
||
|
||
- option arrow -> `11_right_arrow`
|
||
- map header -> `Map_icon_action`
|
||
- close action -> `1_exit_s`
|
||
|
||
### UI Shell / Panels
|
||
|
||
- app shell -> `Background_fill`
|
||
- world selection buttons -> `1_orange_button`, `1_violet_button`(极扁条形图,切片须符合尺寸约束,见「已知问题」)
|
||
- character selection card -> `pick_hero_frame` + `pick_hero_bg`
|
||
- tab shell -> `Shop_tab`, `Shop_tab_picked`
|
||
- section panel -> `Frame_bg_big_2`
|
||
- story panel -> `Dialogue_frame`
|
||
- inventory panel -> `Inventory_bg`
|
||
- stats panel -> `Stats_bar`
|
||
- choice button -> `Options_bar`
|
||
- modal shell -> `Popup_window`
|
||
- map info plate -> `Dialogue_frame`
|
||
- map node cells -> `Map_frame`
|
||
- map diagram canvas -> `Frame_bg_big_2`
|
||
- scene title -> `Title_frame_m`
|
||
- app / root chrome -> `Background_fill` + light tint(避免整块纯深蓝底)
|
||
|
||
### Equipment / Inventory
|
||
|
||
- `武器` -> `Icon_Eq_Weapon`
|
||
- `护甲` -> `Icon_Eq_Chest`
|
||
- `饰品` -> `Icon_Eq_ring`
|
||
- `消耗品` -> `12_potion`
|
||
- `稀有品` -> `68_relic`
|
||
- `专属品` -> `47_treasure`
|
||
- `材料` -> `45_crystal`
|
||
|
||
## State Handling
|
||
|
||
- Selected tab/button: prefer `_pick` or `_picked`.
|
||
- Disabled/unavailable action: prefer `_d`.
|
||
- Toggle widgets: prefer `_on` / `_off`.
|
||
- Size changes must use authored sizes like `_s` / `_xs`; avoid CSS-downscaling a large ornamental asset when a small authored version exists.
|
||
|
||
## Assets To Avoid By Default
|
||
|
||
These names are ambiguous and should not be used as standards until art is confirmed:
|
||
|
||
- `* copy`
|
||
- `Avatar_ref`
|
||
- `map_ref`
|
||
- `special_button`
|
||
- `Skill_bar_long_nopt`
|
||
- `Skill_bar_noColbs`
|
||
- `Options_button_save_p`
|
||
- `11_Q_d`
|
||
|
||
## Review Checklist For New UI
|
||
|
||
- Does the component use `src/uiAssets.ts` instead of ad-hoc file paths?
|
||
- Does the chosen icon name match the feature meaning?
|
||
- If the control has selected/disabled states, are those states backed by asset variants?
|
||
- Is the asset from `UI` for chrome and `Icons` for item semantics?
|
||
- Is pixel rendering preserved?
|
||
|
||
## Example
|
||
|
||
```tsx
|
||
import { PixelIcon } from './components/PixelIcon';
|
||
import { TAB_ICONS } from './uiAssets';
|
||
|
||
<PixelIcon
|
||
src={active ? TAB_ICONS.inventory.active : TAB_ICONS.inventory.inactive}
|
||
className="h-4 w-4"
|
||
/>
|
||
```
|
||
|
||
## Known issues / 已知问题
|
||
|
||
### 开局「武侠 / 仙侠」按钮中部发空 / 像透明
|
||
|
||
**现象**:世界选择页上,`1_orange_button` / `1_violet_button` 套 9-slice 后,中间区域异常,像没画上或透出背景。
|
||
|
||
**正确原因(切片几何,而非文字冲突)**:
|
||
|
||
1. **图源尺寸**:`1_orange_button.png` / `1_violet_button.png` 实际为 **125×28** 的横向条(可用设计工具或脚本读 IHDR 确认)。
|
||
2. **非法切片**:若 `border-image-slice` 在竖直方向取 `top + bottom >= 高度(28)`,则图源里用于「中间横条」的像素行数为 **0**。此时 `fill` 没有可拉伸的中间带,浏览器无法正确铺满内容区,中间会发空或表现异常。
|
||
3. **错误补救**:用 `baseColor` 铺底虽能盖住背景,但**与素材本身的渐变/内描边不一致**,且中间没有原画的边框层次,视觉上「只有一块平色 + 外框」,不符合预期。
|
||
|
||
**不是**:`PixelIcon` 与 `pixel-world-button__label` 抢层级或颜色把中间「挡住」;二者在内容区内,不会导致 9-slice 中间带消失。
|
||
|
||
**正确修改方式**:
|
||
|
||
- **先按像素量体裁衣**:保证 `slice.top + slice.bottom < 图高`,`slice.left + slice.right < 图宽`。对 28px 高的条,上下 slice 宜各约 **8~10px**(需与素材圆角/浮雕厚度对齐),留出 **约 8~12px** 高的中间行供纵向拉伸。
|
||
- 横条在 UI 里被拉得很高时,中间带主要靠 **纵向 stretch**;`border-image-repeat` 对边可用 `stretch`(条形成品常见),按观感在 `round` / `stretch` 间微调。
|
||
- 仅在确认图源中心 **确实透明**(RGBA)且引擎式叠色才是需求时,再考虑 `baseColor` 或换一张中心有底的按钮图。
|
||
|
||
**维护提醒**:新增 9-slice 配置前,先读 **宽高**,再填 slice,避免 `top+bottom` 或 `left+right` 把中间切片挤没。
|