切换外观
C 流动形态 · 动画记录
状态:2026-10-20。把第三轮概念动画的六步流程(reference → data → story → motion → direct → ship)推到 C 流动形态:先写参考、数据与故事(第 1–3 节),交互的标准写在第 4 节,故事定下之后才改代码;实现、与故事的出入、定格与验证见第 5–6 节。原型探索,不是规范。C 打磨时的做法与交互清单见 temporal.md,范本是整理脉冲(一粒一条)、份额拨盘(一涨一缩同时落定)与 D 手机首页(答一件少一件、回执跟着条目走),两层法则见 CONCEPTS.md。数字都由 scenario.mjs 回放现算,规则见 2.1;推算脚本只留在本机,不入库。
C 和其他阅读工具的问题是反的:别的形式本来是静图,要想办法让它在节拍上动;C 一直在动(液滴有弹簧、会回弹、会拉长),但动的东西没有节拍,也没和信息层的数量对上。这一轮要做的是让液滴场守信息层的账。
1 参考(reference)
1.1 C 现在怎么动
读 c.mjs 与 c-field.mjs 所见(2026-10-20,main 245d554):
| 时刻 | 现在怎么动 | 断在哪 |
|---|---|---|
| 换轮 | 布局求解一次,每滴的 x、y、半径各挂一根弹簧(位置 k=92、ζ=0.8,半径 k=150、ζ=0.56),同时起跑,约 0.6–0.8 秒停稳 | 没有先后:份额、位置、过水面、数字在同一刻开始变。半径的阻尼比 0.56 过冲约 12%,面积过冲约 25%,一滴从 6 份长到 12 份的途中一度有 15 份大;同一刻各滴的过冲互不相抵,64 份不守恒 |
| 份额与大小 | 面积 ∝ 份额,但布局放不下时整层按 1、0.88、0.76 三档缩小 | 档位按帧选,同样 6 份在不同的轮里不一样大 |
| 输入 | 画面上没有;信号行写“本轮输入 158 条”,概览写处置计数 | 一轮进来上百条,液滴场一粒也不表现,“涌入”只在字里 |
| 小珠 | 不足 3 份的是底部一排 DOM 小珠,半径一律 7 px | 1 份与 2 份一样大;和液滴不是同一套比例 |
| 推后 | 按下“推到…”的那一刻液滴就沉进托盘,托盘里半径取 clamp(r × 0.4, 9, 18) | 页面替 Agent 抢了一步:回执还写着“已保存,等下一轮整理确认”,液滴已经沉了。托盘里的大小夹在 9–18 px,24 份和 6 份几乎一样大 |
| 作答 | 阅读区重建,回应控件换成回执;标题行的“N 件等你回应”不变,另注“1 件你推后了” | 没有“答一件,少一件” |
| 悬停 | 液滴自己亮一点(light 0.42),没有问题的出一个黑底提示框;预算格同步提亮 | 其余不压暗、播放不停;提示框和 ix 的概要卡不是同一套 |
| 点击 | 阅读区切到这一件 | 没有弹出、引线与详情卡;和拨盘、脉冲、回应时序的标准不一致 |
| 静止 | 全部停稳后不再渲染 | 已经安静,这一条保留 |
1.2 从脉冲、拨盘、D 搬什么,不搬什么
| 要素 | 出处 | 搬过来 | 不搬 |
|---|---|---|---|
| 一粒一条 | 脉冲:云里一颗亮粒子就是一条输入,去向与面板分类一一对应 | 每轮采用的输入一条一粒从水面上方落下,落进它所属的那一滴或那颗小珠;暂缓的是水底的泥,粒数 = 暂缓条数,不飞;噪音、重复与未判不画(初版每条都画,用户看后嫌多,见第 6 节) | 尘埃、辉光、余辉、收拢弧;脉冲那样每条都画 |
| 节拍 | 脉冲:涌入 → 折叠 → 一问,拍外静止 | 涌入 → 长缩 → 沉浮 → 数字落定,四段依次走,每段只做一类事 | 4.25 秒的固定节拍器与首尾循环:C 是阅读工具,不自己连播 |
| 一涨一缩 | 拨盘:同一轮里一涨一缩同时落定,不画成谁推谁 | 长缩拍里所有份额走同一条缓动、同起同落,任何时刻各滴份额加留白都是 64;只有明确的 merged_into 画成一滴流进另一滴 | 拨盘扇区的 3.7% 过冲:体积过冲就是多出来的份额 |
| 数字只在节拍上翻 | 拨盘:计数在切点翻牌;脉冲:卡上不提前说后面节拍的事 | 滴里的份数、标签、朱红环、64 格带、标题行与阅读区都在“数字落定”那一刻一帧硬切;悬停停在轮中途时,卡上写“本轮的份额还没落定” | — |
| 答一件,少一件 | D:卡原地收成回执,数字减一;下一轮整理先确认回执,再带着让出的份额沉下去 | 回应控件原地收成回执,“N 件等你回应”当场减一;液滴不动,等下一轮整理读到了才沉进托盘(推后),回执在那一轮的切点填实 | D 的列表编排(离场 → 移位 → 进场) |
| 回执跟着条目走 | D:回执按问题 id 认,条目改写后仍在 | 本页记录本来就按条目 id 取(latestRecord),条目改写后液滴旁仍写“✓ 你推到 8/16 09:00 · 已收到” | — |
| 交互 | 拨盘、脉冲、回应时序:悬停概要卡、点击弹出 8 px + 引线 + 详情卡,共用 motion/ix.mjs | 同一个模块;弹出沿“容器中心 → 液滴中心”这条线向外推 8 px,体积不变;引线沿同一条线出去再连到卡 | 详情里的“去回答 →”:C 自己能作答,换成“在右栏作答” |
| 讲一遍 | 回应时序、让位图、腕上一瞥:只在顶层直接打开时演一次,任意键跳过,有“重演” | 同一条规则 | — |
1.3 概念层占比:三到四成,静止时为零
拨盘与脉冲的概念层约占八成,因为官网首屏要先让人感到量;让位图、回应时序是复盘工具,概念层不到一成。C 在两者之间:它本来就是用液体讲份额的形式,液滴的凝胶质感、落下的粒子、水底的泥就是它的画法,所以概念层可以占三到四成。但它仍是阅读工具,会被停在任何一轮仔细看,所以两条规矩比拨盘严:
- 概念层的每一样东西都是信息层某个数的样子:粒子数 = 本轮采用的条数,泥的粒数 = 本轮暂缓条数,液滴面积 = 份额,过水面 = 3 份阈值或推后。没有纯装饰的粒子。
- 落定之后零运动:不呼吸、不漂、不闪。泥停在水底,液滴停在原处,直到下一次换轮、作答或交互。
2 数据(data)
2.1 口径
用 node 直接导入 variants/shared.mjs(replayScenario 得到的 records、currentAsk)现算,与页面同一套推导。
| 量 | 怎么算 | 出处 |
|---|---|---|
| 份额、留白 | 当前层 items[].share;留白 = 64 − used | records[i].state.root |
| 液滴与小珠 | 份额 ≥ 3 是液滴,< 3 是水面以下的小珠 | c.mjs 的 SMALL = 3 |
| 沉下、浮起(Agent) | 同一 id 在根层相邻两帧从 ≥ 3 变成 < 3 是沉下,从 < 3 变成 ≥ 3 是浮起;沉了几轮 = 份额 < 3 的连续帧数 | records[i].state.root.items |
| 推后(你) | event.feedback[] 里 meaning: 'snooze' 的反馈,带 until;到点提醒是带 reminder 的事件 | frames[i].events |
| 推后的问题 | frame.asks 里 expects: 'defer' 的登记 | frames[i].asks |
| 一粒一条 | 每条 frame.events 按 disposition 定去向:used 进它所属的事项(先看本轮批次声明的 sources 引用了它,再看 relatedIds[0],沿父项与合并找到当前层的那一件,与脉冲同一规则),落进当前层某一滴或某颗小珠的画一粒;deferred 只记进泥的粒数;其余不画 | frames[i].events、frames[i].batch.declarations |
| 等你回应 | 当前层条目里 currentAsk 不为空的件数,减去本页已经回应了同一个问题 id、且没有到点浮回的 | shared.mjs currentAsk;本页记录 |
2.2 两周里推后了几件
composite:0 件。 29 帧 3196 条输入里,没有一条 meaning: 'snooze' 的反馈,也没有一条到点提醒。登记过两个推后的问题(6 次登记),都没有人回答:
| 问题 | 事项 · 份额 | 开着 | 结局 |
|---|---|---|---|
| ask-13「#13295 复审推到哪天?」 | pr-13295-writeout · 1 份(一颗小珠) | f12–f17 | f18 事项并入 pr-reviews-pending,问题随之关上 |
| ask-22「三个旧 PR 推到哪天?」 | pr-reviews-0814(待审 PR)· 6 份 | f23–f28 | 到 f28 仍开着,份额 6 → 3 |
story:1 件,沉了 0 轮。 10/22 18:52 林苒对“苏禾”(hiring)点了稍后提醒,推到 10/23 09:30(frames[32].events 的 f30-e04,via: button);09:30 到点(f30-e05,reminder.feedback = 'f30-e04'),09:31 她回复,答掉 ask-35「苏禾提议周六 10 点」(f30-e02,action: call-morning)。三件事都落在 f29(18:40)之后、f30(09:40)之前,所以回放里它一轮也没有沉:f29 与 f30 都是 3 份,f30 的标题已是“苏禾周六早上通话,已回复”。story 没有登记过推后的问题。
所以“你推后的事沉下去、到点浮起”在两套真实数据里都没有发生过;它只在你在本页作答时发生(2.6)。真实数据里每轮都在发生的,是 Agent 自己让能等的事沉下去(2.3),和一轮里被判暂缓的输入沉到水底(2.4)。
2.3 沉下又浮起:Agent 让它等
composite 根层两周沉下 26 次、浮起 8 次,其中沉下之后又浮起来的 6 次:
| 事项 | 沉下 | 在水下 | 浮起 | 浮起时份额 |
|---|---|---|---|---|
猫看病 smilla-vet | f06,3 → 1 | 9 轮(f06–f14) | f15「周一上午:看兽医」 | 1 → 6 |
主水管 driveway-wet | f18,3 → 1 | 6 轮(f18–f23) | f24「车道边湿了一整周」 | 1 → 6 |
master CI ci-urls-master | f19,3 → 1 | 4 轮(f19–f22) | f23「打了诊所;母亲自己定了」 | 1 → 6 |
FTPS 计时 issue-13587 | f25,3 → 1 | 1 轮 | f26「车道下的铜管裂了」 | 1 → 6 |
Kerberos issue-13466 | f02,3 → 1 | 14 轮(f02–f15) | f16「车道边是湿的;不改签」 | 1 → 3 |
后刹 kajsa-bike-brake | f21,6 → 1 | 5 轮(f21–f25) | f26 | 1 → 3 |
另 20 次沉下之后再没浮起(撤下、并入或停在 1 份到两周结束),2 次是一出现就是小珠、后来浮起(H2 push f06 1 → 3,开学文件 signe-school-start f28 1 → 6)。根层的小珠在 composite 里全是 1 份;水面以下最多的一轮是 f15,17 件 17 份,水面上 8 件 45 份。
猫看病是“沉下去、到点浮起”最完整的一次,全部来自回放:
| 帧 | 份额 | 事项标题(records[i].state 里 smilla-vet 的 title) | 发生了什么 |
|---|---|---|---|
| f04 8/5 00:00 | 12 | Smilla兽医仍未约,你称正在约 | 开着 ask-05「Smilla 约上兽医了吗?」 |
| f05 8/5 12:00「约好兽医,vdns 合并」 | 12 → 3 | Smilla兽医已约8/10 10:15 | 08:07 iMessage 里“Bokat mån 10:15 på Kvarnbacken”(feedback: {ask: 'ask-05', action: 'done', meaning: 'act', via: 'source'}),问题关上 |
| f06 8/6 00:00 | 3 → 1 | 同上 | 沉到水面以下,成为小珠 |
| f07–f14 | 1 | f09 起改写为“Smilla周一兽医,周末Gunnel喂” | 每轮整理都又看了它一眼,让它留在水底;这 9 轮里有 9 条输入进了它(f09 4、f10 1、f11 2、f13 2) |
| f15 8/10 12:00「周一上午:看兽医」 | 1 → 6 | Smilla牙龈炎,每日半片药共7天 | 10:15 看完兽医之后的第一轮整理,它带着结果浮上来;本轮 4 条输入进了它 |
它之后还会长到 24 份(f22 药量),那是另一件事。story 用同一条规则选(下面 3 节的“英雄事项”):费用 expenses f07 3 → 1(“API费用拆分待核,疑有重复任务”),在水下 13 轮(f07–f19),f20 浮起 1 → 12(“API月费预测6400元,上限待定”),浮起时带着新登记的 ask-27「定本月 API 上限」。story 另两次:链路 f23–f25 3 轮、f26 浮起 1 → 6;苏禾 f20–f28 9 轮、f29 浮起 1 → 3(就是 2.2 里那次稍后提醒之前的一刻)。
2.4 一轮涌入:一粒一条采用的输入
composite 3196 条 = 采用 1177 + 暂缓 1117 + 噪音 864 + 重复 33 + 未判 5,每轮 54–234 条(中位 108.5,与脉冲同)。story 112 条 = 采用 84 + 噪音 12 + 重复 10 + 暂缓 6,每轮 0–9 条,f17、f27 两次零事件整理。
画面只画采用、并且落进当前层某一滴或某颗小珠的,一条一粒:composite 根层两周 1136 粒(采用的 1177 条里有 41 条进的事项那一轮已不在这一层),每轮 7–94 粒;story 82 粒(采用 84),每轮 0–8 粒。初版每条输入一粒,composite 每轮 54–234 粒,用户看后嫌多(第 6 节)。暂缓的不落,只是水底泥的粒数;噪音、重复、未判不画,数字留在信号行与概览里。
暂缓按 SKILL.md 的定义是“它带来新的请求、机会或话题,但现在纳入不会改变用户下次回来时的理解或行动”,就是 Agent 判断“这一条可以推后”。两周 1117 条暂缓里,后来被任何事项的 sources 引用过的是 0 条(逐帧查 records[i].state 全部条目的 sources[].ref)。所以泥只沉不浮:概念层不能把它画成会浮回来。
故事用到的三轮:
| 帧 | 收到 | 采用 · 暂缓 · 噪音 · 重复 | 落下的粒子 | 进了猫看病 |
|---|---|---|---|---|
| f05 | 70 | 35 · 20 · 15 · 0 | 32 | 4 |
| f06 | 158 | 38 · 79 · 40 · 1 | 38 | 0 |
| f15 | 71 | 54 · 1 · 9 · 7 | 51 | 4 |
2.5 守恒:每轮的长缩
相邻两帧根层份额之差,变小与撤下记让出,变大与新增记拿到,留白也算一方;回执不记谁让给谁(与拨盘 rules.yield 同),画面只能同一轮里一齐变。
| 帧 | 让出 | 拿到 | 留白 |
|---|---|---|---|
| f05 | 猫看病 12 → 3(−9),vdns 搬迁 1 → 0 撤下(−1) | master CI 3 → 6(+3),H2 push 0 → 1(+1) | 0 → 6 |
| f06 | 定时器重构 6 → 3,猫看病 3 → 1,受邀演讲 1 → 0 撤下,共 −6 | 抬棚屋、回环提速、幼儿园开园各 +3,H2 push 1 → 3,测试名长度 +1,共 +12 | 6 → 0 |
| f15 | 幼儿园开园 6 → 0 撤下,钓鱼短信、NDEBUG、Windows 支持各 3 → 1,共 −12 | 猫看病 1 → 6,NTLM 复用 +3,另 4 件各 +1,共 +12 | 2 → 2 |
| f23 | 母亲换水管 24 → 6,猫看病 24 → 12,共 −30 | master CI 1 → 6(+5,同时浮起) | 0 → 25(两周最大) |
2.6 作答样张:f25 把待审 PR 推后
默认链接 f25(8/15 12:00「周六早上:水表在转」)有 3 件等你回应:待审 PR(6 份,ask-22「三个旧 PR 推到哪天?」,defer)、Ture 发烧(3 份,ask-19「VAB 报了吗?」)、开学文件(1 份小珠,ask-21「Signe 的同意书签了吗?」)。推后的三个时刻按数据集时区(Europe/Stockholm)算:1 小时后 13:00、2 小时后 14:00、明早 8/16 09:00(respond.mjs 的 deferOptions)。
| 时刻 | 选“明早 09:00”之后 |
|---|---|
| f25 按下 | 回应控件原地收成回执“◷ 8/16 09:00 再提醒 · 已保存”;3 件 → 2 件;液滴不动,旁边写“推到 8/16 09:00 · 下一轮整理读到才沉” |
| f26 8/16 00:00 | 切点回执填实“已收到”;沉浮拍里液滴沉进托盘,下方标“◷ 09:00”;64 格带里它的 6 格变斜纹;回放里这一轮等你回应的是 5 件(主水管、FTPS 计时、待审 PR、Ture、开学文件),减去推后的 1 件写 4 件 |
| f27 8/16 12:00 | 已过 09:00:沉浮拍里它从托盘浮回原处,概览出现“◷ 到点浮起”;回放里 ask-22 仍开着,所以它重新算进等你回应,4 → 5 |
| 份额 | 6 → 6 → 6:推后不改份额,沉了 1 轮,浮起时份额没有变 |
选“1 小时后”或“2 小时后”:下一轮整理(f26,00:00)读到时已经过了时刻,它不沉,回执直接写“已到点,在 8/16 00:00 这一轮浮回”。
3 故事(story)
论点。 能等的事沉下去,不是丢掉:它在水底留着一份,到该看它的那一轮整理,带着新消息浮上来。64 份是同一池水:它沉下去时让出的,同一轮别处接住;它浮上来要的,同一轮别处让出。
英雄数字:9(轮)。 英雄事项在水下待了几轮整理。规则:根层沉下又浮起的事项里,浮起时份额涨得最多的;一样多取沉得最久的。composite 选出猫看病(+5,9 轮;主水管、master CI、FTPS 计时同为 +5,沉得更短),story 选出费用(+11,13 轮)。帧号由规则现算,不写成常量。
- 它是一只按轮数的钟,单位用“轮”:一轮就是一次整理,Agent 每一轮都重新看了它一眼、让它继续留在水底。composite 每轮正好 12 小时,9 轮就是 108 小时;story 不定时,只写轮。
- 第 1 拍它沉到水底的那一刻出现在它的小珠旁,写“在水下 1 轮”,字高约为水底那一条的三分之一,全场只有它这么大。
- 每一轮的切点硬切加一,不滚动:数字只在节拍上动。
- 第 3 拍它浮起时数字停在 9,标签换成“沉了 9 轮 · 1 → 6 份”,随它浮出水面后淡去。
- 讲完之后的日常阅读里,它变成卡与面板上的一行:悬停一颗小珠写“在水下第 N 轮”,浮起过的液滴写“沉过 9 轮,f15 浮起”。
3.1 讲一遍:三拍
只在顶层直接打开、地址不带 ?frame=、没有 referrer 时演一次(约 20 秒);任意键或点一下跳到终态,顶栏“▶ 讲一遍”重演;带帧的链接、画廊、合页、从别处链来一律静止。括号里是 story。
| 拍 | 时长与帧 | 发生什么 | 观众此刻 |
|---|---|---|---|
| 1 沉下去 | 0–6.5 秒,f04 → f05 → f06(f05 → f06 → f07) | 停在 f04,静 0.4 秒;f05 一轮按全节拍走:70 条输入里 32 粒落下(采用、并且落进这一层的),4 粒落进猫看病;长缩拍里猫看病 12 → 3,同一时刻 master CI 3 → 6、H2 push 0 → 1、留白 0 → 6;f06 一轮:38 粒落下,水底的泥原地淡入到 79 粒;猫看病 3 → 1,沉浮拍里穿过水面成为小珠,旁边亮出“在水下 1 轮”与它的标题“Smilla兽医已约8/10 10:15” | 约好了,它就不用占地方了 |
| 2 等 | 6.5–13.5 秒,f07 → f14,8 轮、每轮约 0.85 秒(f08 → f19,12 轮、每轮 0.6 秒) | 每轮都按四段走,只是快:粒子落下、上面的液滴此消彼长、偶尔有别的沉下或浮起;水底那颗小珠不动,切点上计数 2 → 9;f09 它的标题改写为“Smilla周一兽医,周末Gunnel喂”,标签同一刻硬切 | 上面一直在变,它一直在底下等 |
| 3 到点浮起 | 13.5–20 秒,f15(f20) | 全节拍:51 粒落下,其中 4 粒穿过水面落进那颗小珠;长缩拍里它在水底长到 6 份(水下比例),同一时刻幼儿园开园撤下、三件从 3 份缩到 1 份;沉浮拍里它浮出水面回到上面,三件同时沉下;计数停在 9,标签“沉了 9 轮 · 1 → 6 份”,新标题“Smilla牙龈炎,每日半片药共7天”;落定 0.6 秒后它沿自己的线弹出 8 px,引线出到详情卡,停 2.4 秒后收回 | 到点了,它带着结果回来 |
3.2 换轮:一轮的四段
讲一遍、▶ 连播(每 5.6 秒一轮)和 ← / → 往后一步都走同一张表。秒,从切点起。
| 段 | 时刻 | 什么动 | 怎么动 |
|---|---|---|---|
| 切点 | 0 | 日期、“第 N / 29 次整理”、旁白 frame.title | 一帧硬切;本页回执若在这一轮被读到,空心勾 0–0.3 秒填实,写“已收到”;信号行的数写“…”,泥不动 |
| 涌入 | 0–1.0 | 本轮采用的输入一条一粒,从水面上方落下 | 只画采用、并且落进当前层某一滴或某颗小珠的;噪音、重复、未判与暂缓的不画。按 event.at 先后在 0–0.45 秒里放出,每粒 0.5 秒先慢后快地落下,落进它所属的那一滴就不见了,落向小珠的穿过水面后变慢;信号行“本轮输入 N 条”在涌入段末(1.0 秒)一帧翻成 N,不随粒子数(粒子数是采用的条数,不等于 N)。零事件整理不下雨 |
| 长缩 | 1.0–1.7 | 份额变化、留白、新增、撤下、合并,以及留在水面上的液滴的位置 | 所有变化走同一条缓入缓出、同起同落,不过冲:任何时刻各滴份额加留白都是 64。新增的从它的粒子落点长出来;撤下的缩到没有、往下沉一点;并入的流进它并入的那一滴(只有这里画“谁到谁”)。要过水面的这时只在原处长缩,不动位置。水底的泥同一条缓动原地换成本轮的暂缓条数:多的淡入、少的淡出,不走路径 |
| 沉浮 | 1.7–2.5 | 跨过 3 份的、本轮读到的推后、到点的推后 | 沉下的先竖直落到水面再滑向小珠位或托盘,浮起的反过来,各走一段;过水面时按深度比例变成九分之一面积。没有要过水面的轮跳过这一段 |
| 数字落定 | 2.5 | 滴里的份数、标签、朱红环、64 格带、水底与托盘的说明、标题行、阅读区 | 一帧硬切;新出现的标签 0.2 秒淡入 |
往回走(←)、拖时间轴或一次跨几帧:没有粒子,长缩与沉浮并成 0.42 秒一段,泥在这一段里原地换成那一轮的条数,数字在末尾落定。任何时刻换轮都从屏幕上当前的样子起算,不跳;被打断的那一轮的粒子 0.2 秒淡掉。prefers-reduced-motion 时直接到终态。
3.3 两层
- 信息层(约六成,少、准、有出处): 液滴的大小与位置(份额)、滴里的份数、问题的标签与朱红环、小珠、托盘与“◷ 09:00”、64 格带、水底与托盘的说明、标题行、信号行、阅读区。墨黑、各类别的颜色与一个朱红;只在“数字落定”与作答时变。
- 概念层(三到四成,只用灰): 液滴的凝胶质感(透光、焦散、接触阴影、移动时沿速度方向微微拉长)、落下的粒子、水底的泥。粒子与泥是墨色的低透明度圆点,不出文字、数字,不用朱红;粒子比泥还轻(半径约 1 px、不透明度约三成)。
- 共用几何与数量: 两层用同一个容器:同一条水面、同一个水底、同一套面积比例(水面以上 1,水面以下九分之一)。粒子落进的就是信息层上那一滴;粒子数 = 本轮采用的条数(落进这一层的;详情卡“本轮进了它 N 条”就是落进它的粒数),泥的粒数 = 概览里的“暂缓 N”;液滴面积 = 64 格带里它的格数。
- 不打架: 粒子画在液滴下面,落进液滴就被它挡住;泥在小珠与托盘下面;悬停与展开时概念层降到四成,其余液滴降到七成(压暗在着色器与 Canvas 退路里用同一个公式)。
3.4 动起来之前的约束
- 绝不动: 水面的位置、64 格带的位置与格数、颜色的含义(颜色 = 根层事项的类别;朱红只给登记的问题与它的期限)。
- 体积守恒: 长缩拍里不过冲;同一份额在任何一轮都一样大(整层的比例按这一层两周里最挤的一轮定,不再逐帧换档)。
- 每个元素一轮只走一段: 一滴在长缩拍只长或缩一次,在沉浮拍只过一次水面。
- 不提前: 下一轮整理读到之前,推后的不沉;“数字落定”之前,份数、标签、朱红环与格带不变。
- 泥不浮起: 1117 条暂缓没有一条后来被引用,所以泥只表示这一轮的暂缓条数,下一轮在长缩段里原地换成那一轮的条数,不积累,不飞,也不画成会回来。
- 静止时零运动。
- 只读同一份回放: frames、批次、asks、feedback,不加只为动画服务的字段,也不改契约;本页作答只在浏览器内存里,不改写回放。
4 交互
与份额拨盘、整理脉冲、回应时序同一套标准,时序取 motion/ix.mjs 的 IX,卡片是同一套概要卡与详情卡(换成 C 的纸色与字体)。这里只写 C 的几何。
- 悬停
- 触发:鼠标移到液滴(命中区放宽到液滴外 12 px)、它的标签、水底的小珠(外 8 px)、托盘里的事或 64 格带里它的格子;Tab 到它(每件一个隐藏按钮,顺序按 rank);触屏轻点一下。
- 动什么:液滴场的钟停在当前时刻(轮中途也停),播放停住;这一滴保持原样并微微从里面亮起,其余液滴与小珠降到七成,粒子与泥降到四成;概要卡贴在液滴朝外的一侧,一根细引线从液滴边上连到卡。卡上写来自哪条流、几份、本轮怎么变、本轮进了几条输入、开着的问题与期限,C 另加一行位置:“在水面上”“在水下第 4 轮”“推到 8/16 09:00 · 已收到”。
- 节拍:停在轮中途时,粒子没落完写“本轮的输入还在路上”,数字落定前写“本轮的份额还没落定”,本轮新登记的问题落定前不列。
- 收回:离开 0.15 秒后卡 0.12 秒淡出,再过 0.4 秒钟与播放接着走。
- 展开
- 触发:点液滴、标签、小珠、托盘里的事、格子或概要卡;Tab 后按 Enter 或空格。
- 0 秒切点:旁白改写为“这一件 · 8/15 12:00 · 条目标题”,右栏切到“这一件”(摘要、回应、份额理由、依据、来源处的回应、两周版本)。
- 0–0.3 秒:液滴沿“容器中心 → 液滴中心”这条线向外推 8 px(缓出,过冲 3.7%),大小不变;水底的小珠与托盘里的事向上推 8 px。
- 0.4–0.6 秒:引线从液滴边上沿同一条线出去,过了它的标签再连到卡。
- 0.6–0.9 秒:详情卡从引线末端展开,先沿接上的那条边、再向外;卡贴在容器离它远的一侧,避开它自己与标签。
- 卡上是 ix 的详情(摘要、子层、份额逐轮、本轮进了它的输入、开着的问题与期限、你的回应),问题旁不是“去回答 →”,是“在右栏作答”:C 自己能作答,点它把焦点移到右栏的回应控件。
- 收回:Esc、点空白、再点同一件:卡 0.2 秒收起、引线 0.15 秒缩回、液滴 0.2 秒落回,然后一帧切回旁白与右栏,0.4 秒后接着走。点另一件先收回再展开。←/→、拖时间轴、▶ 与合页顶栏选帧会立即收起并照常执行。
prefers-reduced-motion时直接到终态。
- 作答(右栏):按下回应控件,控件原地收成回执(0.2 秒),“N 件等你回应”减一;液滴旁的问题标签原地换成回执一行,朱红环换成墨色虚线环。下一轮整理的切点回执填实、虚线变实线;推后的在那一轮的沉浮拍里沉进托盘。撤回逆着走;已经沉下的撤回后马上浮回(0.6 秒)。
- 脚本:
window.foldC有与foldDial同名同义的hover / unhover / open / close / ixAt / ix / paused / pointOf / ixState,名字可用条目 id 或短名(猫看病、待审 PR);另有beatAt(t)把换轮停在切点后 t 秒(定格用)、tell()与skip()、answer(id, action)。
5 实现(motion、direct)
改的文件:prototype/variants/c.mjs(重写)、c-field.mjs、c.css;scripts/check-ix.mjs 的 PAGES 末尾加了 c。temporal.mjs、ix.mjs、explore.mjs、shared.mjs、respond.mjs 没有改。
- 一个钟,按段取值。 每件事是一个“体”,位置、份额、深度(0 水面上,1 水面下)、透明度、颜色、环各有一段
[起, 止, t0, t1, 缓动],每帧在同一个换轮钟上取值。换轮时先把所有体冻结在屏幕上的当前值,再按四段的时刻表排新的段,所以任何时刻打断都从眼前起算,不跳。种类:turn(一轮往后,四段全走)、fast(讲一遍第二拍,四段按比例压缩到一轮约 0.9 秒,story 约 0.6 秒)、jump(往回、拖轴、跨几轮:长缩与沉浮并成 0.42 秒)、layer(进出子层 0.5 秒)、local(作答撤回引起的沉浮 0.6 秒)、instant(首屏、减少动态、后台标签页)。 - 守恒是构造出来的。 长缩段里所有份额(新增的从 0、撤下与并入的到 0)用同一条缓入缓出、同一对 t0 / t1,所以各体份额之和 = 前后两轮已分配的同一插值,留白 = 64 减它。f25 → f26 定在 1.35 秒时量得体的份额和 57.5、留白 6.5(52 → 63 的正中)。
- 信息层只在落定时换。 页面留着一份
shown快照:份数、标签、环的墨色与虚实、64 格带、水底与托盘的说明、标题行、阅读区都读它,只在落定那一帧换成新的;信号行“本轮输入 N 条”是唯一在落定之前动的数:切点写“…”,涌入段末(1.0 秒,讲一遍第二拍按比例压缩)一帧翻成 N。 - 粒子与泥 画在液滴下面的一张 2D 画布上,墨色低透明度;WebGL 与 Canvas 2D 退路共用这一张,两条路一起改到。只画采用、并且落进当前层某一滴或某颗小珠的输入,一条一粒,半径 1.05 px、不透明度 0.29(初版 1.5 px、0.42 的七成)。粒子按
event.at在 0–0.4 秒放出;落进的体用batch.declarations[].sources里的event:引用,否则relatedIds[0],沿合并与父级上到当前层(与整理脉冲同一规则,详情卡的“本轮进了它 N 条”用的也是这个规则)。泥不由粒子落成:容器最下 27 px 里按编号排着一串固定点位(半径 1.3 px、不透明度 0.26),第 j 个在 j 小于本轮暂缓条数时显示;换轮时每个点位从眼前的不透明度沿长缩段(1.0–1.7 秒)的缓动走到新目标,多的淡入、少的淡出,打断时从眼前接着走;往回走、跳帧在那 0.42 秒里换,首屏直接铺好。 - 排布。 每次换轮只解一次布局(
solveLayout),以各滴上一轮的位置为初值;要沉下去的滴作为固定节点占着原位,别的滴绕开它;每滴的“家”按层记住,所以沉下又浮起的事回到原处。整层的比例按这层两周里最挤的三轮定一次(约 0.7–1),不再逐帧换档。 - 压暗 在着色器与 Canvas 退路里用同一个式子:其余体向纸色混 30%(
DIM_TO_PAPER),阴影与环乘同一个系数;粒子与泥降到四成。液滴、小珠、托盘里的事都画在 WebGL 里(最多 32 个体),这样压暗、弹出与命中对每一种都一样。c-field.mjs加了每体的压暗与环墨色,把分组参数并成一个vec4数组,片元着色器的 uniform 向量用量低于 WebGL2 保证的 224。 - 交互 接
motion/ix.mjs:命中按到圆心的距离(液滴边缘外 12 px,小珠与托盘外 8 px),标签与 64 格带的格子也算;弹出方向是“容器中心 → 液滴中心”,小珠与托盘向上;详情卡站在弹出的那一侧,放不下时换到另一侧,引线从液滴朝卡的那一点直连到卡边。▶ 是 C 自己的播放器:每 5.6 秒一轮,悬停或展开时连同剩余时间一起停住,移开后从剩下的时间接着走;底栏的空格也转到它。 - 作答。 右栏控件原地收成回执(盒子高度 0.2 秒缓到回执的高度,回执在 45% 之后淡入);同一刻标题“N 件”减一,液滴旁的标签换成回执一行,朱红环换成墨色虚线环,液滴不动。下一轮切点虚线变实线,落定时回执写“已收到”;推后的在那一轮的沉浮段沉进右侧托盘,下面挂“◷ 09:00”,64 格带里它的格子换成斜纹;到点那一轮的沉浮段浮回原处,信号行写“◷ 1 件到点浮起”,右栏顶上列出。已经沉下的撤回后马上浮回(这一段不受悬停停表的约束)。
- 讲一遍。 只在顶层、加载地址不带
frame=(读performance的导航记录,地址栏已被改写过)、没有 referrer、不在prefers-reduced-motion下时开始。步骤等前一轮落定再走;第二拍每轮固定间隔。英雄数字是小珠下方的一枚小签:“N 轮在水下 · 标题”,每个切点后落定时加一;浮起后写“9 轮之后浮起 · 1 → 6 份”,详情卡出来时让开。任意键或按下一次跳到浮起那一帧;顶栏“▶ 讲一遍 / 跳过 ▸▸”。嵌入(is-embedded)时按钮不显示。 - 钩子
window.foldC:与foldDial同名同义的hover / unhover / open / close / ixAt / ix / paused / pointOf / ixState;另有index、go(i)、play()、stop()、tell()、skip()、telling()、beatAt(t)(把换轮钟钉在切点后 t 秒,null放开)、beat()、pick()(水面上最大的一滴)、nearOf(name, d)、geom(name)、census(i)(第 i 轮的条数n、进了这一层的used、画成粒子的drawn、成泥的deferred;beat().grains是这一轮实际放出的粒数)、waiting()、answer(name, action)、defer(name, until)。
6 与故事的出入、定格与验证(ship)
与第 3、4 节的出入
- 讲一遍实测:composite 21.0 秒(f07 在 6.0 秒,f15 在 13.5 秒,详情卡 16.9 秒出现),story 20.8 秒(f22 在 13.3 秒)。第二拍每轮 composite 约 0.9–1.0 秒(压缩后的四段比 0.875 秒的间隔略长,以落定为准)、story 0.6 秒。
- 英雄签不是“字高为水底那一条的三分之一”的大字,是小珠正下方一枚 22 px 数字的小签(放在水底说明那一行下面,不压字);浮起后写“9 轮之后浮起 · 1 → 6 份”,不是“沉了 9 轮”,并且在详情卡出来时让开,而不是浮出后 1.2 秒淡去(卡上同一行“沉过 9 轮,第 16 次整理浮起”接着讲)。
- 标签:第 3.2 节写“数字落定前份数、标签不变”。实现里,滴会移动或标签内容会变的那些标签在长缩开始时淡出、落定时带着新内容回来(0.2 秒),不动的标签留着;否则旧标签跟着长大的滴走会压到别的滴上。
- 回执:切点上只把环的虚线填实;回执的字“已保存 → 已收到”跟其他文字一起在落定时换。
- 引线:没有沿“中心 → 液滴”那条线先出去再拐,而是从液滴朝卡的一点直连到卡边;卡站在弹出的一侧(放不下时换边),所以多数时候两者是同一个方向,换边时直连不会横穿液滴。
- 作答之后不重排:回执比问题标签矮,原位换上不会压到别处;沉不沉只看下一轮整理。
- 减少动态时不自动讲;顶栏的“▶ 讲一遍”仍在,按下仍逐轮走一遍,每轮直接到终态。
- 卡与面板上的位置行写“第 16 次整理浮起”(与顶栏“第 N / 29 次整理”同一数法),不写
f15。 - 帧标题与面板里的“这一件”取的是展开的那一件,不改
model.select;所以合页与地址栏不记它。 - 体最多 32 个(composite 单层最多 26 件,story 9 件);换轮时离开的体加新来的超过 32 时,最小的一颗小珠会少画不到一秒。
- 粒子减量(上线后):用户看后说换轮时“飞的小点点有点多了”,其他都好。composite 一轮 54–234 条,每条一粒,满屏在下雨。改成只画采用、并且落进当前层某一滴或某颗小珠的,一条一粒;噪音、重复、未判不画;暂缓的不再一粒一粒穿过水面,泥照旧等于暂缓条数,但不飞,切点后在长缩段里原地淡入到新数量(多的淡入、少的淡出);粒子直径与不透明度各降到约七成,落下时序不变;信号行的数不再随粒子逐粒数,切点写“…”,涌入段末(1.0 秒)一帧翻成 N。所以第 1.3、3.3 节的“粒子数 = 本轮条数”改为“粒子数 = 本轮采用的条数”。f25 → f26:composite 158 → 39 粒(采用 40 条,1 条进的事项这一轮已不在根层;暂缓 84 条成泥),story 4 → 3 粒;两周合计 composite 3196 → 1136 粒,story 112 → 82 粒。
定格(1440×900,composite,--gpu;本机临时目录,不提交):f25 → f26 钉在 beatAt(0.35) 涌入中(粒子减量之后:39 粒,信号行写“…”,水底还是上一轮的泥;初版这一刻是 158 粒满屏)、beatAt(1.35) 长缩守恒中(份额和 57.5 + 留白 6.5 = 64,疑漏水 12 → 24 正在长,泥半淡入,向 84 粒走)、beatAt(2.1) 沉浮(#13587 与 Kajsa 旧车从水底浮起、Ture 那滴 3 → 1 沉成小珠);回到 f25 把“三个旧 PR”推到明早 09:00 之后的回执(2 件等你、墨色虚线环、标签换成回执一行);悬停疑漏水的压暗;展开疑漏水 ixAt(0.9)。
验证
node scripts/check-ix.mjs(全部页面,composite 与 story,rebase 到 #27 之后重跑):整理脉冲、份额拨盘、腕上一瞥、批次记谱、两周小倍数、回应时序、让位图、B 与 C 各 16 / 16,D 手机首页 23 / 23。node scripts/shoot-forms.mjs --only c,all --gpu --check all-sync:两套数据无报错,合页逐节跟上所选帧。- 帧率(无头 Chrome,从 f03 连播 17 秒):
--gpu时 WebGL2 60 帧,帧间隔 p95 16.8 ms、没有超过 20 ms 的;不开显卡退到 Canvas 2D 也是 60 帧、p95 16.7 ms;每帧脚本 p50 0.3 ms、最差 5.5 ms(换轮那一帧,布局求解 2.5–6.4 ms)。落定后data-c-motion回到idle。 - 打断:轮中途展开一件、作答、再往回一轮,过水面的体从眼前的深度接着走完(修过一处:被打断的过水面会停在半深处)。
- 减少动态:换轮直接到终态(
beat().kind === 'instant'),不自动讲。 - 作答流:f25 推后“三个旧 PR”→ 等你 3 → 2;f26 沉进托盘(4 件等你,“你推后的 1 件 6 份”);f27 浮回原处(5 件等你,“◷ 1 件到点浮起”),与第 2.6 节一致。
make check通过;npm run site:build通过;预览图site/previews/c.webp按 docs/development.md 的命令重截(--frame 25,嵌入时不显示“讲一遍”)。- 粒子减量之后重跑:
node scripts/check-ix.mjs全部页面两套数据都过(C 16 / 16);shoot-forms.mjs --only c,all --gpu --check all-sync两套数据无报错;帧率(从 f03 连播 17 秒)--gpuWebGL2 60 帧、p95 16.8 ms、没有超过 20 ms 的,Canvas 2D 退路 60 帧、p95 16.8 ms;落定后data-c-motion回到idle,之后 2 秒一帧不画。f25 → f26 钉住看:信号行 0.99 秒仍是“…”,1.0 秒翻成 158;泥 0.35 秒还是上一轮的,1.35 秒半淡入,2.6 秒停在 84 粒。make check通过。