William & Wall Presents

2026 World Cup Challenge

USA · Canada · Mexico  ·  11 June – 19 July 2026

🕐 Results last updated: Not yet updated
See any glitches? Please report them to us at admin@williamandwall.com.
Presented By
Futstrikers Club
Official Sponsor
Key Stats
Stats load once entries are submitted.
Full Standings
The flag beside each rank shows that player's predicted World Cup champion.
Loading...
Submissions Open
Your Details

Your Bracket Name is shown publicly on the leaderboard, along with your first name and last initial. Your last name, email, and phone number are kept private — phone is for admin verification only. One submission per email. Entries lock at the deadline.

1Group Stage Picks
2Knockout Bracket Picks
3Submit Predictions

Generate a complete bracket instantly. You can review and edit any pick before submitting.

Group stage (72 matches): Enter your predicted score. +1 pt correct result, +1 pt exact score. Draws allowed. Standings update live — your group winners auto-seed the bracket.
Best 3rd-Place Teams — top 8 advance to Round of 32 · updates as you enter scores
Fill in group scores above to see 3rd place rankings update live.
Knockout bracket (31 matches): Your bracket auto-seeds from your group stage picks. The team names shown reflect whoever you predicted to finish 1st/2nd/3rd in each group. For each match enter a score — if it's a draw you must pick a penalty winner. You earn win-prediction pts (2–6 by round) + 1 pt for exact score.
Final step — submit your predictions. Review your picks in Steps 1 and 2, confirm the statement below, then submit. Nothing is saved until you click the button.
Industrious
Are you a member of the Industrious coworking space? *

Additional terms, conditions, eligibility requirements, and prizes may apply.

More Information

Supplemental stats and references for the tournament. For the full live standings, see the Home tab.

Current Standings — Top 5
No entries yet — be the first!

Showing the top 5 — see the Home tab for the full standings.

Most Picked Winners
Loading...
FIFA World Ranking

As of 6/2/2026. Men’s national teams in the 2026 World Cup. The left column ranks them within the field; the right column is each team’s actual FIFA world ranking. (Next official FIFA update: 11 June 2026.)

#TeamWorld rank
1France#1
2Spain#2
3Argentina#3
4England#4
5Portugal#5
6Brazil#6
7Netherlands#7
8Morocco#8
9Belgium#9
10Germany#10
11Croatia#11
12Colombia#13
13Senegal#14
14Mexico#15
15USA#16
16Uruguay#17
17Japan#18
18Switzerland#19
19Iran#21
20Türkiye#22
21Ecuador#23
22Austria#24
23South Korea#25
24Australia#27
25Algeria#28
26Egypt#29
27Canada#30
28Norway#31
29Panama#33
30Ivory Coast#34
31Sweden#38
32Paraguay#40
33Czechia#41
34Scotland#43
35Tunisia#44
36DR Congo#46
37Uzbekistan#50
38Qatar#55
39Iraq#57
40South Africa#60
41Saudi Arabia#61
42Jordan#63
43Bosnia-Herz.#65
44Cape Verde#69
45Ghana#74
46Curaçao#82
47Haiti#83
48New Zealand#85
Odds to Win the World Cup

Outright title odds (American format), fixed at pre-tournament prices — a snapshot taken before kickoff that won’t change once play begins (BetMGM, late April 2026). “Win %” is the implied probability with the bookmaker margin removed, normalized to 100%.

#TeamTo winWin %
1France+45014.3%
2Spain+50013.1%
3England+65010.5%
4Argentina+8008.8%
5Brazil+8008.8%
6Portugal+1,0007.2%
7Germany+1,4005.3%
8Netherlands+2,0003.8%
9Norway+2,5003.0%
10Belgium+3,3002.3%
11Morocco+4,0001.9%
12Colombia+4,0001.9%
13USA+4,0001.9%
14Uruguay+5,0001.5%
15Japan+5,0001.5%
16Croatia+6,6001.2%
17Senegal+6,6001.2%
18Mexico+6,6001.2%
19Switzerland+6,6001.2%
20Ecuador+6,6001.2%
21Türkiye+6,6001.2%
22Sweden+6,6001.2%
23Austria+10,0000.8%
24Canada+15,0000.5%
25Paraguay+15,0000.5%
26Ivory Coast+20,0000.4%
27Czechia+20,0000.4%
28South Korea+25,0000.3%
29Algeria+25,0000.3%
30Egypt+25,0000.3%
31Scotland+25,0000.3%
32Ghana+25,0000.3%
33Bosnia-Herz.+25,0000.3%
34Iran+50,0000.2%
35Australia+50,0000.2%
36Tunisia+50,0000.2%
37DR Congo+75,0000.1%
38Panama+100,0000.1%
39Qatar+100,0000.1%
40Uzbekistan+100,0000.1%
41Iraq+100,0000.1%
42Saudi Arabia+100,0000.1%
43Jordan+100,0000.1%
44South Africa+100,0000.1%
45Cape Verde+100,0000.1%
46New Zealand+100,0000.1%
47Curaçao+250,000<0.1%
48Haiti+250,000<0.1%
William & Wall 2026 World Cup Challenge — Rules & Prizes
How It Works
Open to the public — anyone is welcome to join.
Free to enter — no purchase necessary.
One entry per participant — a single bracket per person.
Playing & Prizes
Submit before kickoff — lock in your bracket before the tournament begins.
Follow the live leaderboard — track your standing throughout the tournament.
🥇1st Place: $250 American Express Gift Card — awarded to the top bracket, plus a Futstrikers Club match ball.
🥈2nd Place: $100 American Express Gift Card — awarded to the runner-up bracket, plus a Futstrikers Club match ball.
🥉3rd Place: $50 American Express Gift Card — awarded to the third-place bracket, plus a Futstrikers Club match ball.
All Participants: everyone who enters receives a discount code for Futstrikers Club FIFA Basic and NFHS-certified match balls.
Good luck, and may the best bracket win!
Scoring System

When auditing your scores, look for green checkmarks to confirm you were awarded point(s) for the corresponding match.

Correct Prediction
✓✓ Exact Score Point Awarded
Group Stage
Bracket Stage
1
pt · correct group result (W/D/L)
+1
pt · exact score, group stage
2
pts · correct R32 winner
3
pts · correct R16 winner
4
pts · correct Quarter-final winner
5
pts · correct Semi-final winner
6
pts · correct Final winner
+1
pt · exact score, any playoff match

Playoff note: if you predict the right winner with the right scoreline, +1 pt even if the wrong teams ended up in that bracket slot. Drawn playoff predictions require you to pick a penalty winner — no ties allowed.

Max Points By Round
Group Stage
Bracket Stage
72
Game Points
1 pt × 72 matches
72
Exact Score Points
+1 pt × 72 matches
88
Game Points
2–6 pts × 31 matches
31
Exact Score Points
+1 pt × 31 matches
263
Max Points Possible
How Scoring & Tiebreakers Work
Scoring

Group stage: +1 for the correct result (win/draw/loss), and +1 more for the exact scoreline.
Knockout stage: points for each correct winner by round — 2 (R32), 3 (R16), 4 (QF), 5 (SF), 6 (Final) — plus +1 for the exact scoreline. Knockout scoring is by team identity: if you picked a team to win a round and it did, you earn the points no matter the opponent or bracket slot.

Checkmarks & Exact Scores

When you open an entry, each match shows green checkmarks that mirror the points awarded:
— winner/result point awarded.
✓✓ — winner and exact-score point awarded (right winner with the right scoreline).
No checkmark means no points for that match. A perfect entry scores the 263 max shown above.

Tiebreakers (for entries level on points)
1Highest total points.
2Correct World Cup champion — only applies once the champion has been decided; before then this step is skipped.
3Most exact scores correct.
4Most bracket-stage points.
5Most group-stage points.
6Earliest submitted bracket.
Terms & Conditions

The William & Wall 2026 World Cup Challenge is a free promotional contest open to individuals who are at least 18 years of age. No purchase, engagement, referral, or business relationship with William & Wall is required to enter or win.

Participation is limited to one bracket per person. Duplicate or fraudulent entries may be disqualified at William & Wall’s sole discretion.

Brackets must be submitted prior to the start of the tournament. Once the tournament begins, bracket selections may not be modified.

Scoring will be determined exclusively by the official William & Wall World Cup Challenge scoring system. All scoring calculations, leaderboard rankings, tie-breakers, and contest results are final.

In the event that two or more entries are level on points, rankings are determined in the following order: (1) most total points; (2) a correct prediction of the World Cup champion, which applies only once the champion has been decided and is otherwise skipped; (3) most exact scores predicted correctly; (4) most bracket-stage points; (5) most group-stage points; and (6) the earliest submitted bracket. The complete scoring system and tiebreaker order are described on the Rules page.

Prizes will be awarded as follows: the first-place finisher will receive a $250 American Express Gift Card, the second-place finisher a $100 American Express Gift Card, and the third-place finisher a $50 American Express Gift Card. Each of the three winning entrants will also receive a Futstrikers Club match ball. All gift card prizes are non-transferable and may not be redeemed for cash.

William & Wall reserves the right to correct scoring errors, disqualify fraudulent entries, modify contest rules, suspend the contest, or cancel the contest if technical issues, data integrity concerns, or circumstances beyond its reasonable control affect the operation of the challenge.

Multiple Entries. William & Wall reserves the right, in its sole discretion, to disqualify any contestant who submits multiple entries, duplicate entries, or fraudulent entries, or who otherwise attempts to circumvent the official rules of the contest.

Verification of Winner Information. William & Wall reserves the right to verify the identity and contact information of any potential winner prior to awarding a prize. Failure to provide accurate and verifiable contact information may result in disqualification.

Eligibility. To be eligible to receive a prize, contestants must be legal residents physically residing within the contiguous United States. Residents of Alaska, Hawaii, U.S. territories, and international jurisdictions are not eligible to receive prizes unless otherwise approved by William & Wall in its sole discretion.

Prize Claim Deadline. Potential winners must respond to the prize notification and claim their prize within seven (7) calendar days of notification. Failure to respond within the required timeframe may result in forfeiture of the prize, and William & Wall reserves the right to select an alternate winner.

By participating, entrants agree to these Terms & Conditions and the decisions of William & Wall regarding contest administration and scoring.

Google Sheets Setup Guide
Data saves to your own Google Sheet. One-time setup, ~5 minutes.

Create a new Google Sheet
Go to sheets.google.com, create a blank spreadsheet, name it WC2026 Entries.

Open Apps Script
Extensions → Apps Script. Delete any existing code.

Paste this script

const ADMIN_PW = 'williamwall2026'; // CHANGE THIS function doPost(e) { try { const data = JSON.parse(e.postData.contents); const ss = SpreadsheetApp.getActiveSpreadsheet(); const action = data.action; if (action === 'submit') { // HARD BACKEND DEADLINE LOCK — June 10 2026 11:59 PM PT = June 11 2026 06:59 UTC const DEADLINE = new Date('2026-06-11T06:59:00Z'); if (new Date() >= DEADLINE) { return out({ok: false, msg: 'Bracket entry is closed. The deadline was June 10, 11:59 PM PT.'}); } let sh = ss.getSheetByName('Entries') || ss.insertSheet('Entries'); if (sh.getLastRow() === 0) sh.appendRow(['Timestamp','Name','Email','Predictions','Score','Breakdown','BracketName','Phone','Industrious']); const rows = sh.getDataRange().getValues(); for (let i = 1; i < rows.length; i++) { if (rows[i][2].toLowerCase() === data.email.toLowerCase()) return out({ok: false, msg: 'This email has already submitted an entry.'}); } // Calculate score against existing results so new entries score immediately let initialScore = 0; try { const rs = ss.getSheetByName('Results'); if (rs && rs.getLastRow() > 0) { const existingResults = JSON.parse(rs.getRange(1,1).getValue() || '{}'); if (Object.keys(existingResults.groups||{}).length > 0 || Object.keys(existingResults.bracket||{}).length > 0) { initialScore = calcScore(data.predictions, existingResults); } } } catch(err) {} sh.appendRow([new Date().toISOString(), data.name, data.email, JSON.stringify(data.predictions), initialScore, '', data.bracketName || '', data.phone || '', data.industrious || '']); return out({ok: true}); } if (action === 'getLeaderboard') { const sh = ss.getSheetByName('Entries'); if (!sh) return out({ok: true, entries: []}); const rows = sh.getDataRange().getValues().slice(1); const entries = rows.map(r => ({ name: r[1], email: r[2], score: Number(r[4]) || 0, predictions: JSON.parse(r[3] || '{}'), submitted: r[0], bracketName: r[6] || '' })).sort((a, b) => b.score - a.score); return out({ok: true, entries}); } if (action === 'adminGetEntries') { if (data.pw !== ADMIN_PW) return out({ok: false, msg: 'Wrong password'}); const sh = ss.getSheetByName('Entries'); if (!sh) return out({ok: true, entries: []}); const rows = sh.getDataRange().getValues().slice(1); const entries = rows.map(r => ({ name: r[1], email: r[2], predictions: JSON.parse(r[3] || '{}'), score: Number(r[4]) || 0, submitted: r[0], bracketName: r[6] || '', phone: r[7] || '', industrious: r[8] || '' })); return out({ok: true, entries}); } if (action === 'saveResults') { if (data.pw !== ADMIN_PW) return out({ok: false, msg: 'Wrong password'}); let rs = ss.getSheetByName('Results') || ss.insertSheet('Results'); rs.clearContents(); rs.appendRow([JSON.stringify(data.results)]); rs.appendRow([JSON.stringify(data.slotOverrides || {})]); rs.appendRow([new Date().toISOString()]); // row 3: last updated timestamp const sh = ss.getSheetByName('Entries'); if (sh) { const rows = sh.getDataRange().getValues(); // Merge slotOverrides into results so calcScore team-identity logic works const resultsWithOvr = Object.assign({}, data.results, { slotOverrides: data.slotOverrides || {} }); for (let i = 1; i < rows.length; i++) { const pred = JSON.parse(rows[i][3] || '{}'); // Skip empty entries if (!pred || (!Object.keys(pred.groups||{}).length && !Object.keys(pred.bracket||{}).length)) continue; const score = calcScore(pred, resultsWithOvr); const breakdown = calcBreakdown(pred, resultsWithOvr); sh.getRange(i + 1, 5).setValue(score); sh.getRange(i + 1, 6).setValue(JSON.stringify(breakdown)); } } return out({ok: true, count: sh ? sh.getLastRow()-1 : 0}); } if (action === 'getResults') { const rs = ss.getSheetByName('Results'); if (!rs || rs.getLastRow() === 0) return out({ok: true, results: {}, slotOverrides: {}, lastUpdated: null}); const lastUpdated = rs.getLastRow() >= 3 ? rs.getRange(3, 1).getValue() : null; const slotOvr = rs.getLastRow() >= 2 ? JSON.parse(rs.getRange(2, 1).getValue() || '{}') : {}; return out({ok: true, results: JSON.parse(rs.getRange(1, 1).getValue() || '{}'), slotOverrides: slotOvr, lastUpdated: lastUpdated || null}); } if (action === 'getAnnouncement') { const cs = ss.getSheetByName('Config'); const msg = (cs && cs.getLastRow() > 0) ? cs.getRange(1, 1).getValue() : ''; return out({ok: true, announcement: msg || ''}); } if (action === 'saveAnnouncement') { if (data.pw !== ADMIN_PW) return out({ok: false, msg: 'Wrong password'}); const cs = ss.getSheetByName('Config') || ss.insertSheet('Config'); cs.getRange(1, 1).setValue(data.announcement || ''); return out({ok: true}); } if (action === 'deleteEntry') { if (data.pw !== ADMIN_PW) return out({ok: false, msg: 'Wrong password'}); const sh = ss.getSheetByName('Entries'); if (!sh) return out({ok: false, msg: 'No Entries sheet found'}); const rows = sh.getDataRange().getValues(); for (let i = 1; i < rows.length; i++) { if (rows[i][2].toLowerCase() === data.email.toLowerCase()) { sh.deleteRow(i + 1); return out({ok: true, msg: 'Entry deleted'}); } } return out({ok: false, msg: 'Email not found: ' + data.email}); } return out({ok: false, msg: 'Unknown action: ' + action}); } catch(err) { return out({ok: false, msg: err.toString()}); } } function doGet() { return out({ok: true, msg: 'WC2026 API running'}); } function out(obj) { return ContentService .createTextOutput(JSON.stringify(obj)) .setMimeType(ContentService.MimeType.JSON); } function calcBreakdown(pred, real) { // Uses team-identity comparison (same as calcScore) for accuracy var items = []; var rdpts = {r32: 2, r16: 3, qf: 4, sf: 5, fin: 6}; var ovr = real.slotOverrides || {}; // Group stage var pg = pred.groups || {}, rg = real.groups || {}; Object.keys(rg).forEach(function(id) { var p = pg[id], r = rg[id]; if (!p || r.h == null) return; var pr = p.h > p.a ? 1 : p.h < p.a ? -1 : 0; var rr = r.h > r.a ? 1 : r.h < r.a ? -1 : 0; var result = pr === rr; var exact = p.h === r.h && p.a === r.a; if (result || exact) { items.push({id: id, pred: p.h+'-'+p.a, real: r.h+'-'+r.a, pts: (result ? 1 : 0) + (exact ? 1 : 0)}); } }); // Bracket stage — ROUND-LEVEL team identity (same model as calcScore) var realMap = gasBuildBracketWinnerMap(real.groups, real.bracket, ovr); var predMap = gasBuildBracketWinnerMap(pred.groups, pred.bracket, {}); var realRW = gasBuildRoundWinners(realMap, real.bracket); var predRW = gasBuildRoundWinners(predMap, pred.bracket); ['r32','r16','qf','sf','fin'].forEach(function(rnd) { var realTeams = realRW[rnd], predTeams = predRW[rnd]; Object.keys(realTeams).forEach(function(tid) { var ps = predTeams[tid]; if(!ps) return; var rs = realTeams[tid]; var exact = ps.gf === rs.gf && ps.ga === rs.ga; items.push({id: rnd+':'+tid, pred: ps.gf+'-'+ps.ga, real: rs.gf+'-'+rs.ga, pts: (rdpts[rnd] || 0) + (exact ? 1 : 0)}); }); }); return items; } // ── BRACKET SCORING DATA ───────────────────────────────────────────────────── // These mirror the frontend GROUPS, R32, and ADVANCEMENT constants exactly. var GAS_GROUPS = { A: [{id:"A1"},{id:"A2"},{id:"A3"},{id:"A4"}], B: [{id:"B1"},{id:"B2"},{id:"B3"},{id:"B4"}], C: [{id:"C1"},{id:"C2"},{id:"C3"},{id:"C4"}], D: [{id:"D1"},{id:"D2"},{id:"D3"},{id:"D4"}], E: [{id:"E1"},{id:"E2"},{id:"E3"},{id:"E4"}], F: [{id:"F1"},{id:"F2"},{id:"F3"},{id:"F4"}], G: [{id:"G1"},{id:"G2"},{id:"G3"},{id:"G4"}], H: [{id:"H1"},{id:"H2"},{id:"H3"},{id:"H4"}], I: [{id:"I1"},{id:"I2"},{id:"I3"},{id:"I4"}], J: [{id:"J1"},{id:"J2"},{id:"J3"},{id:"J4"}], K: [{id:"K1"},{id:"K2"},{id:"K3"},{id:"K4"}], L: [{id:"L1"},{id:"L2"},{id:"L3"},{id:"L4"}], }; var GAS_ANNEXC_SLOTS = ['A','B','D','E','G','I','K','L']; var GAS_CLUSTER2SLOT = {'CEFHI':'A','EFGIJ':'B','BEFIJ':'D','ABCDF':'E','AEHIJ':'G','CDFGH':'I','DEIJL':'K','EHIJK':'L'}; var GAS_ANNEXC_RAW = `ABCDEFGH=HGBCAFDE ABCDEFGI=CGBDAFEI ABCDEFGJ=CGBDAFEJ ABCDEFGK=CGBDAFEK ABCDEFGL=CGBDAFLE ABCDEFHI=HEBCAFDI ABCDEFHJ=HJBCAFDE ABCDEFHK=HEBCAFDK ABCDEFHL=HFBCADLE ABCDEFIJ=CJBDAFEI ABCDEFIK=CEBDAFIK ABCDEFIL=CEBDAFLI ABCDEFJK=CJBDAFEK ABCDEFJL=CJBDAFLE ABCDEFKL=CEBDAFLK ABCDEGHI=HGBCADEI ABCDEGHJ=HGBCADEJ ABCDEGHK=HGBCADEK ABCDEGHL=HGBCADLE ABCDEGIJ=EGBCADIJ ABCDEGIK=EGBCADIK ABCDEGIL=EGBCADLI ABCDEGJK=EGBCADJK ABCDEGJL=EGBCADLJ ABCDEGKL=EGBCADLK ABCDEHIJ=HJBCADEI ABCDEHIK=HEBCADIK ABCDEHIL=HEBCADLI ABCDEHJK=HJBCADEK ABCDEHJL=HJBCADLE ABCDEHKL=HEBCADLK ABCDEIJK=EJBCADIK ABCDEIJL=EJBCADLI ABCDEIKL=EIBCADLK ABCDEJKL=EJBCADLK ABCDFGHI=HGBCAFDI ABCDFGHJ=HGBCAFDJ ABCDFGHK=HGBCAFDK ABCDFGHL=CGBDAFLH ABCDFGIJ=CGBDAFIJ ABCDFGIK=CGBDAFIK ABCDFGIL=CGBDAFLI ABCDFGJK=CGBDAFJK ABCDFGJL=CGBDAFLJ ABCDFGKL=CGBDAFLK ABCDFHIJ=HJBCAFDI ABCDFHIK=HFBCADIK ABCDFHIL=HFBCADLI ABCDFHJK=HJBCAFDK ABCDFHJL=CJBDAFLH ABCDFHKL=HFBCADLK ABCDFIJK=CJBDAFIK ABCDFIJL=CJBDAFLI ABCDFIKL=CIBDAFLK ABCDFJKL=CJBDAFLK ABCDGHIJ=HGBCADIJ ABCDGHIK=HGBCADIK ABCDGHIL=HGBCADLI ABCDGHJK=HGBCADJK ABCDGHJL=HGBCADLJ ABCDGHKL=HGBCADLK ABCDGIJK=CJBDAGIK ABCDGIJL=CJBDAGLI ABCDGIKL=IGBCADLK ABCDGJKL=CJBDAGLK ABCDHIJK=HJBCADIK ABCDHIJL=HJBCADLI ABCDHIKL=HIBCADLK ABCDHJKL=HJBCADLK ABCDIJKL=IJBCADLK ABCEFGHI=HGBCAFEI ABCEFGHJ=HGBCAFEJ ABCEFGHK=HGBCAFEK ABCEFGHL=HGBCAFLE ABCEFGIJ=EGBCAFIJ ABCEFGIK=EGBCAFIK ABCEFGIL=EGBCAFLI ABCEFGJK=EGBCAFJK ABCEFGJL=EGBCAFLJ ABCEFGKL=EGBCAFLK ABCEFHIJ=HJBCAFEI ABCEFHIK=HEBCAFIK ABCEFHIL=HEBCAFLI ABCEFHJK=HJBCAFEK ABCEFHJL=HJBCAFLE ABCEFHKL=HEBCAFLK ABCEFIJK=EJBCAFIK ABCEFIJL=EJBCAFLI ABCEFIKL=EIBCAFLK ABCEFJKL=EJBCAFLK ABCEGHIJ=HJBCAGEI ABCEGHIK=EGBCAHIK ABCEGHIL=EGBCAHLI ABCEGHJK=HJBCAGEK ABCEGHJL=HJBCAGLE ABCEGHKL=EGBCAHLK ABCEGIJK=EJBCAGIK ABCEGIJL=EJBCAGLI ABCEGIKL=EGBAICLK ABCEGJKL=EJBCAGLK ABCEHIJK=EJBCAHIK ABCEHIJL=EJBCAHLI ABCEHIKL=EIBCAHLK ABCEHJKL=EJBCAHLK ABCEIJKL=EJBAICLK ABCFGHIJ=HGBCAFIJ ABCFGHIK=HGBCAFIK ABCFGHIL=HGBCAFLI ABCFGHJK=HGBCAFJK ABCFGHJL=HGBCAFLJ ABCFGHKL=HGBCAFLK ABCFGIJK=CJBFAGIK ABCFGIJL=CJBFAGLI ABCFGIKL=IGBCAFLK ABCFGJKL=CJBFAGLK ABCFHIJK=HJBCAFIK ABCFHIJL=HJBCAFLI ABCFHIKL=HIBCAFLK ABCFHJKL=HJBCAFLK ABCFIJKL=IJBCAFLK ABCGHIJK=HJBCAGIK ABCGHIJL=HJBCAGLI ABCGHIKL=IGBCAHLK ABCGHJKL=HJBCAGLK ABCGIJKL=IJBCAGLK ABCHIJKL=IJBCAHLK ABDEFGHI=HGBDAFEI ABDEFGHJ=HGBDAFEJ ABDEFGHK=HGBDAFEK ABDEFGHL=HGBDAFLE ABDEFGIJ=EGBDAFIJ ABDEFGIK=EGBDAFIK ABDEFGIL=EGBDAFLI ABDEFGJK=EGBDAFJK ABDEFGJL=EGBDAFLJ ABDEFGKL=EGBDAFLK ABDEFHIJ=HJBDAFEI ABDEFHIK=HEBDAFIK ABDEFHIL=HEBDAFLI ABDEFHJK=HJBDAFEK ABDEFHJL=HJBDAFLE ABDEFHKL=HEBDAFLK ABDEFIJK=EJBDAFIK ABDEFIJL=EJBDAFLI ABDEFIKL=EIBDAFLK ABDEFJKL=EJBDAFLK ABDEGHIJ=HJBDAGEI ABDEGHIK=EGBDAHIK ABDEGHIL=EGBDAHLI ABDEGHJK=HJBDAGEK ABDEGHJL=HJBDAGLE ABDEGHKL=EGBDAHLK ABDEGIJK=EJBDAGIK ABDEGIJL=EJBDAGLI ABDEGIKL=EGBAIDLK ABDEGJKL=EJBDAGLK ABDEHIJK=EJBDAHIK ABDEHIJL=EJBDAHLI ABDEHIKL=EIBDAHLK ABDEHJKL=EJBDAHLK ABDEIJKL=EJBAIDLK ABDFGHIJ=HGBDAFIJ ABDFGHIK=HGBDAFIK ABDFGHIL=HGBDAFLI ABDFGHJK=HGBDAFJK ABDFGHJL=HGBDAFLJ ABDFGHKL=HGBDAFLK ABDFGIJK=FJBDAGIK ABDFGIJL=FJBDAGLI ABDFGIKL=IGBDAFLK ABDFGJKL=FJBDAGLK ABDFHIJK=HJBDAFIK ABDFHIJL=HJBDAFLI ABDFHIKL=HIBDAFLK ABDFHJKL=HJBDAFLK ABDFIJKL=IJBDAFLK ABDGHIJK=HJBDAGIK ABDGHIJL=HJBDAGLI ABDGHIKL=IGBDAHLK ABDGHJKL=HJBDAGLK ABDGIJKL=IJBDAGLK ABDHIJKL=IJBDAHLK ABEFGHIJ=HJBFAGEI ABEFGHIK=EGBFAHIK ABEFGHIL=EGBFAHLI ABEFGHJK=HJBFAGEK ABEFGHJL=HJBFAGLE ABEFGHKL=EGBFAHLK ABEFGIJK=EJBFAGIK ABEFGIJL=EJBFAGLI ABEFGIKL=EGBAIFLK ABEFGJKL=EJBFAGLK ABEFHIJK=EJBFAHIK ABEFHIJL=EJBFAHLI ABEFHIKL=EIBFAHLK ABEFHJKL=EJBFAHLK ABEFIJKL=EJBAIFLK ABEGHIJK=EJBAHGIK ABEGHIJL=EJBAHGLI ABEGHIKL=EGBAIHLK ABEGHJKL=EJBAHGLK ABEGIJKL=EJBAIGLK ABEHIJKL=EJBAIHLK ABFGHIJK=HJBFAGIK ABFGHIJL=HJBFAGLI ABFGHIKL=HGBAIFLK ABFGHJKL=HJBFAGLK ABFGIJKL=IJBFAGLK ABFHIJKL=HJBAIFLK ABGHIJKL=HJBAIGLK ACDEFGHI=HGECAFDI ACDEFGHJ=HGJCAFDE ACDEFGHK=HGECAFDK ACDEFGHL=HGFCADLE ACDEFGIJ=CGJDAFEI ACDEFGIK=CGEDAFIK ACDEFGIL=CGEDAFLI ACDEFGJK=CGJDAFEK ACDEFGJL=CGJDAFLE ACDEFGKL=CGEDAFLK ACDEFHIJ=HJECAFDI ACDEFHIK=HEFCADIK ACDEFHIL=HEFCADLI ACDEFHJK=HJECAFDK ACDEFHJL=HJFCADLE ACDEFHKL=HEFCADLK ACDEFIJK=CJEDAFIK ACDEFIJL=CJEDAFLI ACDEFIKL=CEIDAFLK ACDEFJKL=CJEDAFLK ACDEGHIJ=HGJCADEI ACDEGHIK=HGECADIK ACDEGHIL=HGECADLI ACDEGHJK=HGJCADEK ACDEGHJL=HGJCADLE ACDEGHKL=HGECADLK ACDEGIJK=EGJCADIK ACDEGIJL=EGJCADLI ACDEGIKL=EGICADLK ACDEGJKL=EGJCADLK ACDEHIJK=HJECADIK ACDEHIJL=HJECADLI ACDEHIKL=HEICADLK ACDEHJKL=HJECADLK ACDEIJKL=EJICADLK ACDFGHIJ=HGJCAFDI ACDFGHIK=HGFCADIK ACDFGHIL=HGFCADLI ACDFGHJK=HGJCAFDK ACDFGHJL=CGJDAFLH ACDFGHKL=HGFCADLK ACDFGIJK=CGJDAFIK ACDFGIJL=CGJDAFLI ACDFGIKL=CGIDAFLK ACDFGJKL=CGJDAFLK ACDFHIJK=HJFCADIK ACDFHIJL=HJFCADLI ACDFHIKL=HFICADLK ACDFHJKL=HJFCADLK ACDFIJKL=CJIDAFLK ACDGHIJK=HGJCADIK ACDGHIJL=HGJCADLI ACDGHIKL=HGICADLK ACDGHJKL=HGJCADLK ACDGIJKL=IGJCADLK ACDHIJKL=HJICADLK ACEFGHIJ=HGJCAFEI ACEFGHIK=HGECAFIK ACEFGHIL=HGECAFLI ACEFGHJK=HGJCAFEK ACEFGHJL=HGJCAFLE ACEFGHKL=HGECAFLK ACEFGIJK=EGJCAFIK ACEFGIJL=EGJCAFLI ACEFGIKL=EGICAFLK ACEFGJKL=EGJCAFLK ACEFHIJK=HJECAFIK ACEFHIJL=HJECAFLI ACEFHIKL=HEICAFLK ACEFHJKL=HJECAFLK ACEFIJKL=EJICAFLK ACEGHIJK=EGJCAHIK ACEGHIJL=EGJCAHLI ACEGHIKL=EGICAHLK ACEGHJKL=EGJCAHLK ACEGIJKL=EJICAGLK ACEHIJKL=EJICAHLK ACFGHIJK=HGJCAFIK ACFGHIJL=HGJCAFLI ACFGHIKL=HGICAFLK ACFGHJKL=HGJCAFLK ACFGIJKL=IGJCAFLK ACFHIJKL=HJICAFLK ACGHIJKL=HJICAGLK ADEFGHIJ=HGJDAFEI ADEFGHIK=HGEDAFIK ADEFGHIL=HGEDAFLI ADEFGHJK=HGJDAFEK ADEFGHJL=HGJDAFLE ADEFGHKL=HGEDAFLK ADEFGIJK=EGJDAFIK ADEFGIJL=EGJDAFLI ADEFGIKL=EGIDAFLK ADEFGJKL=EGJDAFLK ADEFHIJK=HJEDAFIK ADEFHIJL=HJEDAFLI ADEFHIKL=HEIDAFLK ADEFHJKL=HJEDAFLK ADEFIJKL=EJIDAFLK ADEGHIJK=EGJDAHIK ADEGHIJL=EGJDAHLI ADEGHIKL=EGIDAHLK ADEGHJKL=EGJDAHLK ADEGIJKL=EJIDAGLK ADEHIJKL=EJIDAHLK ADFGHIJK=HGJDAFIK ADFGHIJL=HGJDAFLI ADFGHIKL=HGIDAFLK ADFGHJKL=HGJDAFLK ADFGIJKL=IGJDAFLK ADFHIJKL=HJIDAFLK ADGHIJKL=HJIDAGLK AEFGHIJK=EGJFAHIK AEFGHIJL=EGJFAHLI AEFGHIKL=EGIFAHLK AEFGHJKL=EGJFAHLK AEFGIJKL=EJIFAGLK AEFHIJKL=EJIFAHLK AEGHIJKL=EJIAHGLK AFGHIJKL=HJIFAGLK BCDEFGHI=CGBDHFEI BCDEFGHJ=HGBCJFDE BCDEFGHK=CGBDHFEK BCDEFGHL=CGBDHFLE BCDEFGIJ=CGBDJFEI BCDEFGIK=CGBDEFIK BCDEFGIL=CGBDEFLI BCDEFGJK=CGBDJFEK BCDEFGJL=CGBDJFLE BCDEFGKL=CGBDEFLK BCDEFHIJ=CJBDHFEI BCDEFHIK=CEBDHFIK BCDEFHIL=CEBDHFLI BCDEFHJK=CJBDHFEK BCDEFHJL=CJBDHFLE BCDEFHKL=CEBDHFLK BCDEFIJK=CJBDEFIK BCDEFIJL=CJBDEFLI BCDEFIKL=CEBDIFLK BCDEFJKL=CJBDEFLK BCDEGHIJ=HGBCJDEI BCDEGHIK=EGBCHDIK BCDEGHIL=EGBCHDLI BCDEGHJK=HGBCJDEK BCDEGHJL=HGBCJDLE BCDEGHKL=EGBCHDLK BCDEGIJK=EGBCJDIK BCDEGIJL=EGBCJDLI BCDEGIKL=EGBCIDLK BCDEGJKL=EGBCJDLK BCDEHIJK=EJBCHDIK BCDEHIJL=EJBCHDLI BCDEHIKL=EIBCHDLK BCDEHJKL=EJBCHDLK BCDEIJKL=EJBCIDLK BCDFGHIJ=HGBCJFDI BCDFGHIK=CGBDHFIK BCDFGHIL=CGBDHFLI BCDFGHJK=HGBCJFDK BCDFGHJL=CGBDHFLJ BCDFGHKL=CGBDHFLK BCDFGIJK=CGBDJFIK BCDFGIJL=CGBDJFLI BCDFGIKL=CGBDIFLK BCDFGJKL=CGBDJFLK BCDFHIJK=CJBDHFIK BCDFHIJL=CJBDHFLI BCDFHIKL=CIBDHFLK BCDFHJKL=CJBDHFLK BCDFIJKL=CJBDIFLK BCDGHIJK=HGBCJDIK BCDGHIJL=HGBCJDLI BCDGHIKL=HGBCIDLK BCDGHJKL=HGBCJDLK BCDGIJKL=IGBCJDLK BCDHIJKL=HJBCIDLK BCEFGHIJ=HGBCJFEI BCEFGHIK=EGBCHFIK BCEFGHIL=EGBCHFLI BCEFGHJK=HGBCJFEK BCEFGHJL=HGBCJFLE BCEFGHKL=EGBCHFLK BCEFGIJK=EGBCJFIK BCEFGIJL=EGBCJFLI BCEFGIKL=EGBCIFLK BCEFGJKL=EGBCJFLK BCEFHIJK=EJBCHFIK BCEFHIJL=EJBCHFLI BCEFHIKL=EIBCHFLK BCEFHJKL=EJBCHFLK BCEFIJKL=EJBCIFLK BCEGHIJK=EJBCHGIK BCEGHIJL=EJBCHGLI BCEGHIKL=EGBCIHLK BCEGHJKL=EJBCHGLK BCEGIJKL=EJBCIGLK BCEHIJKL=EJBCIHLK BCFGHIJK=HGBCJFIK BCFGHIJL=HGBCJFLI BCFGHIKL=HGBCIFLK BCFGHJKL=HGBCJFLK BCFGIJKL=IGBCJFLK BCFHIJKL=HJBCIFLK BCGHIJKL=HJBCIGLK BDEFGHIJ=HGBDJFEI BDEFGHIK=EGBDHFIK BDEFGHIL=EGBDHFLI BDEFGHJK=HGBDJFEK BDEFGHJL=HGBDJFLE BDEFGHKL=EGBDHFLK BDEFGIJK=EGBDJFIK BDEFGIJL=EGBDJFLI BDEFGIKL=EGBDIFLK BDEFGJKL=EGBDJFLK BDEFHIJK=EJBDHFIK BDEFHIJL=EJBDHFLI BDEFHIKL=EIBDHFLK BDEFHJKL=EJBDHFLK BDEFIJKL=EJBDIFLK BDEGHIJK=EJBDHGIK BDEGHIJL=EJBDHGLI BDEGHIKL=EGBDIHLK BDEGHJKL=EJBDHGLK BDEGIJKL=EJBDIGLK BDEHIJKL=EJBDIHLK BDFGHIJK=HGBDJFIK BDFGHIJL=HGBDJFLI BDFGHIKL=HGBDIFLK BDFGHJKL=HGBDJFLK BDFGIJKL=IGBDJFLK BDFHIJKL=HJBDIFLK BDGHIJKL=HJBDIGLK BEFGHIJK=EJBFHGIK BEFGHIJL=EJBFHGLI BEFGHIKL=EGBFIHLK BEFGHJKL=EJBFHGLK BEFGIJKL=EJBFIGLK BEFHIJKL=EJBFIHLK BEGHIJKL=EJIBHGLK BFGHIJKL=HJBFIGLK CDEFGHIJ=CGJDHFEI CDEFGHIK=CGEDHFIK CDEFGHIL=CGEDHFLI CDEFGHJK=CGJDHFEK CDEFGHJL=CGJDHFLE CDEFGHKL=CGEDHFLK CDEFGIJK=CGEDJFIK CDEFGIJL=CGEDJFLI CDEFGIKL=CGEDIFLK CDEFGJKL=CGEDJFLK CDEFHIJK=CJEDHFIK CDEFHIJL=CJEDHFLI CDEFHIKL=CEIDHFLK CDEFHJKL=CJEDHFLK CDEFIJKL=CJEDIFLK CDEGHIJK=EGJCHDIK CDEGHIJL=EGJCHDLI CDEGHIKL=EGICHDLK CDEGHJKL=EGJCHDLK CDEGIJKL=EGICJDLK CDEHIJKL=EJICHDLK CDFGHIJK=CGJDHFIK CDFGHIJL=CGJDHFLI CDFGHIKL=CGIDHFLK CDFGHJKL=CGJDHFLK CDFGIJKL=CGIDJFLK CDFHIJKL=CJIDHFLK CDGHIJKL=HGICJDLK CEFGHIJK=EGJCHFIK CEFGHIJL=EGJCHFLI CEFGHIKL=EGICHFLK CEFGHJKL=EGJCHFLK CEFGIJKL=EGICJFLK CEFHIJKL=EJICHFLK CEGHIJKL=EJICHGLK CFGHIJKL=HGICJFLK DEFGHIJK=EGJDHFIK DEFGHIJL=EGJDHFLI DEFGHIKL=EGIDHFLK DEFGHJKL=EGJDHFLK DEFGIJKL=EGIDJFLK DEFHIJKL=EJIDHFLK DEGHIJKL=EJIDHGLK DFGHIJKL=HGIDJFLK EFGHIJKL=EJIFHGLK`; var _gasAnnexC = null; function gasAnnexCTable(){ if(_gasAnnexC) return _gasAnnexC; var t = {}; GAS_ANNEXC_RAW.trim().split('\n').forEach(function(line){ var p = line.split('='); if(p.length === 2) t[p[0]] = p[1]; }); _gasAnnexC = t; return t; } function gasAnnexCFromThirds(thirds){ var ranked = (thirds || []).filter(function(x){ return x && x.team; }); if(ranked.length < 8) return null; var qual = ranked.slice(0,8).map(function(x){ return x.group; }); var key = qual.slice().sort().join(''); if(key.length !== 8) return null; var row = gasAnnexCTable()[key]; if(!row || row.length !== 8) return null; var map = {}; for(var i=0;i<GAS_ANNEXC_SLOTS.length;i++) map[GAS_ANNEXC_SLOTS[i]] = row[i]; return map; } var GAS_R32 = [ {id:"p_r32_0", home:"1E", away:"3ABCDF"}, {id:"p_r32_1", home:"1I", away:"3CDFGH"}, {id:"p_r32_2", home:"2A", away:"2B"}, {id:"p_r32_3", home:"1F", away:"2C"}, {id:"p_r32_4", home:"2K", away:"2L"}, {id:"p_r32_5", home:"1H", away:"2J"}, {id:"p_r32_6", home:"1D", away:"3BEFIJ"}, {id:"p_r32_7", home:"1G", away:"3AEHIJ"}, {id:"p_r32_8", home:"1C", away:"2F"}, {id:"p_r32_9", home:"2E", away:"2I"}, {id:"p_r32_10", home:"1A", away:"3CEFHI"}, {id:"p_r32_11", home:"1L", away:"3EHIJK"}, {id:"p_r32_12", home:"1J", away:"2H"}, {id:"p_r32_13", home:"2D", away:"2G"}, {id:"p_r32_14", home:"1B", away:"3EFGIJ"}, {id:"p_r32_15", home:"1K", away:"3DEIJL"}, ]; var GAS_ADVANCEMENT = { // R32 → R16 (left: pairs 0+1→0, 2+3→1, 4+5→2, 6+7→3; right: 8+9→4, 10+11→5, 12+13→6, 14+15→7) 'p_r32_0': {next:'p_r16_0',slot:'home'}, 'p_r32_1': {next:'p_r16_0',slot:'away'}, 'p_r32_2': {next:'p_r16_1',slot:'home'}, 'p_r32_3': {next:'p_r16_1',slot:'away'}, 'p_r32_4': {next:'p_r16_2',slot:'home'}, 'p_r32_5': {next:'p_r16_2',slot:'away'}, 'p_r32_6': {next:'p_r16_3',slot:'home'}, 'p_r32_7': {next:'p_r16_3',slot:'away'}, 'p_r32_8': {next:'p_r16_4',slot:'home'}, 'p_r32_9': {next:'p_r16_4',slot:'away'}, 'p_r32_10': {next:'p_r16_5',slot:'home'}, 'p_r32_11': {next:'p_r16_5',slot:'away'}, 'p_r32_12': {next:'p_r16_6',slot:'home'}, 'p_r32_13': {next:'p_r16_6',slot:'away'}, 'p_r32_14': {next:'p_r16_7',slot:'home'}, 'p_r32_15': {next:'p_r16_7',slot:'away'}, // R16 → QF 'p_r16_0': {next:'p_qf_0',slot:'home'}, 'p_r16_1': {next:'p_qf_0',slot:'away'}, 'p_r16_2': {next:'p_qf_1',slot:'home'}, 'p_r16_3': {next:'p_qf_1',slot:'away'}, 'p_r16_4': {next:'p_qf_2',slot:'home'}, 'p_r16_5': {next:'p_qf_2',slot:'away'}, 'p_r16_6': {next:'p_qf_3',slot:'home'}, 'p_r16_7': {next:'p_qf_3',slot:'away'}, // QF → SF 'p_qf_0': {next:'p_sf_0',slot:'home'}, 'p_qf_1': {next:'p_sf_0',slot:'away'}, 'p_qf_2': {next:'p_sf_1',slot:'home'}, 'p_qf_3': {next:'p_sf_1',slot:'away'}, // SF → Final 'p_sf_0': {next:'p_fin_0',slot:'home'}, 'p_sf_1': {next:'p_fin_0',slot:'away'}, }; // Build a map of which team (by id) occupies each bracket slot and who won. // groups: groups results object, bracket: bracket results object, ovr: slot overrides function gasBuildBracketWinnerMap(groups, bracket, ovr) { ovr = ovr || {}; var gScores = groups || {}; var hasG = Object.keys(gScores).length > 0; // Compute simple standings: for each group, rank teams by points/gd/gf var standings = {}; if(hasG) { Object.keys(GAS_GROUPS).forEach(function(g) { var teams = GAS_GROUPS[g]; var stats = {}; teams.forEach(function(t) { stats[t.id] = {id:t.id, pts:0, gd:0, gf:0, mp:0}; }); // Tally group results Object.keys(gScores).forEach(function(mid) { var parts = mid.split('_'); // g_A_0_1 if(parts[1] !== g) return; var r = gScores[mid]; if(!r || r.h == null) return; var i = parseInt(parts[2]), j = parseInt(parts[3]); var ti = teams[i] && teams[i].id, tj = teams[j] && teams[j].id; if(!ti || !tj) return; stats[ti].mp++; stats[tj].mp++; stats[ti].gf += r.h; stats[ti].gd += (r.h - r.a); stats[tj].gf += r.a; stats[tj].gd += (r.a - r.h); if(r.h > r.a) { stats[ti].pts += 3; } else if(r.h < r.a) { stats[tj].pts += 3; } else { stats[ti].pts += 1; stats[tj].pts += 1; } }); var sorted = Object.values(stats).sort(function(a,b) { if(b.pts !== a.pts) return b.pts - a.pts; if(b.gd !== a.gd) return b.gd - a.gd; return b.gf - a.gf; }); standings[g] = sorted; }); } // All thirds sorted for 3rd-place slot resolution var thirds = []; Object.keys(standings).forEach(function(g) { if(standings[g][2]) thirds.push({group: g, team: standings[g][2]}); }); thirds.sort(function(a,b) { var ta=a.team, tb=b.team; if(tb.pts !== ta.pts) return tb.pts - ta.pts; if(tb.gd !== ta.gd) return tb.gd - ta.gd; return tb.gf - ta.gf; }); // FIFA Annex C assignment for the 8 reserved third-place R32 slots (or null). var annexMap = gasAnnexCFromThirds(thirds); // Resolve a slot code to a team id // NOTE: mp >= 3 check removed — scoring must work mid-tournament. function resolveSlot(code, usedSet) { if(!code) return null; var rank = code[0], grps = code.slice(1); if(rank === '1') { var g1 = standings[grps]; return (g1 && g1[0]) ? g1[0].id : null; } if(rank === '2') { var g2 = standings[grps]; return (g2 && g2[1]) ? g2[1].id : null; } if(rank === '3') { // Official Annex C path: cluster -> reserved slot -> FIFA-assigned group. var slotKey = GAS_CLUSTER2SLOT[grps]; if(slotKey){ if(annexMap && annexMap[slotKey]){ var ag = annexMap[slotKey]; var ast = standings[ag]; return (ast && ast[2]) ? ast[2].id : null; } return null; } for(var k=0; k<thirds.length; k++) { var x = thirds[k]; if(grps.indexOf(x.group) !== -1 && x.team && !usedSet[x.team.id]) { usedSet[x.team.id] = true; return x.team.id; } } return null; } return null; } var teamMap = {}; var used3rd = {}; // Seed R32 GAS_R32.forEach(function(m) { var hId = (ovr[m.home] && ovr[m.home].id) || resolveSlot(m.home, used3rd); var aId = (ovr[m.away] && ovr[m.away].id) || resolveSlot(m.away, used3rd); if(ovr[m.home] && hId) used3rd[hId] = true; if(ovr[m.away] && aId) used3rd[aId] = true; var r = (bracket || {})[m.id]; var winnerId = null; if(r && r.h != null && r.a != null) { var side = r.h > r.a ? 'home' : r.h < r.a ? 'away' : (r.pen || null); winnerId = side === 'home' ? hId : side === 'away' ? aId : null; } teamMap[m.id] = {homeId: hId, awayId: aId, winnerId: winnerId}; }); // Propagate through later rounds var rounds = [{id:'r16',count:8},{id:'qf',count:4},{id:'sf',count:2},{id:'fin',count:1}]; rounds.forEach(function(rnd) { for(var i=0; i<rnd.count; i++) { var id = 'p_' + rnd.id + '_' + i; var hId2 = null, aId2 = null; Object.keys(GAS_ADVANCEMENT).forEach(function(src) { var adv = GAS_ADVANCEMENT[src]; if(adv.next === id) { var srcE = teamMap[src]; if(srcE && srcE.winnerId) { if(adv.slot === 'home') hId2 = srcE.winnerId; else aId2 = srcE.winnerId; } } }); var r2 = (bracket || {})[id]; var wId2 = null; if(r2 && r2.h != null && r2.a != null) { var side2 = r2.h > r2.a ? 'home' : r2.h < r2.a ? 'away' : (r2.pen || null); wId2 = side2 === 'home' ? hId2 : side2 === 'away' ? aId2 : null; } teamMap[id] = {homeId: hId2, awayId: aId2, winnerId: wId2}; } }); return teamMap; } // Per-round winners keyed by TEAM IDENTITY, with the scoreline from the winner's // perspective (gf = goals for winner, ga = goals against). Mirrors the frontend // buildRoundWinners — makes knockout scoring slot/side independent + override-safe. function gasBuildRoundWinners(winnerMap, bracketScores) { var out = {r32:{}, r16:{}, qf:{}, sf:{}, fin:{}}; var bs = bracketScores || {}; Object.keys(bs).forEach(function(id) { var sc = bs[id]; if(!sc || sc.h == null || sc.a == null) return; var rnd = id.split('_')[1]; if(!out[rnd]) return; var entry = winnerMap[id]; var wid = entry && entry.winnerId; if(!wid) return; var gf, ga; if(sc.h === sc.a) { gf = sc.h; ga = sc.a; } else if(wid === entry.homeId) { gf = sc.h; ga = sc.a; } else { gf = sc.a; ga = sc.h; } if(!out[rnd][wid]) out[rnd][wid] = {gf:gf, ga:ga, slot:id}; }); return out; } function calcScore(pred, real) { var ovr = real.slotOverrides || {}; var pts = 0; var rdpts = {r32:2, r16:3, qf:4, sf:5, fin:6}; // Group stage var pg = pred.groups || {}, rg = real.groups || {}; Object.keys(rg).forEach(function(id) { var p = pg[id], r = rg[id]; if(!p || !r || r.h == null) return; var pr = p.h > p.a ? 1 : p.h < p.a ? -1 : 0; var rr = r.h > r.a ? 1 : r.h < r.a ? -1 : 0; if(pr === rr) pts++; if(p.h === r.h && p.a === r.a) pts++; }); // Bracket: compare team identities var realMap = gasBuildBracketWinnerMap(real.groups, real.bracket, ovr); var predMap = gasBuildBracketWinnerMap(pred.groups, pred.bracket, {}); var realRW = gasBuildRoundWinners(realMap, real.bracket); var predRW = gasBuildRoundWinners(predMap, pred.bracket); // ROUND-LEVEL: a user scores a round when a team they picked to win that round // actually won that round, regardless of slot/opponent. Exact bonus compares the // winner-relative scoreline, so it is side-independent and override-safe. ['r32','r16','qf','sf','fin'].forEach(function(rnd) { var realTeams = realRW[rnd], predTeams = predRW[rnd]; Object.keys(realTeams).forEach(function(tid) { var ps = predTeams[tid]; if(!ps) return; pts += (rdpts[rnd] || 0); var rs = realTeams[tid]; if(ps.gf === rs.gf && ps.ga === rs.ga) pts += 1; }); }); return pts; }

Deploy as web app
Deploy → New deployment → Web app. Execute as: Me. Access: Anyone. Copy the URL.

Configure in Admin
Go to Admin tab, log in, paste your URL in Settings. Save.

Embed in Squarespace
Add a Code Block:

<iframe src="YOUR_FILE_URL" width="100%" height="900" frameborder="0" style="border:none;border-radius:12px"></iframe>
Admin Access

Engine Test Suite
Runs automated tests on the tiebreaker logic, bracket seeding, and scoring engine. Check console for full output.
Live Tournament Results
Based on admin-entered results
Group Stage
Match Results
Bracket
Bracket populates as knockout matches are played. Admin enters results to update.
Score Audit
Click Refresh Audit to load. Requires Admin login and Google Sheets connection.
System Health
Google Sheets
Not checked
Football API
Not checked
Results Data
Not checked
Entry Count
Not checked
Score Integrity
Not checked
Entry-by-Entry Breakdown
Every point is explained. If a score looks wrong, expand the entry to see exactly how each point was calculated.
Run audit to populate.
Quick Scenario Test
Test any specific scoring scenario without affecting real data.