Tai Chi — 方法说明
本项目唯一的事实来源。 品牌、设计系统、证据基础、处方逻辑、内容政策和安全边界 都写在这里。如果代码和本文档不一致,其中之一就是 bug——请在 PR 里说明是哪一个。
状态:活文档 · 最近一次实质性修订:2026-08-02
本文是英文文档
docs/METHODOLOGY.md的翻译,逐节对应,顺序一致。这种对应是 结构性的:/zh/method的标题 id 按位置取自英文文件,因此/method#42-joints与/zh/method#42-joints指向同一节。本译文尚未经过母语审校;见 §11。
1. 这是什么,以及它拒绝成为什么
Tai Chi 是一个免费、公开、无需登录的太极陪练。你打开它,它知道今天是哪天, 然后根据你站得有多稳、有多少时间、想练什么,给你一次练习。
它是:
- 一个包在别人教学外面的排程器和计时器。
- 一个轮换引擎,主要工作是确保明天不会感觉像今天。
- 一个诚实的中介:每个视频都注明出处、链接回去,并在课程库页面完整列出。
它刻意不是:
- 老师。它不纠正你的姿势,看不见你,也永远不会说某个动作是「正确的」。
- 医疗器械或临床干预。它总结已发表的证据来解释为什么练习是这个形状; 它不诊断、不治疗,也不针对某种疾病做个性化。
- 一个带账号的产品。没有登录、没有邮箱、没有服务器端资料、没有分析统计。
一切都存在单台设备的
localStorage里。
1.1 为什么不做登录
目标练习者中 65 岁以上的比例过高。每一道注册门槛都是一次流失, 而一个要求 78 岁的人去验证邮箱的健康工具,是一个他们只会打开一次的工具。 这个决定的代价是真实的,我们在界面里公开承认:不能跨设备同步, 清除站点数据会清除连续记录。
2. 品牌
2.1 名字
Tai Chi(太極,tài jí)命名的是这门练习本身,而不是围绕它的某种氛围。之所以选它而不是别的:
- 它准确说明这个应用是做什么的,用的正是人们去找它时已经在用的词。
- 它具体。「Flow」「Balance」「Serenity」是装成产品的形容词。
- 它不携带任何宣称。这个应用调度的是别人的教学;一个自造的品牌名会暗示 它有自己的一套方法,而它没有。
2.2 语气
平实、平静,略带干燥。短句。绝不喘息,绝不神秘,绝不像一个大喊加油的教练。
| 我们这样写 | 我们不这样写 |
|---|---|
| 「这里没有什么要达成的。站着就好。」 | 「拥抱你内在的宁静!」 |
| 「酸胀没关系;锐痛不行。」 | 「倾听你的身体 🙏」 |
| 「休息是训练,不是训练里的空缺。」 | 「别断了你的连续记录!」 |
| 「这个在这里放不了。」 | 「哎呀!出错了。」 |
内疚绝不用作动力。这里没有「你漏了 3 天」这种状态,连续记录中断只是如实呈现, 不加评论。
2.3 标志
两道交错的弧线——一升一沉,共用一个中心——描画的是「云手」中双手行走的路径。 它承载了这门练习所依据的阴阳交替,却没有使用太极图;后者在视觉上早已用尽, 读起来像 1997 年的商标。单一笔画粗细,24 单位网格,在 favicon 尺寸下依然可辨。
2.4 颜色——深夜与月光
这个应用默认是深色的,而深色是设计出来的状态,不是一种迁就。 太极在黎明练,在光线昏暗的房间里练,往往在开灯之前。早上六点一张明亮的白页 是有敌意的。浅色模式存在且完整,但那是同一个品牌在正午,不是另一个品牌。
屏幕是一片只有一个光源的夜空。其余一切都由此而来:月亮固定在右上角, 玻璃面板的高光边缘假定光来自右上方,没有任何表面自己发色——它们只是接住月光。
一切都用 oklch,所以明度阶在感知上是均匀的,半透明图层的混合也可预测。
| 色阶 | 作用 | 为什么是这个色相 |
|---|---|---|
| 夜 | 背景与结构 | 蓝黑,绝不是中性灰。正是那一点蓝,让强调色读起来像光而不是涂料 |
| 月光 | 主色。唯一的光源,也是强调色 | 淡而低饱和的蓝。用于播放控件、计时器圆环和激活状态——几乎不用在别处 |
| 青瓷 | 练习色:气功、热身、柔和 | 一种收得极克制的玉色,读起来像月下的釉面瓷,而不是「养生绿」 |
| 灯火 | 今天、连续记录、中等强度 | 常规使用中唯一的暖色。严格配给,好让它有意义 |
| 余烬 | 大强度、破坏性操作 | 唯一的热色。整个应用里大概只出现在两个界面上 |
颜色永远不是唯一的信号。 每个阶段标签都把颜色与文字和图标配在一起;
强度圆点带有 sr-only 的文字等价物。
2.5 字体
两个字族,按主题而不是按流行来选。
- Cormorant(标题)——一款书法感的旧式衬线体,笔画调制近似毛笔。 它承载了这门练习的水墨气质,而不必求助于拼贴式的「东方风」字体, 那既难看又略带冒犯。仅用于标题尺寸:它太纤细,不适合排正文,我们从不这么做。
- Inter(所有功能性文字)——界面、控件,尤其是计时器。x 高度大、中性, 且有真正的等宽数字。
计时器以及任何原地变化的数值都强制使用等宽数字,这样数字替换时不会跳动。
3. 设计系统
3.1 令牌层级
三层,一个依赖方向。组件只消费第 3 层。
- 原始层 —
--ch-moon-400、--ch-night-950。品牌原始值。 组件绝不直接引用。 - 语义层 —
--primary、--phase-form、--glass-blur。承载意义, 可随主题切换。明暗互换的正是这一层。 - 组件层 — 在
@theme inline中生成的 Tailwind 工具类 (bg-phase-form、text-timer、rounded-3xl)。
一个直接进入组件的新颜色,是一条评审意见,而不是一个补丁。
3.2 液态玻璃,写清楚
一块玻璃面板是四层信号的叠加。少了其中任何一层,它看起来就是一个半透明矩形, 而不是玻璃。
- 折射 —
backdrop-filter: blur() saturate() brightness()。真正让人信服的 是饱和度;只有模糊会读成磨砂塑料。在深色版本里,面板是烟熏而不是磨砂: 填充降到约 6%,靠提亮来做事。 - 本体 — 一层半透明的色调填充(
--glass-tint/--glass-fill)。 - 边缘 — 顶部一条明亮的细线(
inset 0 1px 0),底部一条深色的接触线。 正是它暗示了厚度。 - 高光 — 通过
::before的一道对角高光扫过,再加上通过::after的 3.5% 噪点颗粒。颗粒的存在是为了打败大面积模糊填充的塑料感。
以 @utility glass、glass-faint、glass-strong、glass-specular、
glass-grain、glass-press 暴露,并通过唯一的 <Glass> 原语消费,
它有三个轴:depth、tone、radius。
规则:绝不要在组件上手搓 backdrop-blur。 用 <Glass>,或者加一个变体。
有两件事让这套东西是可持续的,而不只是装饰:
- 夜空。 只有当背后有值得折射的东西时,玻璃才读起来像玻璃。
一轮固定的月亮加上两片缓慢、清冷的地面色场,在
-z-10层坐在一切之后。 纯 CSS,没有 canvas,没有 rAF。上面一层遮罩保证无论色场落在哪里, 正文文字的对比度都成立——天空是装饰,文字绝不能依赖它。 @supports回退。 在不支持或已禁用backdrop-filter的地方, 填充跳到 88–98% 不透明度。文字对比度从不依赖模糊生效。
3.3 动效
| 规则 | 原因 |
|---|---|
| 只用两条缓动曲线,且都是自定义的 | --ease-glass 用于材质,--ease-settle 用于到位。超过两条就读作不一致。 |
| 没有任何东西从侧边动画进入 | 余光里的动作会拉扯视线,破坏站姿。提示以原地淡入的方式出现。 |
| 环境循环时长 28–44 秒 | 慢到不会被注意;再快就变成你必须对抗的节拍器。 |
prefers-reduced-motion 取消漂移 | ……但保留不透明度和颜色过渡,因为它们承载练习的状态。 |
3.4 无障碍承诺
- 触控目标:下限 44px,播放控制条 48px,播放键 64px。
- 每个控件都有真正的
aria-label;计时器圆环是aria-hidden, 另有一个粗粒度的sr-onlylive 区域,这样它不会每秒被朗读一次。 - 设置选项使用
role="radiogroup",让方向键和位置播报都能工作。 - 跳转链接、可见的焦点环、导航与当前区块上的
aria-current。 - 不设
maximumScale。 这个应用面向 65 岁以上的人;缩放不是可选项。 - 播放器默认开启字幕——课程库里有很大一部分是安静的、带口音的,或者是普通话, 而观看的人在房间另一头。
3.5 响应式架构
第一版在约 400px 以下就坏掉了,对于一个用户最可能把它开在靠着台灯的手机上的 应用来说,这不可接受。现在的规则:
- 导航是移动,而不是缩小。 在
md以下,头部只保留品牌标志和两个控件; 四个导航目的地变成一条固定的玻璃底部标签栏,感知安全区, 并且同时有图标和文字。纯图标的栏对这个应用面向的年龄段来说是读不懂的。 html和body上overflow-x: hidden,每个元素上min-width: 0。 任何东西都不应比视口更宽;如果有,那是个 bug——但这个 bug 绝不能变成 一个可以横向滚动的页面。- 周条必须在 330px 下容纳七个格子。 文字标签在
sm以下消失; 星期首字母、日期和色点保留。格子无论如何保持高度 ≥44px—— 它是全应用中被点击最多的控件。 - 到处都有安全区:头部、标签栏和设置面板上都有
env(safe-area-inset-*)。 - 计时器按它的圆环来定尺寸,而不是按视口。基于
vw的clamp在宽屏上会溢出圆环。 - 控件上
font-size: max(16px, 1em),这样 iOS 永远不会缩放页面。
3.6 国际化
语言在首次访问时自动从浏览器检测,之后记住。
- 读取
navigator.languages——用户自己的有序偏好列表——取第一个受支持的条目。 列表是[ca, es, en]的人会得到西班牙语,而不是英语。 - 只匹配主子标签:
es-419、es-MX和es都解析为西班牙语。 所有中文变体都解析为简体——在没有真正繁体译文的情况下发布zh-Hant, 会比显示简体更糟。 - 在设置面板中的显式选择会覆盖检测结果,并写入
localStorage。 「跟随浏览器」会清除这个覆盖。
发布的语言:英语、西班牙语、法语、德语、简体中文。
有两个架构决定让这件事真正成立,而不是做了一半:
- 引擎输出的是键,从不是散文。
prescribe和select是纯函数, 且对语言无感知,所以它们返回Note = { key, values },由界面去解析。 因此理由说明、注意事项和分段备注都是被翻译的,而不只是外壳。 当某个值本身就是一个可翻译的术语时(「你要求的是柔和」), 引擎把它放在一个 NUL 哨兵之后输出,translate再向内解析一层。 - 英语既是事实来源,也是类型。 其他每种语言都被类型化为
Record<keyof typeof en, string>,所以缺一个键就是编译错误。 运行时缺失的键会回退到英语,而不是把原始键显示出来。
所有路由家族都已翻译:外壳、课程页面、十三篇 /for/* 长文、安装教程,以及本文档。
src/lib/i18n/coverage.ts 仍然是「哪一个可以在哪种语言下被索引」的事实来源,
而收回一项主张,每个家族只需要一行。
有两类内容刻意不翻译,还有一类刻意以一种不寻常的方式翻译:
- 不翻译:段位名称(专有名词,如级和段)以及视频标题与频道名 (署名必须与来源一致)。
- 不翻译:方法文档的标题锚点。
/method#42-joints在每种语言下都能解析, 因为渲染文档的 id 是按位置取自英文文件的,而不是把译文标题转成 slug。 锚点是标识符,而标识符不翻译。代价是:译文不能增加、删除或重排任何一节; 测试会强制这一点。 - 由厂商翻译,而不是由我们翻译:
/install的菜单标签。 iOS、Chrome、Firefox 和三星浏览器都会本地化自己的菜单, 所以install.label.*携带的是各厂商在每种语言下自己的写法。 某个厂商没有翻译某个标签时,保留英文字符串才是正确的值—— 这是词典里唯一成立此规则的地方。
3A. 进阶
进度以你出现过的天数衡量——从不以分钟,也从不以强度。糟糕的那天 一次五分钟的坐式练习,和状态好的那天一套四十五分钟的套路,算得一样多。
这不是在放水。奖励训练量会把这个应用真正服务的人——年纪更大、更僵硬、 更没把握的人——推向做得太多,而那正是运动干预造成伤害的主要途径。
段位(由完成天数推导,从不存储):
| 段位 | 天数 | 段位 | 天数 | |||
|---|---|---|---|---|---|---|
| 初心 | 初心 | 0 | 白鹤 | 白鶴 | 60 | |
| 初光 | 初光 | 1 | 长江 | 長江 | 100 | |
| 静水 | 靜水 | 5 | 磐石 | 磐石 | 180 | |
| 青竹 | 青竹 | 15 | 古松 | 古松 | 365 | |
| 行云 | 行雲 | 30 |
里程碑只会获得,永不失去。其中之一——回来了——专门用于标记 离开两周之后的回归,因为那比连续记录更难,而且没有任何习惯追踪器会奖励它。
这个页面拒绝的反模式:
- 没有「你断了连续记录」的状态,任何地方都没有红色。
- 今天还没练不会中断连续记录。这一天还没结束。
- 不和别人比较。这里没有别人。
- 一切都在每次渲染时从记录日志推导出来。 把连续天数存在产生它的历史旁边, 正是每个习惯追踪器最终显示出一个与自身数据矛盾的数字的原因。
4. 证据基础
下面每一项主张都带着它的来源和核实状态:✅ 已核实表示撰写时已对照来源
核对过数字;⚠️ 未核实表示它出自一般常识,在用于任何面向公众的营销说法之前
应当先确认。
4.1 跌倒
- 太极把社区居住老年人的跌倒发生率降低约 19%(率比 0.81,95% CI
0.67–0.99;7 项研究共 2655 名参与者;低确定性证据)。Sherrington 等,
Cochrane Database of Systematic Reviews,2019。✅ 已核实
- 同一篇综述给平衡与功能性训练的评价更高:降低 24%,高确定性证据 (率比 0.76,95% CI 0.70–0.81;39 项研究)。太极是好的,不是最好的, 而且其确定性更低。
- 在高风险人群中,一套治疗性太极拳方案相比拉伸把跌倒减少了 58%, 相比多模式运动减少了 31%(相比拉伸 IRR 0.42,95% CI 0.31–0.56)。 Li 等,JAMA Internal Medicine,2018,670 名 70 岁及以上、有跌倒史或 行动受限的成年人。✅ 已核实
我们从中取用的: 平衡训练是对年长练习者价值最高的一段,
所以 balance 目标大致把该阶段翻倍,而「防跌倒」这个框架是诚实的、
而不是被夸大的。
4.2 关节
- 对膝骨关节炎,太极在 12 周时产生的 WOMAC 改善与标准物理治疗相当 (太极 167 分,95% CI 145–190;物理治疗 143 分,95% CI 119–167; 组间差异不显著)。Wang 等,Annals of Internal Medicine,2016,n=204。 ✅ 已核实
- Paul Lam 医生的 Tai Chi for Arthritis(孙式、高架势、无深屈膝)
是这个领域传播最广的方案,也是
joints目标和 75+ 年龄段的默认form内容。 ⚠️ 未核实(指该方案的普及程度,不是一项疗效宣称)
4.3 剂量
- 世卫组织 2020 年指南,65 岁以上成年人:每周 150–300 分钟中等强度 有氧活动,每周 ≥2 天肌肉强化,以及每周 ≥3 天、强调功能性平衡与力量、 中等或更高强度的多样化多成分活动。✅ 已核实
我们从中取用的: 默认计划是每周 5 天,而周期安排保证平衡重点会出现在 多个不连续的日子上。选择少于 3 天的练习者不会被警告——唠叨与品牌相悖—— 但计划仍会分散重点,而不是重复训练同一种能力。
4.4 强度
- 一般太极通常被归类为轻到中等强度,约 3.0 MET (Compendium of Physical Activities)。⚠️ 未核实——仅作为设计经验值使用, 从不作为数字展示给用户。
太极里的强度不是速度。它由以下因素调节:
- 架势高低 — 影响远大于其他,是主导变量。
- 动作幅度。
- 单腿支撑时间 — 训练效果和跌倒风险的主要驱动因素。
- 连续性 — 练习中有多少是不间断的移动。
所以界面在强度控件正下方写着「太极里这指的是架势高低和单腿时间,不是速度」。
4.5 呼吸
- 引导段落的节奏大约是每分钟 5.5 次呼吸(11 秒一个循环), 落在通常所说的共振频率区间内,该区间与心率变异性和压力感受性反射敏感度上 最清楚的短期效应相关。⚠️ 未核实——它是作为一项建议呈现的; 应用里没有任何东西要求你与之匹配。
4.6 习惯
- 我们真正能拉动的、对坚持影响最大的杠杆,是让下一次练习可见且已经决定好。 因此周条被固定在最上方,休息日是画出来而不是省略。⚠️ 作为具体效应量未核实; 按产品原则处理。
4.7 证据不支持什么
明说出来,免得有人在沙上盖房:
- 没有证据显示太极在防跌倒上优于设计良好的常规平衡训练。
- 专门针对太极的证据确定性总体上是低到中等;试验规模小,无法设盲, 注意力对照组也不一致。
- 这里没有任何内容支持关于免疫力、「能量」、脏腑功能或疾病逆转的宣称。 应用从不作出这些宣称,气功相关文案里也不会,尽管视频标题有时会。 我们逐字复制标题是为了署名——那不是背书。
5. 处方模型
src/lib/engine/prescribe.ts — 进去一份画像,出来一张蓝图。这里不挑选任何内容,
这使得练习的形状可以在完全不碰视频库的情况下被测试。
5.1 五阶段弧线
每一次练习,无论多长,都遵循同一条弧线。熟悉感正是重点: 不能因为内容变了,仪式就跟着变。
| 阶段 | 中文 | 目的 | 基础占比 |
|---|---|---|---|
| 入静 | 無極 | 到场。安定呼吸,找到垂直的中正。 | 8% |
| 热身 | 熱身 | 在无痛范围内活动踝、髋、脊柱和肩。 | 17% |
| 气功 | 氣功 | 可重复的、与呼吸相连的动作。建立协调。 | 27% |
| 套路 | 套路 | 连续的编排。认知与体态的核心。 | 30% |
| 平衡 | 平衡 | 有意识的重心转移、受控的迈步、狭窄的步距。 | 13% |
| 收功 | 收功 | 降下来。放慢呼吸,回到房间里。 | 5% |
入静和收功永远存在,并且永远由应用讲述,而不是由视频。 三个理由: 没有人会发布一段好的两分钟放松;固定的开头和结尾是可得的最强的习惯线索; 而且这意味着即使某个视频被下架或者网络断了,一次练习依然成立。
5.2 按目标重新加权
目标会乘以基础占比,然后整体重新归一化——所以一个目标可以强调某个阶段, 但永远无法靠算术把某个阶段删掉。
| 目标 | 侧重 |
|---|---|
| 平衡 | 平衡 ×2.0,热身 ×1.1,套路/气功 ×0.85 |
| 关节 | 热身 ×1.55,气功 ×1.2,套路 ×0.8 |
| 平静 | 入静 ×2.2,收功 ×2.0,气功 ×1.2,套路 ×0.7 |
| 力量 | 平衡 ×1.45,套路 ×1.15,入静 ×0.7 |
| 耐力 | 套路 ×1.3,气功 ×1.2,入静 ×0.5 |
| 认知 | 套路 ×1.55,平衡 ×1.1,气功 ×0.8 |
5.3 阶段下限与砍除
每个阶段都有一个最小值,低于它就不值得做了——你会把整段时间都花在 找视频的起始位置上。
center 90s · warmup 120s · qigong 180s · form 240s · balance 120s · close 90s
分配是迭代的:计算占比 → 找出资源不足的阶段 → 砍掉可砍的、权重最低的那个 →
重新计算。迭代很重要,因为砍掉一个阶段可能把另一个抬到它的下限之上。
center 和 close 受保护,永不砍除;qigong / form / balance
中至少要有一个存活。最终时长按 15 秒取整,余数推给最大的那一段。
这就是为什么一次 10 分钟的练习有四段,而 45 分钟的练习有六段, 却不需要任何按时长写死的模板。
5.4 安全上限
行动能力是硬闸门。年龄是软闸门。 一位稳健的 80 岁练习者胜过一位 不稳的 55 岁练习者,而界面先问行动能力,正是为了表明这是一个安全问题, 而不是一个人口统计问题。
mobility ceiling: seated → gentle supported → gentle steady → vigorous
age ceiling: <50 → vigorous 50–64 → vigorous
65–74 → moderate 75+ → gentle
effective = min(requested, mobility ceiling, age ceiling)
一个例外:站得稳_并且_练习已经稳定,可以把年龄上限抬高一档。 已证明的能力胜过出生年份。行动能力的上限永远不会被抬高。
当所请求的强度被限制时,应用会说出来,并说明是哪一条限制起了作用—— 「起限制作用的是站立支撑,不是年龄。」
其他结构性后果:
seated→ 平衡阶段被整个移除(单腿站立没有诚实的坐式等价物), 它的时间给了气功,因为躯干控制和重心转换坐着也能练。seated或supported→requireChairFriendly,这是对内容的硬性筛选, 不是偏好。new→ 热身 ×1.25,套路 ×0.85,并优先选择逐步讲解的老师。established→ 套路 ×1.2,热身 ×0.85。
5.5 短格式
同一次练习,换一种切法。prescribe() 接受一个格式——"full" 或 "shorts"——
练习者在练习界面上切换。真正要紧的东西都是共享的:同一份画像、同样的分钟数、
同样顺序的五阶段弧线,以及同一个安全上限,由同一段代码计算。
短练习不是更轻的练习。
变化的是:
- 阶段被切成卡片。 目标 75 秒,绝不低于 45 秒或高于 105 秒,最多 26 张卡。 两端都被夹住:只设下限时,一个 110 秒的阶段会作为一张卡通过, 那是穿着卡片外衣的完整练习分段。
- 阶段下限下降到大约每张卡一个(
center 45 · warmup 45 · qigong 60 · form 60 · balance 45 · close 45),分配粒度从 15 秒变成 5 秒。 因此一次 15 分钟的短练习可以容纳全部五个阶段,而完整格式会砍掉其中两个—— 弧线更完整了,而不是更少。 - 内容必须装进一张卡。 选择器把候选时长上限设为 5 分钟,可放宽到 10 分钟, 不再往上;一门 42 分钟的坐式课程只放 75 秒,那不是一节课,是它的一个片段。
- 大约每三张卡就有一张是引导而非拍摄的,这还不算永远如此的入静和收功。
那 29 个
micro段落正是为此存在的:每个一项有名字的基本功, 卡片上带中文和拼音。
这个格式为什么存在:完整练习假定你已经决定要练。信息流则假定你还没有, 只向你要一分钟。关于剂量的文献(§4.3)谈的是累积的练习周数, 而不是单次练习的长度,所以把十五分钟拆成十二张卡,并不是任何东西的打折版本。
它刻意不从那些看起来相似的信息流那里借来的东西:它会结束, 没有任何内容为了填满画面而被裁切,而且决定一张卡什么时候结束的是时钟, 不是视频。
6. 防重复引擎
src/lib/engine/select.ts。整个产品的生死系于这一部分:
它绝不能让人觉得每天都是同一件事。
一个天真的「最佳匹配」评分器立刻就会失败——最佳匹配是确定性的, 所以第 2 天和第 1 天一模一样。因此新鲜度在这里不是平局时的决胜条件。 它是评分中权重最大的单项。
6.1 评分
score = 0.42 · novelty # days since last served / 21, capped at 1
+ 0.30 · durationFit # how well the runtime covers the block
+ 0.20 · fit # focus, intensity, level, teaching style
+ 0.08 · jitter # seeded random — what "Shuffle" perturbs
× 0.55 if the channel appeared in either of the last two sessions
× 0.85 if the style appeared in the last session
短格式卡片重新加权同样的几项,而不是换一个评分器:
novelty 0.32 · durationFit 0.46 · fit 0.17 · jitter 0.05。
时长匹配占主导,因为一张卡是 75 秒,而一节课在卡片中途放完的惩罚非常重。
语言是在挑选之后应用的,不在评分之内。 有十五节课没有语言; 其余都有讲解,几乎都是英语。所有语言的排序是完全一致的,只有在那之后—— 在它本来就在挑选的那个洗牌窗口之内——如果抽到的那节不可理解, 非英语观看者才会拿到第一节能听懂的课。反过来,如果按语言给评分加权, 就会把整份列表按语言重排,于是当天的轮换索引在每种排序里指向不同的课程, 一位西班牙语观看者可能就会拿到那节刚刚在他自己的列表里被一节无语言课程 挤开的英语课。「无语言课程绝不会比英语观看者拿到的更少」这个承诺, 现在在构造上就是成立的。
6.2 硬性规则,以及它们让步的顺序
| 规则 | |
|---|---|
| 硬 | 同一次练习里不出现同一个视频两次 |
| 硬 | 同一次练习里不出现同一个频道两次——这是「感觉都差不多」的最大单一来源 |
| 硬 | 不出现 cooldownDays 之内看过的视频 |
| 软 | 对最近两次练习中出现过的频道扣分 |
| 软 | 对上一次练习中出现过的流派扣分 |
cooldownDays = clamp(floor(eligiblePoolSize × 0.6), 1, 14)。
它随该阶段实际能触及的范围伸缩:有 15 个合格视频的阶段得到 9 天冷却,
只有 3 个的得到 1 天。一个固定常数要么会让窄阶段挨饿,要么会让宽阶段几乎不轮换。
如果某个阶段将会为空,约束会按既定顺序放宽——(1) 经验等级匹配,
(2) 冷却期,(3) 强度档位与频道多样性,(4) 同一次练习内不重复——
而且安全上限在任何层级都不会放宽。第 4 级只为短格式信息流存在,
它要求多达 26 个不同的东西,而完整练习只要求六个;即便在那里,
同一节课也绝不会连着两张卡出现,而这正是唯一真正有人会注意到的重复。
卡片长度上限在第 3 级而不是第 4 级放宽,所以选择器宁可重复一节短课,
也不会把一节 40 分钟的课当成 75 秒的卡片端上来。哪些规则被放宽了,
会在 session.rationale 中回报并展示给用户,因为默默无视自己的约束,
正是一个推荐系统腐坏的方式。
6.3 时长匹配是刻意不对称的
视频比它的分段更长没有问题——你练前 N 分钟,计时器把你带走, 和上课一模一样。视频比它的分段更短,会让你在寂静中站着, 那是糟糕得多的失败。所以:
r = budget / runtime
r > 1 → max(0, 1 − (r−1) × 1.25) # too short: punished hard
r ≤ 1 → r ^ 0.45 # long is acceptable, not preferred
6.4 确定性
给了你的那次练习,不能在你练到一半刷新时悄悄重新洗牌。每一次随机抽取
都来自一个由 hash(date, profileKey, variant) 播种的 mulberry32 伪随机数生成器。
variant 是唯一能改变某一天练习内容的东西,而且只有一个控件会让它加一:
「换一次练习」。
6.5 课程库为什么是这样搭起来的
引擎只能在已有的东西里轮换。所以课程库刻意收录由不同老师教授的同一套经典套路 ——七个版本的八段锦、六个版本的十八式、八个版本的杨式二十四式。 诀窍就在这里:内容保持熟悉(你在学一套,不是五十套), 而声音、节奏和机位在轮换。熟悉的练习,新鲜的一次。
编辑目标:每个(阶段 × 强度)单元格 ≥5 个不同频道。
7. 每周周期安排
src/lib/engine/week.ts。一周里每次练习都一样,是最快让人放弃的方式,
而且本身就是糟糕的训练。
- 一个长日 — 用练习者自己的目标,时长 ×1.5,放在他们所选的一周中的 最后一天(通常是周末,通常也是唯一有时间的一天)。这是真正能学会一套套路的 那次练习。需要 ≥3 个练习日。
- 一个轻松日 — 强度降一档,目标切换为
stress,放在一周中间。 这是恢复,不是空缺。需要 ≥4 个练习日。 - 其余的日子在所选目标和它的互补目标之间交替,这样没有任何一种能力 会在连续两天被训练。
balance ↔ strength strength → joints joints → stress
stress → balance cardio → balance cognition → joints
- 休息日会被画出来,不会被藏起来。 空着的星期四是计划作出的一个决定。 选中它会提供「还是练一下」,那会以降低的强度生成,而不是拒绝你。
一周从星期一开始——人们谈论「这一周」时就是这么算的, 而且这样自然的长日落在末尾而不是中间。
8. 内容政策
8.1 核验
src/lib/content/videos.ts 中的每一条在发布前都经过机器核验:
https://www.youtube.com/oembed?url=…→ 存在、公开、真实标题、真实频道。- 观看页面 →
lengthSeconds,以及playableInEmbed: true。
用 bun run verify:library 可以再跑一遍。
标题一直在撒谎。 「10 min」经常挂在一个 14 分钟的视频上。
seconds 字段永远是 YouTube 自己的数字,因为计时器是按它来安排的。
在 52 个候选中,51 个发布了;有一个(ILFcKMnMkPQ)报告
playableInEmbed: false,于是被丢弃,而不是作为一个死掉的画框发布出去。
8.2 新增条目的编辑规则
seconds里放真实时长。绝不用标题里的数字。chairFriendly是一项安全声明,不是一个方便标签。 只有当视频是坐式的,或者足够慢、步距足够窄、可以扶着椅背做时,它才为真。- 绝不要把一个视频标进三个以上的阶段。 过度打标签正是一个 50 节课的库 开始让人觉得像只有 6 节的原因。
- 目标是每个(阶段 × 强度)单元格 ≥5 个不同频道。
8.3 署名与合理使用
不托管、不转传、不下载、不剥离。视频通过 YouTube 自己的播放器嵌入
(youtube-nocookie 域),保留原生控件,在每个界面显示频道名,
并在每张课程卡上放一个「在 YouTube 上观看」的链接。完整课程库在 /library
公开可见。
如果创作者要求下架,就删掉那一条。不谈判,也不说「可是引擎需要它」。
8.4 优雅失败
视频会被删除,嵌入权限会变化。当播放器出错时,画面降级为一张带封面图、 一段说明和一个外部链接的卡片——并且计时器继续走。 空白的 iframe 永远不可接受。
9. 安全边界
这个应用会做的:
- 在其他任何事情之前先询问站立的稳定性。
- 强制执行一个任何放宽路径都无法推翻的硬性强度上限。
- 每一次练习都在播放器上方展示注意事项,而不是只在初次设置时展示一次。
- 通用注意事项:出现锐痛、胸痛、头晕或久不缓解的气短就停下; 膝盖绝不要超过脚尖;膝盖沿脚的方向走。
- 条件性注意事项:
supported时椅子放在伸手可及处;seated时用结实、 无轮的椅子,双脚平放;中等及以上强度时提示「先把架势升高,再缩短练习」。
它明确不做、并且在没有临床专业人员参与的情况下不得开始做的:
- 筛查禁忌症(近期手术、急性心脏事件、前庭疾病、未控制的高血压)。
- 针对某个具名诊断做个性化。
- 对某一个人宣称任何治疗效果。
10. 架构
src/
app/ routes: / · /progress · /library · /method
components/
brand/ mark, wordmark, lockup
glass/ NightSky, Glass — the material layer
ui/ shadcn primitives (extended, not forked)
layout/ header, bottom tab bar, skip link, nav definition
practice/ stage, timer, transport, rail, screen,
full player + short-form feed, format switch
progress/ rank, milestones, stats
calendar/ week strip
setup/ option groups, setup sheet
library/ library browser
hooks/ timer, session runtime, chime, wake lock, hydration gate
lib/
domain/types.ts the vocabulary. Nothing decorative lives here
content/ verified video registry + app-narrated guided blocks
engine/ prescribe → select → week. Pure, locale-blind
progress/ ranks + derived stats. Pure
i18n/ detection, dictionaries (en·es·fr·de·zh), provider
store/ the only persisted state
docs/METHODOLOGY.md this file
docs/methodology/ its translations
scripts/verify-library.ts
约定
lib/engine/*和lib/progress/*是纯的:没有 React、没有 DOM、 没有隐式的Date.now(),也没有语言。每个函数都把日期作为参数接收, 这正是让练习可复现、可测试的原因,而且它们返回的是词典键而不是句子。- 只有当某条引擎规则会读它、或某个标签会渲染它时,
domain/types.ts里才会存在这个字段。 - kebab-case 文件名、具名导出、直接导入、没有 barrel 文件。
- 客户端组件是叶子节点。
PracticeScreen持有所选日期和所选格式, 其余一切都由此推导。 - 两个播放器共享同一个运行时。
useSessionRuntime拥有计时器、提示音、 屏幕常亮锁、夜空冻结和记录日志,因此完整练习和信息流不可能对 「你练了多少」产生分歧——也不会对「你中途离开会怎样」产生分歧, 那是超过两分钟记为部分完成,不足两分钟则不记。
10.1 具体说说计时器
有三个决定值得辩护:
- 时钟是一个标量。 一个数字——整次练习已过去的毫秒数—— 当前分段是拿它去和累计边界比对推导出来的。跳过、后退和拖动都是同一个操作。 把每段的倒计时和整次练习的总计分开,正是这两个数字开始互相偏离的方式。
- 用挂钟时间,而不是数 tick。 已过时间是
committed + (now − startedAt), 每次 tick 重新计算。每个间隔累加+250 ms,在一次 45 分钟的练习里 会丢掉好几分钟,因为后台标签页会严重节流计时器。 - 因此它经得起切到后台。 一部在练习中途锁屏的手机, 恢复时会回到正确的时间点,而不是它锁屏时的时间点。
计时器同时也是视频的事实来源:暂停练习会暂停 YouTube, 而视频自己播完并不会结束这一段。
11. 已知的欠缺
一份诚实的清单。它们没有被藏在「未来工作」后面。
-
还没有测试。
prescribe、select、week和progress/stats都是纯的, 写的时候就考虑了可测试性;但一个测试都还没写。这是差距最大的一项欠缺。 -
只有
localStorage。 不同步、不能导出、不能导入。一个导出/导入 JSON 的按钮是最便宜的有意义的补救,而且还能让人把一年的练习记录搬到新手机上。 -
语言在挂载之后才应用,所以第一帧画出来的是英文。已修复。 语言是一个路由段:英文以无前缀的方式在/library提供,其余四种在/zh/library之类的路径下提供,而<html lang>从第一个字节起就是正确的。 既没有挂载时的纠正,也没有英文一闪而过。 -
页面已修复: 它们现在和其他一切一样来自词典。<title>和 meta description 只有英文。 -
只有三个路由家族真正被翻译了。已关闭。 现在每一个家族都以五种语言提供——课程页面、十三篇/for/*长文、安装教程 和本文档,连同它们的元数据、JSON-LD、markdown 镜像和llms.txt。src/lib/i18n/coverage.ts仍然是「什么可以被索引」的事实来源:路由家族 提供的语言 可索引 首页、 /library、/library/teachers、课程页面、/for/*、/method、/install全部五种 是,带互指的 hreflang/progress— 永不,任何语言都不 对于并不提供某种语言的 URL,不会输出该语言的
hreflang标注; 编造一个比没有更糟。这条规则没有变——变的是这些 URL 现在真的提供那种语言了。 -
这些译文没有经过母语审校,而这现在是最大的内容欠缺。 它们是机器产出的。 对于外壳和课程页面来说,这是一个低风险的赌注:字符串短、事实性强, 并且被周围的数据紧紧约束。对于
/for/*长文和本文档来说则不是, 原因写在 §9:这是本站上「弄错了可能会伤到人」的内容, 而对动作指导的机器翻译,恰恰是那种读起来很流畅的错误。 补救办法就写在上面的表格里:把某个家族在coverage.ts中改回[DEFAULT_LOCALE]只是一行,那会让这些变体变成noindex、退出站点地图, 并在每个指向它们的链接上恢复「(英文)」的标注。 -
/install的菜单标签是最可能出错的部分,而且错的方式是读者无法绕开的: 它们必须是各厂商在那种语言下自己的写法,而不是对英文的翻译。 在把那一页当作完成之前,请对着真机核对一遍。 -
中文不是靠
hreflang就能解决的。 百度会忽略它, 并且对域名和主机信号的加权方式也不同,所以让/zh有排名是另一件事, 不是把同样的工作再做一遍。 -
坐式课程库比站式更窄,而且偏向两个频道。因此对坐式练习者来说, 频道多样性规则会更早放宽。
-
没有离线模式。 引导段落在设计上就能离线工作;视频段落不能。
-
仍有两项主张是
⚠️ 未核实(§4.4 MET,§4.5 呼吸频率), 在核实之前不得用于面向公众的文案。
12. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-08-01 | 初次构建。品牌、三层令牌、液态玻璃系统、51 个视频的核验课程库、处方引擎、防重复选择器、每周周期安排。 |
| 2026-08-01 | 视觉方向改为深夜与月光,深色优先。Cormorant + Inter 取代 Newsreader + Geist。完整的移动端梳理:底部标签栏、安全区、溢出防护、按圆环定尺寸的计时器。 |
| 2026-08-01 | 自动检测浏览器语言,支持 en · es · fr · de · zh。引擎重构为输出可翻译的键而不是散文。 |
| 2026-08-01 | 新增进阶体系:九个段位、九个里程碑、连续记录——全部由记录日志推导,全部以天数而不是训练量衡量。 |
| 2026-08-02 | 短格式。 prescribe() 接受一个格式;"shorts" 把每个阶段切成 45–105 秒的卡片,信息流自己播放、自己滚动,全屏,并在练习结束时结束。课程库扩充到 287 节课(其中 93 节不到三分钟)和 41 个引导段落,其中 29 个是带中文和拼音的单项基本功。 |
| 2026-08-02 | 所有路由家族均已翻译。 课程页面、七篇 /for/* 长文、/install 和本文档现在都以五种语言存在,连同它们的元数据、JSON-LD、markdown 镜像,以及按语言提供的 llms.txt。方法文档的锚点在构造上保持英文,因此一条引用在任何语言下都能解析。安装菜单标签采用各厂商自己的写法。等待母语审校——见 §11。 |
| 2026-08-02 | 两个播放器现在都会说明接下来是什么,并提供跳过的方式——开头那一段没有视频,好几个人把静止的画面读成了「坏掉了」。语言偏好从一个评分乘数改成了挑选之后的偏好,修复了非英语观看者可能拿到更少无语言课程的情况。引导段落的回退不再绕过强度上限。 |
| 2026-08-15 | 再加六个 /for/* 页面,并给原来的七个各补一节。 新的这几页说的是一样东西的名字,而不是一种处境——杨式二十四式、太极气功十八式、八段锦与传统功法、站桩、二十分钟、五分钟——因为已经知道八段锦是什么的人,不会去搜「关节僵硬的气功」。六个里有三个是按标题模式筛选、而不是按字段,理由写在 topics.ts:「这节课教的是哪一套经典套路」并不是登记表里的字段,而每位老师都会把它写进标题。十三个页面在五种语言下都存在。 |
来源
- Sherrington C. et al. Exercise for preventing falls in older people living in the community. Cochrane Database of Systematic Reviews, 2019
- Li F. et al. Effectiveness of a Therapeutic Tai Ji Quan Intervention vs a Multimodal Exercise Intervention to Prevent Falls Among Older Adults at High Risk of Falling. JAMA Internal Medicine, 2018
- Wang C. et al. Comparative Effectiveness of Tai Chi Versus Physical Therapy for Knee Osteoarthritis: A Randomized Trial. Annals of Internal Medicine, 2016
- WHO Guidelines on Physical Activity and Sedentary Behaviour, 2020 — Recommendations
- Tai Ji Quan: Moving for Better Balance