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.
Two-person NFL draft pool scored against the spread
Translation pending
Sports & fitnessTypeScript · JavaScriptGitHub @willmcdonald21OnlineOwner 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.
A two-person NFL league scored against the spread. Each week a coin flip
decides who drafts first; the two of you draft 5 sides from that week's slate,
and whoever finishes the season with the most ATS wins takes it.Read the full README ↗A two-person NFL league scored against the spread. Each week a coin flip decides who drafts first; the two of you draft 5 sides from that week's slate, and whoever finishes the season with the most ATS wins takes it.
SEA -3.5, not "Seattle."SEA -3.5, NE +3.5 isn't available to your
opponent either.5 - P.Next.js (App Router) + TypeScript + Tailwind, Prisma + Postgres (Neon), hosted on Vercel. Slate, spreads, and final scores come from ESPN's public scoreboard endpoint — no API key.
You need a free Neon Postgres database — there's no local SQLite file any more, so local development talks to the same database as production.
npm install
cp .env.example .env # paste your Neon URLs + pick a league password
npx prisma migrate dev
npm run db:seed # creates the two players
npm run dev
Open http://localhost:3000. Player names are set in
prisma/seed.ts — edit them before seeding.
DATABASE_URL must be Neon's pooled string (the host contains -pooler)
and DIRECT_URL the unpooled one. Serverless functions open a connection per
invocation, so queries go through the pooler, while prisma migrate needs a
direct connection.
DATABASE_URL, DIRECT_URL,
ADMIN_PASSWORD, and CRON_SECRET (generate with openssl rand -hex 32).CRON_SECRET value as a GitHub Actions repository secret
(Settings → Secrets and variables → Actions → New repository secret) so the
automated spread sync can authenticate — see How spreads freeze below.npx prisma migrate deploy and npm run db:seed once against Neon.package.json runs prisma generate on postinstall because Vercel caches
node_modules — without it the deployed Prisma Client can silently go stale
against a changed schema.
ESPN drops the odds once a game goes final — a line that isn't captured before kickoff is gone for good. So each game's spread stays live — re-syncing keeps it current with the market — right up until that game's own kickoff, then it locks permanently. Both players are always graded against that one frozen line, which guarantees exactly one side covers (or it's a push); no after-the-fact line movement can change that.
If ESPN has no line for a game, that game isn't draftable until you set one by hand under Games missing a spread in Admin.
Nothing needs to stay open for this — the site and database are fully hosted and run on their own 24/7. Re-syncing does need to be triggered, though, which .github/workflows/sync-spreads.yml does automatically:
Both call /api/cron/sync-spreads, authenticated with CRON_SECRET. This runs
on GitHub Actions rather than Vercel's own Cron Jobs because Vercel's Hobby
plan caps cron jobs at once per day — nowhere near frequent enough here.
You can still click Sync slate in Admin any time; it does the same thing manually, and is the only way to load a week that hasn't been synced yet (the automation only refreshes weeks that already exist).
With homeSpread stored as the home team's line (negative = home favored):
adjusted = homeScore + homeSpread - awayScore
adjusted > 0 → home side covers
adjusted < 0 → away side covers
adjusted = 0 → push
ADMIN_PASSWORD is a single shared league password. It gates entering picks
and every admin action; viewing the week and the standings is open to anyone
with the link. It keeps strangers out, but it does not stop the two of you
from entering each other's picks — there are no per-player logins.