BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2 或任何藍牙控制器,搭配 MyWhoosh、Rouvy、Zwift 等。iOS、Android、Windows、macOS 均可免費試用。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Unofficial WNBA season viewer — tip-off times in your timezone, where to watch, live scores, weekly calendar, playoff bracket, league-wide standings, league leaders, follow-teams, and calendar subscription. React + Vite.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
🔗 Live: ismayc.github.io/wnba-schedule ·
the-wnba-schedule.netlify.app
A React + Vite web app showing every game of the WNBA season in your timezone —
with live scores, where to watch on your streaming services, league-wide standings, the
playoff bracket, and league leaders.
No backend, no API keys, no tracking. The whole app is a static bundle plus a committed
snapshot of the season.查看完整 README ↗🔗 Live: ismayc.github.io/wnba-schedule · the-wnba-schedule.netlify.app
A React + Vite web app showing every game of the WNBA season in your timezone — with live scores, where to watch on your streaming services, league-wide standings, the playoff bracket, and league leaders.
No backend, no API keys, no tracking. The whole app is a static bundle plus a committed snapshot of the season.
| View | What it does |
|---|---|
| 📋 Schedule | Every game grouped by the calendar day you see, opening on today — previous days are hidden behind a toggle. Filter by team or by the teams you follow. |
| 📆 Week | A Sun–Sat grid you can page through, collapsing to a two-column agenda on a phone. |
| 📊 Regular Season | League seeding with the playoff cutline, or conference tables. W/L, PCT, GB, home/road splits, last-10, streak, net rating. |
| 🔀 Scenarios | Pick winners for the games left and every combination of the rest is played out under the official tiebreakers. A grid shows each seed every team can still reach (as a share of the possible outcomes). Tap a cell to see exactly which combinations of results produce it (with how many outcomes each covers) and which tiebreakers decide it; tap a combination to pick it. Point-differential tiebreaks are checked against every margin a picked game could finish by, and seeds that depend on them are marked. Once every game is picked, it shows the exact seeding, how each tie broke, and the first-round pairings. |
| 🏆 Playoffs | The bracket, where each slot is a best-of series. Projected from current standings until the real field is set. |
| 🎯 Radial | The same bracket as concentric rings — seeds outside, the title in the middle. |
| 📈 Stats | Season totals, league leaders across 8 categories, scoring margin, and the playoff race with magic numbers. |
| 📜 History | Every completed season back to 2022, in three modes: one season in full (bracket + final table), that season's stats (totals, all leaderboards, scoring margin), and every champion with its seed and the Finals format it won under. |
Star a team from any game card, standings row, or team panel to highlight it
across every view, filter the schedule to "My teams", and scope live alerts to it.
Clicking any team opens a team panel — splits, form, leading scorers, and what's
next. Each game card also flags where to watch — a 📺 badge naming the streaming
services that carry it (YouTube TV, Prime Video, Peacock). Plus: light/dark themes,
spoiler-free mode (on by default — one 🙈 toggle reveals scores, and the choice is
remembered per-device), shareable URLs, live alerts for notable moments, a game-detail modal
with a quarter line score and season series, .ics calendar export (whole season or one
team's), and installable-PWA support.
Everything comes from ESPN's public, keyless, CORS-open feeds.
The season is committed, not fetched. scripts/fetch-schedule.mjs generates
src/data/schedule.js, src/data/teams.js, and src/data/leaders.js, and mirrors team
logos into public/logos/. The app therefore renders a complete season — including
every result so far — with zero requests on load. At runtime, ESPN's scoreboard is
polled only to overlay games that are live or just finished (every 30s while a game is
in progress, 2 min otherwise, never once the season ends).
That snapshot is refreshed twice daily by .github/workflows/refresh-data.yml, which
regenerates the data, runs the test suite against it, and opens a PR. Standings are
derived from the committed scores, so a bad refresh surfaces as a failing test rather
than a quietly wrong table.
These are the difference between "looks about right" and matching the official standings exactly:
seasonType: 'cup' and excluded.media.shortName;
the scoreboard uses names[]. Both are accepted.With those handled, derived W-L, home/road splits, last-10, and streaks match ESPN's published standings exactly for all 15 teams.
The biggest structural difference from a soccer viewer. Goals are rare enough to enumerate as events — scorer, minute, penalty — and a Golden Boot table can be derived from them. Basketball can't work that way: ~65 scoring plays per game, ~20,000 per season.
So the model inverts. Games store a final score, and player leaderboards come from pre-aggregated season stat lines rather than being summed from events. Two things fill the gap that losing the event stream would otherwise leave:
Frequency also changes the live display. A soccer app can show a goal the moment it
lands and be right for the next ten minutes; a basketball score is stale within seconds.
So the live badge shows the period (Q3, HALF, OT) rather than a running game
clock, which would imply a precision a 30-second poll can't deliver. The exact feed
status stays in the tooltip.
And it rules out goal-style alerts. One notification per basket would fire ~65 times a game. The 🔔 toggle instead surfaces the moments that change how a game feels — tipoff, a lead change, a one-possession fourth quarter, and the final — detected by diffing poll snapshots, so no play-by-play feed is needed. A close fourth quarter alerts once on entering that state, not every 30 seconds while it holds, and a buzzer-beater that both flips the lead and ends the game is reported as one moment rather than three.
The WNBA is not a group-stage tournament, and two details drive most of the app's logic:
src/data/history.js holds every completed season back to 2022 — the year the
playoffs became series in every round (best-of-3 first round, best-of-5 semifinals and
Finals). Through 2021 the first two rounds were single-elimination games with double byes
for the top two seeds, a bracket shape this app doesn't draw.
Series lengths are per season, and inferred rather than assumed. The Finals grew from best-of-5 to best-of-7 in 2025, so each archived season carries its own lengths and the bracket is drawn against them — rendering 2023's 3-1 Finals against 2025's rules would show it as unfinished. The lengths are derived from each season's own results: the winner of a best-of-N takes exactly ceil(N/2) games, so N = 2 × wins − 1. A 4-0 sweep can only be a best-of-7. Nothing is hardcoded, so the next format change is picked up automatically.
Each season commits its final standings, its playoff games, its season totals
and its leader boards; the ~200 regular-season games are summarised into the standings
rather than committed. The bracket is rebuilt at runtime by the same buildBracket() the
live season uses, and box scores are fetched from ESPN by event id, so every archived game
opens one.
npm run fetch:history # rebuilds the archive (a season in progress is skipped)
Two data caveats the archive handles explicitly:
seedings() tables today's franchises, so a season before an
expansion team existed would carry phantom 0-0 rows for it (Golden State joined in 2025;
Portland and Toronto in 2026). Those rows are filtered out — 2022-24 show twelve teams,
2025 shows thirteen.npm install
npm run dev # local dev server
npm test # unit + render tests, against frozen data
npm run test:data # the live suite: the real committed data (what a refresh must pass)
npm run build # production bundle
npm run coverage:badge # tests with coverage, writes public/coverage.json
npm run fetch:schedule # regenerate committed data from ESPN
npm run check:schedule # report drift between committed data and the live feed
npm run verify:live # check the live overlay's assumptions against a game in progress
scripts/ uses Node built-ins only — no imports from node_modules — so CI can run
the data jobs on a bare checkout with no install step. A CI job enforces this.
The suite leans on real data rather than hand-made fixtures, because real data contains the edge cases you wouldn't think to invent.
Real, but frozen. There are two suites, and they never mix:
The main suite (npm test, the 100% coverage gate) never sees the modules the
refresh rewrites. A plugin in vite.config.js resolves every import of
src/data/schedule.js, leaders.js, and teams.js to a frozen September 4, 2026
stand-in under test/fixtures/frozen/, whoever the importer is. Coverage therefore
cannot move when the data does: the gate passes unchanged with those three modules
emptied out. Before this, a refresh reddened the gate three times in six weeks, each
time because a branch was covered only by whatever the live data happened to contain.
The live suite (npm run test:data, test/live/) reads the real modules and has no
coverage threshold. It holds invariants (unique ids, known teams, finite stats, a
standings table that recounts from the games) and a smoke render of every view, every
game dialog, and every team panel, checked for the residue of a bad value (NaN,
Invalid Date, 1969). This is the gate a refresh has to pass. Only things true on any
day of any season belong in it.
Standings are checked against the actual 2026 season, and the numbers are independently verifiable against ESPN's published standings.
The bracket is tested against the complete 2025 postseason
(test/fixtures/playoffs-2025.js) — 24 games, 7 series — because the 2026 bracket
doesn't exist until September. It reproduces the real outcome: Las Vegas over Phoenix
4–0. That fixture caught two bugs a synthetic test would have reproduced my own
assumptions right past.
Format invariants that would otherwise depend on this week's results (like "seeding ignores conference") are tested with synthetic data instead, so they don't break when the standings shift.
One gap the suite structurally cannot close: the live overlay's field mapping was
inferred from completed and scheduled games, and the tests mock ESPN using the same
inferences — so they agree with the assumption by construction. npm run verify:live,
run while a game is actually in progress, is the only check that compares those
assumptions to reality.
Built with base: './', so the same dist/ works at a domain root (Netlify) and under
a subpath (GitHub Pages /wnba-schedule/) with no separate build.
ci.yml on every push to main, gated on
tests passing.NETLIFY_AUTH_TOKEN is set
as a repository secret (NETLIFY_SITE_ID is already stored as a repo variable). Until
then that step is skipped and Netlify can be updated with
npx netlify-cli deploy --prod --dir dist.Created by Chester Ismay. Source on GitHub.
Unofficial fan project. Not affiliated with, endorsed by, or sponsored by the WNBA. Team names and logos are trademarks of their respective owners. Schedule, results, and player data via ESPN's public feeds.
MIT licensed.