BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2 或任何藍牙控制器,搭配 MyWhoosh、Rouvy、Zwift 等。iOS、Android、Windows、macOS 均可免費試用。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证An interval timer for gym routines. It runs in the browser, installs to a phone home screen, and works offline.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
An interval timer for gym routines. It runs in the browser, installs to a phone
home screen, and works offline.
Nothing to clone, install or build. Open the link, add it to your home screen
from the browser's share menu, and it keeps working with no signal. The
instructions further down are for changing the code, not for using the app.
It was built for one job: telling you mid-effort how much longer to keep going,
from across a gym, with the phone propped against a rack.查看完整 README ↗An interval timer for gym routines. It runs in the browser, installs to a phone home screen, and works offline.
Live: https://waynetd777.github.io/exercise-timer/
Nothing to clone, install or build. Open the link, add it to your home screen from the browser's share menu, and it keeps working with no signal. The instructions further down are for changing the code, not for using the app.
It was built for one job: telling you mid-effort how much longer to keep going, from across a gym, with the phone propped against a rack.
Imports .tabata files from the Tabata Timer app, its own backups, and plain
text. See the paste format for everything the parser
reads, with an example using every part of it.
Tidies names, putting each step's exercise back under the name the app knows it by, so weights and pictures find it. Counts, weights and brackets are left exactly as they are.
Says how long a routine takes, including the self-paced parts a clock cannot know, and learns your pace from the runs you actually do.
Sends a routine as a link, as plain text, or as a .txt it can read back.
None of them carries a picture, so each says what it left behind rather than
dropping it quietly.
Backs up one routine or the whole library to a .json, images and weights
included. It is the only format that carries everything, so it is the one to
keep.
Owns its images. Local copies, so a routine survives gym wifi and the image host eventually losing a file.
Only needed if you want to change the code.
npm install
npm run dev # http://localhost:35173
npm test # no browser needed
npm run lint # Prettier, oxlint and Ruff; any warning fails it
npm run format # Prettier for the TypeScript and CSS, Ruff for scripts/
npm run typecheck
npm run build
npm run dev also unlocks the sound bench, at Routines then Sounds. It plays
every cue as the full figure and as its terminal sound alone, with the
parameters printed beside it. It is compiled out of a production build.
The screenshots above are retaken with tools/screenshots, which drives the
dev server in headless Chromium from demo data. See its README.
Deployment is automatic. A push to main builds and publishes to GitHub Pages,
gated on typecheck, lint and tests, because a broken timer is worse than a stale one.
Pages serves from a subpath, so VITE_BASE is set in the workflow and every
bundled asset path goes through import.meta.env.BASE_URL.
One principle runs through the codebase: the hard logic is pure, and React is a thin shell over it. Anything with rules worth arguing about is a plain function over plain data, tested without a DOM. That covers the run clock, the timeline compiler, the block tree, undo, the library ordering and the orphan sweep. The components wire those functions to state and markup and do little else.
Every hard bug in this project lived in exactly those rules, and they are only cheap to test when they are not tangled up in a component.
src/
engine/ The timer core: routine -> runs -> position. No React, no DOM.
state/ The run clock, and the React hooks that drive a workout.
audio/ Cues, pre-scheduled on the Web Audio clock.
editor/ Pure operations on a routine's block tree, plus undo.
media/ Images: content-addressed storage, downscaling, offline pinning.
storage/ IndexedDB, the library, backups, share links, read-time migration.
routines/ The .tabata importer, the paste parser, the seed, the catalogue.
ui/ Screens, the type scale, the design tokens.
Each folder has its own README covering the decisions behind it.
Time is derived, never counted. The clock stores { startedAt, pausedTotalMs }
and computes elapsed from performance.now(). Nothing accumulates ticks, so a
throttled tab or a pocketed phone cannot cause drift.
A routine compiles to a flat timeline once. compile() expands the recursive
tree of steps, rounds and ladders into absolute-time entries, and the runner is
a binary search over it. Seeking and skipping become trivial.
A routine that waits is timed runs separated by gates. A rep-based step ends when you tap Next. Inside a run nothing changes, so the absolute timeline, the binary search and the pre-scheduled cues all still apply. At a gate the clock parks until you tap.
Cues are scheduled ahead on the audio clock, never fired from a JavaScript tick, because the audio thread keeps time when the main thread is throttled. Every boundary sounds like what it means: three beeps then a whistle into work, three beeps then a bell out of it, three beeps then a triple ding at the end. On a long step a voice says "ten seconds left", or, for whoever is training beside you, a double beep or nothing: the stopwatch button on the run screen. A get-ready says what it is for, "Get ready for Leg Press 65kg", and not twice across the cardio minute you set the machine up during; the speech-bubble button turns that off.
The UI is sized for viewing distance, not convention. Secondary text starts around 1rem and scales with its container. The countdown is sized from the longest value a step will show, so it never changes size mid-step.
Everything is local. Routines live in IndexedDB, and images live beside them
once pinned. Backups, share links and the .tabata importer are how data moves.
The tests run in Node in a few seconds, with no browser anywhere in the setup. The parts with rules in them are pure and tested directly. The hooks that own timers, listeners and external handles (the tick chain, the cue scheduler, the wake lock, IndexedDB's connection) are tested in jsdom, because a full review found every serious bug hiding in that seam.
Several tests are named after the bug they exist to prevent.
GNU General Public License, version 3 or later. See LICENSE. Use,
change and share the code freely, including commercially, as long as anything
you distribute built from it is under the same licence with its source available.
Every source file carries a copyright and SPDX-License-Identifier: GPL-3.0-or-later header; add both to a new file.
Two things the licence does not cover. The exercise illustrations in
public/exercises/ are crops of the Horizon Torus 5 Exercise Guide, kept here
for personal use, so bring your own images if you reuse this. The whistle is a
CC0 public domain recording and needs no attribution. NOTICE has the detail.