# 胭脂，但是 Web

这是 Python/PySide6 版本的浏览器移植版。它使用同源 popup 窗口复现独立窗口动画。`web/` 已包含运行所需的页面、脚本、图片、字体和音频，可以整体复制到任意静态服务器部署。

## 启动


 `teto1` 到 `teto6` 按各自的 `metadata.json` 映射真实 PNG，所有资源都随 `web/` 一起提供。
权限通过后，页面会先预热时间轴所需的图片、关键帧、字体和音频，再打开 popup；每个 popup 只在自己的页面上下文中预热该角色实际使用的图片，并在所有 popup 完成确认后才开始播放。播放过程中按需解码帧，避免长期占用全部图片的解码内存。

## 当前实现

- 启动前使用 3 个探测 popup 检查弹窗权限，并探测窗口边框补偿。
- 使用 22 个同源 popup 承载序列帧、文字、图片和吊线画面。
- 动画优先跟随 `resources/music.m4a` 的播放位置；音频无法播放时自动切换到 `performance.now()` 时钟。
- 主窗口提供暂停、继续和收起全部窗口。
- 所有时间轴资源在启动前按有限并发预热；popup 按角色拆分资源，避免每个窗口重复等待整套素材。`teto1` 到 `teto6` 按各自的 `metadata.json` 映射真实 PNG，资源目录保持在项目根目录，避免复制图片。
- popup 在目标帧尚未解码或被浏览器回收时会继续显示上一张已完成的帧，避免加载瞬间出现整窗空白。
- 不参与当前场景的 popup 会缩到屏幕角落，参与当前场景的 popup 会恢复设计尺寸；场景切换时会尝试将原版对应的顶层窗口置前。

浏览器会限制 popup 的焦点、最小尺寸、窗口层级和窗口透明度，因此 `focus()` 只能尽量接近 PyQt 的 `raise_()`，部分无边框视觉效果会以浅色背景近似实现。若浏览器拒绝批量 popup，请允许本站弹窗后重新点击启动。

## 与 Python 版的同步点

- 普通序列帧的 `start_loop(3)` 对应 Web 约 10fps；需要快速播放的 `start_loop(1)` 对应 30fps。
- 字幕窗口的抖动按 Python 版约 33ms 更新，图片窗口也使用同一层级的整体抖动。
- TETO 的 `gapL32`、`gapR32`、`gapR64` 已映射到设计坐标；28600ms 的左移使用 200ms 平滑移动。
- `teto1` 到 `teto6` 的关键帧继续依据 `metadata.json` 映射，不使用连续文件名猜测。
- 浏览器不能强制控制原生 popup 的 z-order；窗口创建顺序、几何刷新和 `focus()` 只能近似 PySide 的 `raise_()`。