/** * Player Zoom Card โ€” shows player info with photo, stats, fun fact */ import { escapeHTML, setHTML } from '../utils/dom.js'; export class PlayerCard { constructor(containerEl) { this.containerEl = containerEl; } render() { if (!this.containerEl) return; setHTML(this.containerEl, ` `); this.containerEl.querySelector('.player-card__close')?.addEventListener('click', () => this.hide()); } show(sceneData) { if (!this.containerEl) return; const contentEl = this.containerEl.querySelector('#player-card-content'); if (!contentEl) return; const safeHome = escapeHTML(sceneData.homeTeam || 'Unknown'); const safeAway = escapeHTML(sceneData.awayTeam || 'Unknown'); const safeScore = escapeHTML(sceneData.score || 'unknown'); const safeMinute = escapeHTML(sceneData.minute || 'unknown'); const safeInFocus = escapeHTML(sceneData.inFocus || 'Player in focus'); const safePhase = escapeHTML(sceneData.phase || 'open play'); const safeFunFact = escapeHTML(sceneData.funFact || 'No extra context available for this frame.'); // A named player is only shown when Gemini could legibly identify one. const named = sceneData.player && sceneData.player !== 'Unknown' ? escapeHTML(sceneData.player) : null; const safeConfidence = Number.isFinite(Number(sceneData.confidence)) ? Number(sceneData.confidence) : 0; const notConfigured = sceneData.source === 'not-configured'; const isUncertain = Boolean(sceneData.isUncertain) || safeConfidence < 0.7; const confClass = isUncertain ? 'confidence-tag--low' : 'confidence-tag--high'; const confLabel = notConfigured ? 'AI vision unavailable' : `${isUncertain ? 'Reading' : 'Clear read'} ยท ${Math.round(safeConfidence * 100)}% confidence`; setHTML(contentEl, `
๐Ÿ” AI Vision Read

${safeHome} ${safeScore} ${safeAway}

Match clock ยท ${safeMinute}

${escapeHTML(confLabel)}
In focus ${named ? `${named} โ€” ` : ''}${safeInFocus}
Phase ${safePhase}
What Gemini sees: ${safeFunFact}
`); this.containerEl.hidden = false; const cardEl = this.containerEl.querySelector('.player-card'); cardEl?.focus(); this._bindFocusTrap(cardEl); } showLoading() { if (!this.containerEl) return; const contentEl = this.containerEl.querySelector('#player-card-content'); if (contentEl) { setHTML(contentEl, `

Analyzing camera frame with Gemini Vision...

`); } this.containerEl.hidden = false; } showError(msg) { if (!this.containerEl) return; const contentEl = this.containerEl.querySelector('#player-card-content'); if (contentEl) { setHTML(contentEl, `

${escapeHTML(msg)}

`); } this.containerEl.hidden = false; } _bindFocusTrap(cardEl) { if (!cardEl) return; this._unbindFocusTrap(); this._focusTrapHandler = (e) => { if (e.key !== 'Tab') return; const focusables = cardEl.querySelectorAll('button, [tabindex="0"]'); if (focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; if (e.shiftKey) { if (document.activeElement === first) { last.focus(); e.preventDefault(); } } else { if (document.activeElement === last) { first.focus(); e.preventDefault(); } } }; cardEl.addEventListener('keydown', this._focusTrapHandler); } _unbindFocusTrap() { if (this._focusTrapHandler && this.containerEl) { const cardEl = this.containerEl.querySelector('.player-card'); cardEl?.removeEventListener('keydown', this._focusTrapHandler); this._focusTrapHandler = null; } } hide() { this._unbindFocusTrap(); if (this.containerEl) this.containerEl.hidden = true; } }