NBA/WNBA Schedule is a lightweight Progressive Web App (PWA) that shows NBA and WNBA games in your local timezone, keeps live scores in sync, and lets you filter the schedule for exactly what you care about—without ads or trackers. The UI is written in plain HTML/CSS/JS, backed by a tiny Deno edge function that shields the official NBA and WNBA JSON feeds, normalizes official standings, and caches only the season year for standings and bracket calls.
Visit the live build at nba-spielplan.de and explore the schedule directly in your browser.
For English-speaking users in the US, link directly to nba-spielplan.de/?mode=en-us to open the English UI with US broadcast labels immediately.
The app defaults to the NBA, but users can switch to the WNBA in the top menu. Direct links can use
?league=nba or ?league=wnba; the selected league is stored locally between visits.
-
Today view
- Shows every game scheduled for the current day in your timezone.
- Lets you move backward or forward by day from the headline controls, switching the headline to yesterday, tomorrow, or a specific date.
- Displays team logos, colors, records, and automatically switches between scheduled, live, and final states.
- Shows broadcast labels based on the selected mode: German broadcasts in
DE, US broadcasts inEN (US), and no broadcast labels inEN. - Polls the live scoreboard every minute while at least one game is live, overlaying in-progress scores.
- Cards are clickable and open the built-in overview, boxscore, and play-by-play overlay.
-
More games view
- Lists recent results plus upcoming games, grouped by date.
- Adjusts around the selected Today view date so the currently focused day is not duplicated.
- Filters by franchise (team picker), hides already played games, or restricts the list to “prime time” tip-offs (18:00–23:59 local time).
- Uses team color accents so you can scan cards quickly.
- Shows broadcast labels based on the selected mode.
-
League menu
- Top menu switches between NBA and WNBA schedules.
- Team filters, logos, colors, standings, boxscores, play-by-play data, and recap links follow the active league.
- NBA remains the default for existing links and cached users.
-
Season context
- Progress bar keeps track of the percentage of finished regular-season games for the active league.
- NBA conference standings and WNBA overall standings (W-L, games behind, streak, home/away splits), with NBA divisions and WNBA conferences available as secondary views.
- Dynamically generated playoff bracket that plugs in the top six seeds, projects play‑in winners, and updates round-by-round once results are available for the NBA.
- NBA Cup bracket (In-Season Tournament) that renders quarterfinals onward once the official bracket feed lists all matchups.
-
Offline-ready PWA
- Installable via
manifest.json, Apple touch meta tags, and service-worker registration. - Service worker applies a cache-first, stale-while-revalidate strategy for the shell, fonts, and API calls so the schedule works offline.
- Client-side Cache Storage keeps the most recent schedule/standings payloads and reuses them between visits until fresh data arrives.
- Installable via
-
Quality-of-life touches
- German and English UI localization, with a language and broadcast mode picker in the top menu.
- English users in the US can be sent straight to
https://nba-spielplan.de/?mode=en-us. - Preferences for league, mode, “show scores”, “show game rating”, and “prime time only”
persist in
localStorage. - Data automatically refreshes when the tab becomes visible or when a new day starts.
-
Game detail overlay
- Opens when clicking any live or finished game.
- Splits game details into Overview, Boxscore, and Play-by-Play tabs.
- Shows previous head-to-head matchups between the two teams before the selected game.
- Shows full period scoring (Q1–Q4, OT1+).
- Lists starters and bench with complete statlines (MIN, PTS, REB, AST, STL, BLK, TOV, PF, FG, 3P, FT).
- Adds a direct NBA.com or WNBA.com recap link for finished games, opening the official game recap externally.
- Uses static HTML templates for fast client‑side rendering.
- Works offline if the data was previously cached.
-
Excitement meter (german: Spannungsmeter)
- Calculates an excitement score (0–100) for finished games from play-by-play closeness, lead changes, comebacks, crunch-time moments, offense, and OT bonuses.
- Shows a labeled 1–10 rating above the overlay tabs once a game is final.
- Reads cached ratings from the backend first and falls back to client-side play-by-play calculation for missing games.
- Shows a Top 10 list of the most exciting games once regular-season progress reaches 100%, using cached backend ratings.
-
Play-by-Play Tab
- Live-by-play feed with time, description, and score delta.
- Toggle to show only made shots for a quick scoring view.
-
Lightweight & fast
- Minimal bundle, no frameworks, fast loading even on slow networks.
- Focuses on the essentials only, avoiding the bloat of typical sports apps.
-
Clear, uncluttered UI
- Schedule-first design with clean typography and color‑coded teams.
- Optimized for quick scanning on both mobile and desktop.
-
App-like experience
- Can be added to the Homescreen and behaves like a native app.
- Auto-refreshes on day changes and tab visibility.
-
Privacy-first
- No ads, no third-party scripts, no trackers of any kind.
- Only lightweight Umami analytics, self-hosted and privacy-friendly.
The backend is powered by a Deno Deploy edge function (api/main.js), which proxies and sanitizes NBA
and WNBA endpoints. NBA is the default; league-aware endpoints also accept ?league=nba or
?league=wnba. The schedule endpoint accepts ?region=de, ?region=us, or ?region=none for
broadcast labels.
| Endpoint | Purpose | Notes |
|---|---|---|
/schedule |
Raw league schedule | Fetched fresh per request; client Cache API handles reuse. |
/standings |
Normalized standings | Uses the official standings feed and includes NBA divisions. |
/scoreboard |
Live in-day scoreboard feed | Always proxied without caching; powers the game detail overlay and in-day score updates. |
/top-excitement |
Top excitement-rated games | Read-only KV endpoint for the current season's cached highlight list. |
/backfill-excitement |
Excitement cache warmup | POST endpoint that queues one lazy, batch-limited KV backfill for the active league. |
/fetch-excitement |
Specific cached ratings | POST endpoint that reads requested game IDs from KV and returns scores plus missing. |
/playoffbracket |
Official NBA bracket JSON | NBA-only; uses a 24h-cached season year, then proxies the official bracket feed. |
/istbracket |
NBA Cup (IST) bracket JSON | NBA-only; uses a 24h-cached season year, then proxies the official ISTBracket feed. |
/boxscore/:id |
Per-game boxscore | Uncached proxy to the active league's live boxscore JSON. |
/playbyplay/:id |
Per-game play-by-play | Uncached proxy to the active league's live play-by-play JSON. |
The frontend consumes core endpoints via fetchData, which first checks the Cache API before
hitting the network. When games are live, the app polls /scoreboard every minute and merges the
fresh scores into the already-rendered cards. Excitement ratings are KV-first: after the initial
page data load, the client starts a non-blocking /backfill-excitement request, then batches
specific finished game IDs through /fetch-excitement; only missing IDs fall back to client-side
play-by-play calculation. Once regular-season progress reaches 100%, the client reads
/top-excitement and renders the cached Top 10 list when items are available. The client keeps NBA
and WNBA cache keys separate for schedule, standings, scoreboard, next-game markers, and excitement
ratings. The service worker uses stale-while-revalidate for the app shell and API calls on the same
origin.
- Service Worker toggles live in
js/app.js: setuseServiceWorkertofalsefor local debugging or bumpserviceWorkerVersionto force a fresh cache on deploy.
This project is source-available, not open source.
You may view and modify the code for personal, educational, and non-commercial purposes.
If you publicly redistribute this project or a modified version of it, you must:
- link to the original project repository
- include a visible attribution to the original project and author, where technically and contextually appropriate
- clearly mark your version as modified and unofficial
Commercial use, paid hosting, resale, and misleading rebranding are not allowed without prior written permission.