切换外观
A 空间排布 · 动画记录
状态:2026-10-20,按第三轮的六步流程(reference → data → story → motion → direct → ship)重做 A 空间排布。第 1–4 节(参考、数据、故事、交互)在任何东西动起来之前写下,代码照它做;第 5–7 节是实现、与故事的出入和交付。原型探索,不是规范。A 现在怎样工作、承载哪类回应见 spatial.md;两层法则与交互标准见 CONCEPTS.md;阅读加作答的范本是 D(home-motion.md:答一件少一件、回执跟着条目走)与腕上一瞥(watch-motion.md:换轮三拍、自动讲一遍)。数字都由 scenario.mjs 回放现算,规则见 2.1;推算脚本只留在本机,不入库。
A 和 D 一样是阅读加作答的工具,场景是“桌前,生活急事要当场二选一”。和 D 不同的是 A 有一张桌面:纸片的位置是先后(越急越靠左上),面积是份额,空着的桌面是留白。所以 A 的动画要回答的不只是“答了之后少了一件”,还有“它让出的面积去了哪、谁挪到了哪里”。
1 参考(reference)
1.1 A 现在怎么变
读 a.mjs、a.css 与 spatial-kit.mjs 所见:
| 时刻 | 现在怎么动 | 断在哪 |
|---|---|---|
| 换轮(←/→、时间轴、空格每 5.6 秒) | 纸片用 createFlipBoard:留下的平移缩放 320 ms,新纸片从 60% 大小处晚 58 ms 淡入,撤下的原地收缩,并入的飞进目标;左栏 flipList 280 ms,离开的卡 112 ms 淡出,新卡随后进来;标题、日期、本轮调用一行与份额读数硬切 | 一切挤在 0–0.32 秒里一起动,没有先后。位置跨帧不稳:composite 27 次换轮里纸片共挪了 224 次,其中 163 次(73%)是份额没变、只因别人变了被挤走的,这 163 次共走了 29 345 px(2.4 节)。眼睛分不出谁是真挪了 |
| 作答(choose、approve 一键;稍后、回一句、定时间展开面板) | 卡经 flipList 换成回执(280 ms),下面的卡补位;底部弹 4.2 秒 toast“…已保存,等 Agent 下一轮整理”带撤销;纸片上的“待你选择”交叉淡成“✓ 已回应”(320 ms) | 卡、补位、纸片同一刻变;toast 在屏幕底部中央,离卡和纸片都远 |
| 下一轮整理之后 | 回执按“条目 + 问题”找(createAnswerBook),问题还开着时仍在;问题一关,左栏只列开着的问题,回执随卡一起淡出;纸片上也只在问题开着时写“✓” | 回执凭空消失:f22 答“打给诊所”,f23 猫看病的问题关上,卡和回执一起不见,纸片上什么都没留下;底部再弹一条 toast“Agent 已在 12:00 的整理中收到你的 1 条回应”,与桌面上正在发生的让位没有几何联系 |
| 详情 | 点纸片,右侧滑入 440 px 的阅读面板(240 ms),盖住桌面右侧 | 与被点的纸片没有几何联系;打开时右侧纸片点不到(spatial.md 已记) |
| 悬停 | 只有太小或被裁切的纸片在 220 ms 后弹出全文;其余纸片页脚出现“看详情 ›”;卡片、本人动作与纸片双向描边 | 与第三轮的概要卡不是同一套:没有停表,也没有“本轮怎么变、进了几条输入” |
1.2 从 D、腕上一瞥与第三轮搬什么,不搬什么
| 要素 | 出处 | A |
|---|---|---|
| 切点一帧硬切,0.4 秒后才有第一个大动作 | 拨盘、D 的 turn | 搬。切点只换字;0–0.40 秒只确认你做过的(回执填实)和概念层,0.40 秒英雄数字翻,然后离场 → 主角 → 移位 → 进场 |
| 答一件,少一件;保存不等于收到 | D 的 answer 与 3b | 搬。按下的那一帧卡就原地收成回执;份额与排布要等下一轮整理读到了才动,回执上写“桌面还没变” |
| 回执跟着条目走,不随版本消失 | D 评审 4 | 搬,并落到 A 的几何上:问题关上时,回执从左栏飞到它的纸片上,留成纸片上的一行;同一问题重登记,回执留在卡上;换成另一个问题,卡上写“你答过上一版” |
| 每个元素一次只走一段,主角“端着走” | D 3.6、4.4 | 搬。纸片与卡从旧位置到新位置只有一段运动;你答过的那件用缓入缓出,其余缓出、不过冲 |
| 英雄数字差 1 滚一格,差 2 以上硬翻 | D | 搬 |
| 概念层约八成,一直动 | 拨盘、脉冲 | 不搬。A 是阅读工具,静止是默认。概念层只有上一轮的轮廓:换轮时要挪的纸片在原位留一圈灰影,1.1 秒内退干净 |
| 悬停概要卡、点击沿自身几何弹出 8 px + 引线 + 详情卡 | motion/ix.mjs | 搬。纸片沿自己的框外扩 8 px(四边各 4 px),引线从纸片边出到详情卡;详情卡里就能作答,不要“去回答 →” |
| 自动讲一遍、任意键跳过、重演 | 腕上一瞥、回应时序 | 搬。只在顶层直接打开、地址不带 ?frame=、没有 referrer 时演一次 |
| 粒子、余辉、整屏压暗 | 脉冲 | 不搬。“丝滑”来自同一件事在换轮、作答、展开之间位置连续 |
1.3 两个桌面参照
凭使用记忆描述,没有逐帧量过;时长只作量级。
- Keynote 的「神奇移动」。 两张幻灯片上同名的对象,切换时从旧位置、旧大小一路走到新位置、新大小,其余的淡入淡出。可取的:认得出“同一个东西”是因为它没有断,一次只走一段;要命的是同时走的太多,十几个对象一起飞,眼睛只能跟住一个。A 现在每轮挪 8–15 张纸,大半是被挤走的,所以先要让不该动的别动(2.4),再给该动的排先后。
- 看板上拖一张卡(Trello 一类)。 卡被拿起时抬高一层,经过的列让出一个空位,放下时落进空位,空位两边的卡合拢。可取的:让出的位置先出现、落点清楚;拿起的那张始终在最上层。A 的主角(你答过的那件)同样在最上层走,经过别的纸片时不被盖住。
1.4 桌面 Chrome 的约束
- 60 fps 靠只动
transform与opacity。现有纸片的正文跟着形状裁切(clip-path),这一层在主线程重绘,spatial.md 已记为未解决;这次不改共用底座的画法,只给它加节拍,第 7 节量帧。 - 切点读一次布局,之后只写;左栏与桌面在同一个切点量位置。
prefers-reduced-motion时一切直接到终态,不演讲一遍。
2 数据(data)
2.1 规则
| 量 | 怎么算 | 出处 |
|---|---|---|
| 英雄数字:桌面空着几份 | 64 − state.root.used;子层与“另有 N 件小事”铺开时不显示 | engine.mjs replayScenario |
| 纸片的位置与面积 | 根层按 rank 排成阅读顺序,foldLayer 把不带问题的 1 份小事合成一叠;layoutPapers 按份额切成精确面积,簇的大小 = 已占 / 64。这次加了跨帧稳定:每张纸被拉向它上一轮在簇里的位置(2.4) | variants/a-layout.mjs、spatial-kit.mjs foldLayer |
| 左栏大卡 | openAsks(s, { prefer: 'choose' }):份额 × 2(逾期或 24 小时内截止)+ choose 加 0.5,取最急的 2 个未答问题(窗口高 ≥ 980 px 时 3 个),答过的留在原位,其余折成“另有 N 个问题开着” | spatial-kit.mjs openAsks、a.mjs askPlan |
| 等你的件数 | 左栏里没有本页回执的问题数(含折叠) | 同上 |
| 真实作答 | event.feedback[] 带 ask 指向这个问题;下一帧它不在 openAsks 里就是答掉,没有指向它的反馈就是失效 | 同拨盘 rules.openAsks |
| 让位 | 相邻两帧根层 share 之差;回执不记“谁让给谁”,只能并排同一轮的增减 | 同拨盘 rules.yield |
| 本页回执 | createAnswerBook 按条目与问题 id 存;状态 saved → 下一帧回放后 acknowledged | spatial-kit.mjs、respond.mjs responseStatus |
2.2 桌面通常是满的
composite 28 轮里,13 轮桌面一份不空(f02、f04、f06–f09、f13、f17–f20、f22、f28);空出 6 份以上的只有 7 轮:f01 7(冷启动)、f05 6、f10 14、f11 13、f23 25、f24 13、f25 12。冷启动之外,每一次都紧跟在问题关上之后:
- f05:「Smilla 约上兽医了吗?」与 vdns 合并两问答掉(
frames[5]里 ask-05、ask-09 的反馈),猫看病 12 → 3,留白 0 → 6。 - f09–f10:NDEBUG 一问答掉(f09),9/14 航班一问失效(f10),母亲换水管 24 → 12,留白 0 → 14。
- f23:猫看病一问答掉、#13572 批准,「母亲九月住哪?」失效,留白 0 → 25,空状态之外两周最大(与拨盘 data.json 的
maxBlank同一处)。
空出来的撑不过两轮:f23 的 25 份在 f24 剩 13、f25 剩 12,f26 主水管 12 → 24 之后只剩 1。
2.3 样张:f21 → f22 → 按下 → f23
| 时刻 | 事实 | 出处 |
|---|---|---|
| f21 8/13 12:00「#13567:复用防护可被绕过」 | 97 条输入;根层 19 件、已占 62,空着 2 份;桌面 8 块:母亲换水管 24、猫看病 12、#13567 6、小龙虾派对 3、Ture 发烧 3、开学同意书 1、Göran 棚屋 1,另有 12 件小事一叠 12 份。左栏两张大卡:「母亲九月住哪?」(24 份,8/14 23:59 截止,卡上写“1 天后截止”)、「Smilla 多吃了药:打诊所?」(12 份);另有 4 个问题开着 | records[21];openAsks |
| f22 8/14 00:00「热浪第一天:药量超过失明线」 | 172 条输入(signal.received 173,1 封营销邮件未收入);根层 11 件、已占 64,空着 0 份。猫看病 12 → 24,档位 critical。离场 4 块:#13567 并入待审 PR、开学同意书并入开学、小龙虾派对与 Göran 棚屋撤下;小事一叠 12 件 → 6 件(另有 6 件撤下或并入)。进场 2 块:待审 PR(6 份,「#13572 堵复用绕过:批准?」)、Signe 开学(1 份)。左栏两张大卡不换人:母亲(24 小时后截止,分数 48.5)、猫(24 份);另有 3 个问题开着;5 个等你 | records[22]、records[22].diff;frames[22].asks |
| 本页按下“打给诊所”(模拟) | 卡原地收成回执;5 → 4 个等你;#13572 从折叠里出来成第 3 张卡,另有 2 个;纸片上“待你选择”换成“✓ 打给诊所 · 等整理”;桌面不动,仍空着 0 份 | askPlan 加本页回执 |
| 08:02(真实) | 你给诊所打了电话:家庭流通话记录 fam:A13.1,feedback 为 {ask: ask-16, action: call, via: source};11:38 你在 GitHub 上批准 #13572(ask-20) | frames[23].events[31]、[113] |
| f23 8/14 12:00「打了诊所;母亲自己定了」 | 115 条输入;已占 39,空着 25 份。猫看病 24 → 12(问题答掉)、母亲换水管 24 → 6(「九月住哪」随改写失效,母亲自己交了回执)、master CI 1 → 6(从小事堆里出来成一张纸);其余份额不变。左栏:待审 PR 换成「三个旧 PR 推到哪天?」(defer,新问题)升第一,Ture 的 VAB 第二,另有 1 个;3 个等你 | records[23];frames[23].asks |
| 让给了谁 | 没有谁接住:同一轮只有 master CI 涨了 5 份,其余 25 份成了空桌面。配对不在回执里,画面只并排同一轮的 −12、−18、+5 与空着 +25 | 让位 |
story 数据集对应 f13 → f14 → 按下 → f15(林苒的两周,与拨盘、腕上一瞥的 story 样张同一轮):
| 时刻 | 事实 |
|---|---|
| f13 10/16 17:40 → f14 21:20「用户要求周末优先陪妈妈,关注调整待回应」 | 4 条输入,状态一处没变(records[16].diff 为空),空着 3 份。左栏:「周六 11 点见妈妈,可以吗?」(ask-17,choose:就周六 11 点 / 改时间)、「请店长解释暂存列」 |
| 本页按下“改时间” | 真实回答是 10/17 09:32 林苒回复 Agent“改到周日 11 点”(frames[17].events[0],{ask: ask-17, action: change, via: reply}),落在 f15 的窗口里 |
| f15 10/17 10:00「陪妈妈改约周日11点,双方已确认」 | 已占 46,空着 18 份。陪妈妈 12 → 24(答了反而变大),试用 24 → 6,发布 12 → 3;陪妈妈换成新问题「周日 11 点见妈妈」(ask-21,confirm),卡上写“你答过上一版:改时间” |
2.4 位置跨帧有多不稳
同一块 978 × 520 的桌面(1440 × 900 窗口),按 composite 逐帧排,比较相邻两帧同一张纸的中心与大小(差 4 px 以上算挪了):
| 挪了的纸 | 份额没变却挪了 | 这些纸共走了 | |
|---|---|---|---|
| 现在(每帧单独排) | 224 次 | 163 次(73%) | 29 345 px |
| 拉向上一轮的位置(稳定项 6) | 220 次 | 159 次 | 17 971 px(−39%) |
次数几乎不降,因为纸片按 rank 排成阅读顺序:一件变急,它前后的纸必然换位,这是“位置 = 紧急度”本身,不能拿掉。降下来的是路程:不再为了别人的一点份额变化把整张桌面重排。样张两轮:f21 → f22 从 1 247 px 降到 295 px,f22 → f23 从 737 px 降到 274 px。story 同样从 31 905 px 降到 17 889 px。为了让同一帧无论从哪一帧走来都长一样,页面从第一帧起逐帧链式排(同一桌面尺寸只排一次,29 帧约 35 ms)。
3 故事(story)
一个点。 桌面通常是满的;答掉一件,要等下一轮整理读到了,它的纸片才缩回去,桌面空出一块。你按下的那张卡原地收成回执,留在手指下;整理落地时,回执先变实,再飞到它的纸片上,纸片带着回执缩到新的位置,让出的面积变成空着的桌面。屏幕上每个元素都从原来的位置走到新位置,一次只走一段。
英雄数字:桌面空着几份。 样张里 f21 是 2,f22 变 0(桌面满了),按下“打给诊所”之后仍是 0(保存不等于收到),f23 整理落地变 25。它写在桌面右上角,是这一页唯一的大字(位置与字号永不变),只在一轮整理落地的 0.40 秒翻;作答、撤回、稍后都不动它。三种读法的英雄数字连成一条:脉冲是这一轮收到多少,拨盘是一共能放多少(64),D 是要你答几件,A 是答完之后桌面空出几份。
3.1 三拍
| 拍 | 时长 | 发生什么 | 观众此刻 |
|---|---|---|---|
| 1 换轮 | 换到 f22,约 1.0 秒 | 先换字,0.40 秒空着 2 → 0;撤下与并入的四块离场(并入的飞进待审 PR 与 Signe 开学),猫看病从右上角往左长到半张桌面(12 → 24),其余各走一段,待审 PR 与 Signe 开学进场;左栏两张卡不换人,只换期限与份额 | 桌面满了;最急的是母亲和猫 |
| 2 答一件 | 按下 0.72 秒 | 按“打给诊所”:卡原地收成回执,5 → 4 个等你,#13572 从折叠行滑出补位;纸片上换成“✓ 打给诊所 · 等整理”,桌面一格不动,空着仍是 0 | 已保存,还没让 |
| 3 让位 | 下一轮整理 1.0 秒 | 12:00 那一轮落地:回执先变实(已收到),0.40 秒空着 0 → 25;回执飞向猫看病,纸片端着它缩到左上(24 → 12),母亲换水管随问题失效缩到右边(24 → 6),master CI 从小事堆里出来;空出的 25 份是露出来的桌面 | 答掉的去了哪,桌面空出多少 |
3.2 第 1 拍:换轮(f21 → f22)
| 时刻 | 什么动 | 怎么动 |
|---|---|---|
| 0 | 切点 | 日期时刻、标题「热浪第一天:药量超过失明线」、本轮调用一行(173 条输入 · 窗口 · 你在原处做了 27 件)、旁白、时间轴与帧数一帧硬切。纸片与卡不动 |
| 0–0.40 | 概念层 | 每张要挪、要走的纸片在原位留下一圈灰影(上一轮的轮廓),此刻还被纸片自己盖着 |
| 0.40 | 英雄数字 | 空着 2 → 0,硬翻;“已占 62 / 64”同时翻成 64;左栏“6 个等你”翻成 5 |
| 0.40–0.64 | 离场 | 小龙虾派对、Göran 棚屋原地收缩淡出;#13567 飞进待审 PR、开学同意书飞进 Signe 开学(并入,story 的合并照旧);灰影多留到 0.90 秒;左栏“另有 4 个”翻成 3 |
| 0.48–0.80 | 移位 | 猫看病从右上角长到右半张(左沿往左推),母亲换水管沿自己的框缩成左半张,Ture 发烧与小事一叠各走一段;内容变了的纸 0.56–0.80 换字;左栏两张卡原地换期限与份额 |
| 0.60–0.96 | 进场 | 待审 PR、Signe 开学从目标位置 60% 大小处长出,错开 40 ms |
| 0.70–1.10 | 灰影退场 | 灰影淡去,静止时概念层为零 |
| 0.96–2.56 | 新问题 | 「#13572 堵复用绕过:批准?」所在的待审 PR 外圈朱红描一次 |
3.3 第 2 拍:答一件(f22,本页,即刻)
| 时刻 | 元素 | 从 → 到 |
|---|---|---|
| 0–0.12 | 猫这张卡的题干、按钮、稍后 | 淡出;卡顶“待你选择 · 24 份”一行是锚 |
| 0–0.20 | 卡盒 | 上沿不动,下沿收到回执高度 |
| 0.10–0.30 | 回执 | 淡入“✓ 你选了「打给诊所」 已保存 · 等 Agent 下一轮整理,桌面还没变”,撤销在右端,焦点到撤销 |
| 0.10–0.30 | 猫看病的纸片 | 卡顶“待你选择”换成“✓ 打给诊所 · 等整理”;不挪、不缩 |
| 0.20 | 左栏计数 | 5 个等你 → 4,滚一格 |
| 0.20–0.52 | 其余卡 | 一次补位,不过冲 |
| 0.36 | 折叠行 | “另有 3 个问题开着”硬翻“另有 2 个” |
| 0.36–0.72 | #13572 批准吗 | 从折叠行的位置滑出、揭开,成第 3 张卡 |
| 至 1.12 | 指针保护 | 上一次按下点 32 px 内的点按不接(键盘照常),免得连点落到补上来的卡 |
英雄数字不动。撤销逆序、更快,0.40 秒:#13572 收回折叠行(0–0.15),其余卡下移(0.10–0.30),回执展开回原卡(0.15–0.35),焦点回到“打给诊所”,计数 0.15 翻回 5。“稍后”沿用 A 现有的做法:卡移到“稍后再看”,同一张节拍表,计数同样减一。
3.4 第 3 拍:让位(f22 → f23,12:00)
先确认你做过的,再让份额让位:
| 时刻 | 元素 | 从 → 到 |
|---|---|---|
| 0 | 切点 | 日期时刻、标题「打了诊所;母亲自己定了」、本轮调用一行、旁白、时间轴硬切 |
| 0–0.30 | 回执 | 空心勾填实,小字换成“已收到 · Agent 在 12:00 的整理中读到”;纸片上的“等整理”同时换成“已收到”。列表与桌面其余不动 |
| 0–0.40 | 概念层 | 要挪的纸片在原位留灰影 |
| 0.40 | 英雄数字 | 空着 0 → 25,硬翻;已占 64 → 39;左栏 4 个等你 → 3 |
| 0.40–0.88 | 主角:猫看病 | 回执离开左栏,沿直线飞向猫看病的纸片,越过一切在最上层,边飞边缩到纸片上那一行的大小,0.72 起淡进纸片;纸片同一段里从右上走到左上、按 12 份缩小(缓入缓出),后半段卡顶换成“✓ 你选了「打给诊所」· 已收到” |
| 0.46–0.94 | 母亲换水管 | 晚 0.06 秒起步,从左半张缩到右上的一小张(24 → 6),交错时猫在上层;左栏「母亲九月住哪?」原地淡出(没有回执,问题随改写失效) |
| 0.48–0.80 | 移位 | 待审 PR 从第 3 张升第一,题干 0.56–0.80 换成「三个旧 PR 推到哪天?」;Ture 的 VAB 补到第 2;桌面上待审 PR、Ture、Signe、小事一叠各走一段;簇缩到 39 / 64 的大小,四周露出桌面 |
| 0.60–0.96 | 进场 | master CI 从小事一叠里长出来成一张 6 份的纸;左栏“另有 1 个” |
| 0.70–1.10 | 灰影退场 | 猫看病与母亲换水管原来的轮廓最后淡去:灰影与新纸片之间的那圈,就是这一轮让出的面积 |
| 0.96–2.56 | 新问题 | 「三个旧 PR 推到哪天?」的卡与待审 PR 纸片外圈朱红描一次 |
两种变体用同一张时间表:
- 同一个问题重登记(f26 答主水管 → f27,两轮都是 ask-23):回执照样填实,卡留在左栏原位,仍是回执,不算等你;纸片不挪。
- 换成另一个问题(story f14 答“改时间” → f15,ask-17 → ask-21):回执填实之后,同一张卡原地换脸成新问题「周日 11 点见妈妈」,卡里留一行“你答过上一版:改时间 · Agent 已收到”;陪妈妈的纸片是主角,从右上长到整排第一(12 → 24),空着 3 → 18。
3.5 两层与约束
- 信息层(约八成)。 纸片、卡片、英雄数字、计数、回执与所有字。墨黑加朱红;朱红只给登记了回应路径的问题(卡的左边条、纸片上的“待你…”、新问题描一次);回执是墨色。
- 概念层(约两成,只在换轮的 0–1.1 秒)。 上一轮的轮廓:要挪或要走的纸片在原位留一圈灰影(1 px 灰线加极浅的灰底),纸片走开后露出来,告诉你它从哪来;缩小的纸片外面那圈灰影就是让出的面积。只用灰,不出字与数,不用朱红;作答、撤回、展开不出灰影;静止时为零。
- 绝不动。 英雄数字的位置与字号;左栏与桌面的分界;纸片的阅读顺序(
rank)。动画只表现排出来的结果,不发明顺序。 - 每个元素一次只走一段,不折返;纸片的长缩沿它自己的框。
- 先后。 切点换字 → 你做过的先确认(回执)→ 英雄数字 → 离场 → 主角(你答的、失效的)→ 移位 → 进场 → 新问题描边。
- 不提前。 下一轮整理落地之前,份额不动、排布不动、回执不变实、答过的卡不离开左栏。
- 预算。 一次节拍同时动的元素:桌面最多 15 张纸(composite 的 f14、f17),左栏一般 3–5 张卡与行;进场错开 40 ms、封顶 8 个。
3.6 自动讲一遍
只在顶层直接打开、地址不带 ?frame=、没有 referrer 时演一次(同一页里切到别的方案再切回来不再演),约 9 秒,停在 f23:
| 秒 | 发生什么 |
|---|---|
| 0 | 直接落在 f21(不演过渡),右上角写“讲一遍 · 1 换轮 · 任意键跳过” |
| 0.9 | 换到 f22,第 1 拍 |
| 2.3 | “2 答一件”;猫这张卡上的“打给诊所”按下(按钮压一下 0.12 秒),第 2 拍 |
| 5.2 | “3 让位”;换到 f23,第 3 拍 |
| 6.6 | 悬停猫看病 2.3 秒,概要卡写“猫看病 12 份 · 变小 24 → 12 · 本轮进了 N 条输入” |
| 8.9 | 收起,讲完 |
讲一遍替你按下的“打给诊所”是真实数据里的回答(08:02 你给诊所打了电话),按下后和你自己按的一样:回执留在本页内存里,可以撤销。任意键或点按跳过,按下的键只用来跳过;跳过时直接停在 f23,回执照样在。页底的“▶ 讲一遍”在顶层打开时都有(讲过之后写“↺ 重演”,讲的时候写“■ 跳过”)。悬停或展开会让讲述停在当前时刻,移开后接着讲。story 数据集讲 f13 → f14 → “改时间” → f15。画廊、合页、体验站弹框(都在 iframe 里,或带着 referrer)与带帧地址都不演;prefers-reduced-motion 时也不演。
4 交互(interaction)
共用 motion/ix.mjs,时序用它的 IX 常数,落在 A 的桌面上:
- 悬停:鼠标移到纸片上(命中区比纸片四边各宽 12 px,空桌面上才算),或 Tab 到它(每张纸一个视觉上隐藏的按钮,放在桌面最前)。出概要卡:来自哪条流、几份、本轮怎么变、本轮进了几条输入、开着的问题与期限;其余纸片与左栏其余卡降到七成;卡与左栏里同一件事的卡互相描边(A 原有的联动)。换轮过渡、连播与讲一遍都停在当前时刻,离开 0.15 秒后卡淡出,再过 0.4 秒接着走。停在换轮中途时卡上不提前说后面节拍的事:份额落定(0.80 秒)之前写上一轮的份数与“还没落定”,进场(0.60 秒)之前不列本轮才登记的问题。
- 展开:点击纸片或它的概要卡,或 Tab 后按 Enter。0 秒是切点:标题切到“这一件 · 第 23 轮 · 猫看病 12 / 64 份 · 变小 24 → 12”,桌面上方的面包屑切到“这一件”;0–0.3 秒纸片沿自己的框外扩 8 px(四边各 4 px,过冲 3.7%),阴影抬一档;0.4–0.6 秒引线从纸片朝外的那条边出去;0.6–0.9 秒详情卡从引线末端展开,贴在纸片旁边、留在桌面里。详情卡依次是:这一件的标题与份额、摘要、份额逐轮、本轮进了它、这件事的问题(与左栏同一张作答卡,可以直接答)、你的回应(来源处的回应加本页回执)、“依据与原文 ↗”(打开 A 原有的阅读面板)。A 自己能作答,所以不放“去回答 →”。
- 收回:Esc、点空白、再点同一件:卡 0.2 秒收起、引线 0.15 秒缩回、纸片 0.2 秒落回。换帧、连播、讲一遍与合页顶栏选帧立即收起。键盘打开的,卡展开后焦点进卡,收回后回到那张纸的隐藏按钮。触屏:第一下是悬停,第二下展开。
- 左栏的卡:卡上的条目名(“Smilla剂量疑超失明线… ›”)从点开阅读面板改为展开桌面上那张纸;卡、本人动作与纸片的描边联动保留。子层(story 下钻)与小事铺开后,非根层的纸片仍点开阅读面板。
- 脚本:
window.foldA有与foldDial、foldWatch同名同义的hover / unhover / open / close / ixAt / ix / paused / pointOf / ixState,另有index、go(i)、play()、stop()、beatAt(秒)(把在途的换轮或作答定在某一刻,供静帧;null放开)、beat()、answer(id, action)、undo(id)、waiting()、blank()、papers()、pick()、nearOf(name, px)、tell()、skip()。scripts/check-ix.mjs的 16 项在本页同样跑。
5 实现(motion)
- 文件。 编排在 a.mjs;节拍表与四个部件在 a-motion.mjs:
beatList(左栏按节拍重排)、fly(回执飞向纸片)、traces(上一轮的轮廓)、flipHTML(数字在节拍上翻)。spatial-kit.mjs 的createFlipBoard.update多了可选的timing(kind, id, info)(每张纸自己的起止与缓动、进场从哪里起步、离场去哪里)与timer: false,并返回这一次的移动计划;a-layout.mjs 的layoutPapers多了可选的previous(2.4 的位置粘性)。两处不给新参数时行为与原来相同,原版树图不受影响。 - 一拍一个时钟。 每次换轮或作答新建一拍,这一拍里创建的 Web Animations(纸片、卡、回执、灰影、数字)都挂在它上面:悬停时一起
pause(),foldA.beatAt(t)把它们一起定在第 t 秒,下一拍开始前上一拍直接finish()到终态。连播与讲一遍走页面自己的 rAF 时钟(一轮 5.6 秒,慢速时 ×1.5),悬停时同样停住;页面隐藏时停播。空格由本页接管,不再用实验台的定时器。 - 桌面。 换轮时
timing按 BEATS.turn 排每张纸:离场 0.40–0.64(并入 0.40–0.80),主角 0.40–0.88(你在本页答过、问题随后关上或换掉的那件;没人答、问题自己失效的晚 0.06 秒),只换字不挪的纸 0.56–0.80,其余移位 0.48–0.80,进场 0.60–0.96、错开 40 ms、封顶 8 个;从小事一叠里出来的纸从那一叠起步,归进小事的纸缩向那一叠。作答、撤回、稍后只换那张纸上的字(0.10–0.30),桌面不挪。纸片位置跨帧稳定:页面从 f00 起逐帧链式排布,同一帧无论从哪一帧走来都一样。 - 左栏。 卡、折叠行、回执都以条目 id 为
data-flip-key;同一件事的卡、回执、紧凑行是同一个盒子换脸(data-face),旧字在原处淡出、盒子走到新高度、新字淡入;计数与折叠行(data-step)在自己的拍上一帧硬翻,旧字跟着盒子走到那一刻。从折叠行补上来的卡从折叠行的位置滑出。按下之后 1.12 秒内,按下点 32 px 内的鼠标点按不接(键盘照常)。 - 回执跟着条目走。 同一个问题重登记:回执留在左栏原位,变实。换成另一个问题:卡原地换脸,留一行“你 HH:MM 答过上一版:…· Agent 已收到,问题随后改写”。问题关上:回执在那一轮从左栏飞到它的纸片(越过一切的 fixed 层,0.40–0.88,最后三分之一淡进纸片),纸片上从此多一行“✓ 选了「打给诊所」· 已收到”,展开时详情卡里也有。确认就写在卡上,原来的 toast 撤掉;U 撤销照旧。
- 交互。 根层纸片的正文从按钮改成
div,悬停与点击交给 ix:命中区四边各宽 12 px,只在空桌面上算;隐藏按钮放在桌面最前,Tab 先到它们。8 px 外扩用 CSS 的独立translate/scale叠在 FLIP 的 transform 上,两者互不干扰;压暗是--a-ix-dim,桌面其余纸片与左栏其余卡降到七成。详情卡用 ix 的detailHTML,把“开着的问题”一节换成 A 自己的作答卡(与左栏同一套 respond.mjs 控件,在卡里作答走同一拍),末尾“依据、版本与原文 ↗”打开原有的阅读面板。原来给小纸片弹全文的a-peek撤掉,由概要卡代替。子层与铺开的小事仍是按钮,点开阅读面板。 - 讲一遍。
TELL写明两份数据的样张(composite f21 → f22 → 打给诊所 → f23;story f13 → f14 → 改时间 → f15,按帧 id 找下标),时间表照 3.6。“直接打开”读页面载入时的地址(performance.getEntriesByType('navigation')),因为 explore.mjs 载入后会把?frame=写进地址栏;另要求顶层窗口、没有 referrer、不带embed=1。 - reduced motion。 不建拍:换轮、作答、撤回直接到终态,不出灰影、不飞回执、不描新问题,也不讲一遍;ix 自己的时序在 reduced motion 下同样直接到位。
6 与故事的出入
- 左栏“5 个等你 → 4”是一帧硬翻,没有滚一格;只有英雄数字在相差 1 时滚一格。
- 展开时的切点分在三处:日期处“第 23 / 28 轮”,时刻处“猫看病 12 / 64 份 · 变小 24 → 12”,标题“这一件 · Smilla已减量,24日9:30复诊”;面包屑“全部事项 / 这一件 · 猫看病”。
- 本页的回执不进详情卡的“你的回应”一节(那一节只列数据里的来源处回应),而是在“这件事的问题”一节:问题开着时是那张回执卡,关上后是一行“✓ 你选了「打给诊所」· Agent 在 12:00 的整理中收到”。
- 3.4 的“回执空心勾填实”实现为:飞走的回执一开始就换成“已收到”的字,0–0.30 秒从半透明变实;勾放大回落只在留在左栏的回执上。
- 离场纸片的灰影 0.90 秒起淡、1.10 秒与其余灰影同时退净。
- 讲一遍自己的悬停(6.6 秒起的猫看病)不停表;你的悬停与展开会让讲述停住,按下任意键或点按就跳到终态。
7 交付(ship)
帧时间(headless Chrome --gpu,1440×900,rAF 间隔):换轮(composite f01→f02、f09→f10、f13→f14、f16→f17、f21→f22、f22→f23)切点那一帧的同步脚本 15–28 ms,之后 1.3 秒内 76–78 帧,p95 16.8 ms,每次至多 2 帧 33 ms(各漏一次垂直同步),其余 16.7 ms;作答的同步脚本 5–11 ms,0–2 帧 33 ms。讲一遍全程 10.5 秒 661 帧,p50 / p95 / p99 都是 16.7–16.8 ms,3 帧超过 20 ms(最长 50 ms,都在切点)。reduced motion 下页面上没有任何动画在跑。
检查:make check;node scripts/check-ix.mjs(全部 9 页,两份数据;A 两份数据都是 16 / 16);node scripts/shoot-forms.mjs --only a,all --gpu --check all-sync(两份数据,无报错,合页逐节跟帧);npm run site:build;画册卡片的 site/previews/a.webp 按 development.md 的参数重截(f26,隐藏方案切换栏)。另在本机跑过:撤销、稍后、数字键与 U、对比变化、铺开小事与收回、左栏条目名展开纸片、从详情卡打开阅读面板、空格连播、story 下钻子层与回到根层、390 px 宽无横向滚动,都没有页面报错。
静帧(只留在本机):换轮中途(f21 → f22,0.62 秒)、作答后回执(f22,按下“打给诊所”)、下一轮让位重排(f22 → f23,0.62 秒,回执在飞、灰影在、空着 25)、悬停(f23 猫看病)、展开 0.9 秒(f23 猫看病)。
没做的。 回执只在本页内存里(与 D 相同);问题关上时若那件事归进了小事一叠(1 份、没有问题),回执不飞,要铺开小事才在它的纸片上看到;左栏卡片上的“稍后”沿用原来的面板,没有单独的节拍样张;真机 Safari 与触控板上的手感没有测。