diff --git a/src/components/GroupTable.tsx b/src/components/GroupTable.tsx
index a22ac2b..c02c9ba 100644
--- a/src/components/GroupTable.tsx
+++ b/src/components/GroupTable.tsx
@@ -1,3 +1,4 @@
+import { Link } from '@tanstack/react-router';
import { cn } from '@/lib/cn';
import { teamFlag } from '@/lib/teams';
import type { StandingRow } from '@/lib/types';
@@ -30,7 +31,7 @@ export function GroupTable({ group, rows }: { group: string; rows: StandingRow[]
)}
>
-
+
{teamFlag(r.team)}
{r.team}
-
+
|
{r.played} |
diff --git a/src/components/MatchCard.tsx b/src/components/MatchCard.tsx
index 8fa4c66..7a9161b 100644
--- a/src/components/MatchCard.tsx
+++ b/src/components/MatchCard.tsx
@@ -1,9 +1,28 @@
import { Link } from '@tanstack/react-router';
import { cn } from '@/lib/cn';
+import { teamFlag } from '@/lib/teams';
import type { Fixture } from '@/lib/types';
import { kickoffTime, relativeKickoff } from '@/lib/format';
+import { useTournamentStore } from '@/stores/tournamentStore';
import { TeamLabel } from './TeamLabel';
+/** The model's pre-match lean for scheduled matches — the "expectation" hint. */
+function ModelHint({ num }: { num: number }) {
+ const pred = useTournamentStore((s) => s.model?.matches.find((m) => m.num === num));
+ if (!pred) return null;
+ const { home, draw, away } = pred.probs;
+ const drawLikely = draw >= home && draw >= away;
+ const homeFav = home >= away;
+ const side = drawLikely ? null : homeFav ? pred.home : pred.away;
+ const p = drawLikely ? draw : Math.max(home, away);
+ return (
+
+ model: {side ? <>{teamFlag(side)} {side} > : 'draw '}
+ {Math.round(p * 100)}%
+
+ );
+}
+
const STAGE_LABEL: Record = {
group: 'Group',
r32: 'Round of 32',
@@ -62,6 +81,7 @@ export function MatchCard({ f }: { f: Fixture }) {
+ {f.status === 'scheduled' && }
{f.venue}
);
diff --git a/src/components/OddsTable.tsx b/src/components/OddsTable.tsx
index 32dea25..4ca85de 100644
--- a/src/components/OddsTable.tsx
+++ b/src/components/OddsTable.tsx
@@ -1,3 +1,4 @@
+import { Link } from '@tanstack/react-router';
import { teamFlag } from '@/lib/teams';
import { cn } from '@/lib/cn';
import type { TeamOdds } from '@/lib/types';
@@ -42,10 +43,10 @@ export function OddsTable({ odds }: { odds: TeamOdds[] }) {
{odds.map((o) => (
|
-
+
{teamFlag(o.team)}
{o.team}
-
+
|
{cols.map((c) => (
|
diff --git a/src/features/bracket/BracketPage.tsx b/src/features/bracket/BracketPage.tsx
index 3e87354..7adff9e 100644
--- a/src/features/bracket/BracketPage.tsx
+++ b/src/features/bracket/BracketPage.tsx
@@ -1,4 +1,5 @@
import { useMemo } from 'react';
+import { Link } from '@tanstack/react-router';
import { PageHeader } from '@/components/ui/PageHeader';
import { useTournamentStore } from '@/stores/tournamentStore';
import { teamFlag } from '@/lib/teams';
@@ -51,7 +52,7 @@ function BracketMatch({ f, probs }: { f: Fixture; probs?: MatchProbs | undefined
const decided = f.status === 'finished' && f.homeScore !== null && f.awayScore !== null;
const showProbs = !!probs && !!f.home.team && !!f.away.team && f.status !== 'finished';
return (
-
+
f.awayScore!} />
f.homeScore!} />
@@ -59,7 +60,7 @@ function BracketMatch({ f, probs }: { f: Fixture; probs?: MatchProbs | undefined
M{f.num} · {f.status === 'finished' ? 'FT' : `${kickoffDay(f.kickoff)} ${kickoffTime(f.kickoff)}`}
-
+
);
}
diff --git a/src/features/groups/GroupsPage.tsx b/src/features/groups/GroupsPage.tsx
index 6391c88..f1e901e 100644
--- a/src/features/groups/GroupsPage.tsx
+++ b/src/features/groups/GroupsPage.tsx
@@ -1,3 +1,5 @@
+import { Link } from '@tanstack/react-router';
+import { Users } from 'lucide-react';
import { PageHeader } from '@/components/ui/PageHeader';
import { GroupTable } from '@/components/GroupTable';
import { useTournamentStore } from '@/stores/tournamentStore';
@@ -12,6 +14,11 @@ export function GroupsPage() {
+ All 48 teams
+
+ }
/>
{groups.length ? (
diff --git a/src/features/predictions/PredictionsPage.tsx b/src/features/predictions/PredictionsPage.tsx
index 592c72d..4a2797d 100644
--- a/src/features/predictions/PredictionsPage.tsx
+++ b/src/features/predictions/PredictionsPage.tsx
@@ -1,5 +1,6 @@
import { lazy, Suspense, useMemo } from 'react';
-import { TrendingUp, Trophy } from 'lucide-react';
+import { Link } from '@tanstack/react-router';
+import { Gauge, TrendingUp, Trophy } from 'lucide-react';
import { PageHeader } from '@/components/ui/PageHeader';
import { Card, CardBody, CardHeader } from '@/components/ui/Card';
import { OddsTable } from '@/components/OddsTable';
@@ -64,6 +65,11 @@ export function PredictionsPage() {
+ How & how accurate
+
+ }
/>
diff --git a/src/features/team/TeamProfilePage.tsx b/src/features/team/TeamProfilePage.tsx
index 2d0053e..4a1de9c 100644
--- a/src/features/team/TeamProfilePage.tsx
+++ b/src/features/team/TeamProfilePage.tsx
@@ -52,8 +52,8 @@ export function TeamProfilePage() {
const s = profile.standing;
return (
-
- Groups
+
+ All teams
diff --git a/src/features/teams/TeamsPage.tsx b/src/features/teams/TeamsPage.tsx
new file mode 100644
index 0000000..d600a96
--- /dev/null
+++ b/src/features/teams/TeamsPage.tsx
@@ -0,0 +1,51 @@
+import { useMemo } from 'react';
+import { Link } from '@tanstack/react-router';
+import { PageHeader } from '@/components/ui/PageHeader';
+import { teamFlag } from '@/lib/teams';
+import { useTournamentStore } from '@/stores/tournamentStore';
+
+const pct = (x: number) => (x >= 0.995 ? '>99%' : x < 0.005 ? '—' : `${(x * 100).toFixed(x < 0.1 ? 1 : 0)}%`);
+
+export function TeamsPage() {
+ const model = useTournamentStore((s) => s.model);
+ const snapshot = useTournamentStore((s) => s.snapshot);
+
+ const teams = useMemo(() => {
+ const groupOf = new Map();
+ for (const [g, rows] of Object.entries(snapshot?.tables ?? {})) for (const r of rows) groupOf.set(r.team, g);
+ return (model?.odds ?? []).map((o) => ({ ...o, group: groupOf.get(o.team) ?? null }));
+ }, [model, snapshot]);
+
+ return (
+
+
+ {teams.length ? (
+
+ {teams.map((t, i) => (
+
+ {i + 1}
+ {teamFlag(t.team)}
+
+ {t.team}
+ {t.group ? `Group ${t.group}` : ''}
+
+
+ {pct(t.champion)}
+ advance {pct(t.qualify)}
+
+
+ ))}
+
+ ) : (
+
+ {Array.from({ length: 9 }).map((_, i) => )}
+
+ )}
+
+ );
+}
diff --git a/src/router.tsx b/src/router.tsx
index 76aacae..6f677c1 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -9,6 +9,7 @@ import { StoryPage } from './features/story/StoryPage';
import { MatchPreviewPage } from './features/match/MatchPreviewPage';
import { TeamProfilePage } from './features/team/TeamProfilePage';
import { MethodologyPage } from './features/methodology/MethodologyPage';
+import { TeamsPage } from './features/teams/TeamsPage';
const rootRoute = createRootRoute({
component: RootLayout,
@@ -23,8 +24,9 @@ const storyRoute = createRoute({ getParentRoute: () => rootRoute, path: '/story'
const matchRoute = createRoute({ getParentRoute: () => rootRoute, path: '/match/$num', component: MatchPreviewPage });
const teamRoute = createRoute({ getParentRoute: () => rootRoute, path: '/team/$name', component: TeamProfilePage });
const methodologyRoute = createRoute({ getParentRoute: () => rootRoute, path: '/methodology', component: MethodologyPage });
+const teamsRoute = createRoute({ getParentRoute: () => rootRoute, path: '/teams', component: TeamsPage });
-const routeTree = rootRoute.addChildren([indexRoute, groupsRoute, bracketRoute, predictRoute, storyRoute, matchRoute, teamRoute, methodologyRoute]);
+const routeTree = rootRoute.addChildren([indexRoute, groupsRoute, bracketRoute, predictRoute, storyRoute, matchRoute, teamRoute, methodologyRoute, teamsRoute]);
export const router = createRouter({ routeTree, defaultPreload: 'intent' });
|