BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2,或任何藍牙控制器搭配 MyWhoosh、Rouvy、Zwift 等。可於 iOS、Android、Windows 與 macOS 免費試用。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Live demo ↗ · the only scores, standings and stats that matter — yours.
Most sports apps drown you in every game on earth. Frontrow does the opposite:
pick your teams and players once, and get a single, broadcast-grade dashboard that
shows only what you follow — live scores, recent form, next fixtures, standings,
and season stats.
Real accounts keep it in sync across devices, and the whole thing is yours to
re-skin. No noise. No clutter. Front-row seats.檢視完整 README ↗Live demo ↗ · the only scores, standings and stats that matter — yours.
Most sports apps drown you in every game on earth. Frontrow does the opposite: pick your teams and players once, and get a single, broadcast-grade dashboard that shows only what you follow — live scores, recent form, next fixtures, standings, and season stats.
Real accounts keep it in sync across devices, and the whole thing is yours to re-skin. No noise. No clutter. Front-row seats.
Mood: a floodlit night game from the press box. Midnight slate, broadcast overlays, cobalt as the brand voice, a single live-red pulse, amber for the leaders.
| 🎟️ A dashboard that's only yours | No firehose — just live & upcoming, your teams, your players, and the standings around them. |
| 🔴 Live, but quiet | Auto-refresh polls only while a game is actually live. The rest of the time it's still and fast. |
| 🎨 Make it yours | 7 accent themes, corner roundness, density, motion & glow toggles, a custom greeting, default league, and per-section visibility. |
| 🔐 Real accounts | Supabase email + password auth with confirmation, Row-Level Security, and cross-device sync. |
| 📊 Stats with shape | Season-trend charts, output sparklines, recent form, and league-ranked player numbers. |
| 🌒 Considered everywhere | Loading, empty & error states on every surface; mobile-first; honors prefers-reduced-motion. |
git clone https://github.com/germanndarian/frontrow.git
cd frontrow
npm install
cp .env.example .env.local # add the two public Supabase values (below)
npm run dev # → http://localhost:3000
npm run build && npm run start # production build · Node 18.18+ (built on 24)
ESPN needs no API key — it's read through keyless public endpoints, server-side.
Accounts need Supabase: in production the Vercel integration injects everything;
for local dev, add the two browser-safe values to .env.local.
| Variable | Purpose | Required |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL | ✅ |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Public anon key (RLS-guarded) | ✅ |
SUPABASE_SERVICE_ROLE_KEY | Server-only — account deletion | — |
NEXT_PUBLIC_LIVE_POLL_MS | Live-game refresh cadence | 30000 |
NEXT_PUBLIC_USE_MOCK | Run fully offline on the demo dataset | false |
Apply the schema once from supabase/migrations/0001_init.sql
(Supabase → SQL Editor) — it creates profiles / preferences / settings,
Row-Level Security so a user only ever touches their own rows, and a trigger that
seeds defaults on signup.
┌───────────────┐ ┌─────────────────────┐ ┌──────────────┐
Browser ──▶│ /api/* handlers│ ──▶ │ ESPN (cached · 8s │ ──▶ │ normalizer │ ──▶ UI
│ (server) │ │ timeout · retry-once)│ │ (defensive) │
└───────────────┘ └─────────────────────┘ └──────────────┘
Accounts ─▶ Supabase Auth (cookies · @supabase/ssr) ─▶ Postgres + RLS ◀─ follows & settings
The browser never calls ESPN directly. Each surface is cached server-side (scoreboard 20s · team/standings/player 5m · teams/rosters 24h) and the client polls only while a game is live. Every normalizer is defensive, so an upstream field change degrades to a placeholder instead of a crash.
| Route | Source |
|---|---|
GET /api/scoreboard?leagues= | {sport}/{league}/scoreboard |
GET /api/team/[id]?league= | team detail + teams/{id}/schedule |
GET /api/standings/[league] | v2/.../standings?level=3 |
GET /api/player/[id]?league= | athletes/{id} + athletes/{id}/gamelog |
GET /api/teams · /api/roster | onboarding selectors |
GET /auth/confirm · DELETE /api/account | email confirm · account deletion |
Next.js 16 · React 19 · TypeScript · Tailwind CSS v4 (OKLCH tokens)
Supabase (Auth · Postgres · RLS) · TanStack Query · Zustand · Motion · Recharts
Vitest · Playwright · GitHub Actions · Vercel (Analytics · Speed Insights)
src/
app/
layout.tsx Fonts + providers + Analytics / Speed Insights
page.tsx Dashboard (auth-gated)
login/ · setup/ Email auth · four-step onboarding
settings/ Profile · appearance · dashboard · follows
auth/confirm/route.ts Email-confirmation handler
api/ ESPN route handlers + /api/account
globals.css OKLCH design tokens, base styles, keyframes
lib/
supabase/ Browser + server clients (@supabase/ssr)
auth.ts · sync.ts Supabase Auth store · follows & settings ⇄ Postgres
store.ts · settings.ts In-memory working state (DB-backed)
espn/ Server-only ESPN integration
leagues.ts · types.ts League config + normalized domain models
components/ dashboard · setup · ui · system · brand
supabase/migrations/ SQL schema + Row-Level Security
main is protected — nothing merges red. Every PR runs:
npm run lint # ESLint
npx tsc --noEmit # type-check
npm test # Vitest · pure functions in src/lib
npm run e2e # Playwright · every screen, Chromium + WebKit
npm run build # production build
Both suites are hermetic — the browser tests drive the real app against the bundled
demo dataset, so no ESPN and no Supabase. A separate npm run e2e:upstream calls
ESPN for real and stays out of the merge gate, so a third party can't block a
release. Its twice-a-day schedule is paused for now; run it by hand from
Actions → Upstream watch → Run workflow.
Frontrow · unofficial data via ESPN's public endpoints, refreshed as games unfold.
Built for the only seats worth having — front row. 🏟️