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.