Squares Svelte Themes

Squares

Real-time Super Bowl squares pool — SvelteKit 5 · Supabase Realtime · Ran live with ~50 concurrent players, zero downtime

Football Squares

Real-time multiplayer football squares pools. Friends claim squares on a 10×10 grid; payouts go to whoever owns the cell whose row/column digits match the score at quarter-end. Built and run on Super Bowl Sunday for ~50 concurrent players.

▶ Live Demo — squares.nathankrebs.com

Players claiming squares in real time Grid locked with numbers assigned Quarter winner announced

Demo: create party → claim squares → lock → score → winner

Features

  • Real-time grid sync — sub-100ms cross-client updates via Supabase broadcast + postgres_changes (see ADR-0003)
  • Optimistic claims with rollback — taps feel instant; failed claims roll back with a toast (see ADR-0002)
  • Custom teams — set team names, colors, and logos per party; configurable app-wide via env vars
  • Future-game ready — name a specific event and optional kickoff; pools stay available through game day
  • Editable setup before lock — hosts can correct event details, kickoff, and matchup without recreating a pool
  • Multiple payout structures — Rising / Equal / Big Finish / Custom
  • Host PIN protection for grid lock, score entry, payout edits, party deletion
  • PWA installable on iOS + Android with push notifications
  • Matchup-aware live score assist — links to ESPN-sourced NFL scores when the active game matches the party teams; manual scoring stays available as the fallback
  • Color-coded player legend with click-to-filter
  • Pan and zoom for usable squares on small phones
  • WCAG 2.1 AA targets — ARIA grid markup, dialog modal with focus trap, semantic forms

Tech stack

Layer Choice
Frontend SvelteKit 5 (runes in components, legacy stores in shared state — why)
Styling Tailwind 4 + CSS variables
Language TypeScript (strict, --max-warnings 0)
Backend Supabase (Postgres + Realtime + RPC)
Hosting Cloudflare Pages via @sveltejs/adapter-cloudflare — see DEPLOY.md
Observability Sentry + Web Vitals (optional, no-op without DSN)
Testing Vitest (unit + integration) + Playwright (e2e + visual regression)
CI GitHub Actions — 5 jobs gating every PR

Why this exists

Office and friend-group "squares" pools usually run via a paper grid + Venmo. Anyone joining late can't see the live grid. Anyone leaving early can't see who won which quarter. The host has to manually track scores against a paper-and-pen grid while paying attention to the actual game.

This app collapses all of that into a shared URL. Friends claim cells in real time, the host enters scores at quarter-end, the app computes winners and shows a payout summary. No accounts, no money flowing through the app — just the bookkeeping.

Ran live on Super Bowl Sunday 2026 with ~50 concurrent players. Zero downtime. Zero support requests.

Portfolio case study

This repo is intentionally shaped as a full-stack portfolio artifact, not just a weekend UI. The product constraint was a real event with non-technical users, spotty mobile networks, and a host who needed score entry to work while watching the game.

The senior-engineering work is in the operational details: a transactional create_party RPC, persisted event identity and event-aware retention for arbitrary future football games, source-of-truth Postgres changes paired with low-latency broadcasts, matchup-aware live score detection with manual fallback, optimistic claims with rollback, PIN-protected host actions, RLS hardening, PWA install support, optional Sentry/Web Vitals, and a game-day runbook. CI gates linting, formatting, type checks, coverage, build, bundle size, Supabase integration tests, and Playwright e2e coverage.

For reviewers, the fastest path is:

Quick start

Prerequisites

  • Node 22+
  • A Supabase project (free tier works)

Installation

git clone https://github.com/nkrebs13/Squares.git
cd Squares
npm install
cp .env.example .env.local
# Edit .env.local: set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY
npm run dev                   # http://localhost:5173

Database setup

Apply the migrations to your Supabase project:

# Option A: Supabase CLI (recommended)
supabase link --project-ref <your-ref>
supabase db push
# Option B: SQL Editor — paste each file in numerical order
# supabase/migrations/001_*.sql through the latest numbered migration

[!IMPORTANT] Option B only: If you apply migrations via the SQL Editor instead of the CLI, also verify that Realtime replication is enabled for the parties, squares, numbers, scores, and winners tables in Supabase Dashboard → Database → Replication. The CLI applies 001_schema.sql (which contains the ALTER PUBLICATION supabase_realtime ADD TABLE … statements) automatically; the SQL Editor does not activate Realtime for you — the grid will appear to work but won't sync across clients in real time.

Demo data

Running supabase db reset applies all migrations and seeds a demo party automatically. After reset, visit /party/DEMO01 (PIN: 0000) to see a partially-filled grid with four fictional players. To skip seeding, delete supabase/seed.sql before running reset.

[!TIP] The live demo at squares.nathankrebs.com is always running against a pre-seeded Supabase project. You can explore every game state without setting anything up locally.

Optional: Error tracking

Set PUBLIC_SENTRY_DSN in .env.local to send unhandled errors and Web Vitals (CLS, INP, LCP, FCP, TTFB) to a Sentry project. The free tier is sufficient for portfolio-level traffic; the app works identically with the variable unset.

PUBLIC_SENTRY_DSN=https://[email protected]/...

Deployment

The repo ships with @sveltejs/adapter-cloudflare because Cloudflare Pages is the canonical production target (squares.nathankrebs.com runs there). Forks can still target Vercel, Netlify, or self-hosted Node by swapping the adapter as described in the deploy guide.

Full step-by-step instructions, env-var lists, custom-domain notes, and troubleshooting in docs/DEPLOY.md.

Customizing your fork

Brand strings, default event/team labels, and currency live in src/lib/config.ts and read from PUBLIC_* env vars at build time. To rebrand without touching code, set the relevant entries in .env.local (or your platform's env-var dashboard) before npm run build:

Env var Default
PUBLIC_APP_NAME Football Squares
PUBLIC_APP_URL https://squares.nathankrebs.com
PUBLIC_APP_TAGLINE Football squares pools for any game
PUBLIC_APP_DESCRIPTION Real-time football squares pools. …
PUBLIC_DEMO_PARTY_CODE DEMO01
PUBLIC_DEFAULT_EVENT_NAME Football Squares
PUBLIC_DEFAULT_TEAM_ROW_NAME Seahawks
PUBLIC_DEFAULT_TEAM_ROW_COLOR #69BE28
PUBLIC_DEFAULT_TEAM_ROW_LOGO /logos/seahawks.png
PUBLIC_DEFAULT_TEAM_COL_NAME Patriots
PUBLIC_DEFAULT_TEAM_COL_COLOR #C60C30
PUBLIC_DEFAULT_TEAM_COL_LOGO /logos/patriots.png
PUBLIC_CURRENCY_CODE USD (any ISO 4217 code)
PUBLIC_LOCALE en-US (any BCP 47 locale)

PUBLIC_APP_NAME and PUBLIC_APP_DESCRIPTION are also picked up by the PWA manifest in vite.config.ts. All values fall back to the defaults above when unset, so a stock fork keeps the Football Squares experience.

[!NOTE] Team names and colors can also be set per party from the create-party form — the env vars set the defaults pre-populated in the form.

Architecture

The 10-minute orientation is in ARCHITECTURE.md. The deepest design decisions get their own ADRs:

[!NOTE] Each ADR documents the options considered, the tradeoffs weighed, and the decision made — not just what was built, but why. This is the fastest path to understanding the non-obvious design choices.

If you've cloned the repo and want to know "where does X live and why", that's the path.

For production operations, see GAME-DAY.md — the runbook used on Super Bowl Sunday, covering Supabase monitoring, emergency SQL for stuck party states, service worker cache clearing, and broadcast channel health diagnosis.

Development

npm run dev                        # http://localhost:5173
npm run test                       # unit tests (Vitest)
npm run test:e2e                   # Playwright (Chromium + Mobile Chrome)
npm run lint && npm run check      # quality gates

See CONTRIBUTING.md for local Supabase setup, testing strategy, and contribution guidelines.

Contributing

Please read our Code of Conduct before participating.

  1. Branch from main
  2. Run npm run lint && npm run check && npm run test locally
  3. Open a PR; CI must be green before merge

License

MIT — see LICENSE.

Author

Built by Nathan Krebs. Originally for Super Bowl 2026 with friends; now public as a portfolio piece.

Top categories

Loading Svelte Themes