Skip to content

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 px1 份与 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 − usedrecords[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–f17f18 事项并入 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-vetf06,3 → 19 轮(f06–f14)f15「周一上午:看兽医」1 → 6
主水管 driveway-wetf18,3 → 16 轮(f18–f23)f24「车道边湿了一整周」1 → 6
master CI ci-urls-masterf19,3 → 14 轮(f19–f22)f23「打了诊所;母亲自己定了」1 → 6
FTPS 计时 issue-13587f25,3 → 11 轮f26「车道下的铜管裂了」1 → 6
Kerberos issue-13466f02,3 → 114 轮(f02–f15)f16「车道边是湿的;不改签」1 → 3
后刹 kajsa-bike-brakef21,6 → 15 轮(f21–f25)f261 → 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:0012Smilla兽医仍未约,你称正在约开着 ask-05「Smilla 约上兽医了吗?」
f05 8/5 12:00「约好兽医,vdns 合并」12 → 3Smilla兽医已约8/10 10:1508:07 iMessage 里“Bokat mån 10:15 på Kvarnbacken”(feedback: {ask: 'ask-05', action: 'done', meaning: 'act', via: 'source'}),问题关上
f06 8/6 00:003 → 1同上沉到水面以下,成为小珠
f07–f141f09 起改写为“Smilla周一兽医,周末Gunnel喂”每轮整理都又看了它一眼,让它留在水底;这 9 轮里有 9 条输入进了它(f09 4、f10 1、f11 2、f13 2)
f15 8/10 12:00「周一上午:看兽医」1 → 6Smilla牙龈炎,每日半片药共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)。所以泥只沉不浮:概念层不能把它画成会浮回来。

故事用到的三轮:

帧收到采用 · 暂缓 · 噪音 · 重复落下的粒子进了猫看病
f057035 · 20 · 15 · 0324
f0615838 · 79 · 40 · 1380
f157154 · 1 · 9 · 7514

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,共 +126 → 0
f15幼儿园开园 6 → 0 撤下,钓鱼短信、NDEBUG、Windows 支持各 3 → 1,共 −12猫看病 1 → 6,NTLM 复用 +3,另 4 件各 +1,共 +122 → 2
f23母亲换水管 24 → 6,猫看病 24 → 12,共 −30master 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 秒)--gpu WebGL2 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 通过。