Change secret code to PIN system with explainer text

- Renamed 'secret code' to 'PIN' throughout UI
- Added PIN explainer text explaining how system identifies players
- New players get random 6-char PIN, must save it
- Players use name + PIN to update scores
- PIN ensures unique identification for each player
This commit is contained in:
Gemini AI
2025-12-27 03:06:55 +04:00
Unverified
parent 230178cd7f
commit 744e9c9714
3 changed files with 217 additions and 36 deletions

207
app.js
View File

@@ -253,27 +253,161 @@ function initGame() {
});
});
const playersData = JSON.parse(localStorage.getItem('trae_players') || '{}');
const generateSecretCode = () => {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let code = '';
for (let i = 0; i < 6; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
return code;
};
window.submitScore = (gameType, score, nameInputId, codeInputId, hintId) => {
const nameInput = document.getElementById(nameInputId);
const codeInput = document.getElementById(codeInputId);
const name = nameInput.value.trim() || 'Anonymous';
let secretCode = codeInput.value.trim().toUpperCase();
if (secretCode && playersData[name] && playersData[name].code === secretCode) {
secretCode = playersData[name].code;
} else if (!secretCode) {
secretCode = generateSecretCode();
} else {
alert('PIN not found for this name. Leave blank for new PIN.');
return false;
}
if (!playersData[name]) {
playersData[name] = {
name: name,
code: secretCode,
games: {},
lastPlayed: new Date().toISOString()
};
} else {
playersData[name].code = secretCode;
playersData[name].lastPlayed = new Date().toISOString();
}
if (!playersData[name].games[gameType] || playersData[name].games[gameType].score < score) {
playersData[name].games[gameType] = {
score: score,
date: new Date().toISOString()
};
}
localStorage.setItem('trae_players', JSON.stringify(playersData));
document.getElementById(hintId).innerText = `Your PIN: ${secretCode}`;
nameInput.value = '';
codeInput.value = '';
updateLeaderboardUI();
return true;
};
window.copySecretCode = (code) => {
navigator.clipboard.writeText(code).then(() => {
const btn = event.target;
const originalText = btn.innerText;
btn.innerText = 'Copied!';
btn.style.background = 'var(--trae-green)';
setTimeout(() => {
btn.innerText = code;
btn.style.background = '';
}, 1500);
});
};
// Leaderboard logic
let leaderboard = JSON.parse(localStorage.getItem('trae_leaderboard') || '[]');
let currentTab = 'total';
const updateLeaderboardUI = () => {
leaderboardList.innerHTML = '';
leaderboard.sort((a, b) => b.score - a.score).slice(0, 10).forEach((entry, idx) => {
let displayData = [];
if (currentTab === 'total') {
Object.values(playersData).forEach(player => {
const totalScore = Object.values(player.games).reduce((sum, val) => sum + val.score, 0);
displayData.push({
name: player.name,
code: player.code,
totalScore,
game: 'Total',
score: totalScore,
date: player.lastPlayed
});
});
displayData.sort((a, b) => b.totalScore - a.totalScore);
} else {
Object.values(playersData).forEach(player => {
if (player.games[currentTab]) {
displayData.push({
name: player.name,
code: player.code,
game: currentTab,
score: player.games[currentTab].score,
date: player.games[currentTab].date
});
}
});
displayData.sort((a, b) => b.score - a.score);
}
displayData.slice(0, 50).forEach((entry, idx) => {
const dateStr = new Date(entry.date).toLocaleDateString();
const durationStr = entry.duration ? `${entry.duration}s` : 'N/A';
const div = document.createElement('div');
div.className = 'leader-item';
div.innerHTML = `
<span class="leader-rank">#${idx + 1}</span>
<span class="leader-name">${entry.name}</span>
<span class="leader-score">${entry.score}</span>
<span class="leader-duration">${durationStr}</span>
<span class="leader-date">${dateStr}</span>
`;
if (currentTab === 'total') {
div.innerHTML = `
<span class="leader-rank">#${idx + 1}</span>
<span class="leader-name">${entry.name}</span>
<span class="leader-game">${entry.game}</span>
<span class="leader-score">${entry.totalScore}</span>
<span class="leader-date">${dateStr}</span>
`;
} else {
const gameLabels = {
gift: 'Gift Catcher',
traoom: 'Traoom',
neonpuzzle: 'Neon Puzzle',
rhythm: 'Rhythm Beat',
arena: 'Cosmic Arena',
tetris: 'Crystal Tetris',
platformer: 'Aurora Jumper'
};
div.innerHTML = `
<span class="leader-rank">#${idx + 1}</span>
<span class="leader-name">${entry.name}</span>
<span class="leader-game">${gameLabels[currentTab]}</span>
<span class="leader-score">${entry.score}</span>
<span class="leader-code" onclick="copySecretCode('${entry.code}')">${entry.code}</span>
<span class="leader-date">${dateStr}</span>
`;
}
leaderboardList.appendChild(div);
});
if (displayData.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.className = 'leader-item';
emptyMsg.style.justifyContent = 'center';
emptyMsg.innerHTML = `<span style="color: #888;">No scores yet for ${currentTab === 'total' ? 'Total' : currentTab}!</span>`;
leaderboardList.appendChild(emptyMsg);
}
};
const tabButtons = document.querySelectorAll('.tab-btn');
tabButtons.forEach(btn => {
btn.addEventListener('click', () => {
tabButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentTab = btn.dataset.tab;
updateLeaderboardUI();
});
});
const startTimer = () => {
timeLeft = selectedDuration;
timerDisplay.innerText = timeLeft + 's';
@@ -306,13 +440,11 @@ function initGame() {
};
submitBtn.addEventListener('click', () => {
const name = nameInput.value.trim() || 'Anonymous';
leaderboard.push({ name, score, date: new Date().toISOString(), duration: selectedDuration });
localStorage.setItem('trae_leaderboard', JSON.stringify(leaderboard));
updateLeaderboardUI();
overlay.classList.add('hidden');
startBtn.style.display = 'block';
document.body.style.overflow = '';
if (submitScore('gift', score, 'playerName', 'playerSecretCode', 'codeHint')) {
overlay.classList.add('hidden');
startBtn.style.display = 'block';
document.body.style.overflow = '';
}
});
const resize = () => {
@@ -737,8 +869,7 @@ function initGame() {
// Export/Import Leaderboard
window.exportLeaderboard = () => {
const leaderboard = JSON.parse(localStorage.getItem('trae_leaderboard') || '[]');
const dataStr = JSON.stringify(leaderboard, null, 2);
const dataStr = JSON.stringify(playersData, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
@@ -756,9 +887,13 @@ window.importLeaderboard = (event) => {
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (Array.isArray(data)) {
localStorage.setItem('trae_leaderboard', JSON.stringify(data));
location.reload();
if (typeof data === 'object' && data !== null) {
Object.keys(data).forEach(name => {
playersData[name] = data[name];
});
localStorage.setItem('trae_players', JSON.stringify(playersData));
updateLeaderboardUI();
alert(`Imported ${Object.keys(data).length} player(s)!`);
}
} catch (err) {
alert('Invalid file format');
@@ -1189,6 +1324,13 @@ function initTraoom() {
mouse.y = touch.clientY - rect.top;
}, { passive: false });
document.getElementById('submitTraoomBtn').addEventListener('click', () => {
if (submitScore('traoom', kills, 'traoomPlayerName', 'traoomSecretCode', 'traoomCodeHint')) {
overlay.classList.add('hidden');
startBtn.style.display = 'block';
}
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
@@ -1574,6 +1716,13 @@ function initRhythmBeat() {
}
});
document.getElementById('submitRhythmBtn').addEventListener('click', () => {
if (submitScore('rhythm', score, 'rhythmPlayerName', 'rhythmSecretCode', 'rhythmCodeHint')) {
overlay.classList.add('hidden');
startBtn.style.display = 'block';
}
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
@@ -1849,6 +1998,13 @@ function initCosmicArena() {
});
});
document.getElementById('submitArenaBtn').addEventListener('click', () => {
if (submitScore('arena', kills, 'arenaPlayerName', 'arenaSecretCode', 'arenaCodeHint')) {
overlay.classList.add('hidden');
startBtn.style.display = 'block';
}
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
@@ -2347,6 +2503,13 @@ function initPlatformer() {
window.addEventListener('keydown', (e) => keys[e.code] = true);
window.addEventListener('keyup', (e) => keys[e.code] = false);
document.getElementById('submitPlatformerBtn').addEventListener('click', () => {
if (submitScore('platformer', collected * 10 + Math.max(0, 100 - parseInt(formatTime(gameTime).replace(':', ''))), 'platformerPlayerName', 'platformerSecretCode', 'platformerCodeHint')) {
overlay.classList.add('hidden');
startBtn.style.display = 'block';
}
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);

View File

@@ -164,10 +164,11 @@
<h3 id="overlayTitle">GAME OVER</h3>
<div class="input-group">
<input type="text" id="playerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="playerSecretCode" placeholder="Your secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="playerSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitScoreBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="codeHint">New players get a secret code automatically</p>
<p class="code-hint" id="codeHint">New players get a PIN automatically</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
</div>
<button id="startGameBtn" class="btn-primary">Start Mission</button>
</div>
@@ -216,10 +217,11 @@
<p id="traoomOverlayTime">Time Survived: <span id="finalTime">0:00</span></p>
<div class="input-group">
<input type="text" id="traoomPlayerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="traoomSecretCode" placeholder="Secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="traoomSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitTraoomBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="traoomCodeHint">New players get a secret code</p>
<p class="code-hint" id="traoomCodeHint">New players get a PIN</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
<button id="restartTraoomBtn" class="btn-primary">Play Again</button>
</div>
<button id="startTraoomBtn" class="btn-primary">Start Bug Hunt</button>
@@ -248,10 +250,11 @@
<p id="puzzleOverlayScore">Moves: <span id="finalMoves">0</span></p>
<div class="input-group">
<input type="text" id="puzzlePlayerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="puzzleSecretCode" placeholder="Secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="puzzleSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitPuzzleBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="puzzleCodeHint">New players get a secret code</p>
<p class="code-hint" id="puzzleCodeHint">New players get a PIN</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
<button id="restartPuzzleBtn" class="btn-primary">Play Again</button>
</div>
<button id="startPuzzleBtn" class="btn-primary">Start Puzzle</button>
@@ -281,10 +284,11 @@
<p id="rhythmOverlayScore">Final Score: <span id="finalRhythmScore">0</span></p>
<div class="input-group">
<input type="text" id="rhythmPlayerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="rhythmSecretCode" placeholder="Secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="rhythmSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitRhythmBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="rhythmCodeHint">New players get a secret code</p>
<p class="code-hint" id="rhythmCodeHint">New players get a PIN</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
<button id="restartRhythmBtn" class="btn-primary">Play Again</button>
</div>
<button id="startRhythmBtn" class="btn-primary">Start Rhythm</button>
@@ -314,10 +318,11 @@
<p id="arenaOverlayScore">Enemies Defeated: <span id="finalArenaKills">0</span></p>
<div class="input-group">
<input type="text" id="arenaPlayerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="arenaSecretCode" placeholder="Secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="arenaSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitArenaBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="arenaCodeHint">New players get a secret code</p>
<p class="code-hint" id="arenaCodeHint">New players get a PIN</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
<button id="restartArenaBtn" class="btn-primary">Play Again</button>
</div>
<button id="startArenaBtn" class="btn-primary">Enter Arena</button>
@@ -347,10 +352,11 @@
<p id="tetrisOverlayScore">Final Score: <span id="finalTetrisScore">0</span></p>
<div class="input-group">
<input type="text" id="tetrisPlayerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="tetrisSecretCode" placeholder="Secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="tetrisSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitTetrisBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="tetrisCodeHint">New players get a secret code</p>
<p class="code-hint" id="tetrisCodeHint">New players get a PIN</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
<button id="restartTetrisBtn" class="btn-primary">Play Again</button>
</div>
<button id="startTetrisBtn" class="btn-primary">Start Game</button>
@@ -380,10 +386,11 @@
<p id="platformerOverlayTime">Time: <span id="finalPlatformerTime">0:00</span></p>
<div class="input-group">
<input type="text" id="platformerPlayerName" placeholder="Enter your name" maxlength="15">
<input type="text" id="platformerSecretCode" placeholder="Secret code (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<input type="text" id="platformerSecretCode" placeholder="Your PIN (optional)" maxlength="6" style="width: 120px; text-transform: uppercase;">
<button id="submitPlatformerBtn" class="btn-primary">Save Score</button>
</div>
<p class="code-hint" id="platformerCodeHint">New players get a secret code</p>
<p class="code-hint" id="platformerCodeHint">New players get a PIN</p>
<p class="pin-explainer">Enter your name + PIN to save scores. The system uses your PIN to identify you. Save your PIN - it's unique to you!</p>
<button id="restartPlatformerBtn" class="btn-primary">Play Again</button>
</div>
<button id="startPlatformerBtn" class="btn-primary">Start Game</button>

View File

@@ -1032,6 +1032,17 @@ body {
font-style: italic;
}
.pin-explainer {
color: var(--aurora-cyan);
font-size: 0.75rem;
text-align: center;
margin-top: 8px;
padding: 8px 12px;
border-radius: 8px;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
}
.export-buttons {
display: flex;
gap: 15px;