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.
Track golf bets and scores across multiple game types to settle who owes whom after a round
Translation pending
AI summary · website and README
Sports & fitnessHTML · JavaScriptGitHub @ndipasquale10OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Score. Bet. Settle up.
An offline-first PWA for keeping a golf bet honest. Enter scores hole by hole,
pick a game, and the app tracks the money as you play — then shows exactly who
owes whom, in the fewest transactions, when you walk off 18.
Games: Wolf (with Hammer and Breakout), Nassau with presses, Skins, Match Play,
Stableford, Vegas, Snake, Bingo Bango Bongo, Sixes, Banker, and Trash/Dots.
There is no build step for the app. index.html is the whole thing — open it,
or serve the repository root over HTTP so the service worker registers:Read the full README ↗Score. Bet. Settle up.
An offline-first PWA for keeping a golf bet honest. Enter scores hole by hole, pick a game, and the app tracks the money as you play — then shows exactly who owes whom, in the fewest transactions, when you walk off 18.
Games: Wolf (with Hammer and Breakout), Nassau with presses, Skins, Match Play, Stableford, Vegas, Snake, Bingo Bango Bongo, Sixes, Banker, and Trash/Dots.
The shipped app is one file, index.html — open it, or serve the repository
root over HTTP so the service worker registers:
python3 -m http.server 8000
index.html is generated. The source is under src/: the markup in
src/index.html, the stylesheet in src/css/, and the script in src/js/,
one file per area of the app (players, scoring, games, storage, screens, live
rounds, …). Edit those, then rebuild and commit both:
npm run build # reassemble index.html from src/ and data/courses.json
npm run format # optional: format src/ with Prettier (CI checks it)
The build only concatenates: each <style>/<script> in src/index.html
is filled with its folder's files in filename order, so the numeric prefixes
are the load order. Nothing is transformed, and the shipped code is exactly
the source. npm test fails if index.html is out of date.
Much of the script was recovered from minified code, so many functions still
have single-letter parameter names (function (e, t)). Rename them as you
touch them.
The money math is the product, so it has the most tests.
| Command | What it covers |
|---|---|
npm test | Money math, Wolf/Hammer settlement, the check that index.html is built from the current src/ and course data, and a static design-system audit. A few seconds, no dependencies. |
npm run test:flows | Drives a real browser (Playwright) through complete rounds — the only check that sees bugs in the seam between two screens. |
npm run test:rules | Runs firestore.rules through the real rules engine in the Firestore emulator. Requires Java. |
npm run test:live | Two real browsers sharing one round against the Firestore and Auth emulators and the real security rules — publishing, joining by code, scores and Breakouts crossing the wire, the watch link, and account deletion. Requires Java. |
All three run in CI on every push.
data/courses.json is the single source of truth for the built-in course list;
it is inlined into index.html at build time because the app reads COURSE_DB
synchronously and has to work with no signal. After editing it, run
npm run build and commit both files — npm test fails if they drift.
See data/README.md for the schema and validation rules.
Diagrams of the system, the money engine, live rounds, and CI live in
docs/architecture.md.
index.html — the entire application, built from src/: markup,
styles, and logic in one file, so the service worker caches one shell and the app opens on a course
with no signal. Nothing on the launch path goes to the network. The Firebase
SDK in particular is not a blocking <script src>: it is listed in
FIREBASE_SDK and fetched by ensureFirebase() on idle, after the first
screen is up — as three blocking tags it put 528KB of uncacheable
cross-origin JavaScript in front of the scorecard, and in a dead zone the app
never opened at all. Anything that genuinely needs the SDK (sign-in, joining
a live round, a ?watch= link) awaits ensureFirebase().sw.js — the service worker. Caches the shell and static assets
individually (never addAll, which fails atomically) and announces updates
through the cache so a reloaded page still sees them.src/ — the readable source index.html is built from.data/, scripts/ — the course database, build.js (which assembles
index.html), and the preview builds the browser tests use.test/ — money math, Wolf/Hammer settlement, security rules, browser
flows, and a static design-system audit.Local storage is the source of truth. Everything in Firestore is a mirror of what is already on the device, which is why a signed-out player loses nothing.
scripts/build-preview.py produces the two builds the browser tests drive. Both
work by rewriting the one FIREBASE_SDK array — emptied for flows.mjs, so
ensureFirebase() resolves to "no Firebase" without a request; repointed at
node_modules for live-round.test.mjs, which then drives the real SDK against
the emulators. Rewriting the list rather than deleting the loader keeps the
loader, its retry, and every await ensureFirebase() call site under test.
Google Sign-In (Firebase Auth) is optional and unlocks cross-device sync, live rounds, and the shared course library. Three Firestore collections:
| Collection | Contents | Who can read it |
|---|---|---|
users/{uid} | A private mirror of one player's profiles, courses, and round history | That player only |
liveRounds/{code} | A round in progress. The document ID is the six-character share code. | Anyone holding the code |
sharedCourses/{slug} | The community course library — course data only, no personal information | Any signed-in player |
The share code being the document ID is load-bearing. A round can be fetched by its code and the collection can never be listed by anyone but its own owner, so there is no query that returns rounds you were not invited to.
firestore.rules is the deployed policy, not a copy of it: it is tested in CI
(test/rules.test.mjs, 34 cases against the real rules engine) and deployed
from .github/workflows/deploy-rules.yml on every push to main that touches
it. Editing rules in the Firebase console is how the repository and production
drift apart — don't.
Deploying requires a FIREBASE_SERVICE_ACCOUNT repository secret (a service
account JSON key with the Firebase Rules Admin role). Without it the workflow
still tests the rules and warns instead of deploying.
privacy.html and terms.html are served from
the site root so they have stable public URLs — app stores require a reachable
privacy policy link, and a policy that only exists inside a modal has no URL.
The in-app Settings → Legal screens summarise the same text.LICENSE — proprietary, all rights reserved.Before publishing: both policy pages carry a CONTACT EMAIL placeholder and
name "The Press" rather than a legal entity. Replace both with the real contact
address and the company's registered name once it exists.
The Press is a scorekeeper and a calculator. It never accepts, holds, or transfers money — the settle-up buttons hand off to Venmo, Cash App, or PayPal with an amount pre-filled, and the payment happens between the players in those apps. Keeping it that way is what keeps this a utility rather than a regulated money transmitter or gambling operator.