BBSL Appspiration
A mobile-first auction app that runs free agency for a 30-team fantasy basketball league, with the app as referee so the commissioner can play.
- Client
- BBSL fantasy basketball league
- Role
- Commissioner, sole designer and developer
- Year
- 2026
- Disciplines
- Product design, Interaction design, UI design, Full-stack development

The problem
The BBSL is a 30-team salary dynasty fantasy basketball league. Every offseason its free agents are sold in an open auction. Managers nominate players and bid in $500K steps, and every bid resets a 24-hour clock. A hard $165M salary cap and a 12-player roster limit apply throughout. An opening bid of exactly $1M starts a lottery other teams can join, and the winners then spend a fixed allotment of contract lengths.
For five offseasons all of that ran by hand. Somebody had to notice when a clock expired, check that a team could afford the bid it had just posted, and remember who had already used their four-year contract. When somebody got it wrong at 1am, thirty people found out in the morning. That somebody was usually me: I’m the league’s commissioner, and I also manage a team in it.
Appspiration is the app I designed and built to take that job over. The league used it to run its 2026 free agency.
The app is the referee, so the commissioner can play.
Who it’s for
Thirty managers across US, European and Australian time zones, all adults with jobs who check their phones between other things. The auction runs as a relay: one cluster bids before bed, and the next wakes up to find they’ve been outbid. Their needs were short and concrete. Don’t let me lose a player because I wasn’t looking. Tell me what I can afford without making me do arithmetic. Let me bid from my phone in ninety seconds.
So the app is designed for a phone held in one hand, at 375px first. Touch targets are at least 44px, and the bid field is 46px. Desktop gets density, and mobile loses nothing. The commissioner tools work at phone width too, because an outage happens wherever the commissioner happens to be.
There was a second constraint, and it shaped more of the design than the screen size did. The app is built and run by one of the competitors. Every rule it enforces had to be checkable by the people it was enforced against.
The refusal is the product
Placing a bid is the easy part. The moment that decides whether thirty people trust an app built by a rival is being told no at 4am. I designed that moment first. Every refusal has the same parts, in the same order:
- A plain headline: “This bid was not placed.”
- How far over, in money: $20.0M exceeds your Maximum Bid of $18.0M by $2.0M.
- Reassurance that nothing changed. No event was written, the leader didn’t move, and no clock was touched.
- Every gate, passed or refused. The cap and the roster limit are always reported together, so a pass is proof that the check ran.
- The arithmetic, with the time it was calculated. Every amount sits on the $500K grid, so the one-decimal figures are exact and the breakdown visibly adds up.
What can I afford?
The number every manager needs is the Maximum Bid: the most they can legally offer right now. It’s cap space, minus every bid they’re currently leading, $1M for each lottery entry, and $1M reserved for each roster spot still to fill. It sits in a strip at the top of every screen, next to the roster count and the number of bids in play. Each auction page breaks it down.

The same arithmetic runs on the Your Positions screen, which is where a Discord “you’ve been outbid” mention lands. Each outbid card says in words whether it’s worth coming back. In the screenshot at the top of this page, Saddiq Bey’s next legal bid of $4.0M is within reach. Chet Holmgren’s is $21.5M, which is $3.5M over the limit, and the card says so before the manager has to work it out.
Calm on purpose
An auction app could easily feel like a casino, so I wrote down what this one would never do: no pulsing countdowns, one-tap raises, suggested amounts, “ending soon” banners or celebration animations. Every bid needs a second, deliberate confirmation. Bid volume is listed in the product spec as something not to optimise.
Every time is shown twice: as time remaining, and as a local time in the viewer’s own time zone. Amber is the only attention colour, and it marks outbids and refusals and nothing else. Colour never carries state alone, and the acceptance test was that a greyscale screenshot of any screen had to stay fully readable.
On the Bid Board, each state has its own colour and shape: amber for Outbid, a pale green kept apart from the brand mint for Leading, and a periwinkle bar for the lottery. The other goal was fitting as many auctions as possible on a phone screen. The cards are flat, with no padding spent on shadows, and each fits its facts into four tight rows. Closed auctions collapse to a single row.

The visual identity is a joke told with a straight face. The app dresses as a sustainable-banking startup, after Aspiration, the company at the centre of the alleged Clippers salary-cap circumvention, while being built so that nobody can get round the cap. The tone follows: refusals read the way a bank declines a transfer, and there are no exclamation marks anywhere in the app.
A lottery anyone can check
When a player opens at exactly $1M, any team can join at the same price, and the winner is drawn when the clock runs out. A draw run by a competing commissioner needs proof that it’s fair. The app uses commit-reveal. When the lottery starts, it publishes a hash of a secret seed. At the draw it reveals the seed, and the winner follows from that seed and the entry order. A public page explains how to re-run any draw by hand.

Building it
Appspiration is a SvelteKit app on Supabase’s Postgres, running on free tiers with no budget. Every action is an event in an append-only log, so the audit trail is the system’s own record, not a report written afterwards. Writes go through one at a time behind a single lock. Locking each auction separately wouldn’t be enough, because two co-managers bidding on different players at once could jointly break their team’s cap. The rules live in a pure core with no clock or database access, and the worked examples in the product spec run as tests. The suite has 4,335 of them.
Discord handles sign-in and notifications, because that’s where the league already talks. Fantrax, the league’s host, has no write API, so the app never writes to it. Every 30 minutes a read-only check flags any roster that has drifted from Fantrax, and the commissioner confirms each correction.
I built it on my own, about four weeks from the first spec to the live auction. I used an AI-assisted, spec-first workflow: a product spec, a set of numbered architecture decisions and a UX spec came first, then small stories with layered review.
In use
Before the league saw it, the app ran two pilots with seven league moderators, then a third with all 30 managers. Eight of the nine bids in the first two pilots came from phones or tablets. The live auction opened in mid-September 2026, and real use found what the spec had missed. One was Brooklyn winning a player it only had room for because a starter sat in an injury reserve slot against league rules, which led to a commissioner tool for reversing a closed auction. Another was a minor-league rule that wrongly let teams bid past their cap on eligible players. It was fixed mid-auction, and no settled result needed repair.
Reflection
Designing the refusal first changed everything after it. Once the hardest message had a fixed shape, every other screen inherited its habits: show the arithmetic, name the cause before the effect, and never leave a manager guessing whether something happened. The other lesson came from the live auction. The spec went through eleven revisions, several of them while managers were bidding. An append-only log made those changes safe, because correcting the past only ever meant adding to it.
Screenshots are from a local copy of the app seeded with simulated auction activity. Apart from mine, the manager names are invented.