Our position: RMU +5.5 is still live at +2.1%.
Odds cell
Two lines: the line at full contrast, the price a step down. Three of its states are absences and they must not look alike — an em dash for a market not offered, the word for one that has been pulled, and a struck-through but readable price for one that is stale.
Metric value
Three kinds of model output that must never be mistaken for each other or for a market price. The separation is carried by format rather than colour: two decimals for a model line, a percent sign for a probability, a mandatory sign for EV. The design's fourth kind, edge, is not here — for sports it is the same number as EV, so a board showing both would print one figure under two headings.
Lock
Redaction at three scales. There is no value underneath any of these — nothing is blurred or masked, the server never sent a number. The block is the width of the value it stands for, and its shape is carried by a 1px edge rather than by fill, because a faint filled bar reads as nothing at all on a phone in bad light.
Model EV on all 48 games
Ranked slate · 48 games · 9 qualifying
Expected value, edge and the ranking. Member adds the model's numbers on all 48 games and props, and orders the slate by them.
The odds, line movement and track record stay free.
Bet rating
Five discrete steps, countable at a glance in a way five stars never are. No red at any value: green and red are spoken for by value polarity, and a rating is not a polarity — a 1 is low conviction, not a loss, and a 5 that loses is still a 5.
Data table shell
The chassis every board sits in. Sorting is local and resolves within a frame, so there is no pending state to design — this component accepts no loading prop by design. Every data grid needs a horizontal scroll floor, or a flexible column with a minimum wider than the space left pushes the last column out of view with no scrollbar.
NCAA FB · Saturday 29 August
NCAA FB · anonymous
State family
Six conditions, one grammar: what is happening, when it changes, and one route to somewhere with activity. No apology, no illustration, and no retry button that reloads the same cached page. For any single league the off-season is most of the calendar, so these are core screens rather than edge cases.
Nothing qualifies on today's slate
48 games priced, none above the 2% threshold. That happens; the market is efficient more often than not.
No games match these filters
Two filters are active. Clearing the edge minimum brings back 31 games.
Prices are 14 minutes old
Last captured 18:42. Shown, but treat them as indicative rather than current.
Nothing to click — the data returns on its own.
The odds feed is unreachable
Showing nothing rather than something wrong. Scores and the record are unaffected.
Bet row · three access states
The ranked slate's row, deduped, at 44px. The information hierarchy IS the subscription pitch, so what is present and absent in each state matters more than the styling. Anonymous has nothing to rank by, so its slate is ordered by start time and says so.
Anonymous · market only, ordered by start time
Projection, win probability, EV and a rating on all 48 games
Free account · the member layout, with the scoring locked
Member · ranked, with variants and terminal states
Game row
Browse rather than recommendation, so 34px, all three markets at consensus, and nothing about what to do. The projected score column is ABSENT for anonymous rather than locked — a payload shape built by omission, seen from the component side. The last column carries an edge count before kickoff, a live score during, and a final score after.
Anonymous · market only, ordered by start time
Member · ranked, with variants and terminal states
Scoreboard entry
A card, not a row, phone first, every sport. One summary line chosen by a fixed priority ladder so no card is ever blank. The member and open cards differ by exactly one line, and it is the top one — the clearest demonstration in the system of what a subscription buys: not more data, but the model's opinion about the data everyone can see.
The spread has moved from −4.5 to −5.5 since open.
Virginia covered as a 6.5-point favourite.
Qualifying round.
Filter and sort bar
Applied in the browser over data already loaded, so there is no loading state, no submit and no spinner. The one thing it must never do is filter invisibly: a filter derived from saved preferences gets the accent border, a clear control, and a sentence saying how much is hidden.
Prop group
Hundreds of props per game, so this is volume management before it is layout. Collapsed by default, with the header answering the only question that matters when closed: is there anything in here. A group with nothing for the model to say says 'no model' rather than showing an empty scale, and a player with no posted props stays listed — a missing player reads as a gap in coverage.
Record block
The whole of the site's accountability — users cannot log their own bets here, so every claim the product makes is settled in this block or nowhere. The headline is computed FROM the rows rather than carried alongside them, because any figure a reader can add up will be added up. The losing months are drawn at the same weight as the winning ones, and the worst stretch is a headline figure: volunteering it is the cheapest credibility available.
Bets rated 4 and 5 only. Everything below 3 is excluded because we do not tell anyone to bet it.
Participant identity
Four fields per team, and each variant binds to exactly one of the three name fields. No variant truncates a longer field to fake a shorter one — a clipped long name and a real short name look different, and only one of them is right. The logo slot is never optional: a row whose label steps left because a file failed stops the column being a column.
Status pills
One form, two families, never filled — a filled green pill is a sportsbook's celebration device. Live is the only high-contrast pill on the site, and it is a fact about the game rather than about our data, which the freshness indicator owns separately.
Upgrade prompts
The two asks share a shape and differ in weight: neutral outline and a text dismissal for the account, accent border and a filled button for the subscription. Both name the specific thing the lock was covering rather than listing features — the reader has just told you exactly what they wanted.
Navigation shell and page header
One shell, three access states, differing by the right-hand end only. The vertical switcher stays visible at every state because a visitor arriving from search needs to know what else is here, and a member sees no upgrade button and nothing shouting about membership. The phone menu carries the same destinations in the same order — a reduced tab set is a second navigation to maintain and a second place for a route to go missing.
anonymous
free
member
League board
Page header — the date stepper sits here rather than in the filter bar, because changing the date is a server round trip and everything in the filter bar is not. Keeping instant and non-instant controls apart is what stops the absence of a loading state reading as a bug.
Footer — obligations and secondary routes, not a second navigation. Track record is not repeated because it sits in the header at every access state, and pricing is a section of How it works rather than a page of its own: a pricing page with one tier on it reads as a product with something missing.
Freshness
Reports an age; never animates one. No ticking counter and no pulse. Fresh is deliberately grey rather than green — it is the state of nearly every row nearly all the time, and healthy data is not an achievement.