diff --git a/app.js b/app.js index 21dabba..6258cbd 100644 --- a/app.js +++ b/app.js @@ -19,7 +19,7 @@ let roundPgns = {}; let pollId = 0; async function fetchRoundPGN(round) { - const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/partien/${round}.pgn`); + const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/partien/${round}.pgn?t=${Date.now()}`); if (!res.ok) return null; return await res.text(); } @@ -30,34 +30,19 @@ async function loadPGN(showOverlay = true) { hideError(); try { - if (!window.standingsLoaded) { - await updateStandings(); - window.standingsLoaded = true; - } + await updateStandings(); if (currentRound === 0) { if (showOverlay) showLoading(false); return; } - // Fehlende vergangene Runden einmalig nachladen - for (let r = 1; r < currentRound; r++) { - if (roundPgns[r] === undefined) { - const text = await fetchRoundPGN(r); - if (text !== null) roundPgns[r] = text; - } - } - - // Aktuelle Runde immer frisch holen - const pgnText = await fetchRoundPGN(currentRound); - if (currentPollId !== pollId) return; - if (pgnText !== null) roundPgns[currentRound] = pgnText; - - // Nächste Runde prüfen (sobald verfügbar, einmalig holen) - const nextRound = currentRound + 1; - if (roundPgns[nextRound] === undefined) { - const text = await fetchRoundPGN(nextRound).catch(() => null); - if (text) roundPgns[nextRound] = text; + // Alle Runden immer frisch von der DSJ laden (mit Cache-Busting) + const maxRound = currentRound + 1; + for (let r = 1; r <= maxRound; r++) { + const text = await fetchRoundPGN(r); + if (currentPollId !== pollId) return; + if (text !== null) roundPgns[r] = text; } // Alle PGNs kombinieren @@ -81,6 +66,7 @@ async function loadPGN(showOverlay = true) { updatePlayerInfo(); updateMovesList(); updateAllGamesList(); + updatePGNDisplay(); updateTimestamp(); showLoading(false); @@ -297,6 +283,48 @@ function highlightLastMove() { } } +/** + * Generiert PGN-Text aus einem Spiel-Objekt + */ +function generatePGN(game) { + if (!game) return ''; + + let pgn = ''; + if (game.event) pgn += `[Event "${game.event}"]\n`; + if (game.site) pgn += `[Site "${game.site}"]\n`; + if (game.date) pgn += `[Date "${game.date}"]\n`; + if (game.round) pgn += `[Round "${game.round}"]\n`; + if (game.white) pgn += `[White "${game.white}"]\n`; + if (game.black) pgn += `[Black "${game.black}"]\n`; + if (game.result) pgn += `[Result "${game.result}"]\n`; + if (game.whiteElo) pgn += `[WhiteElo "${game.whiteElo}"]\n`; + if (game.blackElo) pgn += `[BlackElo "${game.blackElo}"]\n`; + if (game.termination) pgn += `[Termination "${game.termination}"]\n`; + + pgn += '\n'; + + const nonResultMoves = game.moves.filter(m => !m.isResult); + for (let i = 0; i < nonResultMoves.length; i += 2) { + const moveNumber = Math.floor(i / 2) + 1; + pgn += `${moveNumber}. ${nonResultMoves[i].san}`; + if (i + 1 < nonResultMoves.length) { + pgn += ` ${nonResultMoves[i + 1].san} `; + } + } + + const resultMove = game.moves.find(m => m.isResult); + if (resultMove) { + pgn += ` ${resultMove.san}`; + } + + return pgn.trim(); +} + +function updatePGNDisplay() { + if (!currentGame) return; + document.getElementById('pgn-text').textContent = generatePGN(currentGame); +} + /** * Aktualisiert die Zugliste */ @@ -405,6 +433,7 @@ function updateAllGamesList() { updatePlayerInfo(); updateMovesList(); updateAllGamesList(); + updatePGNDisplay(); }); list.appendChild(entry); @@ -416,7 +445,7 @@ function updateAllGamesList() { */ async function updateStandings() { try { - const res = await fetch('https://www.deutsche-schachjugend.de/2026/odjm-d/tabelle/'); + const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/tabelle/?t=${Date.now()}`); if (!res.ok) throw new Error('Fehler beim Laden'); const html = await res.text(); @@ -552,6 +581,28 @@ document.getElementById('refresh-btn').addEventListener('click', () => { startAutoRefresh(); }); +/** + * PGN in Zwischenablage kopieren + */ +document.getElementById('copy-pgn-btn').addEventListener('click', async () => { + const text = document.getElementById('pgn-text').textContent; + if (!text) return; + try { + await navigator.clipboard.writeText(text); + const btn = document.getElementById('copy-pgn-btn'); + btn.textContent = '✅'; + setTimeout(() => { btn.textContent = '📋'; }, 1500); + } catch { + // Fallback + const ta = document.createElement('textarea'); + ta.value = text; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } +}); + /** * Pfeiltasten-Navigation */ diff --git a/index.html b/index.html index bb57fed..bab365f 100644 --- a/index.html +++ b/index.html @@ -38,6 +38,13 @@
--:--:--
+
+
+ PGN + +
+

+                
diff --git a/style.css b/style.css index 46a071a..823c98a 100644 --- a/style.css +++ b/style.css @@ -147,6 +147,55 @@ header h1 { max-width: 500px; } +#pgn-panel { + width: 100%; + max-width: 500px; + background: rgba(0, 0, 0, 0.3); + border-radius: 12px; + padding: 12px 16px; +} + +.pgn-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + color: #e94560; + font-weight: bold; + font-size: 0.9rem; +} + +#copy-pgn-btn { + background: rgba(255, 255, 255, 0.1); + border: none; + color: #ccc; + width: 32px; + height: 32px; + border-radius: 6px; + cursor: pointer; + font-size: 1rem; + transition: background 0.2s; +} + +#copy-pgn-btn:hover { + background: rgba(255, 255, 255, 0.2); +} + +#pgn-text { + font-family: 'Courier New', monospace; + font-size: 0.78rem; + line-height: 1.5; + color: #aaa; + white-space: pre-wrap; + word-break: break-all; + max-height: 160px; + overflow-y: auto; + padding: 8px; + background: rgba(0, 0, 0, 0.4); + border-radius: 6px; + user-select: text; +} + /* Info Section */ #info-section { flex: 0 0 380px;