From d5c97659701c6e32a4bd50b2bb8c4ff4d378f6ed Mon Sep 17 00:00:00 2001 From: Dongho Kim Date: Fri, 12 Jun 2026 22:53:08 +0200 Subject: [PATCH] update --- docker-compose.local.yml | 11 ++ src/data.js | 132 ++++++++------ src/main.js | 320 ++++++++++++++++++++++++++++++++-- src/style.css | 360 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 765 insertions(+), 58 deletions(-) create mode 100644 docker-compose.local.yml 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 `
+ ${rank} ${team.flag}
${team.name}
${confBadge(team.confederation)}${hostBadge}
+ ${ptsBadge}
`; } function groupCard(group) { - const teamsHTML = group.teams.map(teamCard).join(''); + const standings = calculateStandings(group); + const teamsHTML = standings.map((item, idx) => { + return teamCard(item, idx + 1, item.pts, item.played); + }).join(''); + const matchCount = group.matches ? group.matches.length : 0; + const finishedCount = group.matches ? group.matches.filter(m => m.status === 'FT').length : 0; + const matchCountStr = finishedCount > 0 ? `${finishedCount}/${matchCount} played` : `${matchCount} matches`; + return `
@@ -37,13 +130,13 @@ function groupCard(group) {

${group.id}

- ${matchCount} matches + ${matchCountStr}
${teamsHTML}
`; } @@ -82,29 +175,81 @@ function buildMatchdayHTML(group) { const away = group.teams.find(t => t.name === match.away); const cardId = `match-${group.id}-${md}-${idx}`; const userLocalTimeStr = getUserLocalTime(match, venue); + + const isFinished = match.status === 'FT'; + + let scoreDisplayHTML = `VS`; + if (isFinished && match.score) { + const homeWinnerClass = match.score.home > match.score.away ? 'mc-team-winner' : (match.score.home < match.score.away ? 'mc-team-loser' : 'mc-team-draw'); + const awayWinnerClass = match.score.away > match.score.home ? 'mc-team-winner' : (match.score.away < match.score.home ? 'mc-team-loser' : 'mc-team-draw'); + + scoreDisplayHTML = ` +
+ ${match.score.home} + โ€“ + ${match.score.away} + FT +
+ `; + } + + let scorersHTML = ''; + if (isFinished && match.scorers) { + const homeScorersList = match.scorers.home ? match.scorers.home.map(s => `${s.name} ${s.min}`).join(', ') : ''; + const awayScorersList = match.scorers.away ? match.scorers.away.map(s => `${s.name} ${s.min}`).join(', ') : ''; + + scorersHTML = ` +
+
โšฝ ${homeScorersList || 'โ€”'}
+
+
โšฝ ${awayScorersList || 'โ€”'}
+
+ `; + } + return ` -
+
+ ${scorersHTML}
๐Ÿ“…
Date${match.date}
๐Ÿ•
Kick-off${match.time} local time${userLocalTimeStr ? ' / ' + userLocalTimeStr + ' your time' : ''}
๐Ÿ“
Venue${venue ? venue.stadium : match.venue}
๐Ÿ™๏ธ
City${match.venue}${venue ? ', ' + venue.country : ''}
๐Ÿ‘ฅ
Capacity${venue ? venue.capacity : 'โ€”'}
+ +
+
๐ŸŽฎ Simulate Match Score
+
+
+ ${home ? home.flag : ''} + +
+ โ€“ +
+ + ${away ? away.flag : ''} +
+
+
+ + ${isFinished ? `` : ''} +
+
`; @@ -154,6 +299,61 @@ function buildSquadHTML(group) { }).join(''); } +function buildStandingsHTML(group) { + const standings = calculateStandings(group); + const rowsHTML = standings.map((item, idx) => { + const rank = idx + 1; + const rankClass = rank <= 2 ? 'rank-adv' : rank === 3 ? 'rank-pot' : 'rank-el'; + const rowClass = rank <= 2 ? 'row-adv' : rank === 3 ? 'row-pot' : 'row-el'; + return ` + + ${rank} + + ${item.flag} + ${item.name} + ${item.isHost ? '๐ŸŸ๏ธ' : ''} + + ${item.played} + ${item.won} + ${item.drawn} + ${item.lost} + ${item.gf} + ${item.ga} + ${item.gd > 0 ? '+' + item.gd : item.gd} + ${item.pts} + + `; + }).join(''); + + return ` +
+ + + + + + + + + + + + + + + + + ${rowsHTML} + +
#TeamPWDLGFGAGDPts
+
+
Top 2 advance to Round of 32
+
Best 3rd-place teams advance
+
+
+ `; +} + // โ”€โ”€ Modal โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function openModal(group) { if (!group || !group.matches || group.matches.length === 0) return; @@ -176,12 +376,16 @@ function openModal(group) {
diff --git a/src/style.css b/src/style.css index 300d2f7..c7fe9c0 100644 --- a/src/style.css +++ b/src/style.css @@ -808,6 +808,366 @@ body { .modal-group-id { font-size: 36px; } } +/* โ”€โ”€ Reset Button in Header/Filter Bar โ”€โ”€ */ +.reset-simulation-btn { + background: var(--bg-element); + border: 1px dashed var(--border); + border-radius: 20px; + color: var(--text-muted); + cursor: pointer; + font-family: inherit; font-size: 12px; font-weight: 600; + padding: 6px 14px; + transition: all var(--transition); + margin-left: auto; + display: flex; + align-items: center; + gap: 6px; +} +.reset-simulation-btn:hover { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.4); + color: #ef4444; + transform: translateY(-1px); +} + +/* โ”€โ”€ Group Card Ranks & Points โ”€โ”€ */ +.team-rank { + font-size: 11px; + font-weight: 800; + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + margin-right: 4px; + flex-shrink: 0; +} +.team-rank.rank-adv { + background: rgba(34, 197, 94, 0.15); + color: #22c55e; +} +.team-rank.rank-pot { + background: rgba(245, 158, 11, 0.15); + color: #f59e0b; +} +.team-rank.rank-el { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} +.team-pts-badge { + font-size: 11px; + font-weight: 800; + color: var(--gold); + background: rgba(255, 215, 0, 0.1); + padding: 2px 6px; + border-radius: 4px; + margin-left: auto; + letter-spacing: 0.5px; +} + +/* โ”€โ”€ Standings Table in Modal โ”€โ”€ */ +.standings-wrapper { + margin-bottom: 16px; +} +.standings-table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; + font-size: 14px; +} +.standings-table th, .standings-table td { + padding: 12px 10px; + text-align: center; + border-bottom: 1px solid var(--border-light); +} +.standings-table th { + font-weight: 700; + color: var(--text-muted); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1px; + background: var(--bg-element); +} +.standings-table tbody tr { + transition: background var(--transition); +} +.standings-table tbody tr:hover { + background: var(--bg-element); +} +.standings-table td.col-team { + text-align: left; + display: flex; + align-items: center; + gap: 8px; +} +.table-flag { + font-size: 20px; + line-height: 1; +} +.table-team-name { + font-weight: 600; + color: var(--text-primary); +} +.table-host-badge { + font-size: 12px; + cursor: help; +} +.col-pos { + width: 40px; +} +.table-rank-num { + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-weight: 800; + font-size: 12px; +} +.table-rank-num.rank-adv { + background: rgba(34, 197, 94, 0.2); + color: #4ade80; +} +.table-rank-num.rank-pot { + background: rgba(245, 158, 11, 0.2); + color: #facc15; +} +.table-rank-num.rank-el { + background: rgba(239, 68, 68, 0.2); + color: #f87171; +} +.col-pts { + font-weight: 800; + color: var(--gold); +} +.font-w-600 { + font-weight: 600; +} + +/* Row-based visual indicators */ +.standings-table tbody tr.row-adv td.col-team { + border-left: 3px solid #22c55e; +} +.standings-table tbody tr.row-pot td.col-team { + border-left: 3px solid #f59e0b; +} +.standings-table tbody tr.row-el td.col-team { + border-left: 3px solid #ef4444; +} + +.standings-legend { + display: flex; + gap: 20px; + margin-top: 18px; + padding: 10px 14px; + background: var(--bg-element); + border-radius: 8px; + flex-wrap: wrap; +} +.legend-item { + display: flex; + align-items: center; + gap: 8px; +} +.legend-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} +.legend-dot.dot-adv { + background: #22c55e; +} +.legend-dot.dot-pot { + background: #f59e0b; +} +.legend-text { + font-size: 12px; + color: var(--text-muted); + font-weight: 500; +} + +/* Hide some columns on mobile */ +@media (max-width: 640px) { + .text-hide-mobile { + display: none; + } + .standings-table th, .standings-table td { + padding: 10px 6px; + font-size: 13px; + } + .reset-simulation-btn { + margin-left: 0; + margin-top: 10px; + width: 100%; + justify-content: center; + } +} + +/* โ”€โ”€ Match Score Displays โ”€โ”€ */ +.mc-score-wrap { + display: flex; + align-items: center; + gap: 6px; + background: var(--bg-card); + padding: 4px 12px; + border-radius: 20px; + border: 1px solid var(--border); + box-shadow: var(--shadow-md); + position: relative; +} +.mc-score { + font-size: 18px; + font-weight: 800; + line-height: 1; +} +.mc-score.mc-team-winner { + color: var(--gold); +} +.mc-score.mc-team-loser { + color: var(--text-muted); +} +.mc-score.mc-team-draw { + color: var(--text-primary); +} +.mc-score-dash { + font-size: 14px; + color: var(--text-faint); + font-weight: 700; +} +.mc-status-badge { + font-size: 9px; + font-weight: 900; + background: var(--border-hover); + color: var(--text-primary); + padding: 2px 6px; + border-radius: 10px; + margin-left: 4px; + text-transform: uppercase; +} + +.match-finished { + background: color-mix(in srgb, var(--match-color, var(--gold)) 3%, var(--bg-element)); +} +.match-finished .home-col .mc-name, +.match-finished .away-col .mc-name { + font-weight: 550; +} + +/* Scorers list */ +.match-scorers-panel { + display: flex; + justify-content: space-between; + padding: 10px 22px; + font-size: 12px; + color: var(--text-muted); + background: var(--bg-element); + border-bottom: 1px solid var(--border-light); + gap: 16px; +} +.scorers-team { + flex: 1; + font-weight: 500; +} +.home-scorers { + text-align: right; +} +.away-scorers { + text-align: left; +} +.scorers-divider { + width: 1px; + background: var(--border-light); +} + +/* โ”€โ”€ Score Simulator Panel โ”€โ”€ */ +.simulator-section { + padding: 16px 22px; + background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--match-color, var(--gold)) 5%, transparent)); + border-top: 1px dashed var(--border-light); + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} +.simulator-title { + font-size: 11px; + font-weight: 800; + letter-spacing: 1.5px; + text-transform: uppercase; + color: var(--match-color, var(--gold)); +} +.simulator-row { + display: flex; + align-items: center; + gap: 14px; +} +.sim-team { + display: flex; + align-items: center; + gap: 8px; +} +.sim-flag { + font-size: 24px; +} +.sim-input { + width: 60px; + height: 38px; + background: var(--bg-card); + border: 2px solid var(--border); + border-radius: 8px; + color: var(--text-primary); + text-align: center; + font-family: inherit; + font-size: 16px; + font-weight: 800; + outline: none; + transition: all var(--transition); +} +.sim-input:focus { + border-color: var(--match-color, var(--gold)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--match-color, var(--gold)) 20%, transparent); +} +.sim-vs { + font-size: 18px; + font-weight: 700; + color: var(--text-faint); +} +.simulator-actions { + display: flex; + gap: 8px; +} +.sim-btn { + font-family: inherit; + font-size: 12px; + font-weight: 700; + padding: 8px 16px; + border-radius: 20px; + cursor: pointer; + border: 1px solid transparent; + transition: all var(--transition); +} +.sim-btn-save { + background: var(--match-color, var(--gold)); + color: #000000; + font-weight: 800; +} +.sim-btn-save:hover { + filter: brightness(1.1); + transform: translateY(-1px); +} +.sim-btn-clear { + background: transparent; + border-color: var(--border-hover); + color: var(--text-secondary); +} +.sim-btn-clear:hover { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.3); + color: #ef4444; +} + @media (min-width: 1200px) { .groups-grid { grid-template-columns: repeat(4, 1fr); } }