wcwBracketSeeds = window.wcMaps.r32Seeds
{
if (_wcGroups == null || _wcSimulation == null) return html`<p class="wcw-loading">Data failed to load — try refreshing (see console for details).</p>`
const statsEscL = window.statsEsc
// Seed → predicted team (pencil), via group projections.
const byGroup = new Map()
const teamToGroup = new Map() // for bracket team click-throughs
for (const r of _wcGroups) {
if (!byGroup.has(r.group)) byGroup.set(r.group, [])
byGroup.get(r.group).push(r)
teamToGroup.set(r.team, r.group)
}
const seedToTeam = new Map()
// 1X / 2X: per-group predicted winner / runner-up. The 2X pick EXCLUDES the
// 1X team — a dominant favourite can top both win_group and runner_up, and
// without the exclusion it would occupy both seeds (same one-slot-per-team
// rule as the title-race bracket). 3rd excludes both.
const groupThird = new Map()
for (const [g, rows] of byGroup.entries()) {
const w = [...rows].sort((a, b) => b.win_group - a.win_group)[0]?.team || "?"
const r = [...rows].filter(x => x.team !== w)
.sort((a, b) => b.runner_up - a.runner_up)[0]?.team || "?"
seedToTeam.set("1" + g, w)
seedToTeam.set("2" + g, r)
const third = [...rows].filter(x => x.team !== w && x.team !== r)
.sort((a, b) => (b.third ?? 0) - (a.third ?? 0))[0]
if (third) groupThird.set(g, { g, team: third.team, score: third.third ?? 0 })
}
// Best-third qualification + slot assignment. FIFA fields the 8 best
// 3rd-placed teams into fixed composite slots ("3A/B/C/D/F" = a 3rd from
// one of those five groups — FIFA's published possibility sets). Take the
// 8 best thirds (by model `third`), then let wcMaps.assignThirdSlots place
// DISTINCT teams into the slots via maximum matching — a perfect
// assignment exists for every qualifying combination by FIFA's design.
const qualifiers = [...groupThird.values()].sort((a, b) => b.score - a.score).slice(0, 8)
{
const assigned = window.wcMaps.assignThirdSlots(qualifiers)
for (const s of wcwBracketSeeds.flat()) {
if (s.startsWith("3") && s.includes("/")) seedToTeam.set(s, assigned.get(s)?.team || "?")
}
}
const champByTeam = new Map(_wcSimulation.map(t => [t.team, t.p_champ]))
const pick = (a, b) => {
if (a === "?" ) return b
if (b === "?") return a
return (champByTeam.get(b) ?? 0) > (champByTeam.get(a) ?? 0) ? b : a
}
// Build the rounds bottom-up. R32 (16 matches → 32 boxes), then derive.
const R32 = wcwBracketSeeds.map(([s1, s2]) => ({ a: seedToTeam.get(s1) || s1, b: seedToTeam.get(s2) || s2 }))
const advance = arr => {
const out = []
for (let i = 0; i < arr.length; i += 2) {
const w1 = arr[i].w ?? pick(arr[i].a, arr[i].b)
const w2 = arr[i+1].w ?? pick(arr[i+1].a, arr[i+1].b)
out.push({ a: w1, b: w2 })
}
return out
}
R32.forEach(m => m.w = pick(m.a, m.b))
const R16 = advance(R32); R16.forEach(m => m.w = pick(m.a, m.b))
const QF = advance(R16); QF.forEach(m => m.w = pick(m.a, m.b))
const SF = advance(QF); SF.forEach(m => m.w = pick(m.a, m.b))
const FIN = advance(SF); FIN.forEach(m => m.w = pick(m.a, m.b))
const champion = FIN.length ? pick(FIN[0].a, FIN[0].b) : "?"
// Render: 5 columns of match cards + a champion plinth. CSS flex/grid
// handles the vertical centring between rounds (cleaner than SVG geometry
// for a poster, and prints as real text).
const flagMini = t => t && t !== "?" ? wcwFlagImg(t, 20) : `<span class="wcw-flag wc-flag-blank" style="width:20px"></span>`
const teamLine = (t, isWinner) => {
const known = t && t !== "?"
const g = known ? teamToGroup.get(t) : null
const nameInner = known ? statsEscL(wcwShort(t)) : "—"
const name = g
? `<a href="world-cup-group.html#group=${g}" class="wcw-bk-link" title="Group ${g} — ${statsEscL(t)}">${nameInner}</a>`
: `<span>${nameInner}</span>`
return `<div class="wcw-bk-team${isWinner ? " is-win" : ""}${known ? "" : " is-tbd"}">${flagMini(t)}${name}</div>`
}
const matchCard = m =>
`<div class="wcw-bk-match">${teamLine(m.a, m.w === m.a)}${teamLine(m.b, m.w === m.b)}</div>`
const col = (label, matches) => {
const c = document.createElement("div")
c.className = "wcw-bk-col"
c.innerHTML = `<div class="wcw-bk-colhead">${label}</div>` + matches.map(matchCard).join("")
return c
}
const wrap = document.createElement("div")
const h = document.createElement("div")
h.className = "wcw-bk-title"
h.innerHTML = `<span>The Knockouts</span><em>model's most-likely path in pencil · results ink in as they land</em>`
wrap.appendChild(h)
const board = document.createElement("div")
board.className = "wcw-bracket"
board.appendChild(col("Round of 32", R32))
board.appendChild(col("Round of 16", R16))
board.appendChild(col("Quarter-finals", QF))
board.appendChild(col("Semi-finals", SF))
board.appendChild(col("Final", FIN))
const champCol = document.createElement("div")
champCol.className = "wcw-bk-col wcw-bk-champcol"
champCol.innerHTML = `<div class="wcw-bk-colhead">Champion</div>
<div class="wcw-champ">
<div class="wcw-trophy">${wcwTrophySvg()}</div>
<div class="wcw-champ-flag">${champion !== "?" ? wcwFlagImg(champion, 40) : ""}</div>
<div class="wcw-champ-name">${champion !== "?" ? statsEscL(champion) : "— — —"}</div>
<div class="wcw-champ-tag">predicted</div>
</div>`
board.appendChild(champCol)
wrap.appendChild(board)
const note = document.createElement("p")
note.className = "wcw-bk-note"
note.innerHTML = `Bracket structure is FIFA's official 2026 bracket (12 group winners + 12 runners-up + 8 best third-placed, matches 73–104); <em>3X/Y/Z</em> slots show FIFA's allowed source groups until the thirds settle. Predicted advancers use champion probability from the 10,000-tournament simulation.`
wrap.appendChild(note)
const asAt = await wcwAsAt
wrap.appendChild(window.editorial.tableSource({
source: "pannadata",
sourceUrl: "https://github.com/peteowen1/pannadata",
license: "CC BY 4.0",
asAt: asAt || "Refreshed weekly",
hint: "Predicted advancers · FIFA 2026 bracket"
}))
return wrap
}