/*! * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) * Copyright 2011-2025 The Bootstrap Authors * Licensed under the Creative Commons Attribution 3.0 Unported License. */ (() => { 'use strict' const getStoredTheme = () => localStorage.getItem('theme') const setStoredTheme = theme => localStorage.setItem('theme', theme) const getPreferredTheme = () => { const storedTheme = getStoredTheme() if (storedTheme) { return storedTheme } return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' } const setTheme = theme => { if (theme === 'auto') { document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')) } else { document.documentElement.setAttribute('data-bs-theme', theme) } } setTheme(getPreferredTheme()) const showActiveTheme = (theme, focus = false) => { const themeSwitcher = document.querySelector('#bd-theme') if (!themeSwitcher) { return } const themeSwitcherText = document.querySelector('#bd-theme-text') const activeThemeIcon = document.querySelector('.theme-icon-active use') const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href') document.querySelectorAll('[data-bs-theme-value]').forEach(element => { element.classList.remove('active') element.setAttribute('aria-pressed', 'false') }) btnToActive.classList.add('active') btnToActive.setAttribute('aria-pressed', 'true') activeThemeIcon.setAttribute('href', svgOfActiveBtn) const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})` themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) if (focus) { themeSwitcher.focus() } } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { const storedTheme = getStoredTheme() if (storedTheme !== 'light' && storedTheme !== 'dark') { setTheme(getPreferredTheme()) } }) window.addEventListener('DOMContentLoaded', () => { showActiveTheme(getPreferredTheme()) document.querySelectorAll('[data-bs-theme-value]') .forEach(toggle => { toggle.addEventListener('click', () => { const theme = toggle.getAttribute('data-bs-theme-value') setStoredTheme(theme) setTheme(theme) showActiveTheme(theme, true) }) }) }) })() function pollForUpdates(gameId) { console.log(`[DEBUG] Starting poll cycle for Game ID: ${gameId} at ${new Date().toISOString()}`); if (!gameId) { console.error("[DEBUG] Game ID is missing. Stopping poll."); return; } const $handElement = $('#card-slide'); const $lobbyElement = $('#lobbybackground'); const $mainmenuElement = $('#main-menu-screen') if (!$handElement.length && !$lobbyElement.length && !$mainmenuElement.length) { setTimeout(() => pollForUpdates(gameId), 5000); return; } const route = jsRoutes.controllers.PollingController.polling(gameId); $.ajax({ url: route.url, type: 'GET', dataType: 'json', success: (data => { if (!data) { console.log("[DEBUG] Received 204 No Content (Timeout). Restarting poll."); return; } if (data.status === "cardPlayed" && data.handData) { console.log("Event received: Card played. Redrawing hand."); const newHand = data.handData; let newHandHTML = ''; $handElement.empty(); if(data.animation) { $handElement.addClass('ingame-cards-slide'); } else { $handElement.removeClass('ingame-cards-slide'); } newHand.forEach((cardId, index) => { const cardHtml = `
`; newHandHTML += cardHtml; }); $handElement.html(newHandHTML); $('#current-player-name').text(data.currentPlayerName) if (data.nextPlayer) { $('#next-player-name').text(data.nextPlayer); } else if (nextPlayerElement) { $('#next-player-name').text(""); } else { console.warn("[DEBUG] 'current-player-name' element missing in DOM"); } $('#trump-suit').text(data.trumpSuit); if ($('#trick-cards-container').length) { let trickHTML = ''; data.trickCards.forEach(trickCard => { trickHTML += `
${trickCard.player}
`; }); $('#trick-cards-container').html(trickHTML); } if ($('#score-table-body').length && data.scoreTable) { let scoreHTML = ''; scoreHTML += `

Tricks Won

PLAYER
TRICKS
` data.scoreTable.forEach(score => { scoreHTML += `
${score.name}
${score.tricks}
`; }); $('#score-table-body').html(scoreHTML); } const cardId = data.firstCardId; if ($('#first-card-container').length) { let imageSrc = ''; let altText = 'First Card'; if (cardId === "BLANK") { imageSrc = "/assets/images/cards/1B.png"; altText = "Blank Card"; } else { imageSrc = `/assets/images/cards/${cardId}.png`; } const newImageHTML = ` ${altText} `; $('#first-card-container').html(newImageHTML); } } else if (data.status === "reloadEvent") { console.log("[DEBUG] Reload event received. Redirecting..."); window.location.href = data.redirectUrl; } else if (data.status === "lobbyUpdate") { console.log("[DEBUG] Entering 'lobbyUpdate' logic."); let newHtml = '' if (data.host) { data.users.forEach(user => { const inner = user.self ? `
${user.name} (You)
Remove` : `
${user.name}
Remove
` newHtml += `
Profile
${inner}
` }) } else { data.users.forEach(user => { const inner = user.self ? `
${user.name} (You)
` : `
${user.name}
` newHtml += `
Profile
${inner}
` }) } $("#players").html(newHtml); } else { console.warn(`[DEBUG] Received unknown status: ${data.status}`); } }), error: ((jqXHR, textStatus, errorThrown) => { if (jqXHR.status >= 400) { console.error(`Server error: ${jqXHR.status}, ${errorThrown}`); } else { console.error(`Something unexpected happened while polling. ${jqXHR.status}, ${errorThrown}`) } }), complete: ((jqXHR, textStatus) => { if (!window.location.href.includes("game")) { console.log("[DEBUG] Page URL changed. Stopping poll restart."); return; } setTimeout(() => pollForUpdates(gameId), 500); }) }) } function createGameJS() { let lobbyName = $('#lobbyname').val(); if ($.trim(lobbyName) === "") { lobbyName = "DefaultLobby" } const jsonObj = { lobbyname: lobbyName, playeramount: $("#playeramount").val() } sendGameCreationRequest(jsonObj); } function sendGameCreationRequest(dataObject) { const route = jsRoutes.controllers.MainMenuController.createGame(); $.ajax({ url: route.url, type: route.type, contentType: 'application/json', data: JSON.stringify(dataObject), dataType: 'json', success: (data => { if (data.status === 'success') { window.location.href = data.redirectUrl; } }), error: ((jqXHR) => { const errorData = JSON.parse(jqXHR.responseText); if (errorData && errorData.errorMessage) { alert(`${errorData.errorMessage}`); } else { alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`); } }) }) } function startGame(gameId) { sendGameStartRequest(gameId) } function sendGameStartRequest(gameId) { const route = jsRoutes.controllers.IngameController.startGame(gameId); $.ajax({ url: route.url, type: route.type, dataType: 'json', success: (data => { if (data.status === 'success') { window.location.href = data.redirectUrl; } }), error: ((jqXHR) => { const errorData = JSON.parse(jqXHR.responseText); if (errorData && errorData.errorMessage) { alert(`${errorData.errorMessage}`); } else { alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`); } }) }) } function removePlayer(gameid, playersessionId) { sendRemovePlayerRequest(gameid, playersessionId) } function sendRemovePlayerRequest(gameId, playersessionId) { const route = jsRoutes.controllers.IngameController.kickPlayer(gameId, playersessionId); $.ajax({ url: route.url, type: route.type, contentType: 'application/json', dataType: 'json', success: (data => { if (data.status === 'success') { window.location.href = data.redirectUrl; } }), error: ((jqXHR) => { const errorData = JSON.parse(jqXHR.responseText); if (errorData && errorData.errorMessage) { alert(`${errorData.errorMessage}`); } else { alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`); } }) }) } function leaveGame(gameId) { sendLeavePlayerRequest(gameId) } function sendLeavePlayerRequest(gameId) { const route = jsRoutes.controllers.IngameController.leaveGame(gameId); $.ajax({ url: route.url, type: route.type, dataType: 'json', success: (data => { if (data.status === 'success') { window.location.href = data.redirectUrl; } }), error: ((jqXHR) => { const errorData = JSON.parse(jqXHR.responseText); if (errorData && errorData.errorMessage) { alert(`${errorData.errorMessage}`); } else { alert(`An unexpected error occurred. Please try again. Status: ${jqXHR.status}`); } }) }) } function handlePlayCard(cardobject, gameId) { const cardId = cardobject.dataset.cardId; const jsonObj = { cardID: cardId } sendPlayCardRequest(jsonObj, gameId, cardobject) } function sendPlayCardRequest(jsonObj, gameId, cardobject) { const wiggleKeyframes = [ { transform: 'translateX(0)' }, { transform: 'translateX(-5px)' }, { transform: 'translateX(5px)' }, { transform: 'translateX(-5px)' }, { transform: 'translateX(0)' } ]; const wiggleTiming = { duration: 400, iterations: 1, easing: 'ease-in-out', fill: 'forwards' }; const route = jsRoutes.controllers.IngameController.playCard(gameId); $.ajax({ url: route.url, type: route.type, contentType: 'application/json', dataType: 'json', data: JSON.stringify(jsonObj), success: (data => { if (data.status === 'success') { } }), error: (jqXHR => { try { error = JSON.parse(jqXHR.responseText); } catch (e) { console.error("Failed to parse error response:", e); } if (error && error.errorMessage.includes("You can't play this card!")) { cardobject.parentElement.animate(wiggleKeyframes, wiggleTiming); } else if (error && error.errorMessage) { alert(`${error.errorMessage}`); } else { alert('An unexpected error occurred. Please try again.'); } }) }) }