From fc8f153e348523214c394789242aa2f166f7b0a1 Mon Sep 17 00:00:00 2001
From: JianFeeeee
Date: Sun, 20 Sep 2026 22:37:06 +0800
Subject: [PATCH] =?UTF-8?q?site:=20=E4=BF=AE=E9=A6=96=E5=B1=8F=E6=BB=9A?=
=?UTF-8?q?=E4=B8=8D=E5=88=B0=E5=BA=95=20+=20=E4=BA=A4=E9=94=99=E8=A1=8C?=
=?UTF-8?q?=E5=87=BA=E5=85=A5=E6=96=B9=E5=90=91=20+=20=E8=AE=A9=E7=A4=BA?=
=?UTF-8?q?=E6=84=8F=E5=9B=BE=E7=9C=9F=E5=8A=A8=E8=B5=B7=E6=9D=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
用户反馈三点:① 拉不到最下面 ② 文字与图片没有进出动画,单调
③ 图片仍是「框框住文本」,且希望有「外界光球打到插件上」。
## ① 滚不到底:真 bug,且是两个独立根因叠加
实测(滑到底后读 scrollY):停在 10283,上限 10669,**差 386px**,
页脚完全不可见。两个原因:
- **末尾两节合计超过一屏**。faq 高 635 + footer 367 = 1002px > 一屏 828px。
`scroll-snap-type: y mandatory` 只允许停在吸附点,于是浏览器被迫二选一:
要么 faq 标题对齐(则页脚滚不到),要么页脚可见(则标题被导航压住)。
实测两种坏法都出现过:h2 顶 = -57 / -38(导航底 67)。
修法:FAQ 六条改双列(省下约 190px),收尾段取消整屏高度,
合计压到 762px,两个要求即可同时满足。
- **只在 footer 单独设吸附点会形成死锁**。试过给 architecture 设
`scroll-snap-align: none`(它高 1180px),结果前一个吸附点把它自己吸回来、
后面又无吸附点接住,实测卡死在 y=5791,怎么滑都不动。
教训写进注释:高节退出吸附不是解法,「装得进一屏」才是。
## ② 交错行:方向与布局相反(结构性错误)
原文给每行写死 `slide-l`(文)与 `slide-r`(图)。但翻转行里图在**左边**,
却仍从右侧飞入 —— 图穿过文字进场,方向与布局相反。5 行里 3 行错。
修法不是逐行改正,而是**从布局推导方向**:`.alt:not(.flip)` 文左图右、
`.alt.flip` 图左文右,方向跟着 `.flip` 走。这样不可能再写错。
退出时不加 `.in`,transform 回到同侧 ——「从左进就从左出」是自动的。
## ③ 示意图:从静态线框变成有语义的动画
现状实测很糟:5 张图共 116 个元素,**只有 4 个在动**(各 1 条虚线),
图 2 完全静止 —— 用户说「框框住文本」是准确的。
按每张图的语义给它自己的动作(13 种 keyframes,47 个动画元素):
- **图 1 隔离**:外部光球从画面外飞入,四种颜色命中四个插件,
激起涟漪 + 插件闪一下;内核一列都没有。这正是「外面的一切都落在插件上」。
- **图 2 零依赖**:四项依赖被**逐条划掉**。用 `pathLength=1` + `stroke-dashoffset`
让线自己「划」过去,不是淡入。
- **图 3 共享内存**:写入/读回两个包沿链路跑,arena 上有光带自左向右扫过。
- **图 4 优先级**:金色包沿四条曲线**插队**到 L1–L4 之前;
四级强度条依次点亮;队列条逐条被取走(先到先处理)。
- **图 5 长任务转投**:积压消息被接走飞向分诊助手,收到时闪一下。
配套修掉三处我自己引入的缺陷:
- `.dv-pkdot` 原用 `pkPulse` 动 `r`,与行进包叠加会闪烁 → 改只动透明度
- `.dv-ripple:nth-of-type(2n)` 按 `` 计数,误配到数据包圆点,
四个涟漪全被染成金色 → 改显式逐色
- 涟漪原画在插件框**内部**,像框里画了个圆 → 移到框下层,从背后漾开
## ④ 顺带发现并修掉的真问题
- **CSS 的 `prefers-reduced-motion` 管不到 SMIL**:实测 reduce 下 6 个
数据包照跑。加 JS 调 `svg.pauseAnimations()`,实测 8/8 SVG 已暂停。
- **窄屏横向溢出 26px**:源于我这次加的入场位移(`translateX(42px)`)
在未入场时探出视口。修法是 `overflow-x: clip`(不用 hidden,避免新建滚动容器)
并窄屏收到 ±20px。实测 390/768/1440 三档溢出均为 0。
## 验证
- 滚动:到底=true,页脚可见,faq 标题不被遮挡(h2 顶=210 > 导航底 67)
- 方向:5 行全部「文在左侧就从左进」,翻转行正确反向
- 入场:逐节停留 13 节全部入场,0 未揭示;
滑到底后 23 个 opacity=0 的元素**全在视口上方**(退场生效),无一是「场内却不可见」
- 回归:深浅双主题、JS 禁用、reduce、390/768/1440 三档 全绿,无控制台错误
- 结构:HTML 解析无未闭合,CSS 括号平衡
---
site/index.html | 530 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 504 insertions(+), 26 deletions(-)
diff --git a/site/index.html b/site/index.html
index 2ed40ac..03be0b5 100644
--- a/site/index.html
+++ b/site/index.html
@@ -328,7 +328,12 @@
html {
scroll-snap-type: y mandatory;
scroll-padding-top: 4.5rem;
+ /* 横向必须裁掉:交错行的入场位移(translateX ±42px)会让未入场的
+ 元素先探头到视口右侧之外,窄屏实测撑出 26px 横向溢出。
+ 用 clip 而不是 hidden —— hidden 会新建滚动容器,可能干扰 sticky。 */
+ overflow-x: clip;
}
+ body { overflow-x: clip; }
section {
min-height: calc(100svh - 4.5rem);
display: flex;
@@ -337,6 +342,33 @@
scroll-snap-align: start;
}
section > .wrap { width: min(1120px, 92vw); margin: 0 auto; }
+ /* 内容比一屏高的节(运行时打 .too-tall):取消 min-height 撑高,
+ 但**保留吸附点**。
+ 为何不让它退出吸附(实测过的死锁):把它设为 snap-align:none 后,
+ 前一个吸附点(memory)会把它自己吸回来,而后面又没有吸附点接住,
+ 结果卡死在 memory 底部(y=5791 不动)—— 页脚与后续内容全部到不了。
+ 正确做法是让它自然高、仍作为吸附点,间距不等距不影响可达性。 */
+ section.too-tall { min-height: auto; }
+ /* 页脚也是吸附点。
+ 不加这条的后果(实测):最后一个 section 顶部到文档底有 1214px
+ (含 footer 386px),而视口只有 900px —— mandatory 只允许停在吸附点,
+ 于是**页脚永远滚不到**(实测卡在 y=10283,上限 10669,差 386px)。
+ scroll-snap-stop: always 确保它自己占一屏,不与其他吸附点抢。 */
+ /* 页脚**不单独做吸附点**。
+ 为何(实测二次修):faq 高 643px + footer 367px = 1010px > 视口 900px。
+ 只要 footer 也是吸附点,浏览器就必须在「faq 顶部对齐」与「页脚可见」
+ 之间二选一 —— 实测选了前者,把 faq 的 h2 吸到 y=-38,被导航栏压住。
+ 让它跟随 faq 自然滚动,两者都可读,也不会出现多一屏空白。 */
+ footer { scroll-snap-align: none; }
+ /* 收尾段:取消整屏高度(它本就不需要占满),恢复吸附。
+ 前提是 faq 已改双列 —— 收尾合计压到一屏内,此时同时满足
+ 「标题不被导航压住」与「页脚可达」两个要求。 */
+ section:last-of-type {
+ min-height: auto;
+ padding-bottom: 0.5rem;
+ }
+ /* 注:footer 的 padding 调优直接写进下面那条原始规则里(约 1022 行)——
+ 同优先级下后出现的规则会胜,在这里再写一条会被它覆盖(已踩过)。 */
/* 吸附容器不能有 transform 祖先:会让 snap 失效(实测过)。
故 .wrap 上不加任何位移类动画,入场只做透明度。 */
@media (prefers-reduced-motion: reduce) {
@@ -510,6 +542,12 @@
}
@media (max-width: 860px) {
.alt { grid-template-columns: 1fr; gap: 1.6rem; margin-top: 2.6rem; }
+ /* 窄屏位移收小:单列时左右各 42px 会让内容显得跳,也更容易探出视口 */
+ .js-fx .alt:not(.flip) .alt-copy { transform: translateX(-20px); }
+ .js-fx .alt:not(.flip) .alt-visual { transform: translateX(20px); }
+ .js-fx .alt.flip .alt-copy { transform: translateX(20px); }
+ .js-fx .alt.flip .alt-visual { transform: translateX(-20px); }
+ .faq-grid { grid-template-columns: 1fr; gap: 0.55rem; }
.alt.flip .alt-copy, .alt.flip .alt-visual { order: 0; }
}
@@ -567,7 +605,7 @@
.dv-ar { fill: url(#gCyan); stroke: var(--border-hi); }
.dv-off { fill: none; stroke: var(--border); stroke-width: 1.2; stroke-dasharray: 4 4; }
.dv-offt { fill: var(--text-muted); font-size: 11.5px; text-anchor: middle; text-decoration: line-through; }
- .dv-no path { stroke: var(--brand-gold); stroke-width: 1.6; opacity: .85; }
+ .dv-no path { fill: none; stroke: var(--brand-gold); stroke-width: 1.6; opacity: .9; stroke-linecap: round; }
.dv-line { fill: none; stroke: var(--border-hi); stroke-width: 1.4; }
/* 流动的虚线:让连接线看起来「有东西在跑」而不是静止装饰 */
.dv-dash,
@@ -599,6 +637,195 @@
.dv-g4 { fill: rgba(139, 92, 246, 0.10); }
/* 抢占线的起点圆点 */
.dv-pkdot { fill: var(--brand-gold); }
+
+ /* ══ 示意图的「活着」层 ══
+ 为何要这一层:图里 19~39 个元素原本只有一个虚线在跑,其余全静 ——
+ 读起来就是「框框住文本」。这里按每张图的语义给它自己的动作:
+ 包在链路上跑、依赖被划掉、共享内存写入、消息分级抢占、任务转投。
+ 所有动画共用一套节奏变量,同一屏内不打架。 */
+ .alt-visual {
+ --dvc: cubic-bezier(.4, 0, .2, 1);
+ }
+ /* 仅在入场完成后开跑(未入场时图偏在屏外,跑动画没人看且耗费帧) */
+ .alt-visual svg .dv-anim { animation-play-state: paused; }
+ .alt.in .alt-visual svg .dv-anim,
+ .alt-visual.in svg .dv-anim { animation-play-state: running; }
+
+ /* ① 数据包沿链路反复穿行(图 1 / 图 5) */
+ @keyframes pktTravel {
+ 0% { offset-distance: 0%; opacity: 0; }
+ 12% { opacity: 1; }
+ 80% { opacity: 1; }
+ 100% { offset-distance: 100%; opacity: 0; }
+ }
+ /* ② 依赖项被依次划掉(图 2):线自己「划」过去,不是淡入。
+ 实现:pathLength=1 把每条线归一化,再用 dashoffset 1→0 逐段绘出。
+ 比单纯 opacity 淡入像"划掉"得多。 */
+ @keyframes strikeDeps {
+ 0%, 8% { stroke-dashoffset: 1; opacity: 1; }
+ 30% { stroke-dashoffset: 0; opacity: 1; }
+ 78% { stroke-dashoffset: 0; opacity: 1; }
+ 92%, 100% { stroke-dashoffset: 0; opacity: 0; }
+ }
+ @keyframes depPulse {
+ 0%, 100% { opacity: .42; }
+ 45% { opacity: .95; }
+ }
+ .dv-deps .dv-plug { animation: depPulse 3.4s var(--dvc) infinite; }
+ .dv-deps .dv-no path { animation: strikeDeps 3.4s var(--dvc) infinite; }
+ .dv-deps .dv-no path:nth-child(1) { animation-delay: .18s; }
+ .dv-deps .dv-no path:nth-child(2) { animation-delay: .34s; }
+ .dv-deps .dv-no path:nth-child(3) { animation-delay: .50s; }
+ .dv-deps .dv-no path:nth-child(4) { animation-delay: .66s; }
+ .dv-deps .dv-x { animation: depPulse 3.4s var(--dvc) infinite; animation-delay: .8s; }
+
+ /* ③ 共享内存:写入光带扫过,头部/arena 依次亮(图 3) */
+ @keyframes shmSweep {
+ 0% { transform: translateX(0); opacity: 0; }
+ 10% { opacity: 1; }
+ 90% { opacity: 1; }
+ 100% { transform: translateX(150px); opacity: 0; }
+ }
+ @keyframes segLight {
+ 0%, 100% { opacity: .55; }
+ 50% { opacity: 1; }
+ }
+ .dv-seg { animation: segLight 2.6s var(--dvc) infinite; }
+ .dv-hd { animation: segLight 2.6s var(--dvc) infinite; animation-delay: .3s; }
+
+ /* ④ 优先级:高优先级包从下往上插队(图 4) */
+ @keyframes preempt {
+ 0% { transform: translateY(0); opacity: 0; }
+ 8% { opacity: 1; }
+ 30% { transform: translateY(-26px); opacity: 1; }
+ 62% { transform: translateY(-26px); opacity: 1; }
+ 80%, 100% { transform: translateY(-26px); opacity: 0; }
+ }
+ .dv-pk-dot { animation: preempt 3.6s var(--dvc) infinite; }
+
+ /* ⑤ 消息排队 → 转投:包先积压再被接走(图 5) */
+ @keyframes msgStack {
+ 0%, 18% { opacity: 0; transform: translateY(6px); }
+ 30% { opacity: 1; transform: none; }
+ 72% { opacity: 1; transform: none; }
+ 88%, 100% { opacity: 0; transform: translateX(16px); }
+ }
+ .dv-msg { animation: msgStack 3.8s var(--dvc) infinite; }
+ .dv-msg:nth-child(2) { animation-delay: .22s; }
+ .dv-msg:nth-child(3) { animation-delay: .44s; }
+
+ /* 插队轨迹:常驻淡线,让「从哪来、到哪去」始终可读;
+ 走动的包用 .dv-pkdot(金色)。 */
+ .dv-pkline { fill: none; stroke: var(--brand-gold); stroke-width: 1.1; opacity: .22; }
+
+ /* 级别强度条(图 4):从 L1 到 L4 依次点亮,表现「级别越低越可等、
+ 越高越紧急」。静态时四条只是深浅不同的背景,看不出递进。 */
+ .dv-gauge-fill rect { opacity: .45; }
+ @keyframes lvlLight {
+ 0%, 100% { opacity: .45; }
+ 50% { opacity: 1; }
+ }
+ .js-fx .dv-g1 { animation: lvlLight 3.2s ease-in-out infinite; }
+ .js-fx .dv-g2 { animation: lvlLight 3.2s ease-in-out infinite; animation-delay: .25s; }
+ .js-fx .dv-g3 { animation: lvlLight 3.2s ease-in-out infinite; animation-delay: .5s; }
+ .js-fx .dv-g4 { animation: lvlLight 3.2s ease-in-out infinite; animation-delay: .75s; }
+
+ /* 写入光带(图 3):沿 arena 自左向右扫过,示意数据落进共享段 */
+ .dv-sweep {
+ fill: url(#gCyan);
+ opacity: 0;
+ }
+ @keyframes shmSweep {
+ 0% { transform: translateX(0); opacity: 0; }
+ 12% { opacity: .75; }
+ 70% { opacity: .75; }
+ 88%, 100% { transform: translateX(196px); opacity: 0; }
+ }
+ .js-fx .dv-sweep { animation: shmSweep 2.8s cubic-bezier(.4,0,.2,1) infinite; }
+
+ /* 收货闪光(图 5):分诊助手接到消息时亮一下 */
+ .dv-recv {
+ fill: none;
+ stroke: var(--brand-cyan);
+ stroke-width: 2;
+ opacity: 0;
+ }
+ @keyframes recvFlash {
+ 0%, 44% { opacity: 0; }
+ 54% { opacity: .95; }
+ 66% { opacity: .2; }
+ 80%, 100% { opacity: 0; }
+ }
+ .js-fx .dv-recv { animation: recvFlash 2.4s cubic-bezier(.4,0,.2,1) infinite; }
+ /* 主 agent 忙碌:呼吸更慢更沉,与「被占住」呼应 */
+ .js-fx .dv-run { animation: busyBreathe 3.2s ease-in-out infinite; }
+ @keyframes busyBreathe {
+ 0%, 100% { opacity: .82; }
+ 50% { opacity: 1; }
+ }
+
+ /* ══ 外界光球(图 1)══
+ 语义:外面的一切都落在插件上,内核一列都没有。
+ 故光球从画面外飞入、命中插件、激起涟漪 —— 让「隔离」这件事看得见。
+ 四个插件各给一个颜色,命中时插件自己也闪一下,这样「打到谁」很清楚。 */
+ .dv-orbpath { fill: none; stroke: none; }
+ .dv-orb { filter: url(#fGlow); }
+ .dv-orb-a { fill: var(--brand-cyan); }
+ .dv-orb-b { fill: var(--brand-gold); }
+ .dv-orb-c { fill: var(--brand-violet); }
+ .dv-orb-d { fill: var(--brand-blue); }
+ /* 涟漪:从命中点向外扩一圈,很快淡去 */
+ .dv-ripple {
+ fill: none;
+ stroke: var(--brand-cyan);
+ stroke-width: 1.4;
+ }
+ /* 涟漪逐色(不能用 :nth-of-type)——
+ 同层还有数据包圆点也是 ,按类型计数会错配,
+ 实测四个涟漪全被染成金色。显式逐色才对得上各自的插件。 */
+ .dv-ripple-a { stroke: var(--brand-cyan); }
+ .dv-ripple-b { stroke: var(--brand-gold); }
+ .dv-ripple-c { stroke: var(--brand-violet); }
+ .dv-ripple-d { stroke: var(--brand-blue); }
+ /* 命中闪光:贴在插件框上,短暂高亮后回落 */
+ .dv-hit {
+ fill: none;
+ stroke-width: 2.2;
+ stroke-linecap: round;
+ opacity: 0;
+ }
+ .dv-hit-a { stroke: var(--brand-cyan); }
+ .dv-hit-b { stroke: var(--brand-gold); }
+ .dv-hit-c { stroke: var(--brand-violet); }
+ .dv-hit-d { stroke: var(--brand-blue); }
+ @keyframes hitFlash {
+ 0%, 28% { opacity: 0; }
+ 34% { opacity: 1; }
+ 46% { opacity: .25; }
+ 60%, 100% { opacity: 0; }
+ }
+ .js-fx .dv-hit { animation: hitFlash 3.6s cubic-bezier(.4,0,.2,1) infinite; }
+ .js-fx .dv-hit-a { animation-delay: 0s; }
+ .js-fx .dv-hit-b { animation-delay: .9s; }
+ .js-fx .dv-hit-c { animation-delay: 1.8s; }
+ .js-fx .dv-hit-d { animation-delay: 2.7s; }
+ /* 注意:这些 SMIL 动画不受 CSS 的 prefers-reduced-motion 约束,
+ reduce 下由 JS 调 pauseAnimations() 统一停(见脚本里的说明)。 */
+
+ /* ⑥ 节点呼吸:内核 / 主 agent 保持轻微脉动,图不平 */
+ @keyframes nodeBreathe {
+ 0%, 100% { opacity: .88; }
+ 50% { opacity: 1; }
+ }
+ .dv-accent { animation: nodeBreathe 4.2s var(--dvc) infinite; }
+ /* reduce 下全停 —— 不只是虚线,上面这些都停 */
+ @media (prefers-reduced-motion: reduce) {
+ .alt-visual svg .dv-anim,
+ .dv-plug, .dv-no path, .dv-x, .dv-seg, .dv-hd,
+ .dv-pk-dot, .dv-msg, .dv-accent, .dv-pk {
+ animation: none !important;
+ }
+ }
.dv-pkcap {
fill: var(--brand-gold);
font-size: 9.5px;
@@ -613,6 +840,16 @@
stroke-dasharray: 5 4;
}
.dv-qitem { fill: var(--dv-queue-item); opacity: 0.95; }
+ /* 队列条逐条被取走:表现「先到先处理」。
+ 用 opacity 而非位移 —— 条是 ,位移会露出底色缺口。 */
+ @keyframes queueTake {
+ 0%, 100% { opacity: .95; }
+ 45% { opacity: .2; }
+ }
+ .js-fx .dv-qitem rect { animation: queueTake 3.2s ease-in-out infinite; }
+ .js-fx .dv-qitem rect:nth-child(1) { animation-delay: 0s; }
+ .js-fx .dv-qitem rect:nth-child(2) { animation-delay: .4s; }
+ .js-fx .dv-qitem rect:nth-child(3) { animation-delay: .8s; }
/* 处理方向指示 */
.dv-qdir { fill: none; stroke: var(--text-muted); stroke-width: 1.2; opacity: 0.6; }
.dv-qdir-tip { fill: none; stroke: var(--text-muted); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; opacity: 0.6; }
@@ -621,10 +858,14 @@
.dv-colcap-dim { fill: var(--text-muted); }
.dv-caps { fill: var(--text-muted); font-size: 8.8px; text-anchor: middle; }
@media (prefers-reduced-motion: no-preference) {
+ /* 只呼吸透明度,不动 r。
+ 为何:.dv-pkdot 现在兼做「沿链路行进的数据包」(靠 SMIL 的
+ animateMotion 定位),若再用 CSS 动 r,包在跑的同时忽大忽小,
+ 看着像闪烁的噪点。半径交给标记本身。 */
.dv-pkdot { animation: pkPulse 1.8s ease-in-out infinite; }
@keyframes pkPulse {
- 0%, 100% { opacity: 1; r: 3.5; }
- 50% { opacity: .55; r: 4.5; }
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: .5; }
}
}
@@ -972,13 +1213,24 @@
.comp .row { grid-template-columns: 1fr; gap: 0.25rem; }
}
- /* ── FAQ ── */
+ /* ── FAQ ──
+ 双列是「一滑一页」的硬约束逼出来的:末尾 faq(635px) + footer(367px)
+ = 1002px > 一屏 828px,mandatory 只允许停在吸附点,于是二者不可兼得
+ —— 实测要么标题被导航栏压住(h2 顶=-57),要么永远滚不到页脚。
+ 六条改成两列省下约 190px,末尾整段才装得进一屏。单列在窄屏恢复。 */
+ .faq-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0.6rem 1.1rem;
+ align-items: start;
+ margin-top: 1.6rem;
+ }
details.faq {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
- padding: 0.95rem 1.15rem;
- margin-bottom: 0.7rem;
+ padding: 0.8rem 1rem;
+ margin-bottom: 0;
}
details.faq summary {
cursor: pointer;
@@ -998,7 +1250,9 @@
/* ── 页脚 ── */
footer {
border-top: 1px solid var(--border);
- padding: 2.4rem 0 3rem;
+ /* 收紧上下内边距:末尾空间要与最后一个 section 一起装进一屏,
+ 否则 mandatory 会把该节标题吸到导航栏底下(实测 h2 顶=-57,导航底=67)。 */
+ padding: 2rem 0 2.2rem;
color: var(--text-muted);
font-size: 0.88rem;
}
@@ -1421,6 +1675,40 @@
}
.js-fx .rise-zoom.in { opacity: 1; transform: none; }
+ /* ── 交错行:文字与示意图从两侧滑入 ──
+ 行本身只做很轻的上浮,重点放在内部两列的相向入场,
+ 这样「左文右图」的对应关系在动的那一下就被看见了。 */
+ /* ── 交错行:文与图各自从「自己所在的那一侧」滑入 ──
+ 方向由 .flip 推导,而不是手写 slide-l/slide-r ——
+ 手写版本踩过坑:翻转子行里图在**左边**却写了 slide-r(从右进),
+ 于是图穿过文字从右侧飞入,方向与布局相反。
+ 退出时不加 .in,transform 回到同侧,所以「从左进就从左退」是自动的。 */
+ .js-fx .alt .alt-copy,
+ .js-fx .alt .alt-visual {
+ opacity: 0;
+ transition: opacity 0.85s var(--ease), transform 0.9s var(--ease);
+ transition-delay: var(--d, 110ms);
+ will-change: transform, opacity;
+ }
+ /* 非翻转:文左、图右 */
+ .js-fx .alt:not(.flip) .alt-copy { transform: translateX(-42px); }
+ .js-fx .alt:not(.flip) .alt-visual { transform: translateX(42px); }
+ /* 翻转:图左、文右 —— 方向跟着换 */
+ .js-fx .alt.flip .alt-copy { transform: translateX(42px); }
+ .js-fx .alt.flip .alt-visual { transform: translateX(-42px); }
+ /* 入场完成 */
+ .js-fx .alt.in .alt-copy,
+ .js-fx .alt.in .alt-visual { opacity: 1; transform: none; }
+
+ /* 交错行自身:仅轻微上浮,不抢内部动画 */
+ .js-fx .alt.rise { transform: translateY(14px); }
+
+ /* ── 卡片错落入场:四张依次亮起,不用手写 --d ── */
+ .js-fx .duties.in .duty:nth-child(1) { transition-delay: 0ms; }
+ .js-fx .duties.in .duty:nth-child(2) { transition-delay: 90ms; }
+ .js-fx .duties.in .duty:nth-child(3) { transition-delay: 180ms; }
+ .js-fx .duties.in .duty:nth-child(4) { transition-delay: 270ms; }
+
/* ── Hero 大标题:渐变流动 + 描边光 ── */
h1 .grad {
background: linear-gradient(110deg, var(--brand-blue), var(--brand-cyan) 32%,
@@ -1720,28 +2008,28 @@
-
+
隔离
崩一个,不赔上全部
插件不在内核里,是另一个进程。崩了就把它注册的东西一并摘掉,其余照跑。
退避重启 1s / 2s / 3s;5 分钟内第 4 次崩溃即停下等人
-
+
调度
拦得住,也接得回
输入分能等与不能等。不能等的可抢占,被打断的现场压栈,稍后原样恢复。
L4 内核独占;外部插件声明 L4 会被夹到 L3
-
+
通信
三条道,各走各的
控制走 stdio,数据走共享内存,通知走事件环。变长内容只传偏移,两边映射到不同地址也能读。
整段写入 + 读回 3.5µs(实测)
-
+
资源
记忆不会越用越肿
上下文按相关性取舍,最近 10 条恒受保护。低分下沉成文档,冷文档再蒸馏成三元组。
@@ -1755,7 +2043,7 @@
-
+
01
内核零 IO,只碰调度
@@ -1770,6 +2058,31 @@
箭头只进出插件 —— 内核一列都没有
@@ -1799,7 +2190,7 @@
-
+
02
部署,从未如此便捷
@@ -1811,7 +2202,7 @@
-