Tournament organisers
Gambit
Pairings you can defend, on a bracket that outlives the event.
Pairings and standings for chess clubs, card-game shops and fighting-game events. Swiss, round robin and double elimination with the tiebreaks always on screen, a public bracket that needs no account, and a player record that follows a person from event to event.
- Next.js 16
- TypeScript
- Supabase
- PostgreSQL
- Row-level security
- React PDF
- Generated share cards
- Magic-link sign-in
- Pairings
- Reproducible
- Tiebreaks
- Always visible
- The bracket
- Public link
same seed, same draw, every time
as columns, configured per event
no account, outlives the event
What was going wrong
Club pairings were done in a program written in 1998 that runs on one laptop, or by hand on paper. Standings were photographed and posted to a group chat. When a player asked why they had been paired down, the honest answer was that nobody could reconstruct it, and that is the answer that makes people stop entering.
What we built
Treat the pairing engine as the product and everything else as an interface over it. It is pure code with no database, no clock and no unseeded randomness, so a draw can be reproduced from the published seed by anyone who doubts it. Tiebreaks are configured per event and always shown as columns. The public bracket is a link with no session behind it, which is what lets it outlive the day.
What it looks like
Every screen shown on a desktop and on a phone, because half of these are used standing up.
Running the event
The organiser's screen between rounds — enter results, generate the next draw, publish when the room is ready.
/organise/[id]
Desktop capture
1440 × 900
Mobile capture
390 × 844
The public bracket
What gets pasted into the group chat. No account, no session, and it still resolves next year.
/e/[slug]
Desktop capture
1440 × 900
Mobile capture
390 × 844
The projector
Pairings, standings and the round clock, sized to be read from the back of a hall.
/e/[slug]/display
Desktop capture
1440 × 900
Mobile capture
390 × 844
A player's record
Rating across events, lifetime head-to-head, and the achievements that came off real results.
/me
Desktop capture
1440 × 900
Mobile capture
390 × 844
What it does
Swiss, round robin, double elimination
Three formats from one entry list, with byes, drops and late entries handled rather than worked around.
Tiebreaks always shown
Configured per event and displayed as columns. Chess Buchholz is not a card game's opponent match-win percentage.
Public bracket
A link players and parents can open with no account, and which still works a year later.
Projector view
Pairings, standings and a round clock sized for the far wall of a hall.
Cross-table as a PDF
The bulletin an organiser posts afterwards, generated rather than assembled.
A rating that travels
Lifetime record and head-to-head against anyone you have played, across every event you have entered.
Who reaches for it, and when
A club running a monthly Swiss
Types twenty names off a clipboard, pairs five rounds, and posts a cross-table by nine o'clock.
A card shop on a Saturday
Sixty-four players, published rounds only, and a bracket the room is watching on their own phones.
The player who got paired down
Opens the standings, sees the tiebreak columns, and gets an answer instead of a shrug.
What makes this one different
Every draw is a function of the event's seed
Same entries, same seed, same pairings — so "why was I paired against him again?" is answerable with a document rather than a shrug. There is no unseeded randomness anywhere in the draw.
The Swiss pairer is minimum-cost matching
Greedy score-group pairing cannot satisfy no-rematch, group cohesion and colour balance at once; three simpler attempts proved it before this one shipped. The engine is pure, has no database access at all, and is covered by tests.
A bye is not a win
Counting a bye as a defeated opponent inflates everyone else's Buchholz, and is the commonest way a cross-table stops being defensible. Withdrawing keeps a player's results for the same reason.
Unpublished rounds are genuinely invisible
The public view filters unpublished rounds *and* recomputes the standings from what is left. Filtering the rounds but keeping the standings is how results leak before the organiser is ready.
Built with
What each piece is doing here — the people who read this section are usually the ones who would have to maintain it after us.
Next.js 16
The application framework. Pages render on the server by default.
TypeScript
Strict mode everywhere. A field renamed in one place fails to compile in the others.
Supabase
Postgres, authentication, file storage and realtime in one managed service.
PostgreSQL
The database. Constraints and permissions live here, not in the interface.
Row-level security
Who can read which row is decided by Postgres. A UI bug cannot leak data.
React PDF
Documents generated on the server, identical every time they are made.
Generated share cards
Per-record images drawn on the server for anything that gets pasted into a chat.
Magic-link sign-in
An emailed link instead of a password nobody can remember.
Want something like Gambit?
It does not have to be your industry — the underlying problem is nearly always the same. Tell us how the work moves through your business and we will tell you what it would take.
Every project is quoted on its own. Turnaround starts at two weeks.