Players become first-class: profile pages, unified names, deeper match data
- /player/:name pages assembled entirely from stored data: tournament totals, per-match log (rating, minutes, G/A, cards), aggregated shot map, suspension status, next-match scorer line. Either source's spelling resolves to the same page (token-set + romanization fold). - Player names are now links everywhere: stats boards, golden boot, discipline, injuries, all-time scorers, lineups, the pitch dots, timeline events and assists, and the search palette (diacritic-folded). - Golden Boot displays the FotMob form of each name — no more "Hwang In-Beom" and "In-Beom Hwang" on the same page. - Match pages: detailed full-time stats card (passes, duels, xGOT… from the stored FotMob report), O/U + spread closing line, "Who scores?" pre-match props board, and a FIFA-confirmed-lineups badge. - Stats hub: new boards — xA, chances created, save percentage, saves per 90. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { fmt, useFormat, useT } from '@/lib/i18n';
|
||||
import { hostAdvantage } from '@/lib/model/hosts';
|
||||
import { knockoutAdvanceProb, predictMatch, type RatingsModel } from '@/lib/model/predict';
|
||||
import { modelBracket, projectedSeeds, teamPath } from '@/lib/whatif';
|
||||
import { PlayerLink } from '@/components/PlayerLink';
|
||||
import type { Fixture, TeamProfile } from '@/lib/types';
|
||||
|
||||
const pct = (x: number | null) => (x == null ? '—' : `${(x * 100).toFixed(1)}%`);
|
||||
@@ -278,7 +279,7 @@ export function TeamProfilePage() {
|
||||
<ul className="space-y-1.5">
|
||||
{profile.injuries.map((i) => (
|
||||
<li key={i.player} className="flex items-center gap-2 text-sm">
|
||||
<span className="truncate text-ink-soft">{i.player}</span>
|
||||
<PlayerLink name={i.player} className="truncate text-ink-soft transition-colors hover:text-accent" />
|
||||
<span className="ml-auto shrink-0 text-[11px] text-faint">{i.reason ?? i.status ?? ''}</span>
|
||||
</li>
|
||||
))}
|
||||
@@ -293,7 +294,7 @@ export function TeamProfilePage() {
|
||||
<ul className="space-y-1.5">
|
||||
{profile.discipline.map((p) => (
|
||||
<li key={p.player} className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<span className="truncate text-ink-soft">{p.player}</span>
|
||||
<PlayerLink name={p.player} className="truncate text-ink-soft transition-colors hover:text-accent" />
|
||||
<span className="flex items-center gap-0.5" aria-hidden>
|
||||
{Array.from({ length: p.yellows }).map((_, i) => <span key={`y${i}`} className="inline-block h-3.5 w-2.5 rounded-[2px] bg-gold" />)}
|
||||
{Array.from({ length: p.reds }).map((_, i) => <span key={`r${i}`} className="inline-block h-3.5 w-2.5 rounded-[2px] bg-loss" />)}
|
||||
@@ -320,7 +321,7 @@ export function TeamProfilePage() {
|
||||
{profile.keyPlayers.map((p, i) => (
|
||||
<li key={p.name} className="flex items-center gap-2 text-sm">
|
||||
<span className="w-4 text-right text-xs font-bold text-faint">{i + 1}</span>
|
||||
<span className="truncate text-ink-soft">{p.name}</span>
|
||||
<PlayerLink name={p.name} className="truncate text-ink-soft transition-colors hover:text-accent" />
|
||||
<span className="tnum ml-auto font-semibold text-ink">{p.goals}</span>
|
||||
{p.pens ? <span className="text-[11px] text-faint">{fmt(t.common.pensShort, { n: p.pens })}</span> : null}
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user