diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..1146519 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,11 @@ +services: + worldcup-app-local: + build: + context: . + dockerfile: Dockerfile + container_name: 2026-worldcup-local + restart: unless-stopped + environment: + - NODE_ENV=development + ports: + - "8080:80" diff --git a/src/data.js b/src/data.js index 540c15e..0b1171d 100644 --- a/src/data.js +++ b/src/data.js @@ -32,12 +32,48 @@ export const groups = [ { name: 'Czech Republic',flag: '๐จ๐ฟ', confederation: 'UEFA', isHost: false }, ], matches: [ - { matchday: 1, home: 'Mexico', away: 'South Africa', date: 'Thu, Jun 11', time: '19:00', venue: 'Mexico City' }, - { matchday: 1, home: 'South Korea', away: 'Czech Republic',date: 'Fri, Jun 12', time: '20:00', venue: 'Guadalajara' }, - { matchday: 2, home: 'Mexico', away: 'Czech Republic',date: 'Mon, Jun 15', time: '22:00', venue: 'Monterrey' }, - { matchday: 2, home: 'South Korea', away: 'South Africa', date: 'Tue, Jun 16', time: '19:00', venue: 'Los Angeles' }, - { matchday: 3, home: 'Mexico', away: 'South Korea', date: 'Sat, Jun 21', time: '18:00', venue: 'Dallas' }, - { matchday: 3, home: 'Czech Republic',away:'South Africa', date: 'Sat, Jun 21', time: '18:00', venue: 'Atlanta' }, + { + matchday: 1, + home: 'Mexico', + away: 'South Africa', + date: 'Thu, Jun 11', + time: '19:00', + venue: 'Mexico City', + status: 'FT', + score: { home: 2, away: 1 }, + scorers: { + home: [ + { name: 'Santiago Gimรฉnez', min: "24'" }, + { name: 'Henry Martรญn', min: "78'" } + ], + away: [ + { name: 'Percy Tau', min: "41' (pen)" } + ] + } + }, + { + matchday: 1, + home: 'South Korea', + away: 'Czech Republic', + date: 'Thu, Jun 11', + time: '21:00', + venue: 'Guadalajara', + status: 'FT', + score: { home: 2, away: 1 }, + scorers: { + home: [ + { name: 'Hwang In-beom', min: "67'" }, + { name: 'Oh Hyeon-gyu', min: "80'" } + ], + away: [ + { name: 'Ladislav Krejฤรญ', min: "59'" } + ] + } + }, + { matchday: 2, home: 'Czech Republic', away: 'South Africa', date: 'Wed, Jun 18', time: '18:00', venue: 'Atlanta' }, + { matchday: 2, home: 'Mexico', away: 'South Korea', date: 'Wed, Jun 18', time: '21:00', venue: 'Guadalajara' }, + { matchday: 3, home: 'Czech Republic', away: 'Mexico', date: 'Tue, Jun 24', time: '18:00', venue: 'Monterrey' }, + { matchday: 3, home: 'South Africa', away: 'South Korea', date: 'Tue, Jun 24', time: '21:00', venue: 'Monterrey' }, ], }, { @@ -50,12 +86,12 @@ export const groups = [ { name: 'Switzerland', flag: '๐จ๐ญ', confederation: 'UEFA', isHost: false }, ], matches: [ - { matchday: 1, home: 'Canada', away: 'Bosnia & Herzegovina', date: 'Fri, Jun 12', time: '19:00', venue: 'Toronto' }, - { matchday: 1, home: 'Qatar', away: 'Switzerland', date: 'Sat, Jun 13', time: '16:00', venue: 'San Francisco'}, - { matchday: 2, home: 'Canada', away: 'Switzerland', date: 'Tue, Jun 16', time: '16:00', venue: 'Vancouver' }, - { matchday: 2, home: 'Qatar', away: 'Bosnia & Herzegovina', date: 'Wed, Jun 17', time: '19:00', venue: 'Toronto' }, - { matchday: 3, home: 'Canada', away: 'Qatar', date: 'Sun, Jun 22', time: '18:00', venue: 'Vancouver' }, - { matchday: 3, home: 'Switzerland', away: 'Bosnia & Herzegovina', date: 'Sun, Jun 22', time: '18:00', venue: 'Kansas City' }, + { matchday: 1, home: 'Canada', away: 'Bosnia & Herzegovina', date: 'Fri, Jun 12', time: '19:00', venue: 'Toronto' }, + { matchday: 1, home: 'Qatar', away: 'Switzerland', date: 'Sat, Jun 13', time: '19:00', venue: 'San Francisco' }, + { matchday: 2, home: 'Switzerland', away: 'Bosnia & Herzegovina', date: 'Wed, Jun 18', time: '19:00', venue: 'Los Angeles' }, + { matchday: 2, home: 'Canada', away: 'Qatar', date: 'Wed, Jun 18', time: '22:00', venue: 'Vancouver' }, + { matchday: 3, home: 'Bosnia & Herzegovina', away: 'Qatar', date: 'Tue, Jun 24', time: '19:00', venue: 'Seattle' }, + { matchday: 3, home: 'Switzerland', away: 'Canada', date: 'Tue, Jun 24', time: '19:00', venue: 'Vancouver' }, ], }, { @@ -70,10 +106,10 @@ export const groups = [ matches: [ { matchday: 1, home: 'Brazil', away: 'Morocco', date: 'Sat, Jun 13', time: '19:00', venue: 'New York' }, { matchday: 1, home: 'Haiti', away: 'Scotland', date: 'Sat, Jun 13', time: '16:00', venue: 'Boston' }, - { matchday: 2, home: 'Brazil', away: 'Scotland', date: 'Wed, Jun 17', time: '16:00', venue: 'Los Angeles' }, - { matchday: 2, home: 'Morocco', away: 'Haiti', date: 'Wed, Jun 17', time: '22:00', venue: 'Miami' }, - { matchday: 3, home: 'Brazil', away: 'Haiti', date: 'Mon, Jun 22', time: '18:00', venue: 'Dallas' }, - { matchday: 3, home: 'Scotland', away: 'Morocco', date: 'Mon, Jun 22', time: '18:00', venue: 'Philadelphia' }, + { matchday: 2, home: 'Brazil', away: 'Scotland', date: 'Fri, Jun 19', time: '19:00', venue: 'Los Angeles' }, + { matchday: 2, home: 'Morocco', away: 'Haiti', date: 'Fri, Jun 19', time: '22:00', venue: 'Miami' }, + { matchday: 3, home: 'Brazil', away: 'Haiti', date: 'Tue, Jun 24', time: '18:00', venue: 'Dallas' }, + { matchday: 3, home: 'Scotland', away: 'Morocco', date: 'Tue, Jun 24', time: '18:00', venue: 'Philadelphia' }, ], }, { @@ -86,12 +122,12 @@ export const groups = [ { name: 'Tรผrkiye', flag: '๐น๐ท', confederation: 'UEFA', isHost: false }, ], matches: [ - { matchday: 1, home: 'United States', away: 'Paraguay', date: 'Fri, Jun 12', time: '22:00', venue: 'Los Angeles' }, - { matchday: 1, home: 'Australia', away: 'Tรผrkiye', date: 'Sat, Jun 13', time: '22:00', venue: 'Vancouver' }, - { matchday: 2, home: 'United States', away: 'Tรผrkiye', date: 'Wed, Jun 17', time: '22:00', venue: 'Seattle' }, - { matchday: 2, home: 'Paraguay', away: 'Australia',date: 'Thu, Jun 18', time: '19:00', venue: 'Atlanta' }, - { matchday: 3, home: 'United States', away: 'Australia',date: 'Mon, Jun 22', time: '22:00', venue: 'Dallas' }, - { matchday: 3, home: 'Tรผrkiye', away: 'Paraguay', date: 'Mon, Jun 22', time: '22:00', venue: 'Houston' }, + { matchday: 1, home: 'United States', away: 'Paraguay', date: 'Fri, Jun 12', time: '21:00', venue: 'Los Angeles' }, + { matchday: 1, home: 'Australia', away: 'Tรผrkiye', date: 'Sun, Jun 13', time: '00:00', venue: 'Vancouver' }, + { matchday: 2, home: 'United States', away: 'Australia', date: 'Thu, Jun 19', time: '15:00', venue: 'Seattle' }, + { matchday: 2, home: 'Tรผrkiye', away: 'Paraguay', date: 'Thu, Jun 19', time: '23:00', venue: 'San Francisco'}, + { matchday: 3, home: 'Tรผrkiye', away: 'United States', date: 'Thu, Jun 25', time: '22:00', venue: 'Los Angeles' }, + { matchday: 3, home: 'Paraguay', away: 'Australia', date: 'Thu, Jun 25', time: '22:00', venue: 'San Francisco'}, ], }, { @@ -106,10 +142,10 @@ export const groups = [ matches: [ { matchday: 1, home: 'Germany', away: 'Curaรงao', date: 'Sun, Jun 14', time: '16:00', venue: 'Kansas City' }, { matchday: 1, home: "Cรดte d'Ivoire", away: 'Ecuador', date: 'Sun, Jun 14', time: '19:00', venue: 'Atlanta' }, - { matchday: 2, home: 'Germany', away: 'Ecuador', date: 'Thu, Jun 18', time: '16:00', venue: 'Seattle' }, - { matchday: 2, home: 'Curaรงao', away: "Cรดte d'Ivoire", date: 'Thu, Jun 18', time: '22:00', venue: 'Houston' }, - { matchday: 3, home: 'Germany', away: "Cรดte d'Ivoire", date: 'Tue, Jun 23', time: '18:00', venue: 'Philadelphia'}, - { matchday: 3, home: 'Ecuador', away: 'Curaรงao', date: 'Tue, Jun 23', time: '18:00', venue: 'Miami' }, + { matchday: 2, home: 'Germany', away: "Cรดte d'Ivoire", date: 'Fri, Jun 20', time: '18:00', venue: 'Philadelphia'}, + { matchday: 2, home: 'Ecuador', away: 'Curaรงao', date: 'Fri, Jun 20', time: '18:00', venue: 'Miami' }, + { matchday: 3, home: 'Curaรงao', away: "Cรดte d'Ivoire", date: 'Wed, Jun 25', time: '18:00', venue: 'Houston' }, + { matchday: 3, home: 'Ecuador', away: 'Germany', date: 'Wed, Jun 25', time: '18:00', venue: 'Seattle' }, ], }, { @@ -124,10 +160,10 @@ export const groups = [ matches: [ { matchday: 1, home: 'Netherlands', away: 'Japan', date: 'Sun, Jun 14', time: '22:00', venue: 'Los Angeles' }, { matchday: 1, home: 'Sweden', away: 'Tunisia', date: 'Mon, Jun 15', time: '16:00', venue: 'Boston' }, - { matchday: 2, home: 'Netherlands', away: 'Tunisia', date: 'Fri, Jun 19', time: '16:00', venue: 'Dallas' }, - { matchday: 2, home: 'Japan', away: 'Sweden', date: 'Fri, Jun 19', time: '22:00', venue: 'Seattle' }, - { matchday: 3, home: 'Netherlands', away: 'Sweden', date: 'Tue, Jun 23', time: '22:00', venue: 'New York' }, - { matchday: 3, home: 'Tunisia', away: 'Japan', date: 'Tue, Jun 23', time: '22:00', venue: 'Kansas City' }, + { matchday: 2, home: 'Netherlands', away: 'Sweden', date: 'Sat, Jun 20', time: '19:00', venue: 'New York' }, + { matchday: 2, home: 'Japan', away: 'Tunisia', date: 'Sat, Jun 20', time: '22:00', venue: 'Kansas City' }, + { matchday: 3, home: 'Netherlands', away: 'Tunisia', date: 'Fri, Jun 26', time: '18:00', venue: 'Dallas' }, + { matchday: 3, home: 'Japan', away: 'Sweden', date: 'Fri, Jun 26', time: '18:00', venue: 'Seattle' }, ], }, { @@ -142,10 +178,10 @@ export const groups = [ matches: [ { matchday: 1, home: 'Belgium', away: 'Egypt', date: 'Mon, Jun 15', time: '19:00', venue: 'Miami' }, { matchday: 1, home: 'Iran', away: 'New Zealand', date: 'Mon, Jun 15', time: '22:00', venue: 'San Francisco'}, - { matchday: 2, home: 'Belgium', away: 'New Zealand', date: 'Sat, Jun 19', time: '16:00', venue: 'Philadelphia'}, - { matchday: 2, home: 'Egypt', away: 'Iran', date: 'Sat, Jun 19', time: '19:00', venue: 'Houston' }, - { matchday: 3, home: 'Belgium', away: 'Iran', date: 'Wed, Jun 24', time: '18:00', venue: 'Atlanta' }, - { matchday: 3, home: 'New Zealand', away: 'Egypt', date: 'Wed, Jun 24', time: '18:00', venue: 'Boston' }, + { matchday: 2, home: 'Belgium', away: 'New Zealand', date: 'Sun, Jun 21', time: '16:00', venue: 'Philadelphia'}, + { matchday: 2, home: 'Egypt', away: 'Iran', date: 'Sun, Jun 21', time: '19:00', venue: 'Houston' }, + { matchday: 3, home: 'Belgium', away: 'Iran', date: 'Thu, Jun 26', time: '18:00', venue: 'Atlanta' }, + { matchday: 3, home: 'New Zealand', away: 'Egypt', date: 'Thu, Jun 26', time: '18:00', venue: 'Boston' }, ], }, { @@ -160,10 +196,10 @@ export const groups = [ matches: [ { matchday: 1, home: 'Spain', away: 'Cape Verde', date: 'Mon, Jun 15', time: '16:00', venue: 'Guadalajara' }, { matchday: 1, home: 'Saudi Arabia', away: 'Uruguay', date: 'Tue, Jun 16', time: '16:00', venue: 'Mexico City' }, - { matchday: 2, home: 'Spain', away: 'Uruguay', date: 'Sat, Jun 20', time: '16:00', venue: 'Monterrey' }, - { matchday: 2, home: 'Cape Verde', away: 'Saudi Arabia', date: 'Sat, Jun 20', time: '22:00', venue: 'Guadalajara' }, - { matchday: 3, home: 'Spain', away: 'Saudi Arabia', date: 'Wed, Jun 24', time: '22:00', venue: 'Dallas' }, - { matchday: 3, home: 'Uruguay', away: 'Cape Verde', date: 'Wed, Jun 24', time: '22:00', venue: 'Miami' }, + { matchday: 2, home: 'Spain', away: 'Uruguay', date: 'Sat, Jun 21', time: '19:00', venue: 'Monterrey' }, + { matchday: 2, home: 'Cape Verde', away: 'Saudi Arabia', date: 'Sat, Jun 21', time: '22:00', venue: 'Guadalajara' }, + { matchday: 3, home: 'Spain', away: 'Saudi Arabia', date: 'Thu, Jun 26', time: '18:00', venue: 'Dallas' }, + { matchday: 3, home: 'Uruguay', away: 'Cape Verde', date: 'Thu, Jun 26', time: '18:00', venue: 'Miami' }, ], }, { @@ -178,10 +214,10 @@ export const groups = [ matches: [ { matchday: 1, home: 'France', away: 'Senegal', date: 'Tue, Jun 16', time: '22:00', venue: 'Houston' }, { matchday: 1, home: 'Iraq', away: 'Norway', date: 'Tue, Jun 16', time: '19:00', venue: 'Philadelphia'}, - { matchday: 2, home: 'France', away: 'Norway', date: 'Sat, Jun 20', time: '19:00', venue: 'New York' }, - { matchday: 2, home: 'Senegal', away: 'Iraq', date: 'Sun, Jun 21', time: '16:00', venue: 'Atlanta' }, - { matchday: 3, home: 'France', away: 'Iraq', date: 'Thu, Jun 25', time: '18:00', venue: 'Seattle' }, - { matchday: 3, home: 'Norway', away: 'Senegal', date: 'Thu, Jun 25', time: '18:00', venue: 'Boston' }, + { matchday: 2, home: 'France', away: 'Norway', date: 'Sun, Jun 22', time: '19:00', venue: 'New York' }, + { matchday: 2, home: 'Senegal', away: 'Iraq', date: 'Sun, Jun 22', time: '16:00', venue: 'Atlanta' }, + { matchday: 3, home: 'France', away: 'Iraq', date: 'Thu, Jun 26', time: '18:00', venue: 'Seattle' }, + { matchday: 3, home: 'Norway', away: 'Senegal', date: 'Thu, Jun 26', time: '18:00', venue: 'Boston' }, ], }, { @@ -230,12 +266,12 @@ export const groups = [ { name: 'Panama', flag: '๐ต๐ฆ', confederation: 'CONCACAF', isHost: false }, ], matches: [ - { matchday: 1, home: 'England', away: 'Panama', date: 'Fri, Jun 19', time: '16:00', venue: 'Houston' }, - { matchday: 1, home: 'Croatia', away: 'Ghana', date: 'Fri, Jun 19', time: '19:00', venue: 'New York' }, - { matchday: 2, home: 'England', away: 'Ghana', date: 'Tue, Jun 23', time: '16:00', venue: 'Philadelphia'}, - { matchday: 2, home: 'Panama', away: 'Croatia', date: 'Tue, Jun 23', time: '19:00', venue: 'Miami' }, - { matchday: 3, home: 'England', away: 'Croatia', date: 'Sat, Jun 27', time: '18:00', venue: 'San Francisco'}, - { matchday: 3, home: 'Ghana', away: 'Panama', date: 'Sat, Jun 27', time: '18:00', venue: 'Atlanta' }, + { matchday: 1, home: 'England', away: 'Croatia', date: 'Tue, Jun 17', time: '15:00', venue: 'Dallas' }, + { matchday: 1, home: 'Ghana', away: 'Panama', date: 'Tue, Jun 17', time: '19:00', venue: 'Toronto' }, + { matchday: 2, home: 'England', away: 'Ghana', date: 'Mon, Jun 23', time: '16:00', venue: 'Boston' }, + { matchday: 2, home: 'Panama', away: 'Croatia', date: 'Mon, Jun 23', time: '19:00', venue: 'Miami' }, + { matchday: 3, home: 'Panama', away: 'England', date: 'Fri, Jun 27', time: '18:00', venue: 'New York' }, + { matchday: 3, home: 'Croatia', away: 'Ghana', date: 'Fri, Jun 27', time: '18:00', venue: 'Atlanta' }, ], }, ]; diff --git a/src/main.js b/src/main.js index 45525be..9975a1b 100644 --- a/src/main.js +++ b/src/main.js @@ -7,6 +7,26 @@ console.log('[WC2026] Loaded groups:', groups.length, '| First group matches:', // โโ State โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ let activeFilter = 'all'; let searchQuery = ''; +let appGroups = []; + +function loadState() { + const saved = localStorage.getItem('wc2026_groups'); + if (saved) { + try { + appGroups = JSON.parse(saved); + return; + } catch (e) { + console.error('[WC2026] Failed to load saved state:', e); + } + } + appGroups = JSON.parse(JSON.stringify(groups)); +} + +function saveState() { + localStorage.setItem('wc2026_groups', JSON.stringify(appGroups)); +} + +loadState(); // โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function confBadge(confederation) { @@ -14,21 +34,94 @@ function confBadge(confederation) { return `${confederation}`; } -function teamCard(team) { +function calculateStandings(group) { + const standings = {}; + group.teams.forEach(team => { + standings[team.name] = { + name: team.name, + flag: team.flag, + confederation: team.confederation, + isHost: team.isHost, + played: 0, + won: 0, + drawn: 0, + lost: 0, + gf: 0, + ga: 0, + gd: 0, + pts: 0 + }; + }); + + group.matches.forEach(match => { + if (match.status === 'FT' && match.score) { + const home = match.home; + const away = match.away; + const homeScore = parseInt(match.score.home, 10); + const awayScore = parseInt(match.score.away, 10); + + if (!isNaN(homeScore) && !isNaN(awayScore) && standings[home] && standings[away]) { + standings[home].played += 1; + standings[away].played += 1; + standings[home].gf += homeScore; + standings[home].ga += awayScore; + standings[away].gf += awayScore; + standings[away].ga += homeScore; + standings[home].gd = standings[home].gf - standings[home].ga; + standings[away].gd = standings[away].gf - standings[away].ga; + + if (homeScore > awayScore) { + standings[home].won += 1; + standings[home].pts += 3; + standings[away].lost += 1; + } else if (homeScore < awayScore) { + standings[away].won += 1; + standings[away].pts += 3; + standings[home].lost += 1; + } else { + standings[home].drawn += 1; + standings[home].pts += 1; + standings[away].drawn += 1; + standings[away].pts += 1; + } + } + } + }); + + return Object.values(standings).sort((a, b) => { + if (b.pts !== a.pts) return b.pts - a.pts; + if (b.gd !== a.gd) return b.gd - a.gd; + if (b.gf !== a.gf) return b.gf - a.gf; + return a.name.localeCompare(b.name); + }); +} + +function teamCard(team, rank, pts, played) { const hostBadge = team.isHost ? '๐๏ธ Host' : ''; + const ptsBadge = played > 0 ? `${pts} PTS` : ''; + const rankClass = rank <= 2 ? 'rank-adv' : rank === 3 ? 'rank-pot' : 'rank-el'; return `