405 lines
11 KiB
HTML
405 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CatDoku - Classifica</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
<style>
|
|
.leaderboard-container {
|
|
max-width: 800px;
|
|
width: 100%;
|
|
padding: 20px;
|
|
}
|
|
|
|
.leaderboard-header {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.leaderboard-header h1 {
|
|
font-size: 36px;
|
|
color: #5d4e37;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.leaderboard-header p {
|
|
color: #8b7355;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.back-btn {
|
|
display: inline-block;
|
|
padding: 10px 20px;
|
|
background: #5d4e37;
|
|
color: #fff;
|
|
text-decoration: none;
|
|
border-radius: 10px;
|
|
margin-bottom: 20px;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.back-btn:hover {
|
|
background: #7a6750;
|
|
}
|
|
|
|
.filter-section {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.filter-section label {
|
|
font-size: 14px;
|
|
color: #8b7355;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.filter-section select,
|
|
.filter-section input {
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
border: 2px solid #d4c5b0;
|
|
font-size: 14px;
|
|
color: #5d4e37;
|
|
background: #fff;
|
|
}
|
|
|
|
.leaderboard-table {
|
|
width: 100%;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.leaderboard-table table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.leaderboard-table th {
|
|
background: #5d4e37;
|
|
color: #fff;
|
|
padding: 15px 10px;
|
|
text-align: left;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.leaderboard-table td {
|
|
padding: 12px 10px;
|
|
border-bottom: 1px solid #f0e8dc;
|
|
font-size: 14px;
|
|
color: #5d4e37;
|
|
}
|
|
|
|
.leaderboard-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.leaderboard-table tr:hover td {
|
|
background: #f8f4ff;
|
|
}
|
|
|
|
.rank {
|
|
font-weight: 700;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.rank-1 { color: #d4af37; }
|
|
.rank-2 { color: #c0c0c0; }
|
|
.rank-3 { color: #cd7f32; }
|
|
|
|
.player-name-cell {
|
|
font-weight: 600;
|
|
color: #4a90d9;
|
|
}
|
|
|
|
.time-cell {
|
|
font-weight: 600;
|
|
font-family: monospace;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.score-cell {
|
|
font-weight: 700;
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: #8b7355;
|
|
}
|
|
|
|
.empty-state-icon {
|
|
font-size: 60px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-summary {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 15px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: #fff;
|
|
padding: 20px;
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
}
|
|
|
|
.stat-card-value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: #5d4e37;
|
|
}
|
|
|
|
.stat-card-label {
|
|
font-size: 12px;
|
|
color: #8b7355;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.clear-btn {
|
|
padding: 8px 16px;
|
|
background: #e74c3c;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.clear-btn:hover {
|
|
background: #c0392b;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div class="leaderboard-container">
|
|
<a href="index.html" class="back-btn">← Torna al Gioco</a>
|
|
|
|
<div class="leaderboard-header">
|
|
<h1>🏆 Classifica CatDoku</h1>
|
|
<p>I migliori tempi e punteggi di tutti i giocatori</p>
|
|
</div>
|
|
|
|
<div class="stats-summary" id="stats-summary"></div>
|
|
|
|
<div class="filter-section">
|
|
<div>
|
|
<label for="filter-player">Giocatore:</label>
|
|
<select id="filter-player">
|
|
<option value="">Tutti i giocatori</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="filter-level">Livello:</label>
|
|
<select id="filter-level">
|
|
<option value="">Tutti i livelli</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="filter-size">Dimensione:</label>
|
|
<select id="filter-size">
|
|
<option value="">Tutte le dimensioni</option>
|
|
</select>
|
|
</div>
|
|
<button class="clear-btn" id="clear-records">Cancella Tutto</button>
|
|
</div>
|
|
|
|
<div class="leaderboard-table" id="leaderboard-table"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
const STORAGE_KEY = 'catdoku_records';
|
|
|
|
function loadRecords() {
|
|
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
|
|
}
|
|
|
|
function saveRecords(records) {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
|
|
}
|
|
|
|
function formatTime(seconds) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function formatDate(dateStr) {
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('it-IT', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
function getUniquePlayers(records) {
|
|
return [...new Set(records.map(r => r.playerName))].sort();
|
|
}
|
|
|
|
function getUniqueLevels(records) {
|
|
return [...new Set(records.map(r => r.level))].sort((a, b) => a - b);
|
|
}
|
|
|
|
function getUniqueSizes(records) {
|
|
return [...new Set(records.map(r => r.gridSize))].sort((a, b) => a - b);
|
|
}
|
|
|
|
function renderStats(records) {
|
|
const statsContainer = document.getElementById('stats-summary');
|
|
const totalGames = records.length;
|
|
const uniquePlayers = getUniquePlayers(records).length;
|
|
const bestTime = records.length > 0 ? Math.min(...records.map(r => r.time)) : 0;
|
|
const bestScore = records.length > 0 ? Math.max(...records.map(r => r.score)) : 0;
|
|
|
|
statsContainer.innerHTML = `
|
|
<div class="stat-card">
|
|
<div class="stat-card-value">${totalGames}</div>
|
|
<div class="stat-card-label">Partite Giocate</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-value">${uniquePlayers}</div>
|
|
<div class="stat-card-label">Giocatori</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-value">${formatTime(bestTime)}</div>
|
|
<div class="stat-card-label">Miglior Tempo</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-value">${bestScore}</div>
|
|
<div class="stat-card-label">Miglior Punteggio</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderFilters(records) {
|
|
const playerSelect = document.getElementById('filter-player');
|
|
const levelSelect = document.getElementById('filter-level');
|
|
const sizeSelect = document.getElementById('filter-size');
|
|
|
|
const players = getUniquePlayers(records);
|
|
const levels = getUniqueLevels(records);
|
|
const sizes = getUniqueSizes(records);
|
|
|
|
playerSelect.innerHTML = '<option value="">Tutti i giocatori</option>' +
|
|
players.map(p => `<option value="${p}">${p}</option>`).join('');
|
|
|
|
levelSelect.innerHTML = '<option value="">Tutti i livelli</option>' +
|
|
levels.map(l => `<option value="${l}">Livello ${l}</option>`).join('');
|
|
|
|
sizeSelect.innerHTML = '<option value="">Tutte le dimensioni</option>' +
|
|
sizes.map(s => `<option value="${s}">${s}x${s}</option>`).join('');
|
|
}
|
|
|
|
function renderLeaderboard(records) {
|
|
const container = document.getElementById('leaderboard-table');
|
|
|
|
if (records.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🐱</div>
|
|
<h3>Nessuna partita registrata</h3>
|
|
<p>Completa un livello per apparire in classifica!</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// Ordina per tempo (crescente)
|
|
const sorted = [...records].sort((a, b) => a.time - b.time);
|
|
|
|
let html = '<table><thead><tr>';
|
|
html += '<th>#</th>';
|
|
html += '<th>Giocatore</th>';
|
|
html += '<th>Livello</th>';
|
|
html += '<th>Dimensione</th>';
|
|
html += '<th>Tempo</th>';
|
|
html += '<th>Punteggio</th>';
|
|
html += '<th>Errori</th>';
|
|
html += '<th>Data</th>';
|
|
html += '</tr></thead><tbody>';
|
|
|
|
sorted.forEach((record, index) => {
|
|
const rank = index + 1;
|
|
const rankClass = rank <= 3 ? `rank rank-${rank}` : 'rank';
|
|
|
|
html += '<tr>';
|
|
html += `<td class="${rankClass}">${rank <= 3 ? ['🥇', '🥈', '🥉'][rank-1] : rank}</td>`;
|
|
html += `<td class="player-name-cell">${record.playerName}</td>`;
|
|
html += `<td>${record.level}</td>`;
|
|
html += `<td>${record.gridSize}x${record.gridSize}</td>`;
|
|
html += `<td class="time-cell">${formatTime(record.time)}</td>`;
|
|
html += `<td class="score-cell">${record.score}</td>`;
|
|
html += `<td>${record.errors}</td>`;
|
|
html += `<td>${formatDate(record.date)}</td>`;
|
|
html += '</tr>';
|
|
});
|
|
|
|
html += '</tbody></table>';
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function applyFilters() {
|
|
const records = loadRecords();
|
|
const playerFilter = document.getElementById('filter-player').value;
|
|
const levelFilter = document.getElementById('filter-level').value;
|
|
const sizeFilter = document.getElementById('filter-size').value;
|
|
|
|
let filtered = records;
|
|
|
|
if (playerFilter) {
|
|
filtered = filtered.filter(r => r.playerName === playerFilter);
|
|
}
|
|
if (levelFilter) {
|
|
filtered = filtered.filter(r => r.level === parseInt(levelFilter));
|
|
}
|
|
if (sizeFilter) {
|
|
filtered = filtered.filter(r => r.gridSize === parseInt(sizeFilter));
|
|
}
|
|
|
|
renderStats(filtered);
|
|
renderLeaderboard(filtered);
|
|
}
|
|
|
|
// Event listeners
|
|
document.getElementById('filter-player').addEventListener('change', applyFilters);
|
|
document.getElementById('filter-level').addEventListener('change', applyFilters);
|
|
document.getElementById('filter-size').addEventListener('change', applyFilters);
|
|
|
|
document.getElementById('clear-records').addEventListener('click', () => {
|
|
if (confirm('Sei sicuro di voler cancellare tutti i record? Questa azione non può essere annullata.')) {
|
|
saveRecords([]);
|
|
applyFilters();
|
|
}
|
|
});
|
|
|
|
// Init
|
|
const records = loadRecords();
|
|
renderStats(records);
|
|
renderFilters(records);
|
|
renderLeaderboard(records);
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|