BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.