Boardsesh
Works with Kilter, Tension, and MoonBoard. Track sessions, control LEDs, climb together.
College football Saturdays on one screen: venue, kickoff-hour stadium weather, TV and streaming, publicly posted spread and total, then live scores and cover/total state for every FBS game. Free, no a
Sports & fitnessPython · SwiftGitHub @ChristianVerdinOnlineOwner 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
CFB GameDay Board: college football slate with venue, kickoff weather, posted lines, and live cover/total. Web at cfbgameday.app, iOS app, stdlib Python + vanilla JS.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Local, single-user college football slate board. Built for scanning a Saturday
slate in one place: venue, kickoff-hour weather, DraftKings lines (via ESPN),
implied scores, TV/streams, then live score / cover / total state once games
start.
Dark, mobile-first, ESPN-style cards. No build step, no framework, no accounts.Read the full README ↗College football slate board. Built for scanning a Saturday slate in one place: venue, kickoff-hour weather, DraftKings lines (via ESPN), implied scores, TV/streams, then live score / cover / total state once games start, and a game sheet (scoring, box score, leaders, drives, ESPN win probability) for any game that has kicked off.
Dark, mobile-first, ESPN-style cards. No build step, no framework, no accounts.
How it works and why it is built this way, in a five-minute read: case study.
Last updated 2026-11-02 12:15 CT. The live board holds Week 10 (63 games, Tue 11/3 to Sat 11/7), from the snapshot generated 2026-11-02 11:48 CT. iOS 1.1.0 has been live since 2026-10-22 08:00 CT.
main on every push. The
snapshot rebuilds itself seven times a week via GitHub Actions (two on Tuesday, Wed,
Thu, Fri and two on Saturday), each well ahead of kickoff, and heals itself if a run
drops kickoff forecasts.ios/. On the App Store
since 2026-09-10, currently 1.1 (Today's Games widget and universal links, since
2026-10-22); Rivalry Games quick action and cfbgameday:// links since 1.0.5 (2026-10-20),
iPad and a resizable Mac window since 1.0.3 (2026-10-10):
https://apps.apple.com/app/id6809035228#rivalry links straight to the week's trophy games.cfbgameday:// links (from 1.0.5) open the app on a view: rivalry, live, starred,
tv, board, or game/<ESPN id>. The App Store's in-app event cards use them.games.json and /api/live, never odds or implied scores.cfbgameday.app and
its #rivalry, #live, #starred, #tv and #game/<ESPN id> views. Other pages
(/privacy, /support) stay in Safari.127.0.0.1, not a sportsbook
connection. Nothing here logs into or scrapes DraftKings, FanDuel, or any
book.python3 server.py
open http://127.0.0.1:8765/
server.py is a stdlib ThreadingHTTPServer bound to 127.0.0.1:8765. It
serves the folder and exposes two ESPN proxies. GET /api/live returns
scores, clock, situation, and current odds for the slate's dates; the page polls
it every 30 s and merges the result onto the snapshot in games.js.
GET /api/game?id= returns one game's summary for the game sheet.
Opening index.html directly as a file works for the snapshot only. Live mode
needs the server because the browser cannot call ESPN directly.
| File | Role |
|---|---|
index.html | UI shell and all CSS. The block between <!-- games:start --> and <!-- games:end -->, the week kicker, <title> and meta description are generated by refresh_week.py - edit the generator, not the block |
app.js | All client logic: filters, views, live merge, cover/total math |
games.js | Snapshot payload as window.CFB_DATA = {...} (loaded by the page) |
games.json | Same payload as plain JSON |
server.py | Static server + /api/live and /api/game ESPN proxies with per-date / per-game caches |
CONTEXT.md, AGENTS.md | Operator notes: live state and the map of everything that runs unattended. Not needed to run the board |
scripts/refresh_week.py | Rebuilds games.json / games.js for a new date range, plus the generated block in index.html and sitemap.xml |
docs/ARCHITECTURE.md | Snapshot vs live, ESPN endpoints, Open-Meteo process, weekly rebuild |
docs/screenshots/ | The images in this README (live board and iOS simulator, Week 8 2026) |
docs/superpowers/ | Design specs and plans for larger features (e.g. the game sheet) |
scripts/check.py | Offline smoke test: flag table, CT kickoff, cover/total math, snapshot shape, structured data |
scripts/run_summary.py | Prints the build's counts and warnings as Markdown for the GitHub Actions step summary |
scripts/ci_heal.py | Rebuilds once in CI when a run dropped kickoff forecasts (a forecast or venue-geocode failure), keeping the better result; --verify fails the job if still degraded |
scripts/promo_text.py | Builds the week's App Store promotional text from games.json; --apply pushes it with asc |
manifest.webmanifest, , |
The board is a Progressive Web App: a manifest, home-screen icons, and a service worker that caches only the UI shell. Live scores are never cached.
http://<your-mac-ip>:8765/ only if you change the bind address in
server.py; off-LAN needs a tunnel (Cloudflare Tunnel or Tailscale Serve).
The default 127.0.0.1 bind is deliberate, see Limitations.The page shows a one-time Add-to-Home-Screen hint in Safari on iOS; dismiss
with the ×. Ship a new shell by bumping VERSION in sw.js.
Fastest way to get the phone on it from anywhere, on gameday only:
brew install cloudflared
scripts/tunnel.sh # starts server.py if needed, prints the https URL, Ctrl+C ends both
Stable hostnames, Tailscale, and an always-on box are in docs/DEPLOY.md.
Test portrait on a 390 to 430 wide phone (iPhone 13 through 16 Pro Max).
proj implied score is gone
entirely, so nothing collides with the white score; the column stays aligned
against pre-kick cards.The GitHub Action does this on a schedule. To run it by hand:
python3 scripts/refresh_week.py
python3 scripts/promo_text.py --apply # App Store promotional text for the week (needs asc)
With no arguments it targets Tuesday through Monday of the slate in progress
(a new slate starts on Tuesday, so midweek MAC and CUSA games are included). Pass --start 20260911 --end 20260914 to pick dates. Rerun it on a game morning to refresh the forecast to
same-day accuracy, then restart server.py.
--no-enrich skips the per-game ESPN summary pass (~26s of the runtime) when
you just want a fast local rebuild. --seo-only rebuilds only the generated
block in index.html and the sitemap.xml lastmod from the committed
games.json, with no network calls at all. A normal run writes all four files,
so commit games.json games.js index.html sitemap.xml together.
The Action runs it seven times a week - Tue 4 AM with a Tue 10 AM backup, Wed 1 PM, Thu 7 AM, Fri 7 AM, Sat 4 AM and a Sat 8 AM backup. Each is scheduled hours ahead of when it is actually needed because GitHub creates scheduled runs late under load (measured 5h08m late once on this repo) and can drop them entirely.
That pulls the ESPN scoreboard for each date, geocodes each venue city with
Open-Meteo, pulls the hourly forecast at the local kickoff hour (a failed lookup
gets one retry from a per-run budget, since it leaves the game with no forecast), computes the
derived fields (implied score, weather flags, impact notes, CT kickoff), and
writes both games.json and games.js. Details in docs/ARCHITECTURE.md.
Live cards use the posted home spread and the current margin:
margin = home score - away score
coverBy = margin + homeSpread (> 0 means HOME is covering)
overNeed = total - (home + away)
The spread and total are the DraftKings numbers ESPN publishes in its scoreboard feed, refreshed with each live poll. They are a snapshot of the posted line, not a live steam feed.
server.py falls back across three ESPN hosts.enrich block fills in over the season: playing surface and the
ESPN projection are there from the start, but ATS records and attendance come
back empty in the early weeks and stadium capacity is usually null.lines.json; the hosted site keeps
them in the browser's local storage, so they are per device.docs/DEPLOY.md.The committed games.json / games.js is whatever the refresh Action last
wrote; its generated_at and week_label say which slate. Lines and weather
are as of that moment, with the last posted line carried forward once ESPN
drops odds at kickoff.
There are none. The repo must never contain .env files, API keys, AWS
credentials, sportsbook cookies, or model-provider tokens. .gitignore blocks
the usual filenames; scan the tree before adding anything new.
MIT. See LICENSE.
sw.jsicons/| PWA: install metadata, UI-shell service worker, home-screen icons |
api/live.py, vercel.json | The same ESPN proxy as a Vercel function, CDN-cached 20 s; project config |
api/game.py | Game sheet detail (scoring, box, leaders, drives, win probability) from ESPN's game summary |
.github/workflows/refresh.yml | Scheduled snapshot rebuild that commits games.json / games.js |
privacy.html, support.html, site.css | Public pages required for the App Store listing; shared stylesheet |
.well-known/apple-app-site-association | Universal links: lets iOS open board links (/ and /index.html only) in the app; served as JSON via vercel.json |
ios/ | SwiftUI iPhone and iPad app (also runs on Apple silicon Macs): project.yml (XcodeGen spec, versions, team), sources in CFBGameDay/, the Today's Games widget in CFBGameDayWidget/, unit tests and widget renders in CFBGameDayTests/ (frozen Week 8 snapshot), ExportOptions.plist, listing copy in APP_STORE.md, App Review reply kit APP_REVIEW_REPLY.md, live listing metadata in metadata/ (asc round-trip), review narration narration.txt + narrate.mts |
scripts/release_ios.sh | One-command iOS release: archive, export, validate, upload, attach (and --submit) via the asc CLI, altool fallback; stops if the archive or IPA is missing, and ASC_SIGNING_KEY_ID signs the export with an API key when Xcode has no account |
scripts/ios_tab_check.sh | Simulator smoke test: drives the tabs with AXe and fails on a blank revisit |
scripts/review_mux.py | Lays the narration lines over a phone screen recording at cue times, outputs H.264 for App Review |
scripts/tunnel.sh | Gameday Cloudflare quick tunnel for the local server (fallback only) |
CLAUDE.md | Rules and conventions for working in this repo with an AI agent |
.vercelignore | Keeps ios/, docs, scripts, and local state out of the Vercel upload |
docs/DEPLOY.md, deploy/ | Hosting: Vercel (live), tunnels, and self-host drafts |