type
Post
status
Published
date
Jul 15, 2026
slug
04-art-audio-quality-cloudflare-f0369642-cb46-4311-9215-96ae1a5b9956
summary
复盘方寸专注从可运行原型到公开成品的素材管线、Web Audio、响应式、质量门与 Cloudflare Pages 部署。
tags
前端工程
Web Audio
生成式图像
Cloudflare Pages
category
技术分享
icon
password
系列导航:第一篇 · 第二篇 · 第三篇
最后一个玩法合并时,我以为已经接近收尾。实际后面还排着素材风格、真实裁切、移动端层级、暂停与后台生命周期、声音可用性、静态资源预算,以及一次很容易被单元测试漏掉的 CSS 级联。
这一段工作没有再增加多少产品概念,却占了后半程很大一部分时间。

先做风格母版,再生成资产组

最初的场景和角色也是生成图,但每一组单独描述,结果很快失控:有的像淡彩水墨,有的偏 3D 玩具,有的角色比例接近日系动画。单张看都不算坏,放进同一个产品却不像来自同一世界。
后来的素材刷新先做一张 3:2 风格母版,明确这些视觉约束:
  • 工笔启发的清晰轮廓
  • 石绿、石青、朱砂和暖金的矿物色
  • 可见但克制的宣纸纹理
  • 明亮、可读的自然光
  • 适合 6-12 岁的简化剪影,不使用幼儿化大头比例
  • 避开苍白水彩、塑料 3D、动漫脸、西式奇幻符号、文字和 UI
有了母版以后,再按运行场景拆成资产组:七张练习封面、六种地形、八个地标、横竖两张追踪背景、纸鸢和亭子、纸鸢小使的四种情绪。
玩法素材刷新后的运行时接触表
玩法素材刷新后的运行时接触表
这批图片通过 CPA 的 `gpt-image-2` 生成。源 PNG、提示词和接触表保存在 Trellis 任务目录,运行目录只接收经过审核、裁切和压缩的 WebP。生成目录与 `public/assets` 分开,避免把几 MB 的源图、失败版本和未使用变体一起部署。
素材管线最后固定成五步:
普通故事和场景资产上限是 400 KB,画中寻物的透明物件上限是 200 KB。当前 `public/assets` 有 116 个文件,总体积约 11.75 MiB,最大单文件约 339 KiB。30 个寻物素材、28 张故事角色立绘、路线切片和追踪前景都在测试里逐一检查路径、扩展名和预算。
这里有一个实用经验:不要只在文件浏览器里看透明图。角色需要放到实际背景、实际缩放和实际 `object-position` 下检查;一张边缘很干净的 PNG,缩到手机上可能只剩一个难以辨认的色块。

响应式的难点是层级,不是断点数量

项目只用一个全局 `styles.css`,但每类固定格式界面都有稳定尺寸约束。状态栏、进度线、棋盘格、角色槽位和底部操作区不能因为数字变长或提示出现就跳动。
山径推演的每个格子预留四个位置:左上是顺序号,右上是当前位置,中间是地形或地标图,底部是固定高度标签。手机横屏高度不足时可以隐藏冗余徽标,但山帖顺序和当前位置轮廓必须保留。
故事剧场在桌面上使用左右角色槽和底部对话卷,在 390px 手机上改成单角色主视图,工具栏、进度、角色和对话各有固定层级。选择出现时,对话区会为两个按钮留出高度,不把正文推到屏幕外。
手机端故事剧场
手机端故事剧场
一个曾经漏过的 bug 很能说明问题。共享的觉醒背景类加了 `position: relative`,而全屏奖励场景依赖 `position: absolute`。两个选择器权重相同,共享类写在后面,于是奖励容器被改成相对定位;它的子元素又全是绝对定位,最终高度塌成零,只剩一块深色背景。
测试、lint 和 build 全都通过,因为它们不会计算 CSS 布局。修复方式不是再堆一个更强选择器,而是收回所有权:共享装饰类只管理纹理与 stacking context,宿主自己决定 position、display 和尺寸。随后补了一条源码级回归断言,防止共享类再次声明布局属性。

BGM 不是提示朗读

声音也经历了两次返工。第一版只有答题提示和语音;后来加了环境音,却是非常轻的通用合成器,实际设备上几乎听不见。需求里说的是背景音乐,不是把规则再朗读一遍。
最终音乐仍然没有引入音频文件,而是由一个 Web Audio 单例实时合成。五卷分别映射到五组音色:
画卷主题
主导音色
山林
古琴
水境
箫与笛
大地
灯火
琵琶
星河
古筝
每个主题使用五声音阶动机、不同根音、节拍和背景纹理。这里追求的是可辨认的氛围差异,不是假装浏览器振荡器能替代真实演奏录音。
浏览器自动播放规则决定了音乐必须等第一次 pointer 或 keyboard gesture 后启动。`App` 只保存“是否启用”和归一化音量,`ambientAudio.js` 持有 `AudioContext`、master gain、compressor、定时器和当前 voice。主题切换会替换同一个 phrase schedule,不创建第二条循环;页面隐藏、关闭声音或卸载时会清理定时器与有限声部,并渐弱后 suspend/close。
背景音乐、动态效果和年级设置
背景音乐、动态效果和年级设置
音量单独保存在 `focus-classics-ambient-volume-v1`,输入统一限制到 `0..1`。这样调整音量不会触碰整个进度 JSON,旧存档缺少音量时也能直接使用一个听得见但不过分抢戏的默认值。

质量门围绕高风险纯逻辑

当前仓库有 6 个测试文件、72 项测试。测试重点并不是给每个按钮做浅层快照,而是覆盖那些一旦出错就会破坏存档或让关卡无解的纯逻辑:
  • 旧进度迁移、幂等与活动 run 保留
  • 二十四景内容池和静态素材引用
  • 七游戏三程覆盖与卷末四项组成
  • 主线、34 个事件、12 条两段记忆的完整性
  • story skip、选择历史、短期 echo 和终章重试
  • 寻物布局、非完成态拼图和加权路线可解性
  • 伴灵效果合并,避免同类加成重复叠加
  • 五卷主题、五种国乐音色和音量归一化
每次代码改动都跑三道门:
内容测试另外确认 120 个成语、90 首古诗以及每景分级题池有效。可见改动还要在桌面、390px 手机和短横屏检查真实布局;声音则要实际走过首次手势、开关、主题切换、音量持久化、切后台与清理。
这次最终验收从公开域名进入了冒险首页、年级选择、故事剧场、自由练习和山径推演。桌面与手机截图没有控件重叠,浏览器控制台也没有 warning 或 error。

部署到 Cloudflare Pages

应用是一个静态 Vite SPA,没有服务端 API,部署到 Cloudflare Pages 很直接:
项目保留了最初的 Pages 名称 `firefly-focus-kids`,产品品牌已经变成“方寸专注”。生产站点同时通过 Pages 默认域名和自定义域名访问:
2026 年 7 月 15 日的玩法、素材与音频刷新部署已经在两个地址上验证,代表性 WebP 均返回 `200 image/webp`。PicGo 上传的文章截图走 jsDelivr,不进入产品构建包。

如果重新做一次

我会更早做三件事。
第一,在写视觉方案前先拿一个真正的孩子向玩法确认产品方向。最初的成人护眼页工程质量不差,但验证了错误的问题。
第二,从第一版本就给持久化结构加版本号。后面从 21 个阶段改成 24 个场景时,迁移逻辑比功能本身更需要谨慎。
第三,尽早建立浏览器视觉回归。纯函数测试很适合这个项目,却抓不到奖励页高度塌陷、手机状态小字压住进度条、素材焦点被裁掉这类问题。
当前版本也有明确限制:没有多儿童档案、账号和跨设备同步,没有真正的录制乐器音轨,也没有完整的自动化浏览器测试套件。这些都可以继续做,但它们会改变隐私、部署和维护成本,不能当成“顺手加上”的小功能。
从一个三分钟计时页走到现在,反复出现的问题都很具体:规则要能恢复,随机要能复现,内容要有边界,视觉要在真实设备上成立。下一轮最该补的是自动化视觉回归,然后再继续扩充故事内容。
手搓导航站日记 (终章):DevOps 降临!打造“自动驾驶”级的导航站 🚀方寸专注(三):七种小游戏如何长成一个无压力的留存循环
Loading...