[{"data":1,"prerenderedAt":1743},["ShallowReactive",2],{"\u002F2026-08-15":3,"\u002F2026-08-15-rel":376},{"id":4,"title":5,"body":6,"column":359,"date":360,"description":12,"extension":361,"hero_image":362,"meta":363,"navigation":364,"path":365,"seo":366,"series_id":362,"severity":362,"stem":367,"summary":368,"tags":369,"__hash__":375},"posts\u002F2026-08-15-上线前我把整个仓库审了一遍.md","上线前，我把整个仓库审了一遍",{"type":7,"value":8,"toc":347},"minimark",[9,13,16,21,24,47,50,53,57,60,63,66,69,73,76,79,145,148,155,159,165,168,171,178,182,185,196,199,203,206,217,220,234,237,240,244,247,254,260,263,267,270,275,292,297,311,314,317,321,324,344],[10,11,12],"p",{},"上线前做了一次全量静态审查。",[10,14,15],{},"本文只讲方法和结论分布。审查报告本身不入库，也不在这里复述——那份报告带内网拓扑、密钥存放位置和尚未修复问题的文件行号证据，公开它就等于公开一份攻击者的地图。",[17,18,20],"h2",{"id":19},"一审查怎么进行","一、审查怎么进行",[10,22,23],{},"三个部分：",[25,26,27,35,41],"ol",{},[28,29,30,34],"li",{},[31,32,33],"strong",{},"人工列清单。"," 先按「一个系统可能从哪些地方被打穿」列出检查面，而不是按目录扫。",[28,36,37,40],{},[31,38,39],{},"四个并行只读探查代理。"," 每个代理负责一组检查面，只读，不允许改任何文件。",[28,42,43,46],{},[31,44,45],{},"关键证据人工逐条复核。"," 代理解释现象可以，但「这条算不算风险、算多重」由人定。",[10,48,49],{},"第三步是必要的。代理很容易把「写法不理想」报成「高危」，也很容易漏掉需要跨文件才能看出的问题。它适合做的是穷举和取证，不适合定级。",[10,51,52],{},"审查方式记为：静态审查（人工 + 4 个并行只读探查代理）+ 关键证据人工逐条复核。",[17,54,56],{"id":55},"二只读约束的作用","二、只读约束的作用",[10,58,59],{},"让探查代理只能读，有两个好处。",[10,61,62],{},"一是没有副作用。审查过程中仓库保持原样，报告的结论可以对着同一个 commit 复现。",[10,64,65],{},"二是省掉了「代理顺手改一下」的干扰。审查和修改混在一起，会出现「报告说有问题的地方已经被改过」的状态，后续复核就无法对着证据走。",[10,67,68],{},"修改是审查之后单独一轮的事。",[17,70,72],{"id":71},"三20-条与四个级别","三、20 条与四个级别",[10,74,75],{},"产出 20 条风险，编号 R01 到 R20。每一条有：位置、触发条件、影响、修复优先级。",[10,77,78],{},"按修复优先级分四档：",[80,81,82,98],"table",{},[83,84,85],"thead",{},[86,87,88,92,95],"tr",{},[89,90,91],"th",{},"优先级",[89,93,94],{},"条数",[89,96,97],{},"排期",[99,100,101,113,124,135],"tbody",{},[86,102,103,107,110],{},[104,105,106],"td",{},"P0",[104,108,109],{},"2",[104,111,112],{},"本周内",[86,114,115,118,121],{},[104,116,117],{},"P1",[104,119,120],{},"6",[104,122,123],{},"两周内",[86,125,126,129,132],{},[104,127,128],{},"P2",[104,130,131],{},"5",[104,133,134],{},"一个月内",[86,136,137,140,143],{},[104,138,139],{},"P3",[104,141,142],{},"7",[104,144,134],{},[10,146,147],{},"另外有一个独立的「严重级别」列，按严重 \u002F 高 \u002F 中 \u002F 低分：1 \u002F 4 \u002F 7 \u002F 8。两列不对应是有意的——「有多严重」和「该多快修」是两件事。一条严重但需要停机才能修的问题，排期上反而不能放在本周。",[10,149,150,151,154],{},"整体评级：",[31,152,153],{},"高","。",[17,156,158],{"id":157},"四结论里最值得记的一句","四、结论里最值得记的一句",[160,161,162],"blockquote",{},[10,163,164],{},"未发现 SQL 注入、越权与 IDOR、存储型 XSS 等直接可利用的应用漏洞。",[10,166,167],{},"这一句和「整体评级高」摆在一起看，是这个月里最值得记的一条工程结论。",[10,169,170],{},"风险分布和直觉不一致。业务代码那一侧没查出可直接利用的漏洞——那部分每天在改、每天在跑、每天有人看。问题更多集中在业务代码之外：构建、部署、配置、凭据流转、依赖引入、运行时加固。",[10,172,173,174,177],{},"这些东西的共同点是",[31,175,176],{},"平时不跑","。部署脚本一天执行几次，Dockerfile 改一次放很久，集群配置是照着文档抄的。没有日常反馈的东西，问题会一直留在那里，而且不会有人因为「它今天没出事」而去核对它。",[17,179,181],{"id":180},"五修复的排期方式","五、修复的排期方式",[10,183,184],{},"路线图按三档排：P0 本周、P1 两周内、P2\u002FP3 一个月内。",[10,186,187,188,191,192,195],{},"排期依据不是严重级别，是两件事：",[31,189,190],{},"能不能立即修","，和",[31,193,194],{},"修它会不会动到线上","。一条严重但需要停机或改基础设施配置的问题，得等一个发布窗口；一条中等但改动只在一处代码里的问题，可以马上做。P2\u002FP3 是加固项，攒到下一个版本一起走。",[10,197,198],{},"这样做的好处是排期可执行。「两个月内修完 20 条」听起来完整，实际会卡在第一条需要停机的问题上。",[17,200,202],{"id":201},"六报告为什么不入库","六、报告为什么不入库",[10,204,205],{},"仓库的忽略规则里专门有一条：",[207,208,213],"pre",{"className":209,"code":211,"language":212},[210],"language-text","# 安全审查报告：含内网拓扑、密钥存放路径与未修复漏洞的 file:line 证据，绝不入库\n","text",[214,215,211],"code",{"__ignoreMap":216},"",[10,218,219],{},"三条理由，最后一条最关键：",[221,222,223,226,229],"ul",{},[28,224,225],{},"内网拓扑",[28,227,228],{},"密钥存放位置",[28,230,231],{},[31,232,233],{},"未修复漏洞的精确定位",[10,235,236],{},"前两条是常识。第三条是这类文档的特例：一份「待修复清单」在修完之前，本身就是攻击说明书。而它又必须有精确定位，否则修不动。",[10,238,239],{},"所以这份文档的流转方式和代码相反。代码进仓库、留历史；它不进仓库，修完一条删一条，修完的结论改记到提交信息里。",[17,241,243],{"id":242},"七审查之后同一天修的东西","七、审查之后，同一天修的东西",[10,245,246],{},"审查和修复放在同一天做，这是这次的一个做法。",[10,248,249,250,253],{},"当天修的一批，主题集中在一类：",[31,251,252],{},"不报错的失败","。队列投递端断线后不重连、依赖返回的包装形状读错导致判定恒为假、入队不带权重插到最前面、参数格式不对被上游全部拒收、重试链被自己的去重压短、管理操作的幂等键缺失、用户可控的文件名直接进对象存储键、唯一漏掉来源校验的 IPC handler、并发删除后返回假成功、过期验证码被复活、临时文件不清理。",[10,255,256,257,154],{},"这些已经有单独一篇记录，这里只提它们的共性：",[31,258,259],{},"都不抛异常",[10,261,262],{},"审查报告里那些「不进仓库」的条目和这些「当天修完」的条目，其实是同一批检查过出来的。区别只在于前者需要动基础设施、要等窗口，后者可以立刻改。",[17,264,266],{"id":265},"八静态审查的边界","八、静态审查的边界",[10,268,269],{},"这一轮做的是静态审查，能覆盖和不能覆盖的东西都比较明确。",[10,271,272],{},[31,273,274],{},"能覆盖：",[221,276,277,280,283,286,289],{},[28,278,279],{},"配置与依赖：镜像标签、依赖来源、加密参数、权限配置。",[28,281,282],{},"错误处理：异常的吞掉与上抛、失败路径有没有留痕。",[28,284,285],{},"凭据流转：什么地方读、什么地方写、会不会进构建产物。",[28,287,288],{},"部署清单：运行用户、资源限制、网络策略、滚动更新参数。",[28,290,291],{},"代码层面的模式：路径拼接、命令拼接、输入校验的缺失。",[10,293,294],{},[31,295,296],{},"覆盖不到：",[221,298,299,302,305,308],{},[28,300,301],{},"运行时竞态。两个请求同时到、并发删除、租约过期，这些要在真实并发下才暴露。",[28,303,304],{},"真实的流量特征。哪些入口真的被外部打到、参数实际长什么样，静态看只能推测。",[28,306,307],{},"业务逻辑漏洞。越权取数据这类问题需要理解「这个人该不该看到这条」，而业务意图不在代码里。",[28,309,310],{},"组合链。单看每一步都合规，串起来才是问题——这类要动态测试或者真实对抗。",[10,312,313],{},"第三类是最容易被静态审查漏掉的：代码里所有校验都写了，但校验的规则和业务规则不一致。",[10,315,316],{},"所以静态审查的产出不是「系统安全了」，而是「在它能看见的那一面里，还剩哪些要做」。",[17,318,320],{"id":319},"九一次审查真正留下的东西","九、一次审查真正留下的东西",[10,322,323],{},"审完当天修掉的那批，留下的是代码。报告里那些没修的，留下的是三样东西：",[25,325,326,332,338],{},[28,327,328,331],{},[31,329,330],{},"一份带精确定位和优先级的清单","，不进仓库，逐条消掉。",[28,333,334,337],{},[31,335,336],{},"一个判断","：风险集中的地方和日常注意力集中的地方不一样。业务代码天天改，反而干净；构建部署配置平时没人动。",[28,339,340,343],{},[31,341,342],{},"一次复核的习惯","：审查的结论要能对着一个 commit 复现，所以探查只读、修改单独一轮。",[10,345,346],{},"第 2 条比第 1 条重要。清单会修完，但「平时不跑的东西没人核对」这件事不会自己消失。",{"title":216,"searchDepth":348,"depth":348,"links":349},2,[350,351,352,353,354,355,356,357,358],{"id":19,"depth":348,"text":20},{"id":55,"depth":348,"text":56},{"id":71,"depth":348,"text":72},{"id":157,"depth":348,"text":158},{"id":180,"depth":348,"text":181},{"id":201,"depth":348,"text":202},{"id":242,"depth":348,"text":243},{"id":265,"depth":348,"text":266},{"id":319,"depth":348,"text":320},"工程手记","2026-08-15","md",null,{},true,"\u002F2026-08-15",{"title":5,"description":12},"2026-08-15-上线前我把整个仓库审了一遍","一次上线前的静态安全审查：四个并行只读代理加人工逐条复核，产出 20 条分级风险与三档修复路线图。本文记录方法，不复述条目。",[370,371,372,373,374],"安全审查","代码审查","风险分级","修复排期","Agent 协作","bnkC_2bMe8OfgB00HIfDUS9bdRwLUZb_WqDBvXtq__M",[377,576,810],{"id":378,"title":379,"body":380,"column":359,"date":561,"description":562,"extension":361,"hero_image":362,"meta":563,"navigation":364,"path":564,"seo":565,"series_id":362,"severity":362,"stem":566,"summary":567,"tags":568,"__hash__":575},"posts\u002F2026-09-10-Ai项目群的工程化分水岭.md","AI 项目做多以后，真正难的是把边界接起来",{"type":7,"value":381,"toc":549},[382,389,392,395,399,404,414,417,420,436,439,443,462,465,469,478,481,485,488,491,511,514,518,521,524,527,530,533,536,540,543,546],[10,383,384,385,388],{},"这段时间回看 ",[214,386,387],{},"\u002FUsers\u002Fxingye\u002FAi"," 下的项目，最明显的变化不是项目数量增加，而是问题的重心变了。",[10,390,391],{},"早期问题集中在“功能能不能跑起来”：语音推理、网页生成、桌面端启动、小程序页面、视频服务。进入平台化阶段以后，问题变成了另一组：任务是否可追踪，失败是否能恢复，费用是否能对账，版本是否能回滚，用户是否知道发生了什么。",[10,393,394],{},"这是一条从功能开发走向系统工程的分水岭。",[17,396,398],{"id":397},"一项目群已经形成几条清晰的产品线","一、项目群已经形成几条清晰的产品线",[400,401,403],"h3",{"id":402},"agent-与多租户平台","Agent 与多租户平台",[10,405,406,409,410,413],{},[214,407,408],{},"yun-claw"," 与 ",[214,411,412],{},"yun-claude"," 的记录显示，平台主线已经覆盖用户容器、模型接入、画布工作流、代理渠道、后台管理和运营工具。近期提交涉及画布自绘 Select、资产大图预览、代理激活码申请，以及渠道注册页的差异化展示。",[10,415,416],{},"这些功能看起来分属不同页面，底层却共享一套问题：用户身份、资源权限、异步任务和状态反馈必须保持一致。只改页面，不补状态契约，功能就会在跨页面操作时暴露断点。",[400,418,419],{"id":419},"内容生产流水线",[10,421,422,425,426,425,429,409,432,435],{},[214,423,424],{},"ai-write","、",[214,427,428],{},"instantlink-site",[214,430,431],{},"ai-auto-edit",[214,433,434],{},"ai-digital-human-workflow"," 分别覆盖写作、漫剧、自动剪辑和数字人视频。提交记录里反复出现几个关键词：任务网关、切片下载、共享卷、FFmpeg、提示词优化、失败提示和构建复现。",[10,437,438],{},"这说明内容生产已经不是单次调用模型，而是一条有中间产物的流水线。文本、图片、视频、音频和项目资产都有生命周期，任何一步的状态丢失都会让用户无法判断下一步该做什么。",[400,440,442],{"id":441},"交付网关与桌面端","交付、网关与桌面端",[10,444,445,425,448,425,451,425,454,457,458,461],{},[214,446,447],{},"newCodex",[214,449,450],{},"new-openclaw",[214,452,453],{},"openclaw",[214,455,456],{},"服务器管理"," 和 ",[214,459,460],{},"SSHTS"," 的记录，集中在更新、恢复、远程诊断、单实例生命周期和离线环境上。这里的核心指标不是“新版本发布了没有”，而是安装失败能否定位、更新中断能否恢复、客户机器能否留下可审计证据。",[10,463,464],{},"这也是为什么桌面端工程会逐渐靠近运维工程：安装器、后台服务、日志、配置和回滚必须一起设计。",[400,466,468],{"id":467},"网关计费与数据观察","网关、计费与数据观察",[10,470,471,409,474,477],{},[214,472,473],{},"NewAPI",[214,475,476],{},"new-api"," 的近期记录包括按模型统计 Token 消耗、扣费量、时间粒度聚合、昨日活跃用户和订单搜索。网关不只是转发请求，它还承担了模型选择、消耗记录、后台观察和问题追踪。",[10,479,480],{},"一旦计费数据进入用户体验，接口成功与业务成功就不再是同一个概念。请求返回 200，只能说明一次网络调用完成；扣费、产物落盘和任务终态是否一致，需要单独核对。",[17,482,484],{"id":483},"二git-记录里最有价值的变化是边界被写出来了","二、Git 记录里最有价值的变化，是“边界被写出来了”",[10,486,487],{},"多个项目的文档都在补同一类内容：部署手册、验收清单、测试报告、故障复盘、模型环境快照和用户协议。",[10,489,490],{},"这类文档的价值不在于把代码再讲一遍，而在于把系统边界写清楚：",[221,492,493,496,499,502,505,508],{},[28,494,495],{},"输入是什么，哪些字段必须存在；",[28,497,498],{},"状态有哪些，什么条件才能进入下一状态；",[28,500,501],{},"外部服务超时后，如何判断请求是否已经执行；",[28,503,504],{},"产物写入失败时，是否允许重试；",[28,506,507],{},"更新中断后，哪个版本可以安全恢复；",[28,509,510],{},"用户看到的成功，是否和后台记录的成功一致。",[10,512,513],{},"当这些问题没有答案时，系统只能靠人工经验维持。一旦项目数量增加，经验就会变成不可复制的隐性依赖。",[17,515,517],{"id":516},"三下一阶段的重点不是再加一个入口","三、下一阶段的重点不是再加一个入口",[10,519,520],{},"从现有项目的提交和文档可以看到，下一阶段更值得投入的是四个基础能力。",[10,522,523],{},"第一，统一任务状态。不同项目都在处理异步任务，但状态命名、失败原因和重试策略不应各自发明。",[10,525,526],{},"第二，统一产物账本。一个任务产生了哪些文件、消耗了哪些资源、是否对用户可见，需要能从提交到终态完整追踪。",[10,528,529],{},"第三，统一故障出口。401、超时、部分成功、未知执行和版本回滚，应该有明确的用户反馈与后台证据。",[10,531,532],{},"第四，统一发布验收。构建通过不代表线上可用，应该把健康检查、代理路径、真实端口、关键日志和浏览器操作放到同一份验收清单里。",[10,534,535],{},"项目多以后，工程能力不再体现在“能写多少功能”，而体现在“能否让不同功能遵守同一套边界”。",[17,537,539],{"id":538},"四我会保留的判断","四、我会保留的判断",[10,541,542],{},"AI 项目最容易被低估的部分，不是模型调用，而是模型调用之后的系统。",[10,544,545],{},"生成只是起点。身份、任务、费用、存储、交付、恢复和反馈，决定了一个功能能不能进入真实使用。",[10,547,548],{},"当 Git 提交开始频繁出现“补状态”“补恢复”“补验收”“补审计”时，这不是工程变慢了，而是产品从演示阶段进入了可运营阶段。",{"title":216,"searchDepth":348,"depth":348,"links":550},[551,558,559,560],{"id":397,"depth":348,"text":398,"children":552},[553,555,556,557],{"id":402,"depth":554,"text":403},3,{"id":419,"depth":554,"text":419},{"id":441,"depth":554,"text":442},{"id":467,"depth":554,"text":468},{"id":483,"depth":348,"text":484},{"id":516,"depth":348,"text":517},{"id":538,"depth":348,"text":539},"2026-09-10","这段时间回看 \u002FUsers\u002Fxingye\u002FAi 下的项目，最明显的变化不是项目数量增加，而是问题的重心变了。",{},"\u002F2026-09-10-ai",{"title":379,"description":562},"2026-09-10-Ai项目群的工程化分水岭","从多个 AI 项目的 Git 记录和设计文档看，工程难点已经从“能不能生成”转向任务、计费、交付、恢复与用户反馈能否形成闭环。",[569,570,571,572,573,574],"AI 平台","Agent","内容生产","网关","桌面端","工程化","DzOpOX92EsanPp8pLNTusbzBmKH3WOTf3qcaZAFmhM8",{"id":4,"title":5,"body":577,"column":359,"date":360,"description":12,"extension":361,"hero_image":362,"meta":807,"navigation":364,"path":365,"seo":808,"series_id":362,"severity":362,"stem":367,"summary":368,"tags":809,"__hash__":375},{"type":7,"value":578,"toc":796},[579,581,583,585,587,601,603,605,607,609,611,613,615,617,619,621,667,669,673,675,679,681,683,687,689,691,697,699,701,703,708,710,720,722,724,726,728,732,736,738,740,742,746,758,762,772,774,776,778,780,794],[10,580,12],{},[10,582,15],{},[17,584,20],{"id":19},[10,586,23],{},[25,588,589,593,597],{},[28,590,591,34],{},[31,592,33],{},[28,594,595,40],{},[31,596,39],{},[28,598,599,46],{},[31,600,45],{},[10,602,49],{},[10,604,52],{},[17,606,56],{"id":55},[10,608,59],{},[10,610,62],{},[10,612,65],{},[10,614,68],{},[17,616,72],{"id":71},[10,618,75],{},[10,620,78],{},[80,622,623,633],{},[83,624,625],{},[86,626,627,629,631],{},[89,628,91],{},[89,630,94],{},[89,632,97],{},[99,634,635,643,651,659],{},[86,636,637,639,641],{},[104,638,106],{},[104,640,109],{},[104,642,112],{},[86,644,645,647,649],{},[104,646,117],{},[104,648,120],{},[104,650,123],{},[86,652,653,655,657],{},[104,654,128],{},[104,656,131],{},[104,658,134],{},[86,660,661,663,665],{},[104,662,139],{},[104,664,142],{},[104,666,134],{},[10,668,147],{},[10,670,150,671,154],{},[31,672,153],{},[17,674,158],{"id":157},[160,676,677],{},[10,678,164],{},[10,680,167],{},[10,682,170],{},[10,684,173,685,177],{},[31,686,176],{},[17,688,181],{"id":180},[10,690,184],{},[10,692,187,693,191,695,195],{},[31,694,190],{},[31,696,194],{},[10,698,198],{},[17,700,202],{"id":201},[10,702,205],{},[207,704,706],{"className":705,"code":211,"language":212},[210],[214,707,211],{"__ignoreMap":216},[10,709,219],{},[221,711,712,714,716],{},[28,713,225],{},[28,715,228],{},[28,717,718],{},[31,719,233],{},[10,721,236],{},[10,723,239],{},[17,725,243],{"id":242},[10,727,246],{},[10,729,249,730,253],{},[31,731,252],{},[10,733,256,734,154],{},[31,735,259],{},[10,737,262],{},[17,739,266],{"id":265},[10,741,269],{},[10,743,744],{},[31,745,274],{},[221,747,748,750,752,754,756],{},[28,749,279],{},[28,751,282],{},[28,753,285],{},[28,755,288],{},[28,757,291],{},[10,759,760],{},[31,761,296],{},[221,763,764,766,768,770],{},[28,765,301],{},[28,767,304],{},[28,769,307],{},[28,771,310],{},[10,773,313],{},[10,775,316],{},[17,777,320],{"id":319},[10,779,323],{},[25,781,782,786,790],{},[28,783,784,331],{},[31,785,330],{},[28,787,788,337],{},[31,789,336],{},[28,791,792,343],{},[31,793,342],{},[10,795,346],{},{"title":216,"searchDepth":348,"depth":348,"links":797},[798,799,800,801,802,803,804,805,806],{"id":19,"depth":348,"text":20},{"id":55,"depth":348,"text":56},{"id":71,"depth":348,"text":72},{"id":157,"depth":348,"text":158},{"id":180,"depth":348,"text":181},{"id":201,"depth":348,"text":202},{"id":242,"depth":348,"text":243},{"id":265,"depth":348,"text":266},{"id":319,"depth":348,"text":320},{},{"title":5,"description":12},[370,371,372,373,374],{"id":811,"title":812,"body":813,"column":359,"date":1729,"description":817,"extension":361,"hero_image":362,"meta":1730,"navigation":364,"path":1731,"seo":1732,"series_id":362,"severity":362,"stem":1733,"summary":1734,"tags":1735,"__hash__":1742},"posts\u002F2026-08-02-首页改成八屏.md","首页改成八屏",{"type":7,"value":814,"toc":1716},[815,818,824,827,831,834,845,854,857,860,868,875,881,887,952,955,974,979,984,987,993,997,1000,1003,1011,1014,1017,1023,1026,1054,1057,1171,1174,1180,1184,1187,1203,1217,1220,1236,1239,1242,1246,1252,1255,1258,1264,1267,1317,1321,1324,1327,1330,1336,1339,1342,1347,1353,1357,1360,1363,1369,1373,1378,1419,1429,1432,1461,1466,1469,1519,1525,1528,1585,1597,1600,1604,1607,1610,1615,1618,1621,1626,1630,1633,1703,1706,1712],[10,816,817],{},"这个博客的首页原来是连续滚动。改成了整屏分页，一页一件事，共八屏：",[207,819,822],{"className":820,"code":821,"language":212},[210],"首屏 \u002F 自我介绍 01 \u002F 自我介绍 02 \u002F 做过的事 \u002F 精选复盘 \u002F 项目 \u002F 专栏 \u002F 联系\n",[214,823,821],{"__ignoreMap":216},[10,825,826],{},"一屏容纳是逐档实测调出来的，不是估的。",[17,828,830],{"id":829},"一吸附用谁的","一、吸附用谁的",[10,832,833],{},"第一个决定是吸附用哪套机制。",[10,835,836,837,840,841,844],{},"原生有 ",[214,838,839],{},"scroll-snap","，CSS 几行就能写。但这里用不了——页面上的平滑滚动是 Lenis 提供的，它用 JS 驱动 ",[214,842,843],{},"scrollTop","：",[160,846,847],{},[10,848,849,850,853],{},"吸附用 Lenis 自带的 Snap 而不是 CSS scroll-snap——Lenis 是用 JS 驱动 scrollTop 的，和原生吸附会互相抢控制权、滚起来发抖。Snap 挂在同一个 Lenis 实例上没有这个问题，",[214,851,852],{},"onSnapComplete"," 还能驱动指示器。",[10,855,856],{},"两个机制都想控制滚动位置，结果就是抖。",[10,858,859],{},"挂在同一个实例上之后，吸附完成后还能拿到回调，右侧的页码指示器直接跟着它更新。",[17,861,863,864,867],{"id":862},"二mandatory-是错的","二、",[214,865,866],{},"mandatory"," 是错的",[10,869,870,871,874],{},"第一版用 ",[214,872,873],{},"type: 'mandatory'","。用户反馈手感是「被抢走了」。",[10,876,877,878,880],{},"原因是 ",[214,879,866],{}," 在用户还在滑的时候就强行把页面拽向最近一页。滑动过程被插手，手感不是吸附，是失控。",[10,882,883,884,844],{},"改成 ",[214,885,886],{},"proximity",[207,888,892],{"className":889,"code":890,"language":891,"meta":216,"style":216},"language-ts shiki shiki-themes github-light github-dark","type: 'proximity',\ndistanceThreshold: '30%',\nduration: 1.1,\ndebounce: 500,\n","ts",[214,893,894,914,926,939],{"__ignoreMap":216},[895,896,899,903,907,911],"span",{"class":897,"line":898},"line",1,[895,900,902],{"class":901},"sScJk","type",[895,904,906],{"class":905},"sVt8B",": ",[895,908,910],{"class":909},"sZZnC","'proximity'",[895,912,913],{"class":905},",\n",[895,915,916,919,921,924],{"class":897,"line":348},[895,917,918],{"class":901},"distanceThreshold",[895,920,906],{"class":905},[895,922,923],{"class":909},"'30%'",[895,925,913],{"class":905},[895,927,928,931,933,937],{"class":897,"line":554},[895,929,930],{"class":901},"duration",[895,932,906],{"class":905},[895,934,936],{"class":935},"sj4cs","1.1",[895,938,913],{"class":905},[895,940,942,945,947,950],{"class":897,"line":941},4,[895,943,944],{"class":901},"debounce",[895,946,906],{"class":905},[895,948,949],{"class":935},"500",[895,951,913],{"class":905},[10,953,954],{},"三条参数各管一件事：",[221,956,957,962,968],{},[28,958,959,961],{},[214,960,886],{}," 只在停下来且已经接近边界时才轻推一把。",[28,963,964,967],{},[214,965,966],{},"distanceThreshold: '30%'"," 定义「接近」——落点离边界不到 30% 视口高才吸附，停在页面中间就让它停着。",[28,969,970,973],{},[214,971,972],{},"debounce: 500"," 等惯性完全停下来再判断，不在滑行中途插手。",[10,975,976,978],{},[214,977,944],{}," 从 220 提到 500：",[160,980,981],{},[10,982,983],{},"debounce 太小会在惯性还没停时就抢着吸附，手感发涩。",[10,985,986],{},"实测结果：",[207,988,991],{"className":989,"code":990,"language":212},[210],"滑动全程 0 反向帧（原来会被拽回）\n停在离边界 306px（超过 30% 阈值）时保持不动\n停在边界附近才对齐整页\n",[214,992,990],{"__ignoreMap":216},[17,994,996],{"id":995},"三一屏装不装得下","三、一屏装不装得下",[10,998,999],{},"分页的前提是内容能装进一屏。这一条逐档量过。",[10,1001,1002],{},"首屏原高 921px：",[221,1004,1005,1008],{},[28,1006,1007],{},"1440×900 超出 89px",[28,1009,1010],{},"1366×768 超出 201px",[10,1012,1013],{},"精选复盘 788px，在 1366×768 超出 88px。",[10,1015,1016],{},"两个方向可选：砍内容，或者按档收紧间距。选了后者，分三档：",[207,1018,1021],{"className":1019,"code":1020,"language":212},[210],"min-width: 1024px and max-height: 820px   深压\nmin-width: 1024px and max-height: 900px   只压首屏\nmin-width: 1024px and min-height: 1000px  放大间距吃大屏留白\n",[214,1022,1020],{"__ignoreMap":216},[10,1024,1025],{},"中间那档的调整方式值得记一笔。为了挤出 27px，第一版直接砍了字号：",[207,1027,1031],{"className":1028,"code":1029,"language":1030,"meta":216,"style":216},"language-css shiki shiki-themes github-light github-dark","font-size: clamp(46px, 9.2vw, 132px)  →  clamp(30px, 5.4vw, 62px)\n","css",[214,1032,1033],{"__ignoreMap":216},[895,1034,1035,1039,1042,1045,1048,1051],{"class":897,"line":898},[895,1036,1038],{"class":1037},"s9eBZ","font-size",[895,1040,1041],{"class":905},": clamp(46px, 9",[895,1043,1044],{"class":901},".2vw",[895,1046,1047],{"class":905},", 132px)  →  clamp(30px, 5",[895,1049,1050],{"class":901},".4vw",[895,1052,1053],{"class":905},", 62px)\n",[10,1055,1056],{},"砍掉一半多。首屏标题是整页的主体，砍字号会让整屏塌下来。改成全部从内边距要：",[207,1058,1060],{"className":1028,"code":1059,"language":1030,"meta":216,"style":216},"@media (min-width: 1024px) and (max-height: 900px) {\n  .page-hero :deep(.hero) { padding-top: 20px; padding-bottom: 24px; }\n  .page-hero :deep(.hero-foot) { margin-top: 26px; }\n}\n",[214,1061,1062,1103,1143,1166],{"__ignoreMap":216},[895,1063,1064,1068,1071,1074,1076,1079,1082,1085,1088,1090,1093,1095,1098,1100],{"class":897,"line":898},[895,1065,1067],{"class":1066},"szBVR","@media",[895,1069,1070],{"class":905}," (",[895,1072,1073],{"class":935},"min-width",[895,1075,906],{"class":905},[895,1077,1078],{"class":935},"1024",[895,1080,1081],{"class":1066},"px",[895,1083,1084],{"class":905},") ",[895,1086,1087],{"class":1066},"and",[895,1089,1070],{"class":905},[895,1091,1092],{"class":935},"max-height",[895,1094,906],{"class":905},[895,1096,1097],{"class":935},"900",[895,1099,1081],{"class":1066},[895,1101,1102],{"class":905},") {\n",[895,1104,1105,1108,1111,1114,1117,1120,1122,1125,1127,1130,1133,1135,1138,1140],{"class":897,"line":348},[895,1106,1107],{"class":901},"  .page-hero",[895,1109,1110],{"class":905}," :deep(",[895,1112,1113],{"class":901},".hero",[895,1115,1116],{"class":905},") { ",[895,1118,1119],{"class":935},"padding-top",[895,1121,906],{"class":905},[895,1123,1124],{"class":935},"20",[895,1126,1081],{"class":1066},[895,1128,1129],{"class":905},"; ",[895,1131,1132],{"class":935},"padding-bottom",[895,1134,906],{"class":905},[895,1136,1137],{"class":935},"24",[895,1139,1081],{"class":1066},[895,1141,1142],{"class":905},"; }\n",[895,1144,1145,1147,1149,1152,1154,1157,1159,1162,1164],{"class":897,"line":554},[895,1146,1107],{"class":901},[895,1148,1110],{"class":905},[895,1150,1151],{"class":901},".hero-foot",[895,1153,1116],{"class":905},[895,1155,1156],{"class":935},"margin-top",[895,1158,906],{"class":905},[895,1160,1161],{"class":935},"26",[895,1163,1081],{"class":1066},[895,1165,1142],{"class":905},[895,1167,1168],{"class":897,"line":941},[895,1169,1170],{"class":905},"}\n",[10,1172,1173],{},"只有 700px 可用高度那一档才让一档字号。三档实测：",[207,1175,1178],{"className":1176,"code":1177,"language":212},[210],"1440×900    132px\n1920×1080   132px\n1366×768     92.9px\n",[214,1179,1177],{"__ignoreMap":216},[17,1181,1183],{"id":1182},"四检测判据写错过一次","四、检测判据写错过一次",[10,1185,1186],{},"判断页面有没有溢出，第一版用的是：",[207,1188,1190],{"className":889,"code":1189,"language":891,"meta":216,"style":216},"scrollHeight > clientHeight\n",[214,1191,1192],{"__ignoreMap":216},[895,1193,1194,1197,1200],{"class":897,"line":898},[895,1195,1196],{"class":905},"scrollHeight ",[895,1198,1199],{"class":1066},">",[895,1201,1202],{"class":905}," clientHeight\n",[10,1204,1205,1206,1209,1210,457,1213,1216],{},"这个判据永远为假。页面用的是 ",[214,1207,1208],{},"min-height","，内容超了页面会自己长高——",[214,1211,1212],{},"scrollHeight",[214,1214,1215],{},"clientHeight"," 一起变大，比值不变。",[10,1218,1219],{},"改成直接比可用高度：",[207,1221,1223],{"className":889,"code":1222,"language":891,"meta":216,"style":216},"\u002F\u002F 检测判据从 scrollHeight > clientHeight 改为直接比可用高度——\n\u002F\u002F 页面用的是 min-height，内容超了页面会自己长高，前者永远比不出溢出\n",[214,1224,1225,1231],{"__ignoreMap":216},[895,1226,1227],{"class":897,"line":898},[895,1228,1230],{"class":1229},"sJ8bj","\u002F\u002F 检测判据从 scrollHeight > clientHeight 改为直接比可用高度——\n",[895,1232,1233],{"class":897,"line":348},[895,1234,1235],{"class":1229},"\u002F\u002F 页面用的是 min-height，内容超了页面会自己长高，前者永远比不出溢出\n",[10,1237,1238],{},"这个错误值得单独说：判据本身写错了，所以「三档零溢出」这个结论在修正之前是不成立的。",[10,1240,1241],{},"修正之后三档均为 8\u002F8 页零溢出。",[17,1243,1245],{"id":1244},"五不分页的三种情况","五、不分页的三种情况",[207,1247,1250],{"className":1248,"code":1249,"language":212},[210],"窄屏（\u003C 1024px）\n矮视口（\u003C 620px）\n减弱动效（prefers-reduced-motion）\n",[214,1251,1249],{"__ignoreMap":216},[10,1253,1254],{},"三种都退回连续滚动。",[10,1256,1257],{},"前两种有实测依据：",[207,1259,1262],{"className":1260,"code":1261,"language":212},[210],"实测 390px 下自我介绍 1448px、精选复盘 1114px，强行一屏只会截断内容\n",[214,1263,1261],{"__ignoreMap":216},[10,1265,1266],{},"第三种是硬要求。整屏吸附会强制改变滚动位置，这和「尊重减弱动效偏好」直接冲突。CSS 里也要同步禁用：",[207,1268,1270],{"className":1028,"code":1269,"language":1030,"meta":216,"style":216},"@media (prefers-reduced-motion: reduce) {\n  .page { min-height: 0; }\n  .pager { display: none; }\n}\n",[214,1271,1272,1279,1296,1313],{"__ignoreMap":216},[895,1273,1274,1276],{"class":897,"line":898},[895,1275,1067],{"class":1066},[895,1277,1278],{"class":905}," (prefers-reduced-motion: reduce) {\n",[895,1280,1281,1284,1287,1289,1291,1294],{"class":897,"line":348},[895,1282,1283],{"class":901},"  .page",[895,1285,1286],{"class":905}," { ",[895,1288,1208],{"class":935},[895,1290,906],{"class":905},[895,1292,1293],{"class":935},"0",[895,1295,1142],{"class":905},[895,1297,1298,1301,1303,1306,1308,1311],{"class":897,"line":554},[895,1299,1300],{"class":901},"  .pager",[895,1302,1286],{"class":905},[895,1304,1305],{"class":935},"display",[895,1307,906],{"class":905},[895,1309,1310],{"class":935},"none",[895,1312,1142],{"class":905},[895,1314,1315],{"class":897,"line":941},[895,1316,1170],{"class":905},[17,1318,1320],{"id":1319},"六分页之后每页只剩半屏内容","六、分页之后，每页只剩半屏内容",[10,1322,1323],{},"八屏分配完之后，出现一个反向问题：每页内容太少。",[10,1325,1326],{},"实测 1440×900 下多数页只填了 47%~63%，「做过的事」那页 63% 是空的。",[10,1328,1329],{},"既然分了页，每页就得撑得住。逐页补实：",[207,1331,1334],{"className":1332,"code":1333,"language":212},[210],"做过的事：加页首概览条（项目数\u002F时间跨度\u002F人手），每项补详细说明、关键指标与技术标签。308px → 548px\n项目：3 张卡补到 6 张，两行三列。362px → 511px\n联系：补引言、about 与 timeline 两个入口、站点信息带。452px → 791px\n专栏：每张卡补该栏最新一篇标题。512px → 597px\n自我介绍 01：四张能力卡各补首组的真实条目。504px → 596px\n自我介绍 02：补技术栈横带。522px → 603px\n",[214,1335,1333],{"__ignoreMap":216},[10,1337,1338],{},"填充率从 47%~63% 提到 61%~100%。",[10,1340,1341],{},"同一轮里把两个区块的入场动画也补了常驻循环，因为：",[160,1343,1344],{},[10,1345,1346],{},"实测入场动画本来就在跑（轴线 scaleX 0→0.99、圆点带回弹、卡片 clip-path 从 78% 揭到 4%、指标读数往上跳），问题是太隐蔽：一次性、1 秒内结束、触发点又在区块刚露头时，等正眼看过去通常已经播完。",[207,1348,1351],{"className":1349,"code":1350,"language":212},[210],"触发点 top 88% → 95%，轴线 0.9s → 1.25s，圆点与节点错峰 0.11 → 0.16\n",[214,1352,1350],{"__ignoreMap":216},[17,1354,1356],{"id":1355},"七断点从-900-降到-820","七、断点从 900 降到 820",[10,1358,1359],{},"第一版把「矮视口」定在 900px 高。1440×900 的屏幕因此被当成矮视口，白白砍掉了列表摘要——精选复盘从 788px 缩到 389px。",[10,1361,1362],{},"而 1440×900 明明有空间。断点降到 820：",[207,1364,1367],{"className":1365,"code":1366,"language":212},[210],"820 以下   深压\n900 以下   只压首屏\n1000 以上  放大间距吃掉大屏留白\n",[214,1368,1366],{"__ignoreMap":216},[17,1370,1372],{"id":1371},"八两个-css-优先级问题","八、两个 CSS 优先级问题",[10,1374,1375],{},[31,1376,1377],{},"首屏被挤成一个框。",[207,1379,1381],{"className":1028,"code":1380,"language":1030,"meta":216,"style":216},".page { padding: 24px 0; }\n.page-hero { padding: 0; }\n",[214,1382,1383,1404],{"__ignoreMap":216},[895,1384,1385,1388,1390,1393,1395,1397,1399,1402],{"class":897,"line":898},[895,1386,1387],{"class":901},".page",[895,1389,1286],{"class":905},[895,1391,1392],{"class":935},"padding",[895,1394,906],{"class":905},[895,1396,1137],{"class":935},[895,1398,1081],{"class":1066},[895,1400,1401],{"class":935}," 0",[895,1403,1142],{"class":905},[895,1405,1406,1409,1411,1413,1415,1417],{"class":897,"line":348},[895,1407,1408],{"class":901},".page-hero",[895,1410,1286],{"class":905},[895,1412,1392],{"class":935},[895,1414,906],{"class":905},[895,1416,1293],{"class":935},[895,1418,1142],{"class":905},[10,1420,1421,1424,1425,1428],{},[214,1422,1423],{},".page-hero { padding: 0 }"," 写在媒体查询之前，被后面同优先级的 ",[214,1426,1427],{},".page { padding: ... }"," 覆盖了。首屏于是四边都留出内边距，看起来像一个框。",[10,1430,1431],{},"修法是在每一档里重新声明一次：",[207,1433,1435],{"className":1028,"code":1434,"language":1030,"meta":216,"style":216},"\u002F* 首屏必须满幅出血。这行不能省——上面那条 .page 同优先级且更靠后，\n   会把顶部声明的 .page-hero { padding: 0 } 覆盖掉。 *\u002F\n.page-hero { padding: 0; }\n",[214,1436,1437,1442,1447],{"__ignoreMap":216},[895,1438,1439],{"class":897,"line":898},[895,1440,1441],{"class":1229},"\u002F* 首屏必须满幅出血。这行不能省——上面那条 .page 同优先级且更靠后，\n",[895,1443,1444],{"class":897,"line":348},[895,1445,1446],{"class":1229},"   会把顶部声明的 .page-hero { padding: 0 } 覆盖掉。 *\u002F\n",[895,1448,1449,1451,1453,1455,1457,1459],{"class":897,"line":554},[895,1450,1408],{"class":901},[895,1452,1286],{"class":905},[895,1454,1392],{"class":935},[895,1456,906],{"class":905},[895,1458,1293],{"class":935},[895,1460,1142],{"class":905},[10,1462,1463],{},[31,1464,1465],{},"标题被大屏规则压小。",[10,1467,1468],{},"大屏档里有一条：",[207,1470,1472],{"className":1028,"code":1471,"language":1030,"meta":216,"style":216},".page :deep(.big) { font-size: clamp(34px, 3.4vw, 56px); }\n",[214,1473,1474],{"__ignoreMap":216},[895,1475,1476,1478,1480,1483,1485,1487,1489,1492,1495,1498,1500,1503,1506,1509,1511,1514,1516],{"class":897,"line":898},[895,1477,1387],{"class":901},[895,1479,1110],{"class":905},[895,1481,1482],{"class":901},".big",[895,1484,1116],{"class":905},[895,1486,1038],{"class":935},[895,1488,906],{"class":905},[895,1490,1491],{"class":935},"clamp",[895,1493,1494],{"class":905},"(",[895,1496,1497],{"class":935},"34",[895,1499,1081],{"class":1066},[895,1501,1502],{"class":905},", ",[895,1504,1505],{"class":935},"3.4",[895,1507,1508],{"class":1066},"vw",[895,1510,1502],{"class":905},[895,1512,1513],{"class":935},"56",[895,1515,1081],{"class":1066},[895,1517,1518],{"class":905},"); }\n",[10,1520,1521,1522,1524],{},"首屏主标题的类名也是 ",[214,1523,1482],{},"。1920×1080 下它从 132px 被压到 56px，首屏整个塌掉。",[10,1526,1527],{},"改成排除首屏：",[207,1529,1531],{"className":1028,"code":1530,"language":1030,"meta":216,"style":216},"\u002F* 必须排除首屏——hero 里的 .big 是那个巨大的主标题，\n   被这条命中会从 132px 压到 56px，整个首屏塌掉 *\u002F\n.page:not(.page-hero) :deep(.big) { font-size: clamp(34px, 3.4vw, 56px); }\n",[214,1532,1533,1538,1543],{"__ignoreMap":216},[895,1534,1535],{"class":897,"line":898},[895,1536,1537],{"class":1229},"\u002F* 必须排除首屏——hero 里的 .big 是那个巨大的主标题，\n",[895,1539,1540],{"class":897,"line":348},[895,1541,1542],{"class":1229},"   被这条命中会从 132px 压到 56px，整个首屏塌掉 *\u002F\n",[895,1544,1545,1548,1550,1552,1555,1557,1559,1561,1563,1565,1567,1569,1571,1573,1575,1577,1579,1581,1583],{"class":897,"line":554},[895,1546,1547],{"class":901},".page:not",[895,1549,1494],{"class":905},[895,1551,1408],{"class":901},[895,1553,1554],{"class":905},") :deep(",[895,1556,1482],{"class":901},[895,1558,1116],{"class":905},[895,1560,1038],{"class":935},[895,1562,906],{"class":905},[895,1564,1491],{"class":935},[895,1566,1494],{"class":905},[895,1568,1497],{"class":935},[895,1570,1081],{"class":1066},[895,1572,1502],{"class":905},[895,1574,1505],{"class":935},[895,1576,1508],{"class":1066},[895,1578,1502],{"class":905},[895,1580,1513],{"class":935},[895,1582,1081],{"class":1066},[895,1584,1518],{"class":905},[10,1586,1587,1588,154,1591,1593,1594,1596],{},"两个问题的成因相同：",[31,1589,1590],{},"同一个类名在两种语义下被复用",[214,1592,1387],{}," 既是容器也是列表页的内边距来源，",[214,1595,1482],{}," 既是区块小标题也是首屏主标题。分开之后就没这个问题。",[10,1598,1599],{},"修完实测：hero 左 0、底 1、右 10（右侧那 10px 是站点滚动条本身的宽度），标题 132px 满值，8\u002F8 页仍零溢出。",[17,1601,1603],{"id":1602},"九加载页期间的一个时序漏洞","九、加载页期间的一个时序漏洞",[10,1605,1606],{},"这个站有一个启动加载页，首次访问时盖几秒。加载页盖着的时候，入场动画被暂停了——否则动画会在遮挡期间自己跑完。",[10,1608,1609],{},"副作用是页面切换的幕布动画也被冻住：",[160,1611,1612],{},[10,1613,1614],{},"实测（加载页 3s 时点击）：t=745 路由已切到 \u002Fcolumns 而幕布 display:none，t=1406 幕布才出现并扫过。",[10,1616,1617],{},"路由在没有幕布的情况下切换了，等加载页结束、时间线恢复之后，被冻住的补间才补播。",[10,1619,1620],{},"结论是补一行守卫：加载页盖着时直接放行，不放幕布。理由是：",[160,1622,1623],{},[10,1624,1625],{},"真实用户点不到——加载页是全屏遮罩。但浏览器前进\u002F后退、程序化跳转仍可能在窗口内触发导航。",[17,1627,1629],{"id":1628},"十整屏分页的账","十、整屏分页的账",[10,1631,1632],{},"这套改动最后收敛成一张数字表：",[80,1634,1635,1645],{},[83,1636,1637],{},[86,1638,1639,1642],{},[89,1640,1641],{},"项目",[89,1643,1644],{},"数值",[99,1646,1647,1655,1663,1671,1679,1687,1695],{},[86,1648,1649,1652],{},[104,1650,1651],{},"页数",[104,1653,1654],{},"8",[86,1656,1657,1660],{},[104,1658,1659],{},"吸附类型",[104,1661,1662],{},"proximity，阈值 30%，防抖 500ms",[86,1664,1665,1668],{},[104,1666,1667],{},"吸附时长",[104,1669,1670],{},"1.1s",[86,1672,1673,1676],{},[104,1674,1675],{},"分页生效条件",[104,1677,1678],{},"宽 ≥ 1024px 且 高 ≥ 620px 且 未开启减弱动效",[86,1680,1681,1684],{},[104,1682,1683],{},"溢出",[104,1685,1686],{},"三档均 0\u002F8",[86,1688,1689,1692],{},[104,1690,1691],{},"填充率",[104,1693,1694],{},"61%~100%（改前 47%~63%）",[86,1696,1697,1700],{},[104,1698,1699],{},"首屏标题",[104,1701,1702],{},"132px（1440×900、1920×1080）、92.9px（1366×768）",[10,1704,1705],{},"每一条都有一次实测在后面。这类改动没别的办法——「一屏装不装得下」这件事只能量，量完才知道该压间距还是该砍内容。",[10,1707,1708,1709,1711],{},"中间还有两处是判断上的错，不是量的问题：一是用 ",[214,1710,866],{}," 吸附（把用户的滑动抢走了），二是把「矮视口」定在 900（把有空间的屏幕当成没空间）。前者的修法是换模式，后者的修法是挪断点。",[1713,1714,1715],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":216,"searchDepth":348,"depth":348,"links":1717},[1718,1719,1721,1722,1723,1724,1725,1726,1727,1728],{"id":829,"depth":348,"text":830},{"id":862,"depth":348,"text":1720},"二、mandatory 是错的",{"id":995,"depth":348,"text":996},{"id":1182,"depth":348,"text":1183},{"id":1244,"depth":348,"text":1245},{"id":1319,"depth":348,"text":1320},{"id":1355,"depth":348,"text":1356},{"id":1371,"depth":348,"text":1372},{"id":1602,"depth":348,"text":1603},{"id":1628,"depth":348,"text":1629},"2026-08-02",{},"\u002F2026-08-02",{"title":812,"description":817},"2026-08-02-首页改成八屏","把博客首页从连续滚动改成八屏整屏分页。吸附用 Lenis 自带能力而不是 CSS 原生，断点按实测调整，每一档的溢出与填充率都有数字。",[1736,1737,1738,1739,1740,1741],"GSAP","Lenis","滚动吸附","响应式断点","页面结构","prefers-reduced-motion","2o9dKTJYPwCLxF__bLLN46O2x_0PMJoQgyZSxZpO428",1789212572876]