This is your captain speaking. Destination: your goal weight.
各位乘客,本次航班目的地:目标体重。
A fat-loss coach that won't let you crash-diet. If your goal is too aggressive, it says no and offers safe paces instead. Then it coaches food, training and sleep together: log meals with a photo, follow a training plan that progresses on its own, and watch your weight trend instead of the daily noise. Mobile-first web app, installable to your home screen, in English and Chinese.
Why "Glidepath": a glide path is the line an aircraft follows down to the runway, at a fixed, safe slope. Weight loss should work the same way: come down at a safe slope, touch down on your goal, then level off into maintenance.
Glidepath is not a medical product and does not replace advice from a doctor or dietitian.
- Onboarding and a feasibility check: height, weight, goal, target date, activity level, sleep schedule, diet, training setup, injuries, and an optional fitness test. If the goal is too fast, the app won't follow it. It offers 2–3 safe options instead: a steady pace, a faster pace, or keeping your date and reaching a milestone weight first.
- Health screening: under 18, pregnant or breastfeeding, a history of eating disorders, or a BMI already under 18.5 means no fat-loss plan, plus help resources. Diabetes medication, another medical condition, age 65+ or a BMI of 40+ means you confirm you'll check with a doctor first.
- Food logging: photo recognition (portion and calories for each item, shown as a range, every item editable by grams and cooking method), a built-in table of 112 common Chinese and Western foods, one-tap recent foods, custom entries, and "same as yesterday" for a meal.
- Today: calories left (on a ruler), protein progress, today's workout, weight, a steps and sleep check-in, and your 7-day average. Anything you switch on in the body log shows up here too.
- Weight trend: daily dots, a 7-day average line and a goal line. When your trend moves about 1 kg, you're prompted to update your calorie target. Plateaus are explained. Once you reach your goal, you move to maintenance: calories rise by 150 kcal a week up to your maintenance level.
- Training plan: built from 28 exercises around your experience, equipment (home with nothing, home with dumbbells, or a gym), training days, session length and injuries. Each exercise has sets, reps, rest time and form cues. During a workout you tick off each set and a rest timer starts. Afterwards the plan progresses automatically (more reps, more weight, or a harder variation), and you can undo the most recent workout.
- Demo photos: 21 of the 28 exercises show start and end photos from free-exercise-db (public domain), alternating so you can see the movement.
- Camera rep counting for squats (bodyweight, pause, goblet, box) and push-ups (standard, knee, incline, wall): point your phone at yourself and it counts each full rep, says the count out loud, and when you save it fills in that set. Half reps don't count and get a "go deeper" cue; push-ups also get "don't let your hips sag" / "hips too high". When your joints are hard to see it keeps counting but stops judging form. Pose detection is MediaPipe running in the browser: video never leaves the device.
- Body log (optional): everything here is off until you switch it on. Waist: weekly measure, 4-week change, and waist-to-height ratio (under 0.5 is the healthy range, per NICE 2022); if the scale stalls for two weeks but your waist drops 1 cm or more, you're told fat is still coming off instead of being shown plateau advice. Menstrual cycle: tap "Period started" / "Period ended"; the app estimates your phase and next period from your own log, shades periods on the weight chart, and in the week before a period (and its first days) it explains that a 0.5–2 kg rise is water and won't prompt a target change; no period logged for 3+ months gets a gentle "see a doctor" note. A hormonal-contraception setting turns predictions off. Estimates are never for contraception. Sleep quality: a one-tap "How did you sleep?" in the check-in; two bad or short (< 6 h) nights out of three bring a note about hunger and easier training. Water: a +1 glass counter against a reference (7 glasses for women, 8 for men, from EFSA's adequate intake).
- Safety signals: three days in a row well under your safe minimum, or losing more than 1.5% of body weight a week after the first three weeks, triggers a gentle note and help resources.
- "Where these numbers come from": the Me tab explains resting metabolism, daily burn, calorie target, safe minimum, protein, steps and sleep, one by one.
- Also: English and Chinese, kg / jin (斤) / lb, dark mode, PWA, export all your data, delete your account, and a demo account (four weeks of sample data, deleted after 24 hours).
| Rule | Value |
|---|---|
| Fastest weekly loss | 1% of body weight (the "steady" pace is 0.5%) |
| Lowest daily intake | Never below resting metabolism, and at least 1200 kcal (women), 1500 (men), 1350 (other) |
| Goal weight | BMI of at least 18.5; lower goals are raised to the BMI 18.5 weight |
| Resting metabolism | Mifflin-St Jeor × activity factor (1.2 / 1.375 / 1.55 / 1.725) |
| 1 kg of fat | ≈ 7,700 kcal; dates are projected week by week, since your daily burn drops as you lose weight |
| Protein | 1.6–2.2 g per kg of goal weight |
The server checks these again when a plan is saved, so the browser can't get around them. A unit test runs 400 random body profiles to make sure no option ever breaks these limits.
Needs Node.js 22.13 or newer. The database is Node's built-in SQLite (node:sqlite), so there's no native module to compile. Node 22 prints one "experimental" warning on start; it's safe to ignore.
npm install
npm start
# open http://localhost:3000Without an API key, photo recognition runs in demo mode: it returns fixed sample results (clearly labelled) so you can try the whole flow.
Use Claude (recommended) or OpenAI. Get a Claude key at platform.claude.com:
ANTHROPIC_API_KEY=sk-ant-... npm startOr keep the key in a .env file, which npm start loads automatically and git ignores. On a Mac, after clicking "Copy key" in the Claude Console:
printf 'ANTHROPIC_API_KEY=%s\n' "$(pbpaste)" > .env
npm startThen sign up for your own account; demo accounts always get sample results unless DEMO_USES_AI=1. The default model, Claude Haiku 5.5, is the fastest and cheapest Claude model: with photos shrunk to 1024 px, a photo costs roughly a tenth of a US cent.
| Variable | Default | What it does |
|---|---|---|
ANTHROPIC_API_KEY |
empty | Claude key; used first when set |
ANTHROPIC_VISION_MODEL |
claude-haiku-5-5 |
e.g. claude-sonnet-5-5 for harder photos (slower, about 20× the price) |
ANTHROPIC_EFFORT |
model default | low is faster and cheaper, high thinks more about portions |
ANTHROPIC_WORKSPACE_ID |
empty | Needed only for a personal or service-account key that isn't tied to one workspace (wrkspc_...) |
OPENAI_API_KEY |
empty | OpenAI key, used when there is no Claude key |
OPENAI_VISION_MODEL |
gpt-4.1-mini |
Change this if the model is retired or you want another |
OPENAI_IMAGE_DETAIL |
auto |
low is cheaper, high reads portions more closely |
AI_PROVIDER |
automatic | anthropic or openai to choose when both keys are set |
RECOGNIZE_DAILY_LIMIT |
30 |
Photo recognitions per account per day |
DEMO_USES_AI |
0 |
Set to 1 to let demo accounts use the real model (costs money on a public site) |
DB_PATH |
./data/glidepath.db |
Where the SQLite database lives |
TRUST_PROXY |
0 |
Set to 1 behind a proxy such as Render |
POSE_MODEL_URL |
Google's hosted Pose Landmarker (lite) | Where the browser downloads the pose model (about 6 MB) |
POSE_MODEL_FILE |
empty | Path to a local copy of the model; the server then serves it itself (useful where Google is blocked) |
PORT |
3000 |
Set a monthly spending limit in your Claude Console or OpenAI account. Photos are shrunk in the browser to 1024 px on the long side before upload, and discarded after recognition.
npm test # 102 unit + API tests (math, safety rules, progression, rep counting, cycle and waist logic, account isolation, the vision-model call…)
npx playwright install chromium # once, before the first browser test run
npm run e2e # 16 end-to-end browser tests at phone size (signup, photo, search, weight, body log, training, camera counting, language, screening, dark mode…)
SHOTS=shots npm run e2e # also save a screenshot of each step to shots/The rep-counting tests use real MediaPipe output: test/fixtures/pose-frames.json holds the landmarks the model found on the demo photos, so the unit tests check the thresholds against what the model actually sees. The camera end-to-end test plays squat photos through Chromium's fake camera and runs the real model in the page; it downloads the model from Google once (or uses POSE_MODEL_FILE) and is skipped if neither works.
render.yaml is included: on Render choose New → Blueprint → this repo → Apply. It deploys in demo mode; when you have a key, add ANTHROPIC_API_KEY (or OPENAI_API_KEY) under the service's Environment tab.
Note: Render's free plan sleeps after 15 minutes without visitors, and its disk isn't persistent, so every restart or deploy wipes all accounts and logs in SQLite. For real users, use a paid instance with a persistent disk and point DB_PATH at it (for example /var/data/glidepath.db).
server/
index.js Express app, security headers, static files
api.js all /api routes
store.js data access, plan creation, workout logging and progression
ai.js photo recognition (Claude or OpenAI vision model + demo results)
demo.js demo account with four weeks of sample data
auth.js scrypt passwords, sessions (stored in the database, so restarts don't log you out)
db.js SQLite schema (built-in node:sqlite) and transactions
public/
js/core/ logic shared by browser and server: plan (plans and safety rules), nutrition,
foods (food table), trend (trend and safety signals), training (exercises and progression),
reps (camera rep counting: joint angles, rep state machine, form cues),
body (waist, sleep and water), cycle (period log, phases, water-retention window)
js/camera.js the camera counter screen; js/pose.js loads MediaPipe
js/pages/ one file per screen
img/exercises/ demo photos from free-exercise-db
js/i18n.js English and Chinese strings
js/charts.js weight trend and weekly intake charts (hand-written SVG)
css/app.css
test/unit, test/api, test/e2e
- Passwords are hashed with salted scrypt; only a hash of each session token is stored.
- Every write request needs a custom header, which blocks cross-site request forgery. Cookies are HttpOnly and SameSite=Lax.
- Each account can only read and write its own data (covered by API tests).
- Camera counting runs entirely in the browser. Video frames are never uploaded or stored.
- Cycle data is only used to read weight changes. It can be cleared on its own at any time, and it's included in the export and removed with the account.
- "Export my data" downloads every record as JSON; "Delete account" removes the account and all its records.
- The database isn't encrypted at the application level. In production, keep it on an encrypted disk (most cloud disks are encrypted by default).
The built-in food table is in public/js/core/foods.js. Calories, protein, carbs and fat per 100 g are typical values from USDA FoodData Central and common Chinese food-composition tables. Chinese dishes vary a lot (especially in oil), so treat them as a starting estimate. Photo and custom entries carry their own nutrition values.
Eating-disorder help lines are listed in RESOURCES in public/js/pages/shared.js (currently NEDIC in Canada and the National Alliance for Eating Disorders in the US). If you serve other regions, replace them with local resources.
- v2 (done so far): exercise demo photos; camera counting for squats and push-ups; the optional body log (waist, cycle, sleep quality, water)
- v2 (still to do): more body log items (resting heart rate, hunger, other measurements, progress photos kept on the device); camera counting for lunges, jumping jacks and plank holds; a daily science card; weekly summaries; a larger food database (USDA FoodData Central); barcode scanning (Open Food Facts); voice logging
- v3: an iOS app (Expo / React Native) with HealthKit (sleep, steps, active energy) and AlarmKit; adaptive calories that work out your real daily burn from what you eat and how your weight changes
MIT, see LICENSE. Third-party work (free-exercise-db photos, MediaPipe, the Archivo font) is listed in THIRD_PARTY_NOTICES.md.
Numbers and Latin text use Archivo (SIL Open Font License, see public/fonts/OFL.txt), self-hosted with no Google Fonts dependency. Chinese uses system fonts.
一个不会让你瞎减的减脂教练。目标太急时它会直接说不,并给出安全的替代节奏;然后从饮食、训练、作息三方面一起管:拍照记饮食,按器材和时间安排会自动加量的训练,看体重趋势而不是每天的波动。网页应用,手机优先,可以“添加到主屏幕”,中英双语。
为什么叫 Glidepath:Glidepath 是飞机降落时的“下滑道”,按固定坡度安全地降到跑道上。体重也一样:按安全的坡度往下走,平稳落地,然后改平飞行,进入维持期。
Glidepath 不是医疗产品,不能代替医生或营养师的建议。
- 建档 + 可行性检查:身高、体重、目标、日期、活动量、作息、饮食偏好、训练条件、伤病、可选体能小测试。目标太急时不照做,而是给 2–3 个安全的替代方案(稳妥节奏 / 较快节奏 / 保留日期先到阶段目标)。
- 健康筛查:未满 18 岁、怀孕或哺乳、进食障碍史、BMI 已低于 18.5 → 不生成减脂计划,并给出求助资源;用降糖药、有其他疾病、65 岁以上、BMI ≥ 40 → 需要确认会先和医生沟通。
- 饮食记录:拍照识别(每样食物估分量和热量,显示范围,可逐项改克数和做法)、搜索内置食物表(112 种中西餐常见食物)、常吃一键添加、自己填、复制昨天的一餐。
- 今天:还能吃多少(刻度尺)、蛋白质进度、今日训练、体重、步数和睡眠打卡、最近 7 天平均。在身体记录里打开的项目也会出现在这里。
- 体重趋势:每天的点 + 7 天平均线 + 目标线;体重变化约 1 kg 时提醒按新体重更新热量目标;平台期解释;到达目标后进入维持期(热量每周加 150 大卡,直到维持水平)。
- 训练计划:按经验、器材(家里无器材 / 有哑铃 / 健身房)、每周哪几天、每次多久、伤病,从 28 个动作里生成;每个动作有组数、次数、休息时间和动作要点;训练时逐组打勾、自动组间休息倒计时;完成后按规则自动加量(加次数 / 加重量 / 换更难的变式),可以撤销最近一次。
- 动作示范图:28 个动作里有 21 个配了起始和结束两张照片,来自 free-exercise-db(公有领域),两张交替显示,能看出动作怎么做。
- 摄像头计数:深蹲(徒手、停顿、高脚杯、坐箱)和俯卧撑(标准、跪姿、上斜、扶墙)可以用摄像头数。手机对着自己,每做完整一次就数一下并念出来,保存后自动填进这一组。蹲得不够低的不算,会提示“再蹲低一点”;俯卧撑还会提示“臀部别塌”“臀部别翘太高”。关节看不太清时只计数、不判动作对错。姿态识别用的是 MediaPipe,在浏览器里本地运行,画面不会离开手机。
- 身体记录(可选):每一项都默认关闭,自己打开才会出现。腰围:每周量一次,看 4 周变化和腰围/身高(低于 0.5 是健康范围,参考英国 NICE 2022 指南);体重两周不动、但腰围少了 1 厘米以上时,不再提示平台期,而是告诉你脂肪还在减。生理期:点“月经来了”“月经结束了”就行;按你自己的记录估算现在是哪个阶段、下次大概什么时候来,在体重图上标出经期;经前一周和经期头几天会说明体重涨 0.5–2 公斤是水分,也不会因此提醒你改目标;超过 3 个月没记到月经会温和地建议看医生。选“激素避孕”就不做预测。预测不能用来避孕。睡眠质量:打卡里点一下“睡得怎么样”;最近三晚有两晚没睡好或不到 6 小时,会提醒你今天会更饿、训练可以轻一点。饮水:点一下加一杯,参考量女性 7 杯、男性 8 杯(按 EFSA 的适宜摄入量)。
- 安全信号:连续 3 天吃得远低于安全下限、或 3 周后每周减重超过体重 1.5% → 温和提示并给求助资源。
- “这些数字怎么来的”:在“我的”里逐条解释基础代谢、总消耗、热量目标、安全下限、蛋白质、步数和睡眠。
- 其他:中英双语、单位支持公斤 / 斤 / 磅、深色模式、PWA、导出全部数据、删除账号、演示账号(4 周示例数据,24 小时后自动删除)。
| 规则 | 数值 |
|---|---|
| 每周减重上限 | 体重的 1%(“稳妥节奏” 0.5%) |
| 每日摄入下限 | 不低于基础代谢,且女性 ≥ 1200、男性 ≥ 1500、其他 ≥ 1350 大卡 |
| 目标体重 | BMI 不低于 18.5,低于时自动调到 BMI 18.5 对应的体重 |
| 基础代谢 | Mifflin-St Jeor 公式 × 活动系数(1.2 / 1.375 / 1.55 / 1.725) |
| 1 kg 脂肪 | ≈ 7700 大卡;日期按体重下降后消耗也会下降来逐周估算 |
| 蛋白质 | 目标体重 × 1.6–2.2 克 |
服务器在保存计划时会重新检查一遍,前端改不了这些限制。单元测试里有 400 组随机身体数据的检查,保证没有任何方案超过上面的限制。
需要 Node.js 22.13 或更新版本。数据库用的是 Node 自带的 SQLite(node:sqlite),不用编译任何原生模块;Node 22 启动时会打印一行 “experimental” 提示,可以忽略。
npm install
npm start
# 打开 http://localhost:3000没有设置 API key 时,拍照识别是演示模式:返回固定的示例结果(会明确标出来),整个流程都能走通。
可以用 Claude(推荐)或 OpenAI。Claude 的 key 在 platform.claude.com 申请:
ANTHROPIC_API_KEY=sk-ant-... npm start也可以把 key 存进 .env 文件:npm start 会自动读取,git 也会忽略它。Mac 上在 Claude Console 点 “Copy key” 之后运行:
printf 'ANTHROPIC_API_KEY=%s\n' "$(pbpaste)" > .env
npm start然后注册一个自己的账号再试;演示账号永远只给示例结果,除非设置 DEMO_USES_AI=1。默认模型 Claude Haiku 5.5 是最快最便宜的 Claude 模型,照片缩到 1024 像素后,一张大约 0.1 美分。
| 环境变量 | 默认值 | 说明 |
|---|---|---|
ANTHROPIC_API_KEY |
空 | Claude 的 key;设置了就优先用 |
ANTHROPIC_VISION_MODEL |
claude-haiku-5-5 |
比如换成 claude-sonnet-5-5 识别更难的照片(更慢,价格约 20 倍) |
ANTHROPIC_EFFORT |
模型默认 | low 更快更便宜,high 估分量时想得更多 |
ANTHROPIC_WORKSPACE_ID |
空 | 只有没绑定工作区的个人 key 或服务账号 key 才需要(wrkspc_...) |
OPENAI_API_KEY |
空 | OpenAI 的 key,没有 Claude key 时才用 |
OPENAI_VISION_MODEL |
gpt-4.1-mini |
模型下线或想换时改这里 |
OPENAI_IMAGE_DETAIL |
auto |
low 更便宜,high 看分量更细 |
AI_PROVIDER |
自动 | 两个 key 都有时,用 anthropic 或 openai 指定 |
RECOGNIZE_DAILY_LIMIT |
30 |
每个账号每天最多识别几次 |
DEMO_USES_AI |
0 |
设成 1 演示账号也会调用真实模型(公开网站上会花钱) |
DB_PATH |
./data/glidepath.db |
SQLite 数据库位置 |
TRUST_PROXY |
0 |
部署在 Render 等代理后面时设成 1 |
POSE_MODEL_URL |
Google 托管的 Pose Landmarker(lite) | 浏览器从哪里下载姿态模型(约 6 MB) |
POSE_MODEL_FILE |
空 | 本地模型文件的路径,填了以后由服务器自己提供(Google 打不开的地区用这个) |
PORT |
3000 |
建议在 Claude Console 或 OpenAI 后台设置每月花费上限。照片在浏览器里先缩小到最长边 1024 像素再上传,识别完就丢掉,不保存。
npm test # 102 个单元 + 接口测试(计算、安全规则、训练进阶、动作计数、生理期和腰围逻辑、账号隔离、拍照识别的模型调用等)
npx playwright install chromium # 第一次跑端到端测试前装一次
npm run e2e # 16 个浏览器端到端测试,手机尺寸(注册建档、拍照、搜索、体重、身体记录、训练、摄像头计数、切换语言、筛查、深色模式…)
SHOTS=shots npm run e2e # 顺便把每一步截图存到 shots/动作计数的测试用的是 MediaPipe 的真实输出:test/fixtures/pose-frames.json 存的是模型在示范照片上识别出的关节点,单元测试拿它检查阈值是否符合模型实际看到的角度。摄像头的端到端测试用 Chromium 的假摄像头播放深蹲照片,在页面里跑真实模型;它会从 Google 下载一次模型(或者用 POSE_MODEL_FILE),两样都不行就跳过。
仓库里有 render.yaml:Render 上选 New → Blueprint → 选这个仓库 → Apply。部署出来是演示模式;有了 key 以后,在服务的 Environment 页面加上 ANTHROPIC_API_KEY(或 OPENAI_API_KEY)。
注意:Render 免费版 15 分钟没人访问会休眠,而且硬盘不持久,重启或重新部署会清空 SQLite 里的所有账号和记录。给真实用户用,需要付费实例 + 持久盘,并把 DB_PATH 指到盘上(例如 /var/data/glidepath.db)。
server/
index.js Express 应用、安全头、静态文件
api.js 所有 /api 接口
store.js 数据读写、建计划、记训练和进阶
ai.js 拍照识别(Claude 或 OpenAI 视觉模型 + 演示结果)
demo.js 演示账号和 4 周示例数据
auth.js 密码 scrypt、登录会话(存在数据库里,重启不会掉线)
db.js SQLite 表结构(Node 自带的 node:sqlite)和事务
public/
js/core/ 浏览器和服务器共用的逻辑:plan(计划和安全规则)、nutrition、foods(食物表)、
trend(趋势和安全信号)、training(动作库和进阶)、reps(摄像头计数:关节角度、计数状态机、动作提示)、
body(腰围、睡眠、饮水)、cycle(经期记录、阶段、存水窗口)
js/camera.js 摄像头计数页面;js/pose.js 负责加载 MediaPipe
js/pages/ 各个页面
img/exercises/ free-exercise-db 的示范图
js/i18n.js 中英文案
js/charts.js 体重趋势图、每周摄入柱状图(手写 SVG)
css/app.css
test/unit, test/api, test/e2e
- 密码用 scrypt 加盐哈希;登录会话的 token 只存哈希值。
- 所有写操作都要带自定义请求头,挡住跨站请求伪造;Cookie 是 HttpOnly + SameSite=Lax。
- 每个账号只能读写自己的数据(有接口测试覆盖)。
- 摄像头计数完全在浏览器里完成,画面不会上传,也不会保存。
- 生理期记录只用来解读体重变化,可以单独一键清空;导出时包含在内,删除账号时一起删除。
- “导出我的数据”下载全部记录(JSON),“删除账号”会删掉账号和所有记录。
- 数据库本身没有在应用层加密:生产环境请把数据库放在加密的磁盘上(大部分云主机的磁盘默认加密)。
内置食物表在 public/js/core/foods.js,每 100 克的热量、蛋白质、碳水、脂肪取自 USDA FoodData Central 和常见中国食物成分表的典型值;中式菜品差别很大(尤其是油),所以只当起点估算。拍照和自己填的食物会带上自己的营养数据。
进食障碍求助热线写在 public/js/pages/shared.js 的 RESOURCES 里(目前是加拿大 NEDIC 和美国 National Alliance for Eating Disorders)。如果面向其他地区的用户,请改成当地的资源。
- v2(已完成):动作示范图;深蹲和俯卧撑的摄像头计数;可选的身体记录(腰围、生理期、睡眠质量、饮水)
- v2(还没做):更多身体记录(静息心率、饥饿感、其他围度、只存在手机上的进度照片);弓步、开合跳的摄像头计数和平板支撑计时;每日科学知识卡片;每周总结;更大的食物库(USDA FoodData Central);条形码扫描(Open Food Facts);语音记录
- v3:iOS app(Expo / React Native)接 HealthKit(睡眠、步数、活动能量)和 AlarmKit;用实际摄入和体重变化反推真实消耗的自适应热量
MIT,见 LICENSE。用到的第三方内容(free-exercise-db 示范图、MediaPipe、Archivo 字体)写在 THIRD_PARTY_NOTICES.md 里。
数字和英文使用 Archivo(SIL Open Font License,见 public/fonts/OFL.txt),本地托管,不依赖 Google Fonts;中文使用系统字体。