// modal.js — match detail modal built on the native element // (focus trap, Esc-to-close and ::backdrop for free). Shows teams, time, // stadium, city, capacity, result (+penalties) and a placeholder section for // future stats (possession, shots, cards). "Add to calendar" lands in step 12. import { getData, formatMatchTime, flagSrc } from './app.js'; import { t, getLocale, translatePhase } from './i18n.js'; import { resolveBracketTeams } from './bracket.js'; import { getFavorites } from './storage.js'; import { exportMatchToICS } from './calendar.js'; let dialog = null; let currentMatchId = null; let lastFocused = null; export function initModal() { document.getElementById('modal-root').innerHTML = ''; dialog = document.getElementById('match-dialog'); // a click on the dialog element itself (not its padded content) = backdrop dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); }); dialog.addEventListener('close', () => { currentMatchId = null; lastFocused?.focus?.(); }); const rerenderIfOpen = () => { if (dialog.open && currentMatchId) renderContent(currentMatchId); }; document.addEventListener('langchange', rerenderIfOpen); document.addEventListener('favchange', rerenderIfOpen); document.addEventListener('timemodechange', rerenderIfOpen); } export function openMatchModal(matchId) { lastFocused = document.activeElement; currentMatchId = matchId; renderContent(matchId); dialog.showModal(); } // -------------------------------------------------------------- render function renderContent(matchId) { const { matches, resultByMatchId, stadiumByName } = getData(); const match = matches.find((m) => m.id === matchId); if (!match) return; const result = resultByMatchId.get(matchId); const status = result?.status ?? 'scheduled'; const s = result?.stats; const stadium = stadiumByName.get(match.stadium); const slots = resolveBracketTeams(match); const numberFmt = new Intl.NumberFormat(getLocale()); const statusChip = status === 'live' ? `● ${t('hero.live')}` : status === 'finished' ? `${t('status.finished')}` : `${t('status.scheduled')}`; let center; if (status === 'finished' || status === 'live') { const pens = result.penalties ? `(${result.penalties.home}–${result.penalties.away} ${t('status.pens')})` : ''; center = ``; } else { center = `${t('hero.vs')}`; } dialog.innerHTML = ` `; dialog.setAttribute('aria-label', `${slots.home.label} ${t('hero.vs')} ${slots.away.label} — ${translatePhase(match.phase)}`); dialog.querySelector('[data-close]').addEventListener('click', () => dialog.close()); dialog.querySelector('#modal-ics').addEventListener('click', () => exportMatchToICS(match, stadium)); } function teamHTML(slot) { if (!slot.team) return `
${slot.label}
`; const fav = getFavorites().includes(slot.team.id); return `
${slot.team.name}
`; } // stat row — shows real home/away values, or "—" when no stats data exists function statRow(home, label, away) { return ` `; }