diff --git a/app/components/Bracket.tsx b/app/components/Bracket.tsx
index b90f211..78b1670 100644
--- a/app/components/Bracket.tsx
+++ b/app/components/Bracket.tsx
@@ -12,7 +12,10 @@ function Side({
// fix = ein echtes Team steht fest (nicht vorläufig). Eigene Klasse für klares Styling.
const fix = s.teamId != null && !s.provisional;
return (
-
+
{s.teamId ? (
<>
diff --git a/app/globals.css b/app/globals.css
index ed0f651..e485afa 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -189,10 +189,15 @@ table.standings { width: 100%; border-collapse: collapse; }
font-family: var(--font-mono); font-size: 12px; color: var(--ink-dim);
}
.bracket-banner .v { font-family: var(--font-mono); font-size: 12px; color: var(--turf); }
-.bracket-scroll { overflow-x: auto; padding-bottom: 36px; }
+.bracket-scroll {
+ max-height: calc(100vh - 260px);
+ overflow: auto;
+ padding-bottom: 8px;
+ scrollbar-gutter: stable;
+}
.bracket {
display: flex; gap: 26px; min-width: max-content; align-items: stretch;
- padding-bottom: 16px;
+ padding-bottom: 8px;
}
.round { display: flex; flex-direction: column; min-width: 220px; }
.round-label {
diff --git a/lib/resolve-bracket.ts b/lib/resolve-bracket.ts
index b0b7e8f..8d02dcc 100644
--- a/lib/resolve-bracket.ts
+++ b/lib/resolve-bracket.ts
@@ -21,6 +21,7 @@ export interface ResolvedSide {
score: number | null;
isWinner: boolean;
provisional: boolean; // true = nur auf Basis aktueller Tabelle, noch nicht fix
+ tooltip: string | null; // Hover-Erklärung (Herkunft + ob fix)
}
export interface ResolvedTie {
@@ -64,19 +65,23 @@ function resolveR32Slot(
matches: Match[],
teams: Team[],
annexResolved: boolean,
-): { teamId: string | null; provisional: boolean } {
+): { teamId: string | null; provisional: boolean; tooltip: string | null } {
const table = (g: GroupId) => tables.find((t) => t.group === g);
if (slot.type === "W") {
const t = table(slot.group!);
const teamId = t?.rows.find((r) => r.rank === 1)?.teamId ?? null;
// Sieger fix, sobald Platz 1 rechnerisch gesichert ist (auch vor Gruppenende).
- return { teamId, provisional: !placeIsSecure(slot.group!, 1, teams, matches) };
+ const provisional = !placeIsSecure(slot.group!, 1, teams, matches);
+ const prefix = provisional ? "aktuell " : "";
+ return { teamId, provisional, tooltip: `${prefix}1. Gruppe ${slot.group}` };
}
if (slot.type === "R") {
const t = table(slot.group!);
const teamId = t?.rows.find((r) => r.rank === 2)?.teamId ?? null;
// Zweiter fix, sobald Platz 2 rechnerisch gesichert ist.
- return { teamId, provisional: !placeIsSecure(slot.group!, 2, teams, matches) };
+ const provisional = !placeIsSecure(slot.group!, 2, teams, matches);
+ const prefix = provisional ? "aktuell " : "";
+ return { teamId, provisional, tooltip: `${prefix}2. Gruppe ${slot.group}` };
}
// 3. Platz: braucht Annex-C-Zuordnung + den Gruppensieger dieses Spiels
if (slot.type === "3" && assignment && winnerGroup) {
@@ -86,16 +91,27 @@ function resolveR32Slot(
// Fix nur, wenn Annex C aufgelöst UND die Quellgruppe abgeschlossen ist
// (Drittplatzierten-Qualifikation ist gruppenübergreifend, bis zuletzt offen).
const fix = annexResolved && groupFinished(thirdGroup, matches);
- return { teamId: row?.teamId ?? null, provisional: !fix };
+ const provisional = !fix;
+ const prefix = provisional ? "aktuell " : "";
+ return {
+ teamId: row?.teamId ?? null,
+ provisional,
+ tooltip: `${prefix}3. der Gruppe ${thirdGroup}`,
+ };
}
}
- return { teamId: null, provisional: true };
+ if (slot.type === "3") {
+ const pool = slot.thirdPool?.join("") ?? "?";
+ return { teamId: null, provisional: true, tooltip: `aktuell 3. Gruppe ${pool}` };
+ }
+ return { teamId: null, provisional: true, tooltip: null };
}
function sideFrom(
teamId: string | null, fallbackLabel: string,
teams: Team[], feed: Match | undefined, which: "home" | "away",
provisional: boolean,
+ tooltip: string | null,
): ResolvedSide {
const team = teamId ? teams.find((t) => t.id === teamId) : undefined;
const score = feed
@@ -109,6 +125,7 @@ function sideFrom(
score,
isWinner: teamId != null && w === teamId,
provisional: teamId != null && provisional,
+ tooltip,
};
}
@@ -133,8 +150,8 @@ export function resolveBracket(
const h = resolveR32Slot(rm.home, winnerGroup, tables, assignment, thirds, matches, teams, annexResolved);
const a = resolveR32Slot(rm.away, winnerGroup, tables, assignment, thirds, matches, teams, annexResolved);
- const home = sideFrom(h.teamId, slotLabel(rm.home, assignment, winnerGroup), teams, feed, "home", h.provisional);
- const away = sideFrom(a.teamId, slotLabel(rm.away, assignment, winnerGroup), teams, feed, "away", a.provisional);
+ const home = sideFrom(h.teamId, slotLabel(rm.home, assignment, winnerGroup), teams, feed, "home", h.provisional, h.tooltip);
+ const away = sideFrom(a.teamId, slotLabel(rm.away, assignment, winnerGroup), teams, feed, "away", a.provisional, a.tooltip);
winners.set(rm.matchNumber, winnerOf(feed));
losers.set(rm.matchNumber, loserOf(feed));
@@ -156,8 +173,10 @@ export function resolveBracket(
const awayProv = !(decided.get(km.fromAway) ?? false);
const homeLabel = `${km.losers ? "Verlierer" : "Sieger"} ${km.fromHome}`;
const awayLabel = `${km.losers ? "Verlierer" : "Sieger"} ${km.fromAway}`;
- const home = sideFrom(homeId, homeLabel, teams, feed, "home", homeProv);
- const away = sideFrom(awayId, awayLabel, teams, feed, "away", awayProv);
+ const homeTtip = `${km.losers ? "Verlierer" : "Sieger"} aus Spiel ${km.fromHome}`;
+ const awayTtip = `${km.losers ? "Verlierer" : "Sieger"} aus Spiel ${km.fromAway}`;
+ const home = sideFrom(homeId, homeLabel, teams, feed, "home", homeProv, homeTtip);
+ const away = sideFrom(awayId, awayLabel, teams, feed, "away", awayProv, awayTtip);
winners.set(km.matchNumber, winnerOf(feed));
losers.set(km.matchNumber, loserOf(feed));
decided.set(km.matchNumber, feed?.status === "FINISHED");