Courtside Analytics
NBA team, player and schedule analytics for front-office decisions, refreshed from nba_api.
Create and view personal college football rankings and compare with others
AI summary · website content
Sports & fitnessJavaScript · CSSGitHub @eashmore1OnlineOwner 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
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A live-updating college football rankings site for you and your friends.
Everyone builds their own Top 25 ballot by dragging team helmets into place, and all ballots are averaged into the combined TNT Ranking — recalculated live the moment anyone changes their ballot.
TNT #3 ▲8 = we have them eight spots higher).
The 2026 preseason Coaches Poll is loaded; the AP preseason poll isn't out
until Aug 17, so it says so until you add it. These polls are typed in by
hand, not scraped — add a week by dropping an entry into
public/polls.js (the format is documented at the top of the file).public/heisman.js and
is hand-entered, so it's easy to update). Your picks stay
completely private — nobody can see anyone else's until they all lock the
moment the first game kicks off (Aug 29, 2026). Until then a live countdown
shows how long is left and who has already locked in (names only, never their
picks); once the clock hits zero everyone's predictions are revealed side by
side.npm install
npm start
Then open http://localhost:3000. Set PORT to change the port.
Locally, everything is stored in data/store.json (created automatically), so
back that file up if you care about your poll history.
The site is built to run on Vercel: public/ is served as static files and
each file in api/ becomes a serverless function. Pushing to the connected
branch deploys it.
One setup step is required, once. Serverless functions have no disk, so
data/store.json has nowhere to live. Worse than losing data on redeploy:
Vercel runs several function instances, each with its own memory, so without a
shared database different people can be served different ballots. Pick either
option below — the app detects whichever one you configure.
Needs no account beyond the GitHub one this repo already lives in, and has nothing to pay for or run out of.
tnt-store.json containing {}. The gist's ID is the last part of its URL.gist scope ticked.GIST_ID and
GIST_TOKEN.Every save writes a gist revision, so its history grows one entry per ballot
change — harmless, just busy-looking. Optional: GIST_FILE to use a filename
other than tnt-store.json.
FIREBASE_DB_URL to the Vercel project's environment variables
(e.g. https://your-project-default-rtdb.firebaseio.com), plus one of:
FIREBASE_DB_SECRET — Project Settings → Service Accounts → Database
secrets. One value, quickest to set up.FIREBASE_SERVICE_ACCOUNT — the whole service-account JSON, pasted in.
This is Google's current supported path; the app signs a JWT with it and
exchanges that for a short-lived access token, cached until it expires.".read": false,
".write": false). Only the server talks to Firebase, and it authenticates,
so no public access is needed — see the note below.Optional: FIREBASE_DB_PATH to store somewhere other than tnt/store.
Either connect one through the Vercel dashboard (Storage → Create
Database → Redis), which injects KV_REST_API_URL and KV_REST_API_TOKEN
for you, or make a database at https://upstash.com directly and paste those
two values in yourself. Then redeploy. The app can't tell the difference.
If something doesn't save, open /api/health on the deployed site. It
reports which backend is actually in use, whether it can be read, and the
current revision — no credentials, just the diagnosis. backend: "memory"
means none of the variables below are reaching the app. Add ?write=1 to
round-trip a write and prove it survives.
Set exactly one of the three. They're checked Firebase → Redis → Gist, so a half-finished migration resolves to a single store rather than quietly splitting the data across two. To confirm it took, check the deployment logs — with none configured the app still runs but logs a loud warning that data is being held in memory.
Why the browser never talks to the database directly. It would be tempting to use a Firebase client SDK and get real-time push for free. But the predictions rule — nobody sees anyone else's picks until the Aug 29 lock — is enforced server-side in
lib/core.js, and it only holds because reads go through the server, which sends other people's picks to nobody. Pointing the client straight at Firebase would put every prediction in the browser and leave security rules as the only thing standing between them, which this app can't write meaningfully because it has no per-user authentication.
npm start runs the same code against data/store.json, so any ordinary
Node host (Render, Railway, Fly.io, a VPS, a Raspberry Pi) works too — just
put data/ on a persistent disk. No Redis needed there.
public/teams.js — add or recolor teams there.api/sync (the single read the
client polls) plus api/ballot and api/predictions for writes. All the
actual rules live in lib/core.js, which both the serverless functions and
the local Express server call into, so there's one implementation regardless
of where it's running.lib/store.js picks its backend automatically — Firebase
Realtime Database, Redis, or a GitHub gist when credentials are present
(Vercel), a JSON file otherwise (local), memory as a last resort. The whole
store is a single JSON document, so each backend is just a get and a put;
adding another one means implementing those two functions. Saving is a
read-modify-write, so writes are conditional: each one only lands if the
document is still what was read, and store.update() re-reads and retries
when it isn't. Without that, two people saving in the same moment would each
write back a document based on what they read before the other's save, and
the later write would silently erase the earlier ballot — while telling both
of them "Saved". Redis does the check with a small EVAL script and Firebase
with an ETag; gists have no conditional update, so there they're only
serialised within one instance.public/net.js polls and presents the same on/emit
interface the app was originally written against, so the app code doesn't
know or care that there's no socket underneath. Polling is deliberate:
serverless functions can't push to other people's browsers, which is exactly
what a shared ranking needs. The loop is adaptive — every 3s while something
is happening or you're touching the page, backing off to 15s when the page
is just sitting there, and stopping entirely on a hidden tab. Everything the
loop needs comes from a single /api/sync call so one tick costs one
function invocation and one storage read. That matters: polling is the
app's steady-state traffic, and a Redis free tier is priced per command.lib/core.js. Before the
lock, other people's picks are never sent to your browser at all, so there's
nothing to find in dev tools.