Split The G is a social web app for scoring how cleanly a pintβs foam line crosses the Guinness βGβ: capture a pour, get a split score, share a card, and compare with friends, pubs, and live competitions.
The current product was substantially revamped by Jonathan Rycx, who leads product direction, design, and full-stack implementation.
- Capture a pour via camera or upload on
/ - Score using Roboflow workflows / Inference.js (env-configured)
- Countdown + βin-windowβ pouring for competitions via
/?competition=...
- Feed (
/feed) and Wall collage (/wall,/collageredirects) - Pubs directory and pub pages (
/pubs,/pubs/:barKey) with map/embed where configured - Shareable, stable pour URLs under
/pour/:pourRef
- Google sign-in via Supabase
- Profile pages with score history and progress
- Global weekly (
/leaderboard), by country, and past 24h leaderboards
- Create public/private competitions + win rules
- Email invites and join/leave flows
- Live leaderboard + Whoβs in tabs
- Friends and comparisons from profile
- Progress hub (
/profile/progress): achievements, streak snapshots, and score insights (distribution, momentum, consistency) with a help panel β copy is fully localized - Achievements (
/profile/achievements): mobile-first grid of badges (no horizontal carousel), hero summary, crown tier pills, and profile tier avatar (gold progress ring + crown chip) on the mobile hub and desktop Account header - Sharing unlocked badges (mobile): branded bottom sheet above the dock with a preview card (
SplitTheGLogo, sticker art, tier, title), slide-up animation (app/app.css), primary Share to social apps (navigator.share) and Copy achievements link; desktop/narrow desktop still uses one-shot Web Share + clipboard fallback (profile-share-achievement.ts) - Account (desktop): Save profile is full width from the
mdbreakpoint up; push notifications sit in a bordered row with the toggle beside the copy; extra spacing before Sign out - Server-side awards and streak updates driven by score events (see
supabase/migrations/*profile_gamification*)
AdSlotBanner(app/components/ad-slot-banner.tsx): horizontal βad slotβ strip (dashed frame, subtle diagonal texture, top gold accent bar, megaphone, Contact mailto CTA) with per-surface i18n- Placements: Feed (above Latest pours), Wall, Competitions (above listings), Pubs list (below filters), Pub detail (above Promos / Competitions / Wall). Pubs list copy pitches directory listings; pub detail copy pitches banner advertising (distinct email subjects per surface)
- Minimal copyright strip only (
AppDesktopFooter,md+); no duplicate branding or nav links. Layout uses a flex column inlang-layout+#rootso short pages still fill the viewport under the fixed header
- Server guards in
app/utils/pour-submission-guards.server.ts: per-user rate limits, duplicate image detection (hash), optional EXIF capture-time freshness window (POUR_RATE_LIMIT_MAX_PER_HOUR,POUR_EXIF_MAX_AGE_MINUTES) - Related migrations under
supabase/migrations/for pour metadata / anti-cheat columns; offline pour queue on the client for flaky networks (see home pour flow)
- React 19
- React Router 7 (SSR + data APIs like loaders/actions)
- Vite 7 build + bundling
- Tailwind CSS 4 (dark Guinness-inspired theme via
@tailwindcss/postcss)
- Supabase (PostgreSQL + RLS, Auth, Storage)
- Roboflow (workflow API + inference)
- Resend (friend-invite emails)
- Google Maps JavaScript API (Places + optional server key)
- Node.js 20+
- npm
- A Supabase project (URL + anon key + run migrations)
- Optional: Google Maps API key
- Optional: Resend API key (friend invites)
- Optional: Roboflow workspace/workflow keys (pour scoring)
npm install
npm run devDefault dev URL: http://localhost:5173
Create a .env.local in the project root. Vite loads client env; server code can read non-VITE_ secrets too.
VITE_SUPABASE_URL=your-supabase-url
VITE_SUPABASE_ANON_KEY=your-supabase-anon-keyVITE_GOOGLE_MAPS_API_KEY=your-google-maps-api-key
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
VITE_POSTHOG_KEY=phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
VITE_POSTHOG_HOST=https://us.i.posthog.com
RESEND_API_KEY=your-resend-api-key
RESEND_FROM_EMAIL="Split The G <noreply@your-domain>"
APP_URL=https://your-site.example
VITE_WEB_PUSH_PUBLIC_KEY=your-vapid-public-key
WEB_PUSH_PRIVATE_KEY=your-vapid-private-key
WEB_PUSH_SUBJECT=mailto:you@example.comPOUR_RATE_LIMIT_MAX_PER_HOUR=5
POUR_EXIF_MAX_AGE_MINUTES=12See app/utils/pour-submission-guards.server.ts for behavior.
VITE_ROBOFLOW_API_URL=https://detect.roboflow.com
VITE_ROBOFLOW_WORKSPACE=your-workspace
VITE_ROBOFLOW_WORKFLOW_ID=your-workflow-id
VITE_ROBOFLOW_WORKFLOW_INFER_URL= # optional full infer URL override
VITE_ROBOFLOW_WORKFLOW_VERSION_ID= # optional workflow version id
VITE_ROBOFLOW_PUBLISHABLE_KEY= # optional; falls back to VITE_ROBOFLOW_API_KEY
VITE_ROBOFLOW_API_KEY= # optional; legacy fallback for some setups
VITE_ROBOFLOW_INFERENCE_MODEL=split-g-label-experiment
VITE_ROBOFLOW_INFERENCE_VERSION=8
# Server-side secret (preferred)
ROBOFLOW_PRIVATE_API_KEY=your-private-api-key- The app supports GA4 + PostHog with a shared typed event layer under
app/utils/analytics/. - Tracking is consent-gated and can be toggled later from profile account settings.
- Core tracked surfaces include page views, pour funnel, competition actions, auth/sign-in, registrations, and profile saves.
- UTM first-touch + last-touch attribution is captured and attached to analytics events.
- Tracking QA and event checklist:
docs/analytics-tracking.md - Prebuilt PostHog dashboard/insight blueprint:
docs/posthog-insights.spec.json
split-the-g/
βββ app/
β βββ routes/ # Pages, loaders, and actions (SSR)
β βββ components/ # Shared UI (nav, competitions, wall, pub, ad-slot-banner, desktop footer, branded)
β βββ utils/ # Supabase client, scoring, maps, emails, paths
β βββ ... # Route tables, i18n, etc.
βββ public/ # Static assets (including mobile dock icons)
βββ supabase/migrations/ # Forward-only SQL migrations (RLS + schema)
βββ docs/ # Developer documentationnpm run dev # Vite dev server (host enabled)npm run build # React Router production build
npm run start # Run built SSR servernpm run lint # typecheck + eslint
npm run typecheck # react-router typegen + tsc- Camera/upload β server action β Roboflow workflow inference
- Extract detections and compute split score
- Store results and render shareable cards
- All database access assumes Row Level Security policies
- Competitions, friends, profiles, and leaderboards are segmented through RLS
- Pub pages use Places data where keys are configured
- Optional server-side Google key is supported for richer details
- Dedicated install banner component with logic for Chrome install prompt and iOS βAdd to Home Screenβ
- Stable, fixed layout with safe-area handling
- React Router SSR for fast initial render
- Image pipeline via
sharp(server processing where needed) - SEO meta per route via
seoMetaForRoute
- Multi-language UI using the appβs i18n context
- Localized routes/links + localized date/time rendering where applicable
- Responsive layout across Feed/Wall/Pubs/Leaderboards
- Mobile dock icons are rendered with SVG masks and
currentColorfor consistent active states
- Supabase + RLS by default
- Secrets should live in
.env.localand not be exposed viaVITE_client env - Roboflow uses
ROBOFLOW_PRIVATE_API_KEYon the server (preferred)
npm run build
npm run startDeploy with any Node-capable SSR environment that can run the built server output. Ensure your host has secrets configured (Supabase, Resend, Roboflow private key, etc.).
- Schema and policies live under
supabase/migrations/ - Feed, wall, and leaderboards use Supabase functions/RPC where applicable
- Friend invites are sent via server-side routes that call Resend with branded templates
Contributions are welcome. If you add features or routes:
- Run
npm run lint - Ensure migrations are forward-only (add new files rather than renaming history)
- Open a PR and describe the change + any DB impacts
No LICENSE file was found in this repository. If usage terms exist, they are expected to be defined by the project owner.
- Jonathan β Lead Developer β Rixouu
- React Router team for the SSR + data primitives
- Supabase for RLS-powered security
- Tailwind CSS for the UI foundation
- Roboflow for scoring workflows
Built with β€οΈ for clean pours and friendly competition.