About

Nostragoalus is an open-source football prediction game: call the scores, earn points for how close you get, and out-foresee your friends.

Built with AI

This entire application - code, design, translations and even the artwork pipeline - was built in collaboration with Claude (Anthropic), with a human football fan directing every decision. The AI wrote the code; the humans own the opinions.

Read the source

Client-code integrity

Chat and direct messages are end-to-end encrypted in your browser, so their security rests on the JavaScript this site serves you. The fingerprint below is the SHA-256 of the client code bundle you were actually delivered. Compare it against the value published for this release: if a build ever swaps the crypto code, the fingerprints stop matching. A server can still ship altered code on the very first load - this makes a later divergence detectable, not impossible.

No integrity fingerprint is available for this build.

Android app

Nostragoalus is also a native Android app: the same predictions, leagues and encrypted chat, built from this repository. It is not on Google Play, so you install the APK yourself.

Download the APK
Version
1.0.2
Size
89.8 MB
Published
2026-09-13
SHA-256
893b9e00 a2fefb27 58e64214 7e6ff043 7e3cd7d5 239608d0 bee0e806 aba28d62

Android asks permission to install from your browser the first time. Check the fingerprint above against the file you downloaded before you install it.

Tech stack

The open-source software this app stands on. Every card links to the project; the badge links to its license.

Frontend

NuxtMITThe intuitive Vue framework
Vue 3MITThe progressive JavaScript framework
TypeScriptApache-2.0JavaScript with syntax for types
PrimeVueMITThe most complete UI component suite for Vue
UnoCSSMITThe instant on-demand atomic CSS engine
TanStack QueryMITPowerful asynchronous state management
TanStack HotkeysMITKeyboard shortcut management
Motion for VueMITA modern animation library for Vue
VueUseMITCollection of essential Vue composition utilities
Nuxt I18nMITInternationalization for Nuxt
LeafletBSD-2-ClauseAn open-source library for mobile-friendly interactive maps
markedMITA markdown parser and compiler built for speed
DOMPurifyApache-2.0 OR MPL-2.0A DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG
libsodium.jsISCThe sodium crypto library compiled to WebAssembly and pure JavaScript
Vite PWAMITZero-config PWA framework-agnostic plugin for Vite
web-pushMPL-2.0Web Push library for Node.js

Backend

Nitro / h3MITThe next-generation server toolkit
Drizzle ORMApache-2.0A TypeScript ORM that feels like SQL
drizzle-zodApache-2.0Generate Zod schemas from Drizzle tables
PostgreSQLPostgreSQLThe world's most advanced open-source relational database
node-postgresMITPostgreSQL client for Node.js
better-authMITComprehensive authentication framework for TypeScript
NodemailerMIT-0Send emails from Node.js
node-qrcodeMITQR code generator for JavaScript
CronerMITTrigger functions and evaluate cron expressions, dependency-free
SatoriMPL-2.0Enlightened library to convert HTML and CSS to SVG
resvg-jsMPL-2.0A high-performance SVG renderer and toolkit, powered by Rust
aws4fetchMITA compact AWS request signer (SigV4) for fetch
RustFSApache-2.0A high-performance, S3-compatible distributed object storage

Mobile app

FlutterBSD-3-ClauseBuild apps for any screen
DartBSD-3-ClauseA client-optimized language for fast apps on any platform
BarlowOFL-1.1A grotesk type family, and the app's scoreboard face
libsodiumISCA modern, portable, easy-to-use crypto library

Tooling & infrastructure

Node.jsMITJavaScript runtime built on Chrome's V8 engine
pnpmMITFast, disk-space-efficient package manager
ViteMITNext-generation frontend tooling
VitestMITA Vite-native test framework
DockerApache-2.0Build, ship, and run applications in containers
maildevMITSMTP server and web UI for testing emails in development
miseMITDev tools, env vars, and task runner
PGliteApache-2.0A lightweight WASM Postgres build
BunMITA fast all-in-one JavaScript runtime

Data sources

Match data comes from publicly accessible FIFA and UEFA endpoints (unofficial, read-only, politely rate-limited). Map tiles by OpenStreetMap contributors. This project is not affiliated with or endorsed by FIFA or UEFA.

Changelog

5.1.02026-09-17
Added
  • Tournaments played as one table can be added, the Six Nations first among them. Until now a competition had to be split into pools: the app worked out which round a match belonged to from its pool letter, and a single table has none, so every fixture was quietly dropped and the tournament could not be added at all. A round is now read from the fixtures themselves - it runs until every team has played once.
Fixed
  • Predicting a rugby score no longer asks whether you meant it. Any realistic rugby scoreline was being flagged as wildly high and needed confirming before it would save, because the check was measured in goals: seven is a rout in football and a single converted try in rugby.
  • Rugby competitions can be read from World Rugby again. World Rugby moved to new identifiers and started refusing the old ones, which took fixtures, squads, the bracket and the team sheets with it for every tournament added before the change, and made six of the nine World Cups on offer impossible to add. Existing tournaments repair themselves.
5.0.22026-09-13
Fixed
  • Admin: the competitions screen uses the same controls as the rest of the app, and the button that lists what a provider carries now looks like one. Its dropdowns were plain browser selects, so the screen looked borrowed from another site and the next step was easy to miss.
  • Admin: FIFA and UEFA can be picked as a provider again. Neither publishes a list of what it carries, and the screen only offered providers that do - even though the check and the save have always accepted any of them. Paste the competition's id instead, and the same dry run decides.
  • Admin: listing a provider's competitions is noticeably faster, and a rate limit is now actually enforced when several requests are in flight rather than only one. ESPN names one league per request and carries over two hundred, which took twenty seconds behind a button with nothing to show, so it read as broken.
  • Admin: a provider's whole archive is no longer a single menu. World Rugby carries over two hundred events going back to 2019: recent seasons are shown by default with the rest a click away, and the list can be typed into to search it.
  • Admin: changing the provider or the sub-feed clears the competitions listed for the previous one. The old list stayed on screen, so a World Rugby event could be checked as though it were an ESPN league.
5.0.12026-09-13
Fixed
  • A rugby match no longer shows the two sides' football history. Head-to-head and Form read FIFA's archive of international football, and the three-letter country codes are the same in both sports - so a rugby quarter-final between France and South Africa was illustrated with their football meetings, goals and all. Those panels are football-only now.
  • Cards are reported in a rugby match. They were counted in the summary but never listed, so the timeline showed no bookings at all - including a red card, which is usually the story of the match.
  • The Players tab of a rugby match lists everyone who scored, not only try scorers. A fly-half who kicked all of his side's points was missing from it entirely. Each player shows tries and points, and the side's leading points scorer takes the place of a top assister - rugby does not record an assist.
  • A rugby scorer who was not in the tournament squad list appeared as a blank line with a score beside it. Those players are now looked up one by one, and the matches already stored are read again so their names appear.
  • A rugby timeline reads like rugby: conversions, penalties and drop goals each get their own line instead of being folded into the try or dropped, and a score is marked with a rugby ball rather than a football.
5.0.02026-09-13
Added
  • Rugby matches carry their detail: try scorers with the minute, cards, substitutions, possession, attendance and the ground, plus a play-by-play that runs the score through every conversion and penalty. The try-scorer board fills itself from that, and picking a best scorer now has a squad to pick from - 33 players and the head coach per team. Team sheets are there too: the 23 shirts, who is starting, who is on the bench, and the captain. And the Stats view shows two rugby boards rather than one - most tries, and most points - because the leading try scorer and the leading points scorer are rarely the same person.
  • A rugby champion pick is priced off the World Rugby ranking, not FIFA's. Backing one of the top four pays the least and a side outside the top twenty pays the most, so picking South Africa is worth what picking South Africa is worth.
  • The crystal ball holds a rugby ball while you are in a rugby tournament. An unlocked skin still wins - that one you went looking for.
  • Rugby is scored on the margin, not the exact scoreline. Nobody calls 27-24 on judgement, so a rugby tournament now rewards getting the winning margin into the right band - within a converted try (1-7), 8-14, or 15 and up - and the exact score becomes a lucky bonus rather than the main prize. The rarity bonus moves with it: it counts how much of the field read the result, instead of handing everyone who called the winner the top tier. A tournament picks all of this up when it is added, and an admin can retune every number.
  • Admin: rugby tournaments can be added, from World Rugby's own match feed rather than a third party. Pools, the knockout rounds and the bronze final all come through correctly, and the 2027 Rugby World Cup - including its new round of 16 - already reads in full.
4.12.12026-09-12
Fixed
  • Signing up from a league invite and confirming your email now lands you back on that invite, instead of dropping you on the matches page. The new-account league prompt no longer opens on top of an invite either, which used to pull you away to the matches page the moment you dismissed it.
4.12.02026-09-12
Added
  • Admin: a Competitions section that sets which tournament the site opens on. It used to be fixed at build time, so changing it meant a new release. Only an active tournament can be picked, and archiving the chosen one hands the role to the newest active season rather than leaving dead links behind.
  • Admin: tournaments can be added from the admin page instead of in code. Pick a data source, browse what it carries, and the app checks the real fixtures before anything is saved - it will not accept a tournament whose matches it would silently drop, and says why in plain language. Adding a supported tournament is no longer a release.
  • Admin: tournaments can be archived and restored. Archiving hides one from the switcher while keeping every prediction, trophy, league and chat room attached to it.
4.11.12026-09-12
Fixed
  • Android app: an @-mention in chat showed a raw user id instead of the person's name, and mentions sent from the app were written as a plain name, so the website could not show them as mentions at all. Both clients now write and read the same thing, and a mention follows a rename.
  • A league member with no display name set corrupted every @-mention written in that league's chat on the website.
4.11.02026-09-12
Added
  • Android app: calls now appear in the conversation, the way they do on the website - who started one and how long it ran, and a missed call you would otherwise never have seen.
  • Android app: a call button in league chat, so a league call can be started from the room itself.
Changed
  • Android app: it now has its own version number, separate from the website's. A release of one no longer implies a release of the other, so what the changelog says about the app is about the app.
  • Android app: the call bar at the bottom of a conversation now appears only while a call is actually running. It used to sit there permanently, which in a direct message meant two competing call buttons on one screen.
Fixed
  • Android app: a password manager now fills the email and the password together on the sign-in screen instead of only the field you tapped, and offers to save a new one.
  • Android app: messages were listed newest first. They read in the order they were sent again, with anything still sending at the bottom.
  • Android app: moving between screens no longer flashes dark before the new screen settles.
4.10.02026-09-11
Changed
  • The Android app download now comes from a CDN-backed storage bucket at a versioned address, so it downloads faster and can be cached properly: fetching it a second time, or from somewhere someone else already fetched it, no longer re-sends the whole file. The old address still works and points at the current build.
4.9.02026-09-08
Added
  • Android app: a "This build" section in Settings that shows which version you are running and, when you press the button, asks the site whether a newer one is published. It never checks on its own.
  • Android app: if the server has stopped supporting the version you have installed, the app now says so plainly and links you to the current build, instead of failing screen by screen with confusing errors.
4.8.02026-09-07
Added
  • Android app: a Chat tab. Your league chats and your direct messages are now one tap from anywhere in the app, with a badge for unread messages, instead of being buried inside a league.
  • Android app: a league picker beside the competition switcher. Pick one of your leagues to see the ranking and the crowd consensus for just that league; the choice is remembered per competition. Crowd bonus points still come from everyone's predictions, and the card says so.
4.7.42026-09-07
Fixed
  • Signing in with a company account (single sign-on) on Android was refused before it started in 4.7.3. It works again.
4.7.32026-09-07
Security
  • Hardened the step that hands a mobile single sign-on back to the Android app: it now only passes along a session that was just created by that sign-in, instead of whatever session the browser already had open.
Fixed
  • Signing in with your company account (single sign-on) now completes in the Android app. It failed at the last step every time, because the sign-in was started from the app rather than from the browser that finished it.
  • Share images render again. The text-shaping component was missing from the production build, so every server start logged a crash and share cards could not be drawn.
4.7.22026-09-06
Fixed
  • The Android app downloaded from the site could not reach the server at all: it was built pointing at a development address, so every screen failed and signing in reported a wrong password no matter what you typed. It now talks to the real site, and its version and size match the build the page advertises.
  • Signing in with your company account (single sign-on) works in the Android app. It always failed at the last step before.
  • The Android app no longer claims your password is wrong when it simply cannot reach the server. It says so instead.
4.7.12026-09-06
Fixed
  • Dismissing a notification now sticks. The "your champion won" notification (and its Golden Boot counterpart) reappeared in the bell within minutes of being deleted, every time, because the background job that awards those bonuses kept re-creating it.
  • The Android download panel on the about page lines its version, size and publication date up in a column instead of staggering them.
4.7.02026-09-06
Added
  • The Android app can now be downloaded from the site. The about page lists the published build with its version, size and fingerprint so you can check the file before installing it, and every public page links to it.
Changed
  • The mobile app has a new design, "Floodlit": a night-match look with the scores, points and ranks set in a scoreboard face, lists on chalk-lined panels instead of card stacks, a live rail on matches in play, and a coherent light mode. Every screen follows it, from sign-in to chat.
4.6.02026-09-03
Added
  • Foundations for a Nostragoalus mobile app, still in development. The server can now sign a native app in over a secure app link, single sign-on included, without a password or session ever appearing in a web address, and it serves the extra match and conversation details the app displays. The website itself is unchanged.
Fixed
  • The match play-by-play commentary no longer prints a raw placeholder in place of a match period it does not recognise.
4.5.12026-09-01
Security
  • Requests to the sign-in system can no longer dodge its guards with a specially crafted web address. The trick got past the checks that keep identity-provider and user-provisioning management admin-only, and past the password confirmation required before a new passkey is added to your account.
  • A profile picture address can no longer point the server at a destination of the sender's choosing. The one identity-provider host whose pictures we re-fetch is now matched exactly, instead of being looked for anywhere inside the address.
  • Updated the site's building blocks. This closes several reported weaknesses, the most serious of which could let a page served to one signed-in person carry another person's data, let access rules be skipped for addresses typed with unusual capitalisation, or let mail be sent with attachments it should have refused.
4.5.02026-08-26
Added
  • League owners and moderators can export their prize winners: an "Export winners (CSV)" button next to "Edit prizes" downloads a spreadsheet of who currently holds each prize, with their email address, so the prizes can actually be handed over. Only owners and moderators of that league can do it, and a player who has hidden their profile stays anonymous in the file.
Changed
  • Joining a league that has members but no owner no longer makes you its owner. That only happens for a league nobody has joined yet; an existing league that lost its owner now stays ownerless until an admin hands the role to one of its members.
4.4.52026-07-21
Fixed
  • The site no longer grinds to a halt for hours. A slow memory leak could leave the server answering every page so slowly that it timed out, without ever crashing, so nothing restarted it. It now restarts itself within seconds instead.
4.4.42026-07-20
Fixed
  • A single sign-on left waiting too long, or a login link reopened from an old tab, no longer dumps you on a bare error page: you land back on the login form with a "sign-in failed" message and can try again.
4.4.32026-07-20
Fixed
  • The chat no longer disappears when you open the Leagues page: the dock keeps the conversation you were reading, and a pinned chat now stays pinned everywhere, including on a league's own page.
  • The chat header fits again: the collapse and pop-out buttons were being cut off on the right edge. The pinned league's name no longer sits next to the bookmark; the league switcher next to it still names the room you are in.
4.4.22026-07-20
Fixed
  • A chat message no longer vanishes while it is being sent: it appears right away marked "Sending...", then turns into the real message once it lands. A send that fails now stays in the conversation marked "Not sent" with Retry and Discard instead of disappearing, so the text is never lost. Same in direct messages and thread replies.
4.4.12026-07-20
Fixed
  • Tournament Wrapped no longer ends your climb on the wrong rank. The standings chart replayed prediction points only, so a champion or best-scorer bonus was missing from the finish line and the last slide could announce a worse rank than the leaderboard and the share card. The chart now closes on your real final standing.
  • Share cards render stylized display names instead of empty boxes. Fancy script letters, dingbats, emoji and letters borrowed from any of ~140 writing systems now pull the matching font, where before only a handful of major scripts did.
  • The cross on the Tournament Wrapped close button is centred again.
4.4.02026-07-20
Added
  • Pin the chat to one conversation: the bookmark in the chat header holds it on the league and thread you are reading, so switching the league filter on the rankings - or focusing another match in multi-view - no longer drags the chat along. The pin follows you across competitions, names the pinned league in the header, and stays put until you unpin it or leave that league.
Changed
  • The Treble achievement now spells out what it wants: three of the four end-of-tournament trophies (Grand Champion, Group Stage Guru, Bracket Master, Madame Irma) in the same competition, and achievement badges do not count.
Fixed
  • Once a competition is over, the fixtures list lands on the final again instead of staying at the top: with nothing live or upcoming left, the auto-scroll now targets the last match.
4.3.32026-07-19
Changed
  • Fixtures now open with the finished rounds folded away: any round whose matches have all been played starts collapsed, so the list lands on what is still to come. The final is never folded, and a link straight to a played match still opens its round.
Fixed
  • The Stats tab no longer shows an empty "Top scorers" board while "Top assists" is full. FIFA publishes the two rankings separately and can lag one behind the other after a matchday; the app now ignores a scorers feed that carries no goals yet and falls back to its own goal data, so both boards stay populated.
4.3.22026-07-19
Changed
  • The production Docker image now runs on a Debian slim glibc base as a non-root user. The Sofascore odds/line-ups TLS helper runs on native glibc instead of the previous Alpine musl compatibility shim, removing a class of runtime failure, and the image now carries only the running CPU architecture's copy of that helper instead of every platform's.
Fixed
  • The line-ups on a match page now keep refreshing on their own until the match is over. They used to stop the moment the first XI was published, so a later formation correction or substitution from the feed only showed after a manual page reload.
4.3.12026-07-16
Added
  • Hovering a scheduled tie whose two sides are already known (a final waiting to be played) now traces both teams' roads back through the draw, one in the team colour of each side, so you can see how the two finalists got there before a ball is kicked.
Changed
  • The bracket roads now grow out of the tie you hover, fanning out toward the final and back toward the group stage at the same time, and lie exactly over the connector lines instead of running alongside them. Each leg draws at a steady pace, so both teams' roads stay in step however far apart their journeys are.
  • The final and the third-place play-off are now stacked with the final just above the semi-finals and the bronze just below, so the roads into them no longer cross in the middle of the bracket.
4.3.02026-07-16
Added
  • Hover any decided tie in the bracket to watch both teams' roads through the tournament draw themselves in: the winner's in green, the loser's in red, with the two names in the tie tinted to match.
4.2.52026-07-16
Fixed
  • The bracket no longer crowns a champion before the final is played. Once both semi-finals were done, the trophy named the team that had reached the final from the second semi, spoiling a final that had not kicked off. The champion is now read from the final's own result.
  • The third-place play-off now sits under the final in the centre of the bracket, instead of in its own column beside it. This also re-centres the final and removes the connector lines that ran to an empty column. The play-off went missing because the bracket feed names it "Bronze final", a spelling the app did not recognise as the third-place tie.
4.2.42026-07-16
Fixed
  • Set and Forget is no longer withheld from players who never touched their picks. Saving a pick could record it twice in the tamper-evidence ledger, which read as an edit. The double-save is fixed, and the badge now compares the scorelines you committed instead of counting ledger entries, so a pick recorded twice with the same score counts as untouched again. Affected players are awarded the badge when their competition next scores a match.
4.2.32026-07-14
Fixed
  • The third-place play-off no longer shows "Final counts double" and no longer doubles points: a match's stage is now frozen when first synced, so a provider relabelling the tie can't drift it into being treated as the final.
4.2.22026-07-14
Fixed
  • The built production app image is now tagged with the app version (nostragoalus-app:<x.y.z>) via mise run deploy, instead of always :local.
  • Fixed player avatars sitting lower than their teammates on the match lineup pitch when a player's name wrapped onto two lines.
4.2.12026-07-14
Fixed
  • The About page's client-code integrity fingerprint now shows on production builds instead of "No integrity fingerprint is available for this build." The fingerprint file was generated at build time but never served, so the check always fell back.
4.2.02026-07-14
Added
  • Voice calls: calls now have sound - an incoming call plays a ring tone and dialing out plays a ringback, until the call is answered, declined or cancelled.
Changed
  • Voice calls: the "You're muted" reminder now flashes right above the call bar instead of popping a toast in a corner of the screen.
Fixed
  • Voice calls: the speaking highlight on a participant's name no longer flickers between words - it holds through the natural pauses of a sentence.
4.1.12026-07-13
Changed
  • Voice calls: your microphone meter is now a smooth live waveform instead of three coarse threshold bars.
Fixed
  • Voice calls: the mute button no longer vanishes once muted - it switched to an icon glyph our icon set does not have, leaving an invisible button and no way to unmute. Muted now shows the microphone with a strike through it.
  • Voice calls: switching browser tabs and coming back no longer hangs up the call - the app force-reconnected its live socket on every return to the foreground, which the server treats as leaving the call. It now only reconnects when the socket is actually dead.
  • Voice calls: a speaking participant's name no longer turns bold (which nudged the whole call bar wider on every utterance) - it lights up in color only.
4.1.02026-07-13
Added
  • Voice calls: the in-call bar now shows who is in the call, with the speaker's name lighting up while they talk, plus a live 3-bar meter for your own microphone. The league "join voice" tooltip also names who is already in.
  • Voice calls: an audio settings dialog in the call bar to pick your microphone and speaker (remembered across calls) and toggle noise suppression. Echo cancellation and auto gain are now always on.
  • Voice calls: talking while muted pops a gentle "You're muted" reminder.
  • Chat: call history lines in the conversation - "call started / ended (with duration) / missed" appear in the timeline for both DMs and league rooms, live as they happen.
  • Voice calls: a connection-quality indicator in the call bar - an amber/red icon when the link degrades, and a "Reconnecting…" notice while a dropped link is being re-established.
Fixed
  • Voice calls: hanging up a DM call now ends it for the other side too - previously they were left stuck "in call" with a running timer and an unresponsive mute button until a reload.
  • Voice calls: a dropped peer connection (switching tabs or networks, a flaky hop) now automatically re-establishes with an ICE restart instead of going silent until redial.
  • Voice calls: the call timer (and chat call-line durations) now roll into hours - "1:07:39" instead of "67:39".
  • Head-to-head: the "Biggest divergences" list now ranks your shared picks by their points gap, instead of only showing matches where you predicted different results. A match where you both called the same winner but one of you landed the exact score with a joker (a 24-point pick against a 1-point one) is exactly the kind of swing the list was hiding.
  • Head-to-head: the two players' names and avatars now link back to their profile pages.
4.0.12026-07-12
Operator notes
  • The app service now disables core dumps (ulimits: core: 0). A hard Node crash previously dumped a multi-gigabyte core file into the container's writable layer, which could fill the Docker data disk - once taking the database down when /var filled up. A crash now just restarts instead of leaving a huge core behind.
4.0.02026-07-12
Operator notes
  • The repository is now a pnpm monorepo. The web app and its full compose stack (Dockerfile, compose*.yaml, .env*) moved under apps/web-nuxt/, with the Docker build context at the repo root. mise run up/dev/preview and mise run release already point at the new paths; a deploy that invokes docker directly must target apps/web-nuxt/compose.yaml (or -f apps/web-nuxt/Dockerfile with context .). No application or database change - the built app is identical.
3.0.32026-07-12
Changed
  • The API documentation (OpenAPI, served at /_docs) now derives every endpoint's request and response schema from the server's own validation, so the published contract always matches exactly what each endpoint accepts and returns.
3.0.22026-07-12
Operator notes
  • Voice-chat coturn: NUXT_TURN_EXTERNAL_IP may be left blank to auto-resolve from NUXT_TURN_HOST at container start, so a dyndns hostname stays the single source of truth for the relay's public IP. NUXT_TURN_HOST must be a name the browser resolves directly, not a CDN-proxied one (Cloudflare can't carry TURN/UDP).
3.0.12026-07-12
Operator notes
  • Voice-chat coturn: the relay now advertises the host's public IP when NUXT_TURN_EXTERNAL_IP is set (needed when the box is behind NAT, so external browsers get a reachable relay address), and its ports are configurable (NUXT_TURN_PORT / NUXT_TURN_TLS_PORT / NUXT_TURN_MIN_PORT / NUXT_TURN_MAX_PORT) so it can coexist with another service already using 3478 or the default relay range. mise run up now brings coturn up with the stack.
3.0.02026-07-12
Added
  • Voice chat. Call someone one-to-one from a direct message, or start a group voice room in a league chat (optionally tied to a match, to watch a game together). A live "N in voice" badge shows a league call is happening, and you can invite specific members into it. Incoming calls ring you anywhere in the app; a missed call lands in your notifications (and as a push, under the new Calls category). Audio is peer-to-peer and end-to-end encrypted - it never passes through the server.
Operator notes
  • Reliable calls behind strict NATs need a TURN relay: a self-hosted coturn service, shipped behind the opt-in voice compose profile (docker compose --profile voice up), configured via NUXT_TURN_SECRET / NUXT_TURN_HOST / NUXT_TURN_REALM (see .env.example). Without it the app runs STUN-only and some calls will fail to connect.
2.34.02026-07-12
Added
  • Head-to-head: compare your picks against another player. From any player's profile hit Compare to see who is ahead over the matches you have both had scored, your win-loss-tie record, how often you agreed on the score and the outcome, the lead swinging round by round, and the calls where you diverged most.
Changed
  • Personal analytics now draws your accuracy round by round as a sparkline, and adds a streak card showing your current run of correct picks and your best of the tournament.
2.33.12026-07-12
Fixed
  • A live match header no longer stays stuck on a score after VAR disallows a goal. The header now tracks the same live goal feed as the scorer list beneath it, so a struck-off goal drops from both at once instead of lingering on the header for a poll or two.
2.33.02026-07-12
Added
  • Trophy cabinet rarity: each badge now shows the share of the competition's players who hold it, per tier - so you can see how rare your haul really is.
Changed
  • Group Guru is now tiered: call every match's outcome in one, two, or three full groups for bronze, silver, gold.
  • Champion's Path now has two tiers: gold for calling the champion's every match outcome, and a new Diamond tier (above gold) for calling every one exact.
  • The "Nemesis" badge is renamed "Open Book" - a team whose exact scores you keep nailing.
Fixed
  • Opening the trophy cabinet from the Achievements menu now scrolls to it reliably, instead of being hijacked by your profile's jump-to-now scroll.
2.32.12026-07-11
Changed
  • Fergie time no longer counts a knockout added-time goal that settles a draw (a late winner or a golden goal). In a bracket a draw at 90' goes to extra time rather than standing, so that goal did not really win or lose you the tie. A stoppage goal struck from a decisive score still counts, including a late equalizer that takes a lead away.
2.32.02026-07-11
Changed
  • Fergie time now measures your real points - the rarity bonus and joker included, not just base points - and replays each match goal by goal, so a score you nailed in stoppage time and then lost to a later goal shows both the gain and the loss. The card now spells out the won / lost split behind the net and lists every match whose points moved in added time.
2.31.02026-07-11
Added
  • Three new achievements, all earned within a single tournament: Form Reader (call the outcome of one team's matches five times, for three, five or seven different teams), Champion's Path (call the outcome of every match the eventual champion plays) and Group Guru (call every outcome in one full group). "Outcome" means the right result (win, draw or loss), not the exact scoreline.
2.30.02026-07-11
Added
  • Fergie time in your personal analytics: a new card showing how many points added-time goals won or cost you, versus the score at the end of regulation. It re-scores each pick against the pre-stoppage line and highlights your biggest late gift and cruellest late twist.
2.29.02026-07-10
Added
  • Regenerate your chat recovery code: from a device where chat still works, mint a fresh code from the chat menu's danger zone. The old code stops working the moment you do.
  • Reset your chat identity: lost both your device's chat key and your recovery code? Start fresh with a brand-new key from the "Lost your recovery code?" prompt. Your direct messages and current league chats come back once each person you talk with re-verifies your new safety number; older league history that had already been re-keyed stays lost.
2.28.02026-07-10
Added
  • Connected devices: your account page now lists every device signed in to your account (device type, IP and when it was last active), with a button to sign out any single device and a "sign out all other devices" button. Handy if you sign in on a shared or lost phone.
Changed
  • Voting on the roadmap now closes once an idea is in progress or shipped: upvotes are a signal for what to build next, so they stop once building has started. Planned and community-suggested ideas stay open to votes as before.
Fixed
  • Sessions now last far longer and refresh as you use the app, fixing "logged out for no reason" - most noticeable when the app is installed to your home screen (PWA) on iPhone, where a short-lived login could be dropped when the app was closed in the background.
2.27.12026-07-10
Fixed
  • Flawless no longer unlocks from a half-played round. A knockout round has only its first match scored while the rest are still to come, and calling that one exactly used to count as a perfect round; Flawless (and Set and Forget) now wait until every match of the round has been played.
  • Badges that reflect a final standing - a perfect round, finishing the whole tournament, a podium or last-place finish - now clear themselves if that standing is undone (say a tournament is reset), instead of lingering on your cabinet forever. Streak, tally and "you called it" badges still keep your best.
  • The Ice Cold badge now shows its thumbs-down icon in the rankings, matching the trophy cabinet, instead of a stray check mark.
2.27.02026-07-09
Added
  • Five new badges. Grand Finale (call the tournament's final exactly - the bookend to First Blood). Bore Draw (call a 0-0 on the nose). Goal Rush (nail a match of five or more goals exactly). Nemesis (call the same team's matches exactly three times). Set and Forget (predict a whole round and never edit a pick).
  • Streak badges (Hot Streak, On Fire) now show your current ongoing run next to your best while the badge is still climbing, so you can see the streak you are on right now, not only your record.
Changed
  • "Opening Act" is now called "First Blood" (nailing the tournament's opening match), and the old "First Blood" (your first exact scoreline of a competition) is now "The Hunt Is On". Both badges keep their own icon.
  • Flawless (a perfect round) no longer counts the final or third-place play-off: those are one-match rounds, and calling them is already its own badge, so Flawless now means a clean sweep of a real multi-match round.
Fixed
  • The pick-timing badges (Early Bird, Night Owl, Deadline Dancer) now unlock the moment you save the qualifying pick, instead of only after the next match is scored - so predicting in the last five minutes before kickoff grants Deadline Dancer right away.
  • "My achievements" in the menu now scrolls straight to your trophy cabinet again, instead of the profile's jump-to-latest-match scroll stealing focus first.
2.26.02026-07-05
Added
  • Trophy-cabinet progress bars: every badge you have not yet maxed out now shows how close you are to its next tier - "3 / 5" toward the next rung, drawn on locked badges too, so you can see what you are chasing instead of only what you already hold.
  • Shareable profile card: a Share button on your own profile mints a link to a rich card of your standing (rank, points, exact scores, trophy and badge haul) that opens without a login, so it unfurls with a preview when you send it to friends. Works mid-tournament, not just at the end.
  • Shareable personal analytics: a Share button on your analytics page mints the same kind of link to a card of your prediction bias (accuracy, exact rate, goal lean, home-team bias), viewable without a login.
2.25.12026-07-05
Changed
  • The per-league "finish your picks" nudges no longer repeat the "N match needs a pick" banner. The panel and the per-match chips now only flag a league when it needs something the banner cannot say - a real score for a normal league, or a stake for a hard league. A plain missing pick shows once, in the banner, so the nudges stay readable when you belong to many leagues.
Fixed
  • Switching competition from the personal analytics page (and the consensus-bot and Wrapped pages) now keeps you on that same page for the new competition, instead of dropping you on its fixtures view.
  • The map now greys out the losers of a semi-final in a tournament with no third-place play-off, such as the Euro, where France and the Netherlands were left un-greyed after losing their semi-finals.
2.25.02026-07-05
Added
  • Personal analytics page: a per-competition bias report on your own predictions - how your picks landed (exact / goal-difference / outcome / miss), whether you over- or under-predict goals, your home-win and draw lean, the teams you over- and under-rate, your accuracy round by round, and your best call and biggest miss. Reachable from the nav once signed in, and unlike Tournament Wrapped it works mid-tournament.
2.24.02026-07-05
Changed
  • The per-league leaderboard now shows the same rich rows as the competition board: rank movement arrows, the champion crown, the best-scorer boot and live provisional points, instead of a plainer standings list.
2.23.12026-07-05
Fixed
  • Direct messages: a busy conversation no longer floods the notification bell with one entry per message - all direct-message notifications now collapse into a single grouped entry. Clicking it opens the conversation when there is only one, or the messages inbox when several people have written, instead of dropping you on the home page.
2.23.02026-07-05
Added
  • Client-code integrity fingerprint on the About page: the SHA-256 of the client JavaScript bundle actually served to your browser, so you can compare it against the digest published for a release and detect a silent swap of the end-to-end-encryption code.
  • Regenerate your calendar-feed link: a button in Preferences revokes every previously shared subscription URL and issues a fresh one, in case a link leaked.
  • Key transparency for chat: the verify dialog now flags a member whose encryption key does not match the public, tamper-evident key log, catching a substituted key.
Changed
  • Shared prediction and recap card links now expire after 180 days instead of lasting forever.
Security
  • Chat automatically re-keys when a member leaves a league, so their old key can no longer unlock messages sent afterwards.
  • Crowd-prediction totals are hidden for a match with only one or two picks, so a small league cannot reveal an individual's not-yet-locked prediction.
  • Two-factor codes are now single-use and cannot be replayed within their validity window.
  • Hardened account deletion's two-factor check, cross-origin request handling and response headers, and the app now refuses to start with a missing or weak secret.
2.22.12026-07-05
Fixed
  • Evil Twin: viewing a player's Evil Twin no longer renders their own picks list a second time below the twin's board.
  • Trophy cabinet: the "My achievements" menu entry and achievement notifications now scroll straight to your Trophy cabinet.
2.22.02026-07-05
Added
  • League modes: a league can now be created in one of three new modes (its mode is locked once the competition kicks off). Easy - call just the winner or a draw, and score by the odds, with longshots paying more. Hard - predict scores and stake a confidence budget across each round; a correct call pays your stake and the exact score pays double. Hardcore - last player standing, where a wrong call costs a life (the owner picks how many) and running out eliminates you, with survivors winning together. Each league shows its mode and a board that fits it (a points table, or a survival list for hardcore).
2.21.12026-07-05
Fixed
  • League description images upload reliably: images added to a league's description are now shrunk in your browser before upload, so large photos no longer fail to attach.
2.21.02026-07-05
Added
  • Direct messages: send private, one-to-one messages to another player from a new messages dock in the corner of every page. Like league chat, they are end-to-end encrypted - only you and the person you are writing to can read them, never the server. You can message anyone you share a league with, plus anyone who has chosen to be discoverable (a setting you can turn off for yourself). You get a bell notification and a push (if you have push on) when a message arrives, and can edit a message you have sent.
2.20.02026-07-05
Added
  • Guided onboarding tour for new players: a spotlight walk-through that dims the page and highlights one thing at a time - switch tournaments, predict a score, pick the champion and top scorer, climb the rankings, catch live notifications, and chat with your league. It starts itself once for a brand-new account (right after the "join a league" prompt) and can be replayed anytime from the account menu.
2.19.02026-07-05
Added
  • Build your own prize list: league owners and moderators can now attach a prize to any of eleven criteria (the original five plus Wooden Spoon, Final Sage, Group Oracle, Knockout Oracle, Sharpshooter and Goal-Difference Guru) and add or remove prizes freely, one per criterion. Each prize still shows its live leader and full ranking.
  • A description for every league: owners and moderators can write an "About this league" blurb - intro, rules, whatever - in Markdown, with headings, lists, links and uploaded images. Everyone who can see the league sees it.
Changed
  • The Team Specialist team is now set per league: instead of one team chosen site-wide in admin, each league owner or moderator picks the team their Team Specialist prize follows, right where they set the prize. Until they pick one, that prize shows as disabled.
Removed
  • Admin Competitions section: the per-competition featured-team picker is gone, replaced by the per-league setting above. The global Team Specialist trophy in the cabinet is no longer awarded (any already earned stay on show).
Upgrade notes
  • The featured team moved from the competition to each league, so leagues that had a Team Specialist prize need an owner or moderator to pick their team again to re-enable it.
2.18.22026-07-05
Fixed
  • Team Specialist rewards every exact scoreline, not just the "best": the Team Specialist prize (and its trophy) now goes to everyone who calls an exact scoreline on the featured team's matches - every exact is a win, so there are many winners, and clicking the prize shows each player's count of exact scorelines (rewards won) rather than a single points ranking.
  • Admin featured-team dropdown lines up with its column header: the "Featured team" picker in the admin Competitions section is no longer offset from its heading.
2.18.12026-07-05
Fixed
  • Wooden Spoon only counts if you actually played: the "finished dead last" badge now goes to the last-placed player who predicted at least half the tournament, so someone who gave up after a game or two is treated as a quitter rather than the loser - and no longer steals the badge from the genuine last-placer.
2.18.02026-07-05
Added
  • Two new achievements: Opening Act - call the exact scoreline of the tournament's very first match (a rare, gold-tier badge) - and, for the record books, a pair of tongue-in-cheek "bad" badges: Ice Cold (five misses in a row) and Wooden Spoon (finish dead last). The bad badges never count toward the hidden "collector" secret.
  • Clear criteria on every badge and trophy: hovering any tile in the trophy cabinet now shows exactly how it is earned - thresholds and all - whether it is locked or already yours, replacing the old vague "keep playing" hint.
Changed
  • Underdog Whisperer is now actually reachable: it unlocks when your champion pick, ranked outside the FIFA top 15 (or unranked), goes on to win - instead of the old rank-41+ bar that almost no real team could clear.
Fixed
  • Completionist waits for the final whistle: the "predicted every match" badge now unlocks only once the tournament is actually over, not partway through when you had merely kept up with every match played so far. The top-three "On the Podium" badge and the new Wooden Spoon settle the same way, at the end.
2.17.02026-07-05
Added
  • See the full ranking behind every prize: click any league prize to open its live standings for that criterion - overall, group phase, knockout, Madame Irma or the Team Specialist - showing where every member sits, settling when the competition ends. Your trophy cabinet now also lists the prizes you are chasing (greyed) next to the ones you hold, and each opens the same ranking.
  • Team Specialist featured team, set from admin: a new Competitions section in the admin panel picks, per competition, the team the Team Specialist prize follows. Until one is chosen, that prize shows as disabled instead of silently never being awarded.
2.16.32026-07-05
Fixed
  • A match's Players tab shows every scorer of its two teams: the per-team scorer and assister lists were drawn from the tournament-wide top-20 leaderboard, so a team with no top-20 scorer looked like it had scored nothing, and a big team's lower-tally scorers were dropped. The tab now lists every goal and assist its two teams recorded.
2.16.22026-07-04
Fixed
  • Backfill achievement badges earned before the feature shipped: a new admin Background-tasks job grants every milestone badge players already earned in past or finished competitions. Milestone badges otherwise only unlock on a match that newly scores, so historical results - and any competition already over - never received them.
2.16.12026-07-04
Fixed
  • @mention autocomplete everywhere you type: the member suggestion popup now also appears in thread replies and when editing a message, not just the main composer.
  • Mentions read as names in reply previews: a mention inside a replied-to or quoted message now shows the person's name (for example @Sam) instead of a raw id, in the "replying to" banner, the quoted-parent preview and the moderation reports queue.
  • Stay signed in on a flaky connection: a dropped request while roaming or on spotty mobile data no longer bounces you to the login screen when your session is still valid.
  • Live updates recover on mobile: the app now detects when a phone network has silently killed the live connection (common on 4G/5G when switching cells) and reconnects on its own, so scores, chat and notifications keep flowing without a manual reload.
2.16.02026-07-04
Added
  • The Equalizer bot: a new bot that calls a draw in every single match. Enable it (and the Crowd Bot) from a single "Bots" button on the leaderboard - each shows up as a ghost row at the rank it would have earned, with its own page listing every pick. It never affects anyone's real standing.
  • Evil Twin on player profiles: every player's profile has an "Evil Twin" toggle - their own picks with every score flipped (a draw is its own twin) - so you can see how betting against them would have gone, and where that twin would rank. Follows the same visibility as their picks.
2.15.02026-07-04
Added
  • Tournament Wrapped: once the final is decided, your personal tournament recap unlocks - a full-screen story you tap through: total points, final rank and top percent, exact-score breakdown, your best call, the one that got away, joker efficiency, your climb through the standings, crowd-defying picks, champion and Golden Boot verdicts, league-chat activity and your trophy haul. It ends on a summary card you can download or share as an image, and a banner on the leaderboard takes you there.
Fixed
  • "Watching now" no longer counts your extra tabs: opening the same match in several tabs counts you as one viewer, not one per tab. Two different people (or two guests) still count as two.
2.14.02026-07-04
Added
  • League prizes: a league owner or moderator can now set a real-world prize (a label, an optional photo, a note and a link) for each of the five winners - best overall, best in the group stage, best in the knockouts, most exact scores, and best predictor of the featured team. Every member sees the prizes on the league page with who is currently leading each one (it updates as matches are played and settles at the end), and your own cabinet shows the prizes you currently hold across your leagues.
  • Showcase your achievements in the rankings: pick up to three of your achievements and they now appear as little badges next to your name on the leaderboards.
  • Quicker access: a "My achievements" shortcut in the account menu opens your cabinet.
Changed
  • The curated "my fridge" is now the "showcase": it holds up to three of your achievements (the ones shown beside your name in the rankings).
2.13.12026-07-04
Fixed
  • Multi-view no longer forces a scrollbar: the match grid now reserves room for the footer, so the page fills the viewport exactly instead of pushing the footer just off-screen.
  • Multi-view play-by-play stays inside its tile: each cell's play-by-play now scrolls within the tile instead of stretching the cell and the whole page, and it shows in every started tile, not just the focused one.
  • Achievement and trophy notifications open your cabinet: clicking an unlocked-achievement or trophy notification now jumps to your trophy cabinet, even for competition-agnostic badges, instead of dropping you on the home page.
  • Chat league switcher: the dock's league switcher no longer lists leagues without chat, and shows just the league icon so its label can't crowd the match/league toggle in the narrow dock (full names stay in the dropdown). Enabling chat on a league now makes it appear in the switcher without a page reload.
  • Roadmap freshness: returning to the roadmap (public board or admin editor) now refetches, so a suggestion added elsewhere shows up without a manual refresh.
2.13.02026-07-02
Changed
  • Roadmap is now a board: the roadmap reads as side-by-side columns - community suggestions, planned, in progress and shipped - instead of a stacked list, so you can take in the whole pipeline at a glance (swipe sideways on a narrow screen).
2.12.02026-07-02
Added
  • Suggest and upvote roadmap features: the roadmap page now has a community suggestions section - signed in, propose a feature in a few words and it appears right away, marked "under review" until we pick it up, for everyone to upvote. You can upvote any roadmap item too, and suggestions are ranked by votes so the most-wanted ideas rise to the top.
2.11.02026-07-02
Added
  • Multi-view: watch several matches at once in a configurable grid (1, 2x1, 2x2 or 3x3). Each cell shows a live tile - score, clock, goal scorers and play-by-play - or switches to the match's video stream when one is available. The matches and layout you pick live in the URL, so a multi-view is shareable and survives a reload. The chat dock follows whichever cell you focus, and clicking a match in the chat inbox jumps to its cell instead of leaving the grid.
2.10.02026-07-02
Added
  • Trophy cabinet and "my fridge": every player now has a trophy cabinet on their profile. Win a competition-end trophy - best overall, best in the group stage, best in the knockouts, the most exact scores ("Madame Irma"), or best predictor of the featured team - and it lands there, alongside achievement badges you unlock as you play (first exact score, hot streaks, beating the crowd, and more, in bronze/silver/gold). Pin your proudest few to your "fridge" to show them off, and get a notification the moment you earn one.
2.9.12026-07-02
Fixed
  • Arabic and French interface gaps filled in: the SSO admin console is now fully translated into Arabic (it previously showed some raw text keys in the onboarding and SCIM panels), and the chat window's dock/undock control now has French labels.
2.9.02026-07-02
Added
  • Guided SSO onboarding: registering an identity provider now walks through draft -> test -> verify -> enable instead of going live the instant it is saved. You can run an automated connection test (OIDC discovery/JWKS, SAML certificate and endpoint reachability), do a real OIDC "test sign-in" that previews the exact claims the IdP returns mapped to our fields, and verify ownership of the email domain with a DNS TXT record - or bypass that as an admin. Providers can be enabled, temporarily disabled or kept as drafts; disabling blocks new sign-ins but leaves existing sessions working.
  • SCIM provisioning: identity providers can create, update and deactivate users automatically over SCIM 2.0. Generate a per-provider token from the admin console (shown once); deactivating a user from the IdP blocks their sign-in and ends their sessions while keeping their leagues, predictions and history intact, and reactivating restores access.
2.8.12026-07-02
Fixed
  • A brand-new install works before any fixtures are imported: a freshly migrated database now gets a default scoring configuration on startup, so the champion and best-scorer pickers (and match finalisation) work straight away instead of erroring with "no active scoring config" until the first fixtures import created one.
2.8.02026-07-02
Added
  • Switch league from inside the chat: the chat window now shows which league you're reading and lets you change league right there, without scrolling up to the league pill. Its list flags any league with unread chat.
  • "New messages" line in chat: opening a chat room draws a divider at the point you last read, so you can see at a glance where you left off. It stays put while you catch up and clears once everything is read.
Fixed
  • Chat no longer marks unread as read before you can read it: opening or switching into a league whose messages this device can't decrypt yet (you haven't entered your recovery key, or the key hasn't reached you) no longer clears its unread badge. It stays unread and clears only once the messages actually become readable.
2.7.02026-07-01
Added
  • Arabic, right-to-left: Nostragoalus is now available in Arabic, with the whole interface mirrored to read right-to-left - menus, layouts, and the knockout bracket included. Pick it from the language selector.
2.6.02026-06-30
Added
  • Self-hosted SSO identity providers: an internal or self-hosted SSO provider whose token endpoint sits on a private network now works - set NUXT_SSO_TRUSTED_ORIGINS to its origin and the login flow trusts it, where the built-in guard otherwise refuses a private-address provider. Public providers like Google need no change.
Fixed
  • No more duplicate goal alerts: a goal push now fires only when the live score climbs past the highest score already announced, so a VAR-disallowed goal that briefly drops the scoreline and then returns no longer pushes the same goal twice.
  • Suspended matches count as live: a match halted mid-play (suspended or interrupted) is now treated as live on the home banner, the world map and the match page, the same as on the fixtures list, instead of looking like it hasn't started.
  • Chat history no longer skips messages: loading older messages could drop or repeat a message when several were sent in the same instant, right at a page boundary. Paging now uses a stable tiebreaker so every message shows exactly once. The same fix applies to the notifications feed.
Security
  • Push subscriptions can't be hijacked: a browser's push endpoint is no longer reassigned to a different account on re-subscribe, so someone who learns your device's push endpoint can't take it over to silence your notifications or redirect them.
  • League existence stays private: trying to manage a private league you're not in now returns the same "not found" as a league that doesn't exist, instead of a "forbidden" that confirmed it was real.
  • Link previews are rate-limited: the chat link-preview fetcher now caps how often it runs per account and overall, and refuses an over-large response before reading it, shrinking its use as a fetch-amplification or scraping vector.
2.5.02026-06-30
Added
  • Never miss a pick: the fixtures page now shows how many upcoming matches still need a score before the next lockout, with a "Jump to first" button that scrolls straight to the soonest unpicked match.
  • Catch fat-finger scores: entering a wildly high scoreline (8 or more goals for one side, or 12 or more in total) now asks you to confirm before it saves, so a slip like 1-33 doesn't get locked in by mistake. You can still confirm it on purpose.
  • Odds line movement and bookmaker breakdown: the small odds display now shows how each price has moved since it opened - a marker per outcome for shortened, drifted or unchanged, with the size of the move - and you can tap it to expand the opening prices and the per-bookmaker 1X2 (when the provider supplies them). Decimal odds throughout, as before.
  • "An earlier pick of yours nailed it": on a match you have predicted, once it kicks off, the match page can tell you when a score you had picked earlier and then changed would have scored better than the one you kept - "your earlier 1-0 would have scored, the one you swapped off." While the match is live it is provisional and updates with the score, settling at full-time. If the winning earlier pick was 0-0, it gets a cheekier line. Only ever shows your own past picks, to you.
  • See who else is watching: a live match page now shows "N watching now" - a real-time count of how many people are on that match right now, updating as they come and go.
  • Live knockout bracket: the bracket now updates as matches play - scores and penalty shootouts tick in place, a LIVE marker shows on in-progress ties, and winners advance into the next round without a reload.
Fixed
  • Live penalty shootouts: the shootout score under a match now ticks up as the kicks are taken, on both the match page and the fixtures list, instead of staying blank or frozen until you reloaded. The regular-time score above it is unchanged.
  • Play-by-play shootout score: each converted spot-kick in the play-by-play now shows the running shootout tally (1-0, 1-1, 2-1...) instead of repeating the frozen regulation score on every line.
2.4.02026-06-30
Added
  • Cross-league chat inbox: the chat dock's "rooms with activity" list now gathers unread rooms from every league you belong to, not just the one you are viewing, and it survives a page reload (it no longer resets to empty when you refresh). Picking a room jumps straight to it, switching league if needed.
  • Mention alerts: being @mentioned in any league chat now sends you a web push and a header-bell notification, regardless of which league or match it was in, with a link straight to that room. Toggle it under Preferences, in the push notifications list.
  • Full timestamp on hover: hovering a chat message's send time now reveals its full date and time.
2.3.02026-06-30
Added
  • The changelog speaks your language: the in-app release notes (the "What's new" entry and the About page) now read in your selected language, falling back to English for anything not yet translated, instead of always being English.
  • Profiles open on the action: a player's profile now scrolls to where its history meets now - the latest and any live picks - instead of starting at the top of a long pick history.
2.2.12026-06-30
Fixed
  • Knockout bracket pairings: the bracket now wires each round-of-16 (and later) match to the correct feeder matches. Previously every pairing could be mismatched - it assumed the provider listed matches in match-number order, which the 2026 World Cup feed does not - so the tree showed teams meeting in the wrong slots. It now follows the official FIFA match numbers.
  • Correct top assists: the Stats tab's assist board now matches the official figures. It used to be built from the top-scorers list (so a playmaker with lots of assists but few goals never showed up) and, for an in-progress World Cup, fell back to approximate locally-derived assists. Assists are now ranked on their own and, while a World Cup is running, taken from FIFA's official live player stats.
  • Tied players share a rank: in the Stats boards, players level on goals (or assists) now show the same position number, with the next player skipping ahead (1, 2, 2, 2, 2, 6...), instead of being numbered off arbitrarily.
2.2.02026-06-28
Added
  • Player stats tab: the matches view has a new "Stats" tab next to Fixtures and Standings, showing the competition's top scorers and top assists side by side. Room to grow into team boards (best attack/defense) later.
Changed
  • Pinned the object-storage images: the bundled rustfs and minio/mc images are now pinned to specific digests, so rebuilding or re-pulling the stack can't silently swap in a different storage backend or backup client.
2.1.02026-06-28
Added
  • Install the app from inside it: when your browser offers to install Nostragoalus, a banner now surfaces it with an Install button instead of the offer being buried. Dismissing it remembers your choice so it won't keep asking.
Changed
  • Clearer update flow: the new-version banner now shows a "downloading update" step while the next build is being fetched, so the reload prompt arrives as the end of something visible rather than popping up out of nowhere. On phones it sits full-width along the bottom instead of as a cramped strip up top.
2.0.02026-06-28
Added
  • Real image storage: avatars and chat images now live in a configurable storage backend - a filesystem path, or any S3-compatible service - instead of inside the database. The Docker stack ships an S3 service (rustfs) and points the app at it by default. A one-time migration (the media:migrate-blobs task on the admin Background-tasks page) moves existing images out of the database; run it until both counts reach zero.
Changed
  • Backups now include images: mise run db-backup dumps the database and mirrors the image storage together, paired by timestamp, and mise run db-restore restores both in one go (pass --no-media to skip the image side on an fs-only deploy).
1.42.12026-06-28
Fixed
  • Link previews work again: a change in 1.42.0 made the server fetch previews in a way that many sites (Cloudflare-fronted ones like 9gag) reject, so previews silently came back blank. They now load again, and the private/internal-host protection stays in place.
1.42.02026-06-28
Added
  • Online status on avatars: a dot shows whether someone is around - green when they're active, amber when they're online but idle, nothing when offline - everywhere their avatar appears.
  • Threads: open a message's thread to reply in a focused side conversation that stays out of the main room; a "N replies" link shows when a thread has activity. This is separate from a plain reply, which still quotes the message inline in the room.
  • Mentions: type @ in the composer to pick a league member - their name shows while you type and edit, links to their profile, and keeps working even if they later rename. When someone mentions you, the minimized chat bubble shows a distinct badge (still counted in the overall unread total) and the rooms menu flags which room it's in.
  • Emoji picker: an emoji button next to the image button opens a searchable, categorized picker that drops the emoji into your message.
  • GIFs: paste a GIF link to render it inline (animated, nothing stored), or attach a GIF from your device and it stays animated.
  • Inline images: paste an image link and it renders right in the message - collapsible, nothing stored.
  • Link previews: the first link in a message unfurls into a collapsible card with the page's title, description and image.
  • Search the chat: a search button filters the loaded messages by text (including mentioned names).
  • Edit with the arrow keys: press Up in an empty message box to edit your last message; Up/Down while editing step between your messages.
  • Message length limit: messages are capped at 2000 characters, with a live counter that warns as you approach it and blocks send past it, explaining why.
Changed
  • Your own messages now align to the right of the chat, like a typical messenger, with everyone else's on the left.
  • Key verification moved into a dialog: the safety-number panel opens as a modal from the chat menu, so it no longer crowds the narrow chat dock.
  • The message box scrolls past a few lines instead of growing without bound.
  • Display-name changes show in chat live, without a refresh.
  • Sharing a pick to chat respects the open tab: if you're on the match chat, the pick lands there instead of always the league room.
Fixed
  • Invite links no longer auto-join: opening an invite asks you to confirm, with a way to decline.
  • Non-Latin usernames render in the share image instead of showing empty boxes.
  • More links get a preview: sites that refused our old request (e.g. 9gag) now unfurl.
Security
  • Link previews are fetched safely: the preview fetcher refuses private/internal hosts and pins each request to the address it validated, so a crafted link can't reach internal services through the server (SSRF protection).
1.41.02026-06-26
Added
  • Filter the world map by crowd lean: the map's lean legend (Underdog / Even / Favoured, and Eliminated) is now clickable - tap a label to hide that group of flags, tap again to bring them back, just like the fixtures filters.
Fixed
  • Teams already out on head-to-head are now greyed on the map: a team that can at most draw level on points with the side above it but has already lost the decisive head-to-head was still shown as alive. The mid-group elimination check now resolves these ties by head-to-head for the competitions that rank it first (2026 World Cup, Euro 2024), so such teams (e.g. a side sitting fourth behind a head-to-head it lost) grey out correctly. It stays cautious where only goal difference could decide, so nothing is greyed by mistake.
1.40.02026-06-26
Added
  • Several images per message, buffered before you send: attach, paste or drop more than one image and they wait in a tray under the composer - reorder your thoughts, drop any you change your mind about, then send the caption and every image as one message. Editing a message can now drop its images or add new ones too, and that counts as an edit just like changing the text.
  • A better image viewer: tap any image to open it full-screen, then step through every image on that message with the on-screen arrows or your keyboard's left/right keys. The caption and reactions show beneath the picture. Copy, download (a properly named PNG, e.g. nostragoalus-chat-myleague-eng-v-fra-2026-06-26.png) and, on mobile, share to any app.
  • Per-chat media gallery: a media button in the chat header opens every image posted in that room, to browse and cycle through.
  • Share a pick straight to chat: the share menu on a prediction gains "Share to chat", which drops the pick card into your league chat composer to review and send.
  • See who's typing: a transient "X is typing…" hint appears while others are composing.
  • Know which rooms have activity: the collapsed chat bubble shows an unread count, the Global/Match toggle dots when the other room has new messages, and a rooms menu lists every room with unread and jumps to it.
  • Load earlier messages: chat history is paged - a "Load earlier messages" button pulls older posts in, so you can scroll all the way back.
  • Undock the chat: pop the chat window out into a draggable, resizable panel you can place anywhere; it remembers where you left it.
  • Copy a message's text, and profiles from chat: tap a name or avatar to open that member's profile.
Changed
  • Chat messages now sit in bubbles with the author's avatar inline, your own messages tinted, and line breaks and links preserved (links open safely in a new tab).
  • Tidier chat controls: verify keys, recovery-code setup and the admin actions (reports, rotate key, turn off) moved into an overflow menu so they are deliberate, not a mis-click next to the composer. The add-reaction button now sits in the row with reply.
Fixed
  • New members no longer show as "Someone" until you reload - their name fills in live as soon as they speak.
  • The chat no longer jumps to the top when you switch browser tabs and come back; your scroll position is kept.
  • Clicking reply or edit now focuses the message box so you can type right away.
  • Copying an image to the clipboard works now (it is converted to PNG, which the clipboard accepts), and a zoomed image is no longer painted over by the world map underneath it.
1.39.22026-06-26
Changed
  • The full-screen goal celebration no longer pops over a live stream you're watching: while an embedded live broadcast is on-screen in the match view (pinned or on the Live tab), a goal updates the score quietly instead of throwing up the overlay - which used to cover the stream and spoil the goal before the delayed feed reached it.
Fixed
  • Player rankings now show assists: the match Players tab and the top-scorer lists read assists again - they had been stuck at zero because FIFA's official aggregate stats aren't published mid-tournament and the per-match feed lists the beaten goalkeeper as the "assist", not the passer. Assists are now read from the match timelines, and a player who only assists (no goals of their own) now appears in the rankings and as a team's top assister.
1.39.12026-06-26
Fixed
  • Head-to-head now shows the full history: the all-time meetings on a match's Head-to-head tab (and each team's recent form) were drawn from a FIFA feed that only carried World Cups, qualifiers and friendlies since 2021, so older friendlies and European Championship matches were silently missing - for example Norway vs France's 2010 and 2014 friendlies, or France vs Spain's Euro meetings in 1984, 1996, 2000 and 2012. They now come from FIFA's full archive going back to the early 1900s, friendlies and continental finals included.
1.39.02026-06-26
Added
  • Eliminated teams greyed on the world map: nations out of the tournament are now greyed out - knockout losers, teams that didn't qualify from their group, and teams already mathematically out mid-group (they can't reach a qualifying place whatever the remaining results work out to). A legend marks them.
Fixed
  • Group standings now use each competition's real tiebreakers: the group tables (and the projected knockout bracket that reads them) ranked every competition by goal difference first. They now follow the official order per tournament - head-to-head first for the 2026 World Cup and Euro 2024, goal difference first for the 2022 World Cup - so teams level on points are separated correctly, head-to-head computed among only the tied teams.
1.38.12026-06-26
Fixed
  • The world map renders again on the built site: it was coming up blank - no tiles, no countries - because the map component's element reference never bound in production. The map now loads its tiles, country flags and the crowd-lean tint as expected.
Changed
  • The crowd-lean tint on the world map is now far more visible: each nation's flag carries a thicker coloured ring and a soft glow (blue when the crowd favours it, red for the underdog), instead of a thin pale outline.
1.38.02026-06-26
Added
  • Prediction lean on the world map: with "Show everyone's totals" enabled, each nation on the map is now tinted by where the crowd expects its current match to go - blue when the field backs that team to win, red when it backs the opponent, pale when it's even. It reads from the live match (or the next one), updates as predictions and scores come in, and a legend explains the scale. Turn it on from Preferences.
1.37.02026-06-26
Added
  • Calendar feed: subscribe to your fixtures and pick deadlines from your own calendar app. Preferences has a "Calendar feed" section with a personal subscribe link (and a one-tap webcal button) you add to Google Calendar, Apple Calendar, Outlook or Thunderbird; it then stays up to date on its own. Each upcoming match you haven't predicted carries a reminder three hours before kickoff. The feed shows fixtures and final scores but never your predicted score, so the link stays safe to hand to a calendar service.
1.36.02026-06-26
Added
  • League chat, front and center: chat now rides along as a collapsible window in the bottom-right corner of competition and match pages (collapsed by default), so it is no longer tucked away on the league page where nobody found it. The window can be expanded for a roomier view. On a match it gains a Global/Match toggle to flip between the league room and that match's thread. The league page keeps its full inline chat.
  • Message reactions: react to any chat message with an emoji, the same set as match reactions. Counts update live for everyone, and tapping your own reaction again removes it.
  • Reply to messages: reply to any message and the original is quoted above your answer (and above the composer while you write it), so a thread is easy to follow. The quote is clickable - it jumps to and highlights the original post. The quote is decrypted on your device like everything else.
  • Images in chat: drag and drop, paste, or attach an image (up to 5 MB). It is downscaled to webp and end-to-end encrypted on your device before sending, so the site only ever stores scrambled bytes - it never sees the picture. Tap an image to enlarge it, then copy, download, or share it.
  • Edit and delete your own messages: fix a message after sending - it shows an "edited" marker with the time, and the new text is re-encrypted on your device and updates live for everyone - or delete it, which shows as "message removed" and keeps any replies intact. You can also withdraw a report you made.
  • Report and moderate chat messages: anyone can report a message they did not send. Once enough distinct members flag one (a quarter of the league, at least three) it auto-hides as "pending review" for everyone except the owner and moderators. Owners and moderators get a Reports list to read each flagged message and either keep it or remove it, and can remove any message directly. Removed messages show as "message removed" and keep replies intact. Reports and removals reflect live for everyone.
Fixed
  • Joining a chat no longer leaves you stuck: a member who joined a league with chat already on used to sit on "Setting up your key..." forever unless someone already in the chat happened to reopen it. Your device now asks the group for your key automatically and unlocks the moment a member who holds it is online, showing a clear "waiting to be let in" message until then.
  • Unmute: muting a member used to be permanent with no way back. Chat now has a "Muted" list where you can unmute anyone you previously hid.
  • Live chat on/off: turning chat on, off or rotating its key now updates for everyone instantly - the chat window appears or disappears for other members without a refresh, instead of going stale until they reloaded.
  • Names of hidden members in chat: a member hidden from the public leaderboard now shows their name in the league chat they belong to, instead of appearing as "Someone".
  • Chat scrolling: reacting, muting or moderating no longer yanks the view to the latest message. New messages while you have scrolled up raise a "new messages" jump button instead of forcing you down.
1.35.02026-06-23
Added
  • League chat key verification: a "Verify keys" panel in chat shows a short safety number for every member (and your own) that you can compare in person or over a trusted channel to confirm no key was tampered with. Your device remembers each member's key the first time it sees it and warns you site-wide if it ever changes, and a keyholder will not hand the group key to a member whose key changed until you accept it, with a one-tap accept or mark-verified.
  • League chat key rotation: a league owner or moderator can rotate the chat key from the panel. Everyone currently in the league gets a fresh key, so anyone who has been removed loses access to new messages, while past messages stay readable for members who already had them. It also recovers a member who somehow ended up unable to read the chat.
1.34.02026-06-23
Added
  • League chat (end-to-end encrypted): a private chat for your league - a general room on the league page plus a per-match thread on each match. Off by default; only a league owner or moderator can turn it on, behind a warning that spells out the trade-offs. Messages are encrypted on your device, so no one else can read them - not even the site owner, who stores only scrambled text and cannot moderate or recover it. Your key enrolls silently on first use (no extra password); save the one-time recovery code to read your history on another device or after clearing your browser. You can mute another member locally.
1.33.12026-06-23
Fixed
  • Interrupted matches: a match the referee halts mid-play now shows as "Interrupted" instead of slipping back to "Scheduled" as if it had never kicked off. Abandoned, postponed and cancelled World Cup matches now read correctly too. A halted match keeps its per-match ranking and play-by-play, the same as a live one, so you do not lose the tabs while play is stopped, and its provisional points keep counting on the live leaderboard.
1.33.02026-06-22
Added
  • Tamper-evident scores: every prediction change is now sealed into an append-only, hash-chained ledger (commit-reveal). Picks stay hidden until kickoff, then their score and salt are revealed so anyone can re-open the seal. The new public Verify scores page (footer link) pulls the whole ledger and recomputes the chain in your own browser - tamper with any sealed entry and the chain stops reproducing the head you snapshotted today. Predictors appear as an opaque hash, never their account. Your browser also remembers the last chain state it checked (kept on your device, never sent to us), so a later edit to anything you have already seen is caught automatically and a warning surfaces site-wide - no need to save the hash yourself.
1.32.12026-06-22
Changed
  • Notification bell: opening it now clears the unread count for you, and the bulk action is "Delete all" (wipes the list) instead of "Mark all read". Per-item dismiss is unchanged.
Fixed
  • Knockout bracket: group winners and runners-up are no longer drawn on the wrong sides before the games are played. The tree now follows the official match order, so the projected path (and who you might meet) is right from the start.
  • Fixtures view: switching to Standings and back to Fixtures now jumps you to the first upcoming match again, the same as on load.
  • Push notifications: the required VAPID keys are now documented in .env.example. Without them the push toggle stays hidden and nothing is delivered, which is why notifications never arrived.
1.32.02026-06-21
Added
  • Shareable prediction cards: a share button on your pick turns it into an image you can post anywhere, with a link that unfurls into the card on social apps. After kickoff it shows your result - the scoreline, your call, the points and how rare it was. Before kickoff you can share a sealed teaser or choose to reveal your own predicted score. The card reads in your own language.
  • Joker straight from the match view: toggle your joker on a fixture's pick row without leaving the match page; single-match rounds (which double for everyone) show the reason instead.
1.31.32026-06-21
Added
  • Line-ups on a real pitch: the XI now sits in its true positions on a half-pitch graphic - exact for UEFA matches, and refined from Sofascore for the World Cup (so a back three reads as a back three, full-backs out wide). Where no positions are available it falls back to the formation-row layout.
Changed
  • Line-ups are now stored once confirmed, so a finished match's XI loads instantly and survives a restart instead of being re-fetched from the feed.
Fixed
  • Odds (and the rare-result bonus that uses them) are flowing again - the odds feed had begun rejecting the app's requests, leaving recent matches unpriced.
1.31.22026-06-21
Fixed
  • Leaderboard movement arrows now clear when your rank doesn't change after a round, instead of leaving a stale up/down indicator from an earlier change.
  • During a live match the movement arrow is measured against the live-inclusive rank shown (not the last settled standings), so the arrow and the rank you see agree.
1.31.12026-06-20
Fixed
  • The line-ups pitch now follows the team's declared formation (e.g. a back three stays a back three) instead of grouping players by their feed position category, so the shape on the pitch matches the formation shown beside it.
1.31.02026-06-20
Added
  • Match line-ups: a Line-ups tab on the match page shows each team's starting eleven on a formation pitch, plus the bench and head coach, with the formation when the feed provides it. Players link through to their team page. The tab appears once the official line-ups drop, about an hour before kickoff.
1.30.32026-06-20
Added
  • The live play-by-play timeline now marks corners, with a corner-flag icon and the taker's name. Covers both the World Cup (FIFA) and UEFA feeds.
1.30.22026-06-20
Fixed
  • The projected knockout bracket never pairs a group's projected third-placed team against that same group's winner or runner-up - a group-stage rematch that can't actually happen.
1.30.12026-06-20
Fixed
  • The projected knockout bracket now fills every best-third slot. A slot whose only currently-qualifying third had been claimed by another slot could be left blank (e.g. the "3rd D/E/I/J/L" slot when only group D's third was among the eight best); the projection now matches slots to thirds so each fillable slot gets a team.
1.30.02026-06-20
Added
  • Projected knockout bracket: once every team in a group has played, the bracket fills that group's knockout slots with the teams currently on course to qualify (from the live group standings, best-third places included). Projected sides are dashed with a "projected" chip, clearly distinct from teams already officially through, and update live as scores change.
1.29.42026-06-20
Fixed
  • Embedded watch-link players now have a working fullscreen button: the player iframe is granted fullscreen both ways (the legacy allowfullscreen attribute and the sandbox allow-fullscreen token), so it works across browsers and for sandboxed providers like YouTube and Twitch.
1.29.32026-06-20
Fixed
  • Embedded watch-link players can now go fullscreen even when their feature-policy omits the fullscreen token (the player iframe always carries allowfullscreen).
  • The match page no longer shows two identically-named tabs in French (and Klingon): the team group standings tab is now distinct from the player ranking tab.
  • Your pick on the match page now locks the moment the match kicks off while you're watching it: the score inputs stayed editable if the match started after the page had loaded.
Changed
  • Multiple watch links of the same kind now sit side by side (with a gap) instead of stacking one per line.
1.29.22026-06-19
Changed
  • Polished the themed error art: the 418 teapot now pours its football out of the spout into the cup (the spout faced the wrong way and the handle was oversized), and the 500 referee raises the red card from the shoulder (not the head) with an impact flash and a flinching, sweating server.
Fixed
  • The production build no longer fails to link: deep shared/* imports from nested route pages now go through the #shared alias so pnpm build succeeds (no user-visible behavior change).
1.29.12026-06-19
Fixed
  • Break substitutions (half-time, and the extra-time interval) in the match timeline no longer fall to the bottom out of order: they now sit at the right break with an "HT" marker instead of a blank minute.
1.29.02026-06-19
Added
  • Reactions on the fixtures list: each kicked-off match card now shows its reaction tally (your own reaction highlighted), so you can see the mood of the matchday at a glance. With a league selected the card shows your league's counts plus the global total, mirroring the crowd line. Tap the card to react on the match page.
1.28.12026-06-19
Changed
  • A match's live watch link is now cleared automatically once the match finishes (replays and highlights stay), so a dead stream link never lingers.
1.28.02026-06-19
Added
  • Themed error pages: a 418 shows a teapot pouring a football ("I'm a teapot"), and a 500 shows the referee sending the server off with a red card (the 404 still loses the ball among the stars). Both honour reduced-motion. New /418 and /500 routes trigger them on demand.
1.27.02026-06-19
Added
  • API-key scopes are now a categorized, extensible list with a read-only leaderboard:read scope, so a machine integration can read the rankings without any broader access.
Changed
  • Admin panels tidy-up: the odds-provider dropdown no longer collapses to a cramped width, and the API-key create form gives scopes and expiry their own rows instead of crowding them onto one line.
Security
  • The leaderboard API now requires a signed-in session (or a scoped API key): the global and per-competition rankings are no longer readable by anonymous callers hitting the endpoint directly.
1.26.02026-06-19
Added
  • Admin watch links gained per-link iframe control: paste a provider's full embed code (the URL and its allow policy are pulled out for you), choose how the embed is sandboxed (auto, force on, or off), and set a custom allow policy. Turning the sandbox off lets players that refuse to run sandboxed (some PPV hosts) embed at all - it carries a warning, since an unsandboxed page can navigate or pop up within the embed.
Changed
  • Match watch links now live in the match's own tabs instead of a block above them: a Live tab before and during the match, and separate Replay and Highlights tabs once it is over (each only when a source exists). A pin lifts the stream above the tabs so it keeps playing while you browse the rest of the match.
  • The Standings view on the fixtures page now scrolls inside the same contained area as the fixtures list, instead of growing the whole page - the two views feel consistent when you switch between them.
Fixed
  • Opening a live match no longer shows an error page: the match view crashed while a match was in play and now loads its live score, timeline and stats as expected.
  • YouTube watch links now play embedded instead of failing with "Video player configuration error": the player is sent the page origin it needs to authorise the embed.
1.25.02026-06-18
Added
  • What's new badge: the account menu now flags when the changelog has entries you haven't seen, and the About page highlights those new versions. Opening "What's new" clears the flag, and your spot is remembered across devices.
1.24.02026-06-18
Changed
  • The match page's League tab is now Ranking and shows even with no league selected: it ranks everyone who predicted that match by the points their pick earns (live then final), leaving out private profiles, leaderboard-hidden accounts and anyone who didn't pick. Pick a league and it still ranks just that league's members.
1.23.02026-06-18
Added
  • Match reactions: react to a match with a quick emoji - 🔥, ⚽, 😮, 🤣, 😢 or 😡 - from kickoff onward and for as long as you like after the final whistle. Counts update live for everyone watching the match, and when you have a league selected you see how your league reacted next to the global tally. One reaction each: tap another to change it, or tap the same one again to clear it.
1.22.02026-06-17
Added
  • Admin odds-provider switch: choose which provider prices each competition's matches (Sofascore, or BetExplorer for fixtures), from the admin panel.
1.21.02026-06-17
Added
  • Push notifications: opt in from Preferences to get alerts on your phone or desktop, even with the app closed - pick reminders, a kickoff and live goal alerts for matches you predicted, your match and tournament results, and league activity. Each kind has its own on/off switch, and goal/kickoff alerts only reach people who predicted that match. Your browser asks for permission once when you enable it on a device.
1.20.12026-06-17
Fixed
  • A live match score in the header now updates the moment a goal appears in the timeline, instead of trailing behind it - a match could read 0-0 with the scorer already listed below. The goal celebration now fires with that scoreline change (not a poll later) and stays up long enough to watch the goal land instead of cutting off mid-animation.
  • The competition Map renders again: it could come up blank in production, leaving an empty panel where the world map should be.
  • The rarity bonus tooltip no longer misreports a common pick as rare. A bonus earned for a bold but correct result (one few players called) is now described as such, instead of being blamed on the exact-score share - which is why a pick shared by most could show "only 60% picked this exact score".
1.20.02026-06-17
Added
  • Group standings on the fixtures page: a Fixtures / Standings switch in the filter row flips the page to every group table at once (P, W, D, L, GD, Pts), live-aware so in-progress matches count at their current score. The switch only shows for tournaments with a group stage, and the view is shareable via a ?view=standings link.
1.19.02026-06-16
Added
  • Match watch links: a new Watch section on the match page where admins can attach Live, Replay and Highlights links. Links from recognised hosts (YouTube, Twitch, Dailymotion, Vimeo) play inline in a sandboxed embed; anything else opens in a new tab, with an admin override to force-embed or force a link for any host. Live links appear around kickoff, replays and highlights once the match is finished. Embedded players always keep an "open in a new tab" fallback in case a host refuses to be framed.
1.18.02026-06-16
Added
  • API clients (admin): a new admin section - plus a mise run create-api-key <name> CLI - to mint scoped, optionally expiring API keys for machine integrations and revoke them. Any admin sees and can revoke every key (including CLI-minted bot keys); the full key is shown once at creation and stored hashed afterwards. Each key carries a permission scope and is bound to an admin owner, so it can be granted only the access an integration needs.
Fixed
  • The notifications panel (and the account menu) now close when you scroll the page, instead of drifting away from the header and overlapping the fixtures below them.
  • The competition map now places a flag for every nation, not just the long-standing World Cup and Euro regulars: newcomers like Cape Verde and Curacao (and any other qualifier) were silently dropped before.
1.17.02026-06-15
Added
  • Notification center: a bell in the header collects what matters to you - a reminder for a match locking soon you haven't predicted, the result of each match you did predict (the scoreline and the points it earned), league activity (joins, role changes, being added or removed), and how your champion and Golden Boot picks finished - with an unread count and live updates as things happen. Reminders only go to players already in the competition, clear themselves once you make the pick or the match kicks off, and like the rest can be marked read or tapped to jump straight to the match, league or competition. Each notification has a dismiss button to remove it, and read ones are tidied away automatically after a week (with an overall cap per person) so the list never piles up.
1.16.02026-06-15
Added
  • The admin Users list has a debounced search (display name fuzzy, or email exact) and now shows each account's join date.
Changed
  • On the fixtures page, the search now lives in the status-filter row (right-aligned, opened by its icon or Ctrl/Cmd+F) and is a country multiselect: pick one or more teams (fuzzy, accent-insensitive typeahead) to filter the fixtures to those, instead of a free-text box.
Fixed
  • The match Stats tab now shows a "No stats yet" message when a match has started but no stats have come in, instead of a near-empty panel.
1.15.02026-06-15
Added
  • The landing banner now settles into its slim header bar reliably on every screen size and stays compact while you read, instead of re-expanding as you scroll back toward the top. A scroll cue beneath it glides the banner up and jumps straight to the matches and your picks; it steps aside while the next-match prompt is showing and respects reduced-motion.
  • A teaser on the signed-out landing page: the next fixture with a countdown, plus how many players have joined and predictions made so far. No names, and each part stays hidden until there's something worth showing.
Fixed
  • The scoring FAQ now documents the result-rarity bonus (the small extra for calling a rare but correct result) that shipped with the tunable scoring update - it previously listed only the exact-score rarity tiers.
1.14.02026-06-15
Added
  • The minute-by-minute play-by-play timeline now covers UEFA competition matches (the Euro), not only the World Cup - goals, cards, substitutions, shots, fouls, period markers and VAR decisions, in their own tab on the match page. Each line reads in your own language like the rest of the timeline; VAR decision text is shown in English where the feed provides it.
1.13.12026-06-15
Fixed
  • The admin Scoring rules page no longer renders cramped: the tier-row and joker inputs overflowed their set width and overlapped the labels and the joker toggle.
1.13.02026-06-15
Added
  • Scoring rules admin page (/admin/scoring): edit the points, bonuses and rarity tiers from the app instead of the database. There is one default ruleset that applies everywhere, plus optional per-competition overrides - tune one tournament without touching the others. Saving recomputes every affected leaderboard right away, and a competition's override can be removed to fall back to the default.
  • Result-rarity bonus: on top of the existing exact-score rarity, a small bonus now rewards calling a rare but correct result (e.g. backing the underdog when most of the field picked the favourite). It is a tunable extra layer on the crowd bonus, on by default for new installs and configurable per competition.
Fixed
  • In a league, hovering the crowd score under a match no longer stacks two overlapping tooltips: the league and everyone totals now show their labels below the line.
1.12.12026-06-14
Added
  • Admins can re-run the champion FIFA-rank backfill from the Background tasks page. It repairs champion picks that saved without a FIFA rank (and so paid the flat bonus instead of the rank tier) when the ranking feed was unreachable during the pick window, restoring the rank and the correct potential points.
1.12.02026-06-14
Added
  • League tab on the match page: when you have a league picked, a match that has kicked off shows a "League" tab ranking your fellow members by the points their pick earns on that match. While the match is live the points update with the score (provisional, same scoring as full-time); once it finishes they settle to the final tally. Picks stay hidden until kickoff, and members who did not predict are summarised at the bottom.
1.11.12026-06-14
Fixed
  • During the second-chance window, the Champion and Best scorer cards now show the halved worth for a previewed pick, including a late first pick (it previously showed the full worth even though such a pick scores half).
1.11.02026-06-14
Added
  • Second chance for champion & Golden Boot picks: once the group stage is nearly over (from the last group round until the knockouts begin), you get one last chance to switch your tournament-winner and top-scorer picks - for half the points. The switch is permanent (reverting doesn't restore the full value), a confirmation spells that out, and your original pick stays on show beside the new one. A player who never picked can also make a late first pick during the window, for half.
Fixed
  • The Best scorer showcase now shows a player's official headshot when one exists only on the squad feed (some players, e.g. Edin Dzeko, had no image at the id-derived path we used and fell back to the team flag).
  • On mobile, the fixtures list now waits for the Champion and Best scorer cards above it to finish loading before it appears, so the on-load jump to the next match lands on the right row instead of stopping short once those cards grow.
1.10.02026-06-14
Changed
  • The admin panel is now organised around a left-hand menu instead of one long scroll: sign-ups, SSO, users, leagues, roadmap and scheduled tasks each get their own full-width page. The scheduled-tasks view folds in alongside the rest (the old /admin/cron link still works), and the page uses the full width of wider screens.
1.9.02026-06-14
Changed
  • On the fixtures page, clicking anywhere on a match card opens the match, not just its title (the score inputs, joker button and team links still work as before).
  • The match play-by-play is now written in your own language (it previously echoed the data feed's English commentary), and each line drops the redundant country name since the team flag already shows the side.
  • Fouls in the play-by-play now show a referee whistle icon instead of a warning sign.
Fixed
  • Joker buttons are now disabled for the rest of a round once that round's joker is locked on a started match (it can't be moved), with a tooltip explaining why - instead of letting the click fail. Any remaining error shows as a toast rather than an inline banner that pushed the page down and broke the match list's scroll area.
  • Dates now follow the selected language everywhere (fixtures, match view, bracket, map, team and profile pages) instead of the browser's locale.
  • The match status tags (Scheduled, Live, Full-time, ...), prediction result tiers (Exact score, Goal difference, Right result, Missed), the group labels and the bracket round names (incl. Champion / 3rd place) are now translated in every language.
  • The match play-by-play no longer stretches the whole page when it has a lot of events: on wider screens the timeline scrolls within a bounded area (the page keeps scrolling normally on mobile).
  • The match play-by-play no longer renders its first rows with the wrong (loading) layout or an uncapped height after a page refresh.
  • Switching tabs on the match view no longer jumps the page back to the top.
1.8.12026-06-14
Changed
  • The Champion and Best scorer picks now sit side by side in a tighter, more compact layout, with loading skeletons so they appear together instead of popping in one after the other.
  • On wider screens the matches list scrolls in its own region so the header, your stats, the picks and the filters stay put (it falls back to normal page scrolling on smaller ones). On load it jumps to the first live match, or the next upcoming one, and each round (Matchday 1, Round of 16, ...) can be collapsed.
1.8.02026-06-14
Added
  • Email verification for sign-ups (admin-toggleable): admins can require new accounts to confirm their email before signing in, from a new Sign-ups section on the admin page. The toggle needs SMTP configured; turning it on marks all existing accounts as verified, so only new sign-ups are affected. Admins can also force-verify any account ("mail never arrived"), and never-confirmed accounts older than 7 days are cleaned up daily. SSO sign-ins are unaffected. After signing up, users see an on-screen confirmation to check their inbox; a blocked sign-in shows a clear notice with a one-click resend, and the link is no longer auto-resent on every attempt. Clicking the verification link signs you straight in and lands on a dedicated confirmation page (instead of flashing a logged-out page on the way to sign-in).
  • Branded HTML emails: verification, password-reset, account-deletion and sign-in-code mails now have a styled HTML version with a clickable button (and the raw link as a fallback), alongside the existing plaintext.
Changed
  • Sign-in, sign-up and password pages now use a focused layout without the main competition navigation.
1.7.02026-06-14
Added
  • Background tasks admin page: every scheduled and on-demand job (live score polling, fixture and bracket refresh, lock-and-score, odds snapshots/backfill, fixture import) with its schedule, next and last run, run count and result. Run any on the spot, and tap a result to see its last output or error.
Changed
  • Fixture import moved onto the Background tasks page (it was a standalone admin button), so all the manual data jobs live in one place.
1.6.02026-06-13
Added
  • League invite links: owners and moderators can mint shareable join links from the league card, with an optional expiry (24 hours to 30 days) and a cap on the number of uses. Opening a link shows the league and joins with one click; signed-out visitors are sent through login and land back on the league automatically.
1.5.22026-06-13
Added
  • The match play-by-play now also shows fouls, and tags each event line with the relevant team's flag.
Changed
  • When the home-page next-match/live prompt is too narrow to fit on one line, the two teams now stack vertically (home, separator, away) instead of wrapping awkwardly, and long team names wrap cleanly within the card.
  • The next-match countdown now sits next to the "Next match" label instead of off to the side, so the card stays centered.
Fixed
  • The fixtures page now keeps up as a match goes live then finishes without a reload: your pick locks at kickoff and the points appear after full-time (the score and status already updated live).
  • Penalty awards now appear in the play-by-play (they were dropped when the feed gave them no commentary text).
  • A few tooltips (second yellow card, captain, joker) were English-only; they are now translated in every language.
1.5.12026-06-13
Fixed
  • The home-page next-match and live-match prompts no longer overflow the screen on narrow viewports: they wrap and stay within the window instead of clipping off the edges.
1.5.02026-06-13
Added
  • Nostragoalus is now an installable PWA (app icons + web manifest), so you can add it to your home screen and launch it like an app.
  • A "new version available" banner appears after a deploy and reloads to the fresh build on your tap, instead of leaving you on a stale bundle. It never reloads mid-prediction, and a later deploy re-surfaces it even if you dismissed an earlier one.
1.4.02026-06-13
Added
  • A "next match" prompt on the landing page: when you're signed in, it surfaces your next fixture with a countdown and a one-tap jump to its pick, plus a live-now pill (a count when several matches are in play) that deep-links straight into the action. Scroll past or follow it to dismiss.
  • Status filter chips on the fixtures page (full-time / live / upcoming) with a clear "Full-time" label; the landing live pill deep-links here pre-filtered to live and scrolled to the match.
Changed
  • The Golden Boot (best-scorer) award now shows a real gold boot icon instead of the plain 👟, so it no longer looks identical to the assist symbol used elsewhere.
1.3.12026-06-12
Fixed
  • Play-by-play: own goals now show on the side that benefits from them (the team whose score went up), matching the timeline under the score.
1.3.02026-06-12
Added
  • Play-by-play: a new tab on the match view with the full minute-by-minute timeline (goals, cards, substitutions, shots, penalties, VAR), newest first. It opens by default on live matches; the quick-glance events under the score stay as they were.
Changed
  • Leaderboard ties: players level on points (then exact scores, correct results, goal difference) now share the same rank, and the next place is skipped. Join date is no longer a tie-breaker. A tooltip on the leaderboard explains the order.
Fixed
  • Leaderboard live ranking: points from in-progress matches now move you up the board, while the displayed total stays your confirmed score with the live points shown as a separate "+N" delta.
  • The live clock now shows "HT" at half-time (FIFA keeps a match marked live through the break, so it previously showed a bare "LIVE").
  • Live substitutions no longer flash "?" for the players involved in the seconds right after a change.
  • A live match view now updates on its own when the match kicks off or finishes, even if its tab was in the background, instead of staying on a stale "Live" until you reload.
1.2.02026-06-12
Added
  • Every page now sets a proper browser-tab title (e.g. "Matches · Nostragoalus", or "Korea Republic – Czechia" on a match), instead of every tab reading "Nostragoalus".
  • The live match view shows the running clock under the score ("61'", or "HT" at the break) next to the live indicator.
Fixed
  • During a live match the goal timeline, stats and possession now update from live data - they previously only filled in once the match finished.
  • The possession bar accounts for contested possession (an "in contest" segment), so the two sides add up to 100%.
1.1.22026-06-12
Changed
  • The About page lists each dependency separately, each with its own one-line description.
Fixed
  • The create-admin, roadmap-seed and roadmap-add CLI tasks work on a production host now - they connect to the dockerized database directly instead of needing a local install.
1.1.12026-06-12
Added
  • mise run roadmap-seed: bootstrap the public roadmap with a curated starter set (idempotent - skips a roadmap that already has items).
Fixed
  • Opening a modal no longer nudges the page sideways (the scrollbar gutter is reserved, so the layout doesn't reflow).
  • The changelog on the About page now renders its markdown (bold, code, links) instead of showing the raw symbols.
  • Crowd-score tooltips are stylized consistently, whether or not a league is selected.
  • The About page and the FAQ now credit Sofascore (the odds source) alongside FIFA, UEFA and OpenStreetMap.
1.1.02026-06-12
Added
  • Roadmap: a public /roadmap page (linked in the footer) showing what's in progress, planned, and shipped. Admins curate it from the admin panel - add, edit, delete items, move them between columns and reorder within a column - and there's a mise run roadmap-add CLI for quick entries.
1.0.22026-06-12
Changed
  • Nostragoalus is now dual-licensed under MIT OR WTFPL - use whichever you prefer (footer link and the in-app license page show both).
  • Landing showcase screenshots refreshed to the current UI and captured in both light and dark, served to match your theme; uniform card sizes (no gap before the caption), a leagues view added, and the admin nav / dev tooling hidden in them.
  • The FAQ "how points are calculated" formula is now translated into all four languages (was English-only).
  • The bot's consensus list marks not-yet-kicked-off (admin-only) matches with the same divider the player-picks view uses, instead of a banner.
  • mise run db-backup can prune old dumps: --keep N / BACKUP_KEEP (newest N) and --max-age-days D / BACKUP_MAX_AGE_DAYS (by age). Both default to unlimited, so nothing is pruned unless you opt in.
Fixed
  • The best-scorer card shows the points a winning pick is worth even once it's locked, not only while picking.
  • The login form submits on Enter from any field (it's a real <form> now).
1.0.12026-06-11
Changed
  • Landing showcase screenshots refreshed to the current UI, and a consensus-bot screenshot added to the carousel.
1.0.02026-06-11
Changed
  • Landing page refreshed for the 1.0 launch: the feature grid now covers private leagues, the Golden Boot pick and the consensus bot; the scoring section and the "how points are calculated" formula match the current rules (correct-result rarity tiers, FIFA-rank champion bonus, Golden Boot bonus); new FAQ entries for the Golden Boot, leagues and live standings.
0.22.02026-06-11
Added
  • Live provisional standings: while matches are in progress the leaderboard folds in what each player would score at the current scoreline (full scoring, bonuses included), ranks provisionally, shows a "+N live" delta per row and a pulsing LIVE badge. It updates over the WebSocket as scores change - no polling.
  • The group standings table on a match view now tracks the live match: the in-progress scoreline counts provisionally and updates in place.
  • Your best-scorer pick now shows on your profile (team flag + 👟) beside your champion, and the best-scorer card shows the points a winning pick is worth.
  • The fixtures list updates live - match status and scores refresh in place as games play, over the WebSocket.
  • While a match is live, its stats and event timeline now stay up to date.
  • Admin viewing a player's picks sees their not-yet-kicked-off picks too, behind a divider that marks them admin-only.
Changed
  • Crowd rarity bonus reworked (MPP-style): a pick's exact-score rarity is now measured among the players who got the correct result, not the whole field, and the tiers are tighter - only a clear minority of that right-result crowd earns a bonus, with a steeper climb for genuinely rare calls. Already-scored games are re-scored under the new rules.
Fixed
  • A match marked full-time on FIFA could stay "live" in the app for up to an hour (FIFA drops finished games from its live feed); the final whistle now lands on the next 2-minute poll.
  • You're always shown on a leaderboard you belong to, even if your account is hidden from public boards - a hidden account no longer saw an empty board in its own league.
  • Live stat/event updates patch in place instead of flashing the loading spinner and redrawing the timeline.
  • The crowd-bot league name no longer overflows the page header on mobile.
  • The banner logo and title now settle together when the bar docks (the title no longer shrank well before the planet).
0.21.02026-06-11
Added
  • Champion pick difficulty: the points a champion pick is worth now depend on the team's FIFA world ranking at pick time - favorites pay the flat bonus, long shots up to 4x. The worth (and the team's FIFA rank) is shown on each option and locked in when you pick; the winner is paid that locked value at the final. Falls back to the flat bonus if the FIFA ranking is briefly unavailable.
0.20.32026-06-11
Fixed
  • Anchor links (e.g. the footer version jump) no longer land under the sticky header - hash navigation is offset by the header height.
0.20.22026-06-11
Changed
  • The version number in the footer now links straight to that version's section in the about-page changelog.
0.20.12026-06-11
Changed
  • The fixtures filter is now opened on demand - a search icon next to "Fixtures" or Ctrl/Cmd+F reveals it, scrolls it to the top and follows the scroll; the icon shows an active state and toggles it off, Escape closes it. The field is debounced with a clear button.
  • The Map view no longer shows the league pill (it has no league-scoped data).
0.20.02026-06-11
Added
  • The leaderboard now shows each player's best-scorer pick (team flag + 👟) beside their champion pick, with the full team / normalized player name in the tooltip.
  • Admin: an "Apply auto-join now" action back-fills SSO league membership for all existing users whose email domain a linked provider captures (no need to wait for each to log in again), plus a total user count.
  • The consensus bot's ghost row appears as soon as anyone has predicted (ranked last at 0 points pre-scoring), not only once it has scored.
Changed
  • Bookmaker odds are now OFF by default (opt-in); the preference and a tooltip explain 1X2 and decimal odds (1 = home win, X = draw, 2 = away win; lower = more likely).
  • The "My Picks" page is gone - its standing boxes and the champion / best-scorer pick cards now live on the Matches page. The rank and player count honor the selected league.
  • Leaderboard movement arrows now reflect only scoring changes, not roster churn (joining, going private, or being removed no longer nudges everyone).
  • Player headshots come from UEFA for UEFA competitions and FIFA for the World Cup; the best-scorer player list shows attackers first.
Fixed
  • Broken avatars: a token-gated identity-provider picture (e.g. Microsoft Graph) is fetched once at sign-in and inlined, or replaced with the placeholder, instead of showing a broken image.
  • Live scores and crowd totals auto-reconnect after a server restart instead of silently freezing; the match page gained the league pill.
  • Champion / best-scorer flag alignment, the joker button's duplicate star, the admin manage-members modal (long names, a crash), and the leaderboard header overflow with a long league name.
0.19.12026-06-10
Fixed
  • Live scores and crowd totals now auto-reconnect after a server restart/deploy (shared reconnecting WebSocket with backoff, re-subscribing and refetching on reconnect) instead of silently freezing until the next navigation.
  • The leaderboard falls back to the competition board (instead of showing an empty board) when a selected league was deleted or membership was revoked.
Changed
  • Crowd-bot consensus is briefly cached per (competition, league, method) to avoid recomputing the full prediction scan on every leaderboard view; league rank snapshots are written in one batched upsert per board.
0.19.02026-06-10
Added
  • Best scorer (Golden Boot): pick the player you think finishes the tournament's top scorer; at the final, everyone who picked a player tied at the top goal count (own goals excluded) gets a bonus, folded into the leaderboard total beside the champion pick. Picker with team/player selects and FIFA headshots, locked at the first kickoff like the champion pick.
0.18.02026-06-10
Added
  • Crowd bot: a synthetic "consensus" player whose pick each match is the crowd's most-common scoreline (MODE, falling back to a rounded average / MEAN below 5 predictors), scored by the real engine with joker, champion and rank. It shows as a ghost row on the leaderboard (toggle, per-competition or per-league) and a /bot detail page; upcoming-match consensus is admin-only. Display-only - it never changes anyone's real rank, and it is kept out of its own rarity-bonus denominator.
0.17.02026-06-10
Added
  • Leagues: competition-scoped player groups. Create your own (private with a shareable join code, or public so anyone can join and view its rankings), join several or none - predictions stay yours either way, leagues only filter the views.
  • A league pill next to the competition switcher remembers your last selection per competition and scopes the leaderboard (third option in the scope toggle) and the crowd totals (league first, global behind a globe - the rarity bonus stays computed from everyone).
  • /leagues page: manage your leagues (rename, copy/regenerate code, public/private toggle, promote moderators, transfer ownership, kick, leave, delete) and browse public leagues; /leagues/:id shows a league's standings - for public leagues even without joining.
  • League roles: owner and moderators manage members and the join code; leaving or being kicked is remembered so SSO auto-join never re-adds anyone against their will.
  • One-time prompt on first sign-in without a league: enter a join code, create a league, browse public ones, or skip forever.
  • Admins manage every league (create, ownerless allowed, members, roles, visibility, delete) and SSO providers can auto-join their domain-captured users into chosen leagues on every login - the same league can hang off several providers.
  • Private profile preference: step out of the global and competition rankings; only league mates (and admins) can open your profile, which answers 404 to anyone else. League boards still rank you for co-members and your predictions keep counting in the anonymous crowd totals.
  • League boards show movement arrows (per-league rank snapshots, refreshed with the global ones) for members and admins; league crowd totals update live over WebSocket, delivered to that league's members only.
  • The first player joining an ownerless league becomes its owner (code, public and SSO auto-joins alike); admins can still hand ownership to anyone. The last member leaving keeps the league alive - empty and reclaimable by its next joiner - and admins have an irreversible "Prune empty leagues" action to clean those up.
  • Join-code attempts are rate limited (10 per minute per account).
0.16.02026-06-10
Added
  • Bookmaker odds: decimal 1X2 odds (Sofascore feed) under every score input - match cards, match page and editable picks - with an opt-out preference. Snapshots are append-only (identical re-polls skip the write); a scheduled task refreshes matches kicking off within two weeks (every 6h, every 30min in the last 2h before kickoff), and an admin backfill recovers closing odds for past tournaments (WC 2022, Euro 2024).
  • The scoring engine's ODDS bonus mode is now live data-backed: when the active scoring config selects ODDS, finished matches score against the closing (pre-kickoff) odds of the actual outcome - backfilled closing odds trigger a rescore of already-scored matches. The default CROWD mode is unchanged.
Changed
  • Admin manual task triggers consistently bypass the cron kill switch (shared force gate); the long-running odds tasks return immediately ({started:true}) and report through the task tooltips instead of holding the request open past proxy timeouts.
0.15.22026-06-10
Fixed
  • Saving a prediction is now an atomic upsert: concurrent double-submits (autosave racing a manual save, a retry) no longer crash with a 500 on the unique (user, match) constraint - the loser updates the existing row instead.
  • A failed SSO sign-in lands back on the login page with a flash ("single sign-on failed…") instead of dumping the user on the site root with raw ?error= query params; the verbose identity-provider description (which can carry trace IDs) goes to the browser console only.
0.15.12026-06-10
Fixed
  • The hot-reload dev container keeps node_modules AND its build output (.nuxt/.output) in container-private volumes: its root-owned artifacts on the bind mount broke host-side pnpm/nuxt runs, and pnpm's no-TTY purge prompt could kill the container start.
  • Admin user list: "Unlink from SSO" only appears for users actually linked to a provider (small link icon shows who is, with the provider ids in its tooltip), and admins can no longer demote themselves (it broke every admin query on the page mid-session).
Added
  • mise run db-backup / mise run db-restore: compressed pg_dump out of the dockerized Postgres into backups/ and the matching (confirmed, destructive) restore - the missing piece for a nightly-backup cron.
  • Forgejo-style footer on every page: app version (links to About), server/client page render time, a themed language switcher, a dark-mode toggle and the API docs link (moved out of the landing footer). The login/signup pages' standalone language/theme controls are gone - the footer covers them.
0.15.02026-06-09
Added
  • Identifier-first login with SSO domain capture: enter your email, Continue either redirects straight to your IdP or reveals the password field. /login?password=1 skips capture (IdP-outage escape hatch for password accounts).
  • SSO providers can capture several email domains (comma-separated, subdomains included); domains already captured by another provider are rejected.
  • SSO providers can be edited in place from the admin page (type and provider id stay fixed; blank secrets keep their stored value; OIDC endpoints re-resolve from discovery on save).
  • The admin SSO form shows everything the IdP side needs as it is filled: OIDC redirect URI, scopes and claims, SAML ACS URL, SP entity ID and a link to the generated SP metadata XML.
  • Admins can hide any user from the leaderboard (eye toggle in the user list); hidden players keep playing and still count in crowd totals. create-admin accounts start hidden.
  • Enter or space in a score input saves and hops home → away → next match, so a whole matchday can be typed without the mouse.
Fixed
  • An SSO sign-in whose email matches an existing password account now links to it (one merged account) instead of failing with account_not_linked.
  • SSO-managed accounts (no local password) no longer see or can reach email change, password, 2FA or passkey management - the IdP owns those; enforced server-side too.
  • Mobile: the FAQ scoring formula no longer forces page-wide horizontal scroll (it scrolls inside its own box), and the page-background gap between an opened FAQ question and its answer is gone.
  • Mobile: the pinned title banner showed a few giant cropped letters (a 19:1 desktop strip cover-cropped to phone width) - fixed by the banner rework under Changed.
  • Mobile: substitution rows no longer overflow the match timeline card; the form tab keeps scores on one line and tucks the competition behind the tappable (dotted-underlined) date.
  • The mobile header nav signals its horizontal overflow with edge fades and scrolls the active link into view.
Changed
  • In-flight API requests are aborted when leaving a page or switching competition (vue-query signals, crowd totals, and the match page's slow FIFA-backed lazy fetches).
  • SSO providers have an optional display name shown to players; SP metadata for a SAML provider can be downloaded before the provider is saved (the IdP side usually needs it first); domain-capture conflicts are now checked in both subdomain directions.
  • Forgot-password flow: reset link from the login page (mail via SMTP), with better-auth creating the local password on reset - this is also the recovery path for users whose SSO provider was deleted. SSO-managed accounts never receive reset mails.
  • Signing up with a password on an SSO-captured domain warns first (with the provider's display name) and requires an explicit "continue anyway".
  • The SSO plugin's own HTTP provider-management endpoints (register/update/delete) are blocked; provider management goes through the admin API only.
  • "SSO-managed" now means a still-registered provider: deleting a provider releases its users, who regain credential management and can set a password via the reset flow.
  • A successful SSO sign-in removes the account's local password (the IdP becomes authoritative); the reset flow is the way back if the provider ever goes away. Admins can also unlink any user from SSO.
  • The admin user list moved its per-user actions into a kebab menu (promote, hide, remove 2FA, unlink SSO, ban, delete) with inline status indicators.
  • Admins are exempt from the SSO password nuke: their password is break-glass access for deleting a broken provider (ultimate fallback: mise run create-admin from the host).
  • With SMTP configured, account deletion is confirmed through a mailed link (works for SSO accounts too - no more one-click deletion); without SMTP the password / fresh-session confirmation stays. The mailed link replaces the TOTP requirement, since it proves mailbox ownership.
  • The landing banner is now one inline SVG for the whole scroll journey, with scroll-driven knobs instead of artwork swaps: the planet scales down in place to stay whole inside the shrinking bar, the subtitle fades out, and the title nudges down so it never clips. Any swap between the two differently-composed banner artworks read as the title jumping sideways; now nothing swaps.
0.14.02026-06-08
Added
  • mise run create-admin <email> [name] provisions an admin on demand: prompts for the password (hidden, never in shell history or the process list), signs up via better-auth (HIBP-checked + hashed), then sets the DB role; idempotent. No default admin password exists - this or NUXT_ADMIN_EMAILS bootstraps the first admin.
Fixed
  • UEFA match assists showed the beaten goalkeeper instead of the assister (a goal event's secondaryActor is the keeper); real assists are separate ASSIST events, now paired to goals by minute. Penalties correctly show no assist.
  • UEFA own goals were never detected (marked as a GOAL with subType 'OWN', not type OWN_GOAL) - 0 recorded across Euro 2024 and each miscredited to the scorer's team; now detected, credited to the beneficiary, with the forcing player's assist.
  • Admin import/sync now invalidates the client query cache, so a previously-loaded (e.g. empty) competition no longer keeps showing stale data until a manual refresh.
Changed
  • Dropped dead config (NUXT_MATCH_PROVIDER, NUXT_FIFA_SEASON_ID, NUXT_WC_SEASON): provider and season are per-competition (DB / live FIFA seasons API), the env vars were never read.
Security / ops
  • Postgres no longer publishes a host port in the prod compose base (the app reaches it in-network); host access for local dev moved to the dev overlay, bound to loopback. The app binds to 127.0.0.1 (put a reverse proxy in front).
  • Slimmed the Docker build context so editing compose files, docs, scripts or tests no longer busts the build cache; removed an accidentally-committed curl cookie jar.
0.13.02026-06-08
Fixed
  • Crowd totals genuinely refresh on a competition switch now: the three consumers shared one static useFetch key, so Nuxt served the previous competition's cached payload. Rewritten as a plain ref + explicit refetch on (preference, competition) change; locked by a component test.
0.12.02026-06-08
Added
  • Component-test harness (@nuxt/test-utils): a nuxt-env Vitest project mounts components/composables with auto-imports + PrimeVue via mountSuspended (pnpm test:components, wired into the release gate and mise check).
Changed
  • God-components split, logic extracted to tested units. account.vue (558->421 lines): the 2FA enrol/disable/regenerate state machine is now useTwoFactor (8 tests) and passkey management usePasskeys (5 tests); image resize moved to app/utils/image.ts. The match view's timeline assembly and head-to-head tally moved to a pure app/utils/match-view.ts (unit-tested). The coverage gate stays on the node logic surface (98.3%); components are covered by their own suite.
0.11.02026-06-08
Added
  • pnpm typecheck gate (strict vue-tsc) wired into the release gate and mise run check - the type-safety net that was configured but never run. Client types (MyPrediction/MatchListItem/LeaderboardRow) now derive from the server query return types (via a Serialized<> helper), so they can't drift from the schema.
  • Runtime request validation: a defineValidatedHandler wrapper (auth guard + Zod body parse + error mapping) on the prediction/joker/champion writes, making the OpenAPI schemas load-bearing (422 on bad input). Handler-level and auth-guard tests added.
Fixed
  • The finalize tick is now one atomic transaction (lock/unlock, scoring, champion awards, voids) - a crash mid-tick can no longer zero champion points or half-score a round.
  • Several latent bugs the new typecheck surfaced: session .value access (the "you" highlight / authed flag were always falsy), predictions never selected penalties (pens never rendered on picks), prediction inputs coerced undefined->NaN.
Changed
  • Dedup + structure: shared provider stage ladder (one ordered table - fixes the fifa/uefa divergence), shared getJson envelope in the FIFA provider, predictionHits scoring predicate, rowFromPerspective h2h decode, AppStage helpers (isSingleMatchStage/countsDouble), semantic colour tokens (--ng-star/--ng-danger/--ng-success).
  • Hardening: 2FA-delete hard-fails on a missing auth secret instead of decrypting against ''; the encrypted-adapter no longer treats a corrupt sealed envelope as legacy plaintext; the scoring-config seed includes championBonus.
0.10.02026-06-08
Fixed
  • Crowd totals refresh when you switch competition (they were stuck showing "–" for the new competition's matches).
  • Tech-stack cards mangled every third entry (monospace, tiny text): the card was an anchor with the license badge as a nested anchor - invalid HTML that Firefox split, leaking the badge style. Card is now a div with a stretched project link and a sibling badge link.
Changed
  • Prediction points reconcile with the joker/final multiplier: a "+N rarity" chip ("only X% picked this") plus a "×2" badge when the joker or the final doubled the score, so the breakdown matches the total.
Added
  • Scoring is now spelled out: predictions show the base points and a separate "+N rarity" chip with an "only X% picked this" tooltip; champion-pick points appear on the leaderboard and player pages; the FAQ carries the full formula in plain notation.
Changed
  • A joker can't be placed on a fixture whose teams aren't decided yet (same rule as predicting it), server-enforced and hidden in the UI.
Added
  • Champion picks visible at a glance: crowned flag beside each name on the ranking and on player pages; player pages gained a competition switcher and a Global scope.
  • Landing showcase is a carousel (circular, autoplay) and the map screenshot now actually shows the map.
Changed
  • Single-match rounds have no joker: the final automatically counts double for everyone (badge says so), the third-place play-off scores normally; placing a joker there is rejected server-side.
Fixed
  • Stats skeleton no longer fights the already-loaded possession bar (possession sits above the skeleton, which lost its fake bar).
Added
  • Landing showcase: six real screenshots (fixtures with crowd totals, match depth, ranking, bracket, map, team page) over a seeded league of two dozen demo oracles; mise tasks seed-demo and shots regenerate everything with headless Firefox.
0.9.02026-06-08
Added
  • Crowd totals update live over the WebSocket (anyone saving a prediction refreshes everyone's view, your own saves included) and reserve their line so cards never resize.
  • Header crystal ball is bigger, includes the pedestal, and each section glows gold under the cursor (five panels, core, and the orb's outer ring).
  • Real 404 page: the shot sails over the bar and becomes a star (clean loop), with a cursor-reactive starfield; the landing starfield got a gravitational lens and the champion pick a holographic hover.
  • "Show everyone's totals" preference: under each prediction input, the combined score of all players' predictions (1-1 + 2-1 + 4-0 shows as 7-2) with the prediction count - on fixtures, the match view and My Picks.
  • Stats tab shows skeletons while match detail loads.
0.8.02026-06-08
Added
  • All-time head-to-head on the match view, sourced from FIFA's full international calendar (World Cups, qualifiers, continental championships, friendlies - back to 1908 where FIFA has it). Works before kickoff, so it doubles as a prediction tool. Tally + goals line + meeting list, linked to our match pages where we hold the fixture.
  • Form shows each team's last five results across ALL international football (friendlies and qualifiers included), with competition and date.
  • Next lists the team's competition games after the viewed match - results shown form-style for games played since.
  • Live-goal celebration: when a live match's score increases, a pixel-art first-person goal animation takes over for three seconds (contributed artwork; reduced-motion respected).
  • Match-page dates include the year (head-to-head reaches back decades).
Changed
  • Head-to-head, Form and the in-house meeting list all cut off at the viewed match's kickoff - the future never dictates the past.
  • The head-to-head tab is always visible; pairs with no recorded meeting get a "first meeting" note instead of a silently missing tab.
  • All commit history rewritten to the Arzaroth identity.
Fixed
  • Knockout brackets aligned feeder matches under the wrong parents (FIFA lists knockout matches arbitrarily; Morocco and Brazil sat on the wrong sides). A shared ordering pass now walks down from the final for every provider.
  • Bracket cards showed (0) penalty scores on matches decided in regulation; pens render as superscripts only for real shootouts.
  • The final is pinned to the semis' midline; connector lines merge mid-gap and lead straight into the next fixture; dates centered on every card.
  • Euro bracket cards showed "Invalid Date" (UEFA bracket matches lacked kickoff times).
  • Match players tab lists contributors only instead of full 26-man rosters of zeros.
  • About: Motion's own mark replaces the Framer design-tool logo; official favicons for Nuxt I18n, node-postgres, Nodemailer, maildev.
0.7.12026-06-08
Added
  • Euro per-match statistics now come from UEFA's official match-centre feed (possession, passes, crosses, distance covered) with event-stream aggregation as fallback.
  • Euro knockout bracket, derived from results (feeders ordered under their parents, champion crowned).
  • Full player rankings for Euro (paged; previously cut at 200, hiding most of any squad on match pages).
  • Finished matches cache their detail and stats for the process lifetime (live ones still refresh every 5 minutes).
Changed
  • Player and coach names render in title case everywhere (FIFA's "Kylian MBAPPÉ" becomes "Kylian Mbappé"; correctly-cased names pass through).
Fixed
  • Coach bookings showed "?" - touchline cards (Nagelsmann, Hjulmand) now carry the coach's name and a clipboard marker, on both providers.
  • Second yellows on Euro matches were dropped (UEFA encodes them as explicit YELLOW_CARD_SECOND / RED_YELLOW_CARD events).
  • Euro matches synced before the stats feed landed had no possession - backfilled.
Added (UX)
  • Match view remembers its open tab in the URL (survives refresh, shareable); stats is the default tab when available.
  • Substitutions on the match timeline (on/off players, both providers) with persisted toggles to hide subs or bookings.
  • Auto-generated API documentation at /docs/api: every route annotated (summaries, descriptions, request bodies, response codes), framework internals filtered out, admin endpoints labeled internal, httpie as the default snippet client; GET responses carry real schemas and examples sampled from the live API.
  • Head-to-head tally bar on the match view (wins / draws / wins, shootouts counted as wins).
  • Public info pages (About, License) share one shell (starfield + footer); footer and About link the now-public source repository.
  • /license page rendering the WTFPL, linked from the footer; the footer (with its own-line "Made with ♥️ from 🇫🇷") is shared with the About page.
  • About: VueUse, Nuxt I18n and node-postgres added to the stack.
  • Fixture search is accent-insensitive and matches country codes ("Tur" finds Türkiye, "FRA" finds France).
  • About: theme-aware mise logo, official TanStack icon, project homepages preferred over GitHub links.
0.7.02026-06-07
Added
  • Euro 2024 feature parity with the FIFA competitions: match events (goals with assists, yellow / second-yellow / red cards), per-match stats derived from UEFA's event stream, official squads with positions, season team stats (UEFA-exact), and top scorers / assisters from UEFA's ranking API.
  • World Cup 2026 announced squads now show before the tournament (team id resolved from the calendar when no match has been played).
  • About page: official logos on every stack card, Bun in the stack.
Changed
  • Competitions ordered newest season first everywhere.
  • VueUse adopted where it simplifies: reactive QR rendering for 2FA enrollment, clipboard, tickers (countdowns, next-run labels), system dark-mode detection.
Fixed
  • Team page competition switcher had no defined order.
0.6.02026-06-07
Added
  • UEFA Euro 2024 fixtures and results through UEFA's public match API (groups, knockouts, penalty shootouts).
  • Two-factor authentication: TOTP authenticator enrollment with QR + setup key, single-use backup codes (confirmed save step, regenerate on demand), trusted devices with revocation, email codes via SMTP.
  • Passkey (WebAuthn) sign-in and management, registration gated behind a fresh password + 2FA confirmation (sudo mode).
  • Have-I-Been-Pwned checks rejecting breached passwords at signup and password change.
  • Admin: ban/unban users, strip 2FA from an account, per-task last-run/last-failure history in the action tooltips, live next-run countdowns.
  • 2FA-gated and last-admin-protected account termination.
  • maildev mail catcher in the dev compose overlay; email-OTP end-to-end test (pnpm e2e:smtp).
  • WTFPL license, coverage badge, this changelog, mise task shortcuts, pinned container images.
Fixed
  • NUXT_CRON_ENABLED=true was coerced to a boolean by the runtime config and silently disabled live score polling.
0.5.02026-06-07
Added
  • Per-user preferences (language, theme incl. system) saved to the account and restored at sign-in; browser/system detection for guests.
  • Thai and Klingon translations alongside English and French.
  • Brand identity: crystal-ball mark, favicon, full-bleed remastered banner with a scroll-driven intro that docks into a slim pinned bar, animated starfield, oracle-eye default avatars.
  • Landing page with feature grid, scoring explainer, and competition showcase.
Fixed
  • First-load hydration failure that kept client-only effects (starfield, banner scrub) from running until a client-side navigation.
0.4.02026-06-06
Added
  • Competition in the URL (/world-cup-2026/matches, …) with a page-title switcher pill; unknown slugs 404.
  • Runtime SSO administration (OIDC, SAML, Google) with envelope-encrypted secrets (KEK -> DEK -> AES-256-GCM).
  • Admin user management (create, promote/demote, delete) on the better-auth admin plugin.
  • Full team pages: official squads with positions and coach, FIFA-exact tournament stats, competition switcher.
  • Match view: football-intelligence stat rows (attempts, passes, distance covered, pressures…), chronological laced timeline with yellow/second-yellow/red cards, editable prediction in place, clickable form/next/head-to-head/standings.
  • World map: team in the URL, selection surviving competition switches, click-to-center, clickable group standings.
  • Personal stats strip on My Picks; leaderboard movement arrows from rank snapshots; kickoff countdowns.
Fixed
  • FIFA card codes decoded correctly (2 = straight red, 3 = second yellow).
  • Penalty-shootout artifacts (0-0 "shootouts") purged at the source and guarded everywhere.
0.3.02026-06-06
Added
  • One ×2 joker per round (movable until kickoff), crowd-rarity bonus, champion pick with country flags.
  • Interactive world map (Leaflet + OpenStreetMap) with per-team panels.
  • Knockout bracket as a real two-sided tree from FIFA's season bracket.
  • Keyless FIFA top scorers, match details (goals, possession, attendance, cards) and live WebSocket score pushes.
0.2.02026-06-05
Added
  • Multi-competition schema: World Cup 2026 (default), World Cup 2022, Euro 2024; per-competition and global leaderboards.
  • Scheduled tasks: hourly fixture refresh, 2-minute live polling gated on a live window, 5-minute finalize (lock + score).
  • i18n (EN/FR), dark mode, redesigned shell.
0.1.02026-06-05
Added
  • Nuxt 4 + PrimeVue + UnoCSS scaffold, Drizzle + Postgres, better-auth email/password.
  • Closeness-tiered scoring engine (exact 3 / goal difference 2 / outcome 1) with idempotent re-scoring.
  • Fixtures, predictions with server-side kickoff locks, leaderboard, admin sync; Vitest suite with a 95% (later 98%) coverage gate.