docs(agents): rewrite AGENTS.md as the single authoritative adaptive-layout spec

Unify the layered-decoupling approach (art in the 9:16 canvas, UI anchored to the device-stage screen container) from docs/adaptive-architecture.md, fold in the dialog fix (cd83c35), drop the wrong 'canvas is deprecated' framing, add a 3-step element-placement decision and the dialog anti-pattern. Codex/teammates read this file to adapt the remaining pages consistently.
This commit is contained in:
km2023
2026-06-30 15:34:29 +08:00
parent cd83c358cf
commit 8e86e22ad2

View File

@@ -1,43 +1,73 @@
# AGENTS.md — VTuber Mini
# AGENTS.md — VTuber Mini 自适应规则(权威 · 统一)
> 给 codex / AI agent:本项目调整页面**自适应布局**时,遵循本文件
> 目标:任何同事用 codex 微调任意页面,做法都统一、不再各搞一套
> 给 codex / AI agent / 同事:本项目调整**任意页面**自适应布局,**以本文件为唯一权威**
> 本文件 = `docs/adaptive-architecture.md` 的分层方案 + 实战修正(对话框 `cd83c35`)整合后的最终版
> 旧版"别用 9:16 画布 / 架构文档已废弃"的说法**作废**——画布要用(放美术),只是 UI 别挂进去。
## 这是什么项目
540px 宽移动端 H5(单 VTuber 粉丝养成 app)。React 19 + Vite + Tailwind 4 + Capacitor(Android)。
桌面浏览器里 = 一540 宽的竖条居中显示
## 项目形态
540px 宽移动端 H5(单 VTuber 养成 app)。React 19 + Vite + Tailwind 4 + Capacitor(Android)。
真实像素、无 rem 缩放;桌面浏览器里 = 一条 ≤540 宽竖屏居中。包管理用 **pnpm**
## 自适应三条规则(就这么简单,别搞复杂)
1. **容器**:页面根容器宽 = `min(100vw, 540px)`,水平居中,高 = `100dvh`。窄屏(<540)自动占满 100%。
- 全局 `.device-stage` 已是这个规则,页面挂在它里面即可。
2. **不要用百分比绝对定位**。元素位置别用 `style={{ left: 'X%', top: 'Y%' }}` 这种绝对定位——手机宽度一变就错乱(这是本项目布局乱的根因)。
- 用正常 flex / 文档流;或贴边的 `absolute`(`bottom-0` / `top-0` / `inset-x-0`)。
3. **导航 / tab 贴屏幕底**:`absolute inset-x-0 bottom-0` + 内部 flex 等分。背景美术用 `object-cover` 铺满容器。
## 一、核心心法:分层解耦(整套体系就这一句)
把页面里每个元素归到两类之一,**各用各的坐标系,别焊死在一起**:
## 两个必避的坑(亲测,栽过)
1. **Tailwind 4 会静默丢弃自定义 CSS 类里的 `cqw / cqh / container-type`**(Lightning CSS 把整条规则删掉,不报错)。
→ 关键尺寸**别写自定义 CSS 类,直接写 inline style,用 `vw/vh`**。例:
```tsx
<div style={{ width: 'min(100vw, 540px)', aspectRatio: '9 / 16' }} />
```
2. **`npm run build` 通过 ≠ 生效**。自定义 CSS 可能编译过但运行时根本没渲染(本项目曾让画布 `height` 塌成 0 却一直没发现,误以为成功)。
→ 改完**必须实测元素几何**,不能只看编译过。
| 层 | 是什么 | 挂哪个容器 | 怎么定位 |
|---|---|---|---|
| **美术层** | 背景视频 / 底图 / 立绘 | **9:16 画布**`.reference-fit-frame` 或 inline `aspectRatio:'9 / 16'` | `object-cover` 铺满,边缘允许裁切 |
| **UI 层** | 对话框 / 底栏 tab / 图标 / 头像 / 按钮——自带完整视觉的独立 PNG、要可点 | **真实屏幕容器**`.device-stage`(`height:100dvh`)或页面级 `h-full`/`100dvh` 容器 | 锚屏幕边(`absolute inset-x-0 bottom-0` 等)+ `safe-area`**绝不挂进画布** |
## 改完怎么验证(别跳过)
一句话:**背景管「好看」可裁UI 管「可用」(锚屏幕、永不裁)。**
## 二、为什么必须这样(第一性 · 记牢这条因果)
- 9:16 画布高度 = `宽 × 16/9`**随宽度变**。
- 元素挂画布里、用百分比纵向定位(`bottom-[X%]`)→ 实际像素 = `X% × 画布高`**随宽度上下漂**
- `.device-stage` 高 = `100dvh`(视口高),**不随宽度联动** → UI 挂这里才钉得住。
## 三、三类页面 → 三种做法
| 类型 | 页面 | 做法 |
|---|---|---|
| 沉浸·交互页 | `home``anniversary` | 美术留画布 + UI 锚屏幕(见 §一)← 重点 |
| 沉浸·演出页 | `gacha``heart-moments` | 整段全屏演出,按设计稿**整体等比 `scale`**,可接受少量裁切 |
| 功能·流式页 | `my`/`shop`/`album`/各 ledger…约 14 个) | flex/grid 文档流 + `safe-area` + `clamp()` 字号,天然较好,只补 token 死角 |
## 四、铁律
**✅ 要这么做**
- UI 元素挂屏幕容器(`.device-stage` 或页面级 `h-full`),贴边 `absolute inset-x-0 bottom-0` / flex 等分。
- 背景 `object-cover` 铺满画布(**不是 `object-fill`**,那会把图标拉胖)。
- 关键尺寸写 **inline style**,用 `vw/vh/dvh/min()`
**❌ 绝不这么做**
- **UI 挂 9:16 画布里用百分比纵向定位**`bottom-[15.8%]`)→ 必随宽度漂。← 本项目布局乱、对话框漂的根因。
- 自定义 CSS 类里写 `cqw / cqh / container-type` → Tailwind 4Lightning CSS**静默删整条规则、不报错**。改用 inline `vw/vh`
- 在有 `transform` 的祖先内用 `position: fixed` → fixed 被该祖先接管、相对它而非屏幕,照样漂。
## 五、判断一个元素该放哪3 步 · 套到任意页任意元素)
1. **它是美术还是 UI** 自带完整视觉的独立 PNG / 要可点 = UI只是画面氛围 = 美术。
2. **查它的定位祖先链**:最近一个 `position:relative/absolute` 的祖先,高度是 **9:16 画布派生**`aspectRatio` / 套在 `.reference-fit-frame` 里)还是 **视口高**`100dvh` / `.device-stage`
3. **若是 UI 却挂在画布派生高度上 → 移出来挂屏幕容器层。** 定位类基本不用动——`bottom-[X%]` 移到屏幕容器后X% 就相对视口高,自然不漂。
## 六、反面教材(真实案例 · 别重蹈)
首页对话框原本 `<div absolute inset-x-[4%] bottom-[15.8%]>` 挂在 9:16 画布的 `flex-1` 容器里 → 拖窄屏对话框下移、和 tab 重叠。
**修复 = 把整个 `{activeDialog ? …}` 块移出画布、挪到 tab`aspectRatio:108/25`)旁边(屏幕容器层)**,它就和 tab 共享屏幕锚定、不再随宽度动。见 `home-page.tsx` + commit `cd83c35`
(注:`docs/adaptive-architecture.md` §4.1 曾说「对话框为对齐角色保留画布百分比」——此条已被本案例推翻,对话框也要锚屏幕。)
## 七、验证别跳过——build 过 ≠ 生效)
本项目栽过「画布 height 塌成 0、编译却过、一直没发现」。改完**必须实测几何**
```bash
npm run build # 1) 先编译过
CI=true npm run dev # 2) 跑起来(CI=true 避免后台进程因 stdin 退出)
pnpm build # 1) 先编译过
pnpm dev # 2) 跑起来
```
然后在浏览器实测关键元素的 `getBoundingClientRect()`,确认:
- 容器宽 = `min(屏宽, 540)`、`height ≠ 0`;
- tab bar 的 `bottom` ≈ 屏幕高(真的贴底)、宽 = 屏幕宽;
- 换几个宽度(360 / 400 / 540)布局都不乱
浏览器**360 / 400 / 540** 三个宽度,实测关键元素 `getBoundingClientRect()`
- UI 距屏幕底的 px`innerHeight - rect.bottom`**三个宽度下不变** = 没漂 ✓
- tab 贴屏幕底、宽 = 屏幕宽
- 整体不乱、UI 不重叠
> 提示:首页有登录 gate(`!isLoggedIn` → LoginPage,见 `App.tsx`),临时验证可把 `isLoggedIn` 初值改 `true`,**验证还原**。
> 首页有登录 gate`!isLoggedIn`→LoginPage见 `App.tsx`)。临时验证可把 `isLoggedIn` 初值改 `true`**验证还原**。
## 参考实现
- `src/pages/home-page.tsx`:首页已按上面三条改过(540 容器、去手机外壳、底部 tab 贴屏幕底)——可作为其他页的样板,但**以本文件规则为准,不要照搬其中的临时痕迹**。
- 其他页(anniversary / shop / my / album / pet …)尚未逐页核对,按本规则调整即可。
## 八、参考实现 & 现状
- `src/pages/home-page.tsx` = **已改对的样板**:背景视频 + 立绘在 9:16 画布tab + 对话框已锚屏幕(画布外)。可照抄分层方式,但**以本文件规则为准,别照搬临时痕迹**。
- 其余 18 页:沉浸页(`anniversary`)按 §三①;演出页(`gacha`/`heart-moments`)按 §三②;流式页维持。**凡发现 UI 还挂在画布内用百分比定位的,按 §五 移出。**
## 已废弃
`docs/adaptive-architecture.md` 是早期的复杂方案(9:16 固定画布 / scale 缩放 / 分层 / cqw),**已废弃、勿参考**,一切以本文件为准。
---
*来源整合:`docs/adaptive-architecture.md`(分层方案调研 2026-06-29+ 对话框实战修正(`cd83c35`2026-06-30。本文件取代旧 AGENTS.md。*