Add archive navigation feature with URL parameter support
- Add URL parameter parsing via getGameNumberFromURL() - Add dynamic MAX_GAME_NUMBER detection from games.json in order to properly handle page overflow - Add runtime validation with redirect for invalid game numbers - Implement Previous/Next navigation buttons - Add keyboard navigation (arrow keys) - Display game number and date in navigation - Update share URLs to include day parameter for archived games - Add navigation UI (HTML/CSS) matching project design
This commit is contained in:
parent
35f5a6391d
commit
b00f41ac80
@ -102,6 +102,11 @@
|
||||
<div id="product-info"></div>
|
||||
</div>
|
||||
<div id="game-stats"></div>
|
||||
<div id="navigation-container">
|
||||
<button id="prev-button" class="nav-button">← Previous</button>
|
||||
<span id="day-indicator"></span>
|
||||
<button id="next-button" class="nav-button">Next →</button>
|
||||
</div>
|
||||
<div id="guesses-container">
|
||||
<div id="warning-toast" class="animate__animated hide">
|
||||
<center>Invalid Guess ⚠️</center>
|
||||
|
||||
106
scripts/main.js
106
scripts/main.js
@ -18,7 +18,29 @@ let warningTimeout;
|
||||
|
||||
//The day Costcodle was launched. Used to find game number each day
|
||||
const costcodleStartDate = new Date("09/21/2023");
|
||||
const gameNumber = getGameNumber();
|
||||
|
||||
// Placeholder value until actual game count is loaded from games.json (10000 is chosen as it's
|
||||
// safely beyond any realistic game count, allowing initial URL validation to pass through)
|
||||
let MAX_GAME_NUMBER = 10000;
|
||||
|
||||
// Get game number from URL parameter or use current date
|
||||
function getGameNumberFromURL() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const dayParam = urlParams.get('day');
|
||||
|
||||
if (dayParam) {
|
||||
const requestedDay = parseInt(dayParam, 10);
|
||||
// Validate the day is within available range
|
||||
if (!isNaN(requestedDay) && requestedDay >= 1 && requestedDay <= MAX_GAME_NUMBER) {
|
||||
return requestedDay;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to current day
|
||||
return getGameNumber();
|
||||
}
|
||||
|
||||
const gameNumber = getGameNumberFromURL();
|
||||
|
||||
//Elements with event listeners to play the game
|
||||
const input = document.getElementById("guess-input");
|
||||
@ -53,7 +75,7 @@ const gameState = JSON.parse(localStorage.getItem("state")) || {
|
||||
playGame();
|
||||
|
||||
function playGame() {
|
||||
fetchGameData(getGameNumber());
|
||||
fetchGameData(gameNumber);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -65,6 +87,19 @@ function fetchGameData(gameNumber) {
|
||||
fetch("./games.json")
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
// Dynamically determine max game number from JSON keys
|
||||
if (MAX_GAME_NUMBER === 10000) {
|
||||
const gameKeys = Object.keys(json).filter(key => key.startsWith('game-'));
|
||||
MAX_GAME_NUMBER = gameKeys.length;
|
||||
}
|
||||
|
||||
// Validate that the requested game actually exists
|
||||
if (!json[`game-${gameNumber}`]) {
|
||||
console.warn(`Game #${gameNumber} not found, redirecting to current day`);
|
||||
window.location.href = window.location.pathname; // Redirect to current day
|
||||
return;
|
||||
}
|
||||
|
||||
productName = json[`game-${gameNumber}`].name;
|
||||
productPrice = json[`game-${gameNumber}`].price;
|
||||
productPrice = Number(productPrice.slice(1, productPrice.length));
|
||||
@ -102,6 +137,9 @@ function initializeGame() {
|
||||
} else {
|
||||
convertToShareButton();
|
||||
}
|
||||
|
||||
// Initialize navigation buttons
|
||||
initializeNavigation();
|
||||
}
|
||||
|
||||
function convertToShareButton() {
|
||||
@ -244,18 +282,24 @@ function copyStats() {
|
||||
navigator.userAgent.match(/IEMobile/i) ||
|
||||
navigator.userAgent.match(/Opera Mini/i);
|
||||
|
||||
// Calculate current day once for share URL logic
|
||||
const currentDay = getGameNumber();
|
||||
const shareUrl = gameNumber !== currentDay
|
||||
? `https://costcodle.com/?day=${gameNumber}`
|
||||
: "https://costcodle.com";
|
||||
|
||||
if (isMobile) {
|
||||
if (navigator.canShare) {
|
||||
navigator
|
||||
.share({
|
||||
title: "COSTCODLE",
|
||||
text: output,
|
||||
url: "https://costcodle.com",
|
||||
url: shareUrl,
|
||||
})
|
||||
.catch((error) => console.error("Share failed:", error));
|
||||
}
|
||||
} else {
|
||||
output += `https://costcodle.com`;
|
||||
output += shareUrl;
|
||||
navigator.clipboard.writeText(output);
|
||||
displayToast();
|
||||
}
|
||||
@ -513,3 +557,57 @@ function getGameNumber() {
|
||||
return Math.ceil(dayDifference) + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Navigation functions for previous/next game
|
||||
*/
|
||||
|
||||
function initializeNavigation() {
|
||||
const prevButton = document.getElementById("prev-button");
|
||||
const nextButton = document.getElementById("next-button");
|
||||
const dayIndicator = document.getElementById("day-indicator");
|
||||
|
||||
// Verify elements exist before proceeding
|
||||
if (!prevButton || !nextButton || !dayIndicator) {
|
||||
console.error('Navigation elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Display current game number and date
|
||||
const gameDate = new Date(costcodleStartDate.getTime() + (gameNumber - 1) * (1000 * 3600 * 24));
|
||||
dayIndicator.textContent = `Game #${gameNumber}`;
|
||||
dayIndicator.title = gameDate.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric'});
|
||||
|
||||
// Disable buttons at boundaries
|
||||
prevButton.disabled = gameNumber <= 1;
|
||||
nextButton.disabled = gameNumber >= MAX_GAME_NUMBER;
|
||||
|
||||
// Add event listeners
|
||||
prevButton.addEventListener("click", navigateToPrevious);
|
||||
nextButton.addEventListener("click", navigateToNext);
|
||||
document.addEventListener("keydown", handleKeyboardNavigation);
|
||||
}
|
||||
|
||||
function navigateToPrevious() {
|
||||
if (gameNumber > 1) {
|
||||
window.location.href = `?day=${gameNumber - 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
function navigateToNext() {
|
||||
if (gameNumber < MAX_GAME_NUMBER) {
|
||||
window.location.href = `?day=${gameNumber + 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyboardNavigation(event) {
|
||||
// Only handle arrow keys when not typing in input fields
|
||||
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
navigateToPrevious();
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
navigateToNext();
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,3 +246,43 @@
|
||||
border-radius: 8px;
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
/* Navigation container */
|
||||
#navigation-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
font-family: "VT323", monospace;
|
||||
font-size: 20px;
|
||||
padding: 8px 16px;
|
||||
color: white;
|
||||
background-color: rgb(0, 96, 169);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.nav-button:hover:not(:disabled) {
|
||||
background-color: rgba(0, 96, 169, 0.8);
|
||||
}
|
||||
|
||||
.nav-button:disabled {
|
||||
background-color: rgb(173, 173, 173);
|
||||
color: rgb(100, 100, 100);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#day-indicator {
|
||||
font-family: "VT323", monospace;
|
||||
font-size: 18px;
|
||||
color: rgb(50, 50, 50);
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user