Leaderboard multiplayer
Vote for pizza toppings, add your own or remove one, and open the page in a second window to watch both lists reorder at once. view(..., viewTransition: true) animates each update, every row has its own view-transition-name, and #[Broadcast(Scope::ROUTE)] sends each change to every tab.
view() makes Datastar apply every update render inside document.startViewTransition(). The render on page load and on SSE connect has none.lb-{id}, lets the browser match each row before and after the update and move it to its new place. Rows without a partner slide in or fade out.sort is a TAB #[Signal], so your order stays yours while the votes stay shared. Switching it animates too.withBroadcastThrottle() for this route's scope, set in the site's config, lets each transition finish. While one runs, its overlay takes the clicks.withWorkerNum().- 1 Mozzarella 14 votes
- 2 Basil 11 votes
- 3 Mushrooms 9 votes
- 4 Pepperoni 8 votes
- 5 Olives 6 votes
- 6 Pineapple 5 votes
- 7 Jalapenos 3 votes
- 8 Anchovies 2 votes
Signals
sort
string tab scope initial: votes The order this tab shows: votes, name or newest.
draft
string tab scope initial: empty string The topping you are typing. Private to this tab.
Actions
vote
Adds a vote to the topping in ?id= and broadcasts the list.
add
Adds the draft as a new topping, up to 12 toppings of at most 24 characters, and broadcasts.
remove
Removes the topping in ?id= and broadcasts.
reset
Puts back the eight starting toppings and their votes, and broadcasts.
sortBy
Sets this tab's sort from ?by= and renders this tab only.
Views
leaderboard.html.twig
The ranked list, with a view-transition-name on each row. view(..., viewTransition: true) animates every update.