---
Task ID: 2-b
Agent: full-stack-developer
Task: v0.16 完善 — loreLayer 已读标记 + idleProjectHistory 详情展开 + 移动端 375px 适配审计

Work Log:
- 必读上下文：worklog v0.15-release 段 + Codex.tsx（v0.15 loreLayers 分层渲染）+ StatsPanel.tsx（v0.15 抽出独立组件）+ types.ts GameState + config.ts FRAGMENTS/loreLayers/isLoreLayerUnlocked + gameStore.ts（actions + persist migrate）+ IdleOperationsPanel.tsx + BeaconPanel.tsx + ExpeditionPanel.tsx
- 子任务 1：loreLayer 已读标记
  - 新建 `src/lib/game/format.ts` 导出 `formatRelativeTime(ts)` 工具函数（<60s "刚刚" / <60m "X 分钟前" / <24h "X 小时前" / <7d "X 天前" / 否则具体日期）
  - `types.ts`：GameState 接口新增 `readLoreLayers: string[]`（位置：v0.14 idle 字段之后、元字段之前，注释标 v0.16 loreLayer 已读）
  - `engine.ts`：
    * `createInitialState` 末尾加 `readLoreLayers: []`（在 idleStats 之后）
    * `performPrestige` 返回对象加 `readLoreLayers: Array.isArray(state.readLoreLayers) ? state.readLoreLayers : []`（跨周目永久保留，与 idle 永久字段同一区域）
  - `gameStore.ts`：
    * GameActions 接口加 `markLoreLayerRead: (fragmentId: string, layer: 1 | 2) => void`（在 tickIdleProjects 之后）
    * 实现该 action：构造 key `${fragmentId}_L${layer}`，已存在则 no-op，否则 set 新数组
    * persist migrate 函数末尾加 `readLoreLayers: Array.isArray(s.readLoreLayers) ? s.readLoreLayers : []`（不改 version，仍是 1）
  - `Codex.tsx`：
    * useShallow 订阅新增 `readLoreLayers` 与 `markLoreLayerRead`
    * LoreLayerBlock 接收 `isRead` + `onMarkRead` props：
      - 已读：层标题旁加 muted "✓ 已读" 标记 + 边框从 amber/fuchsia 高亮降级为 `border-white/10`
      - 未读但已解锁：保持 v0.15 高亮 + 标题旁加 `animate-pulse` 脉冲小圆点 + "新" 字提示
      - 文本区域包成 `<button>` 元素，点击调用 `onMarkRead`（自然交互，不另设按钮）
      - 未读时下方加一行 muted "▸ 点击文本标记已读" 引导文字
    * 列表卡片标题旁小圆点：未读时 `animate-pulse` + glow，已读时 `bg-*-400/30` 静态
    * 顶部统计从单行"深层叙事 X/Y 已解锁"扩展为两行（移动端 stack / 桌面并排）：深层叙事 X/Y 已解锁 + 已读 A/B（A=已读数，B=已解锁层数）
    * 弹层 padding 从 `p-5` 改 `p-4 sm:p-5`，标题 `text-lg` 改 `text-base sm:text-lg`，外层 backdrop padding 从 `p-4` 改 `p-3 sm:p-4`（375px 适配）

- 子任务 2：idleProjectHistory 详情展开
  - `StatsPanel.tsx`：
    * useShallow 订阅新增 `idleProjectHistory` 数组与 `idleProjectSlots`（用于详情中潜在"进行中"状态参考）
    * 删除原"放置工程历史 N 条"的简单行，改为 `col-span-2` 可展开按钮：
      - 左侧 label + "(最近 5 条可展开)" 提示（仅 historyLen > 0 时显示）
      - 右侧 "N 条" + ChevronDown/ChevronUp 图标（lucide-react）
    * 展开后渲染 `col-span-2` 卡片：
      - 空历史 → "暂无放置工程完成记录 · 前往「放置」标签派遣工程"
      - 非空 → 倒序最近 5 条：每行 = 工程图标 + 工程名（truncate）+ 相对时间（formatRelativeTime）
      - 最近一条用 `bg-fuchsia-500/5` 高亮
      - 历史超 5 条时底部加 "仅展示最近 5 条 · 共 N 条历史" 提示
    * 展开状态用本地 `useState<boolean>`
    * useMemo 派生 `recentHistory`（避免每次渲染重切）

- 子任务 3：移动端 375px 适配审计
  - 审计范围：Codex / IdleOperationsPanel / StatsPanel / BeaconPanel / ExpeditionPanel
  - 修复点：
    * `IdleOperationsPanel.tsx`：所有 `text-[9px]` → `text-[10px]`（11 处，符合"移动端字号最小 10px"规范）
    * `BeaconPanel.tsx`：所有 `text-[9px]` → `text-[10px]`（7 处）；信标链底部统计行 `flex items-center justify-between gap-2` → `flex flex-wrap items-center justify-between gap-x-2 gap-y-1`（防 3 组数据在 375px 溢出）
    * `Codex.tsx`：弹层 padding + 标题字号响应式（见子任务 1）；顶部统计区 `flex flex-col sm:flex-row`（移动端纵向堆叠）
    * `StatsPanel.tsx`：detail 区 `text-[9px]` → `text-[10px]`（2 处）
  - 审计通过（无需改）：
    * `ExpeditionPanel.tsx`：节点路径已用 `overflow-x-auto` 自适应窄屏；事件卡 p-3 padding 合理；按钮组 `flex-1` + abort 收起；能量条文字 `text-[10px]` 已达标；路径节点 `h-9 w-9` 36px 足够小且可横向滚动
    * `BeaconPanel.tsx`：4 个里程碑节点 `w-12 h-12`（48px）× 4 = 192px + flex-1 分布，375px 下可容纳；日/周挑战卡 padding p-3 合理；周挑战+信标链已用 `grid-cols-1 lg:grid-cols-2` 移动单列
    * `IdleOperationsPanel.tsx`：4 个 section 已用 `grid-cols-1 sm:grid-cols-N` / `grid-cols-2 sm:grid-cols-3` 响应式降级；DispatchCard 槽位按钮 `h-5 w-7` × 3 在单列布局下不溢出
    * `Codex.tsx` 列表 `grid-cols-2 gap-2`：每卡片约 165px 宽，aspect-[4/3] + p-2 + text-[11px]/text-[9px]（已升 text-[10px]）在 375px 下可读

- 严格 4 色全息：emerald / rose / amber / fuchsia，未引入 blue / indigo
- useShallow：所有 store 订阅用 useShallow（Codex / StatsPanel 均已用）
- 持久化兼容：persist version 仍为 1，新字段 `readLoreLayers` 靠 migrate 兜底，旧存档加载时自动补 `[]`
- 与子代理 A 文件边界：types.ts/engine.ts/gameStore.ts 三个共享文件，A 的 `triggeredMilestones` / `pendingMilestone` / `dismissMilestone` 字段已落地，与本子代理的 `readLoreLayers` / `markLoreLayerRead` 完全无冲突（不同字段、不同 action、不同 migrate 行）

Stage Summary:
- ✅ 子任务 1：loreLayer 已读标记完整实现
  - 类型 + store + UI 三层联动；点击层文本即可标记已读
  - 视觉区分：已读 = muted "✓ 已读" + 边框降级为 border-white/10；未读 = 高亮 + 脉冲小圆点 + "新" 字
  - 顶部统计新增"已读 A/B"行（A=已读数 / B=已解锁层数）
  - 跨周目永久保留（performPrestige 不重置）
- ✅ 子任务 2：idleProjectHistory 详情展开完整实现
  - "放置工程历史" 行可点击展开/收起
  - 展开后显示最近 5 条倒序历史（图标 + 工程名 + 相对时间）
  - 空态友好提示，超 5 条底部加"仅展示最近 5 条"说明
- ✅ 子任务 3：移动端 375px 适配审计完成
  - 审 5 个组件，修 4 个（Codex / IdleOps / StatsPanel / Beacon），ExpeditionPanel 无需改
  - 主要修复：text-[9px] → text-[10px]（共 20 处，符合"移动端字号最小 10px"规范）
  - 信标链底部统计加 flex-wrap 防 3 组数据溢出
  - Codex 弹层 padding + 标题字号响应式
  - Codex 顶部统计区移动端纵向堆叠
- ✅ 新建文件：`/home/z/my-project/src/lib/game/format.ts`
- ✅ 修改文件清单（绝对路径）：
  - `/home/z/my-project/src/lib/game/types.ts`（+readLoreLayers 字段）
  - `/home/z/my-project/src/lib/game/engine.ts`（createInitialState 默认 + performPrestige 保留）
  - `/home/z/my-project/src/store/gameStore.ts`（GameActions 签名 + action 实现 + migrate 补全）
  - `/home/z/my-project/src/components/game/Codex.tsx`（已读标记 + 点击逻辑 + 顶部统计 + 弹层响应式）
  - `/home/z/my-project/src/components/game/StatsPanel.tsx`（idleHistory 展开 + 相对时间 + 字号升级）
  - `/home/z/my-project/src/components/game/IdleOperationsPanel.tsx`（字号 9→10px 升级）
  - `/home/z/my-project/src/components/game/BeaconPanel.tsx`（字号 9→10px 升级 + 信标链底部 flex-wrap）
- ✅ bun run lint 零错误零警告
- ✅ HTTP 200，dev.log 无运行时错误（compile 11ms / render 33ms）
- ✅ 与子代理 A 文件边界完全无冲突（不同字段/action/migrate 行）
- ⚠️ 未做 agent-browser 端到端验证（按工单由主控完成）
