Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>HIGH SCORE DASHBOARD</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@keyframes ledGlow { | |
0% { box-shadow: 0 0 5px #00f, 0 0 10px #00f, 0 0 15px #00f; } | |
25% { box-shadow: 0 0 5px #0f0, 0 0 10px #0f0, 0 0 15px #0f0; } | |
50% { box-shadow: 0 0 5px #f0f, 0 0 10px #f0f, 0 0 15px #f0f; } | |
75% { box-shadow: 0 0 5px #ff0, 0 0 10px #ff0, 0 0 15px #ff0; } | |
100% { box-shadow: 0 0 5px #00f, 0 0 10px #00f, 0 0 15px #00f; } | |
} | |
@keyframes ledHover { | |
0% { box-shadow: 0 0 10px #f00, 0 0 20px #f00, 0 0 30px #f00; } | |
50% { box-shadow: 0 0 20px #0ff, 0 0 30px #0ff, 0 0 40px #0ff; } | |
100% { box-shadow: 0 0 10px #f00, 0 0 20px #f00, 0 0 30px #f00; } | |
} | |
.draggable { | |
cursor: move; | |
border: 1px solid rgba(0,0,0,0.1); | |
position: relative; | |
overflow: hidden; | |
transition: all 0.3s ease; | |
touch-action: none; | |
user-select: none; | |
} | |
.draggable.dragging { | |
opacity: 0.8; | |
transform: scale(1.02); | |
z-index: 100; | |
box-shadow: 0 10px 20px rgba(0,0,0,0.2); | |
} | |
.draggable.over { | |
border: 2px dashed #4f46e5; | |
} | |
.draggable { | |
position: relative; | |
padding: 3px; | |
} | |
.draggable::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
border: 3px solid transparent; | |
border-radius: 12px; | |
background: linear-gradient(90deg, #f00, #0f0, #00f, #ff0, #f0f) border-box; | |
background-size: 500% 500%; | |
-webkit-mask: | |
linear-gradient(#fff 0 0) padding-box, | |
linear-gradient(#fff 0 0); | |
-webkit-mask-composite: destination-out; | |
mask-composite: exclude; | |
animation: ledGlow 5s ease infinite; | |
pointer-events: none; | |
} | |
.draggable:hover::before { | |
animation: ledHover 1.5s ease infinite; | |
border-width: 4px; | |
} | |
.dashboard-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
gap: 1.5rem; | |
} | |
.hide-on-edit { | |
display: block; | |
} | |
.show-on-edit { | |
display: none; | |
} | |
.edit-mode .hide-on-edit { | |
display: none; | |
} | |
.edit-mode .show-on-edit { | |
display: block; | |
} | |
.dark-mode { | |
background-color: #1a202c; | |
color: white; | |
} | |
.dark-mode .container { | |
background-color: #1a202c; | |
color: white; | |
} | |
.dark-mode .game-card { | |
background-color: #2d3748; | |
color: white; | |
} | |
.dark-mode .game-card h3, | |
.dark-mode .game-card h4, | |
.dark-mode .game-card span, | |
.dark-mode .game-card .text-gray-800, | |
.dark-mode .game-card .text-gray-700, | |
.dark-mode .game-card .text-gray-600, | |
.dark-mode .game-card .text-gray-500 { | |
color: white ; | |
} | |
/* Keep edit modal text black for visibility */ | |
.dark-mode #gameEditModal, | |
.dark-mode #gameEditModal * { | |
color: black ; | |
} | |
.dark-mode h1, | |
.dark-mode h2, | |
.dark-mode h3, | |
.dark-mode h4, | |
.dark-mode p, | |
.dark-mode span, | |
.dark-mode .text-gray-800, | |
.dark-mode .text-gray-700, | |
.dark-mode .text-gray-500, | |
.dark-mode .text-gray-600 { | |
color: white ; | |
} | |
.dark-mode .bg-yellow-50 .text-yellow-600 { | |
color: black ; | |
} | |
.dark-mode .bg-yellow-50 .text-gray-500 { | |
color: black ; | |
} | |
.dark-mode .bg-gray-100 { | |
background-color: #2d3748 ; | |
} | |
.dark-mode .bg-white { | |
background-color: #2d3748 ; | |
} | |
.dark-mode .border-gray-300 { | |
border-color: #4a5568 ; | |
} | |
.game-card { | |
transition: all 0.3s ease; | |
background: white; | |
border-radius: 10px; | |
overflow: hidden; | |
} | |
.score-tile { | |
margin-bottom: 0.75rem; | |
padding: 0.5rem; | |
} | |
.game-card h3, | |
.game-card h4, | |
.game-card span, | |
.game-card .text-gray-800, | |
.game-card .text-gray-700, | |
.game-card .text-gray-600, | |
.game-card .text-gray-500 { | |
color: black ; | |
} | |
.led-highlight { | |
position: relative; | |
overflow: hidden; | |
border-radius: 6px; | |
} | |
.led-highlight::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
border: 2px solid transparent; | |
border-radius: 6px; | |
background: linear-gradient(90deg, #f00, #0f0, #00f, #ff0, #f0f) border-box; | |
background-size: 500% 500%; | |
-webkit-mask: | |
linear-gradient(#fff 0 0) padding-box, | |
linear-gradient(#fff 0 0); | |
-webkit-mask-composite: destination-out; | |
mask-composite: exclude; | |
animation: ledGlow 5s ease infinite; | |
pointer-events: none; | |
} | |
.game-card:hover { | |
transform: translateY(-5px); | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen dark:bg-gray-900"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="flex justify-between items-center mb-8"> | |
<h1 class="text-3xl font-bold text-indigo-800">HIGH SCORE DASHBOARD</h1> | |
<div class="flex space-x-4"> | |
<button id="toggleEditBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2"> | |
<i class="fas fa-edit"></i> | |
<span>Edit Mode</span> | |
</button> | |
<button id="addGameBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 show-on-edit"> | |
<i class="fas fa-plus"></i> | |
<span>Add Game</span> | |
</button> | |
<button id="saveChangesBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 show-on-edit"> | |
<i class="fas fa-save"></i> | |
<span>Save Changes</span> | |
</button> | |
<button id="darkModeToggle" class="bg-gray-700 hover:bg-gray-800 text-white px-4 py-2 rounded-lg flex items-center space-x-2"> | |
<i class="fas fa-moon"></i> | |
<span>Dark Mode</span> | |
</button> | |
<button id="exportBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2"> | |
<i class="fas fa-file-export"></i> | |
<span>Export</span> | |
</button> | |
<button id="importBtn" class="bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2"> | |
<i class="fas fa-file-import"></i> | |
<span>Import</span> | |
</button> | |
</div> | |
</header> | |
<div id="uploadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
<div class="bg-white p-6 rounded-lg w-full max-w-md"> | |
<h2 class="text-xl font-bold mb-4">Upload Game Icon</h2> | |
<form id="uploadForm" class="space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Select Image (PNG)</label> | |
<input type="file" id="iconUpload" accept="image/png" class="block w-full text-sm text-gray-500 | |
file:mr-4 file:py-2 file:px-4 | |
file:rounded-md file:border-0 | |
file:text-sm file:font-semibold | |
file:bg-blue-50 file:text-blue-700 | |
hover:file:bg-blue-100"/> | |
</div> | |
<div id="previewContainer" class="hidden"> | |
<p class="text-sm font-medium text-gray-700 mb-1">Preview:</p> | |
<img id="uploadPreview" class="h-16 w-16 object-contain mx-auto"/> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button type="button" id="cancelUpload" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md">Cancel</button> | |
<button type="button" id="confirmUpload" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md">Upload</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
<div id="dashboardContainer" class="dashboard-grid"> | |
<!-- Game cards will be added here dynamically --> | |
</div> | |
<div id="gameEditModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
<div class="bg-white p-6 rounded-lg w-full max-w-md"> | |
<h2 class="text-xl font-bold mb-4" id="modalTitle">Add New Game</h2> | |
<form id="gameEditForm" class="space-y-4"> | |
<input type="hidden" id="editGameId"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Game Name</label> | |
<input type="text" id="editGameName" class="w-full px-3 py-2 border border-gray-300 rounded-md" required> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Game Mode</label> | |
<input type="text" id="editGameMode" class="w-full px-3 py-2 border border-gray-300 rounded-md"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">High Scores</label> | |
<div class="flex space-x-2 mb-2"> | |
<button type="button" id="sortLowToHigh" class="text-xs bg-gray-200 hover:bg-gray-300 px-2 py-1 rounded">Low to High</button> | |
<button type="button" id="sortHighToLow" class="text-xs bg-gray-200 hover:bg-gray-300 px-2 py-1 rounded">High to Low</button> | |
</div> | |
<div class="space-y-2"> | |
<div class="grid grid-cols-12 gap-2 items-center"> | |
<span class="text-sm text-center col-span-1">1.</span> | |
<input type="text" pattern="[0-9]+(\.[0-9]*)?" class="px-2 py-1 border border-gray-300 rounded-md col-span-3" placeholder="Score" id="editScore1"> | |
<input type="text" class="px-2 py-1 border border-gray-300 rounded-md col-span-3" placeholder="Mode" id="editMode1"> | |
<input type="date" class="px-2 py-1 border border-gray-300 rounded-md col-span-5 min-w-[140px]" id="editDate1"> | |
</div> | |
<div class="grid grid-cols-12 gap-2 items-center"> | |
<span class="text-sm text-center col-span-1">2.</span> | |
<input type="text" pattern="[0-9]+(\.[0-9]*)?" class="px-2 py-1 border border-gray-300 rounded-md col-span-3" placeholder="Score" id="editScore2"> | |
<input type="text" class="px-2 py-1 border border-gray-300 rounded-md col-span-3" placeholder="Mode" id="editMode2"> | |
<input type="date" class="px-2 py-1 border border-gray-300 rounded-md col-span-5 min-w-[140px]" id="editDate2"> | |
</div> | |
<div class="grid grid-cols-12 gap-2 items-center"> | |
<span class="text-sm text-center col-span-1">3.</span> | |
<input type="text" pattern="[0-9]+(\.[0-9]*)?" class="px-2 py-1 border border-gray-300 rounded-md col-span-3" placeholder="Score" id="editScore3"> | |
<input type="text" class="px-2 py-1 border border-gray-300 rounded-md col-span-3" placeholder="Mode" id="editMode3"> | |
<input type="date" class="px-2 py-1 border border-gray-300 rounded-md col-span-5 min-w-[140px]" id="editDate3"> | |
</div> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Play Link</label> | |
<input type="url" id="editPlayLink" class="w-full px-3 py-2 border border-gray-300 rounded-md"> | |
</div> | |
<div class="flex justify-between items-center"> | |
<button type="button" id="uploadGameIconBtn" class="text-blue-500 text-sm flex items-center"> | |
<i class="fas fa-image mr-1"></i> | |
Change Icon | |
</button> | |
<div class="flex space-x-3"> | |
<button type="button" id="cancelGameEdit" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md">Cancel</button> | |
<button type="button" id="saveGameEdit" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md">Save</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
</div> | |
<input type="file" id="importFile" accept=".json" class="hidden"> | |
<script> | |
// Sample initial data | |
const initialGames = [ | |
{ | |
id: '1', | |
name: 'Space Adventure', | |
mode: 'Normal', | |
icon: 'https://cdn-icons-png.flaticon.com/512/4237/4237033.png', | |
scores: [ | |
{ score: 12500, date: '2023-06-15', mode: 'Normal' }, | |
{ score: 9800, date: '2023-06-10', mode: 'Hard' }, | |
{ score: 8700, date: '2023-06-05', mode: 'Easy' } | |
], | |
playLink: 'https://example.com/games/space' | |
}, | |
{ | |
id: '2', | |
name: 'Racing Challenge', | |
mode: 'Extreme', | |
icon: 'https://cdn-icons-png.flaticon.com/512/2936/2936886.png', | |
scores: [ | |
{ score: 3200, date: '2023-06-12', mode: 'Extreme' }, | |
{ score: 2900, date: '2023-06-08', mode: 'Nightmare' }, | |
{ score: 2700, date: '2023-06-03', mode: 'Hard' } | |
], | |
playLink: 'https://example.com/games/racing' | |
}, | |
{ | |
id: '3', | |
name: 'Puzzle Master', | |
mode: 'Time Attack', | |
icon: 'https://cdn-icons-png.flaticon.com/512/1063/1063249.png', | |
scores: [ | |
{ score: 5500, date: '2023-05-28', mode: 'Time Attack' }, | |
{ score: 5300, date: '2023-05-25', mode: 'Puzzle Rush' }, | |
{ score: 5200, date: '2023-05-20', mode: 'Classic' } | |
], | |
playLink: 'https://example.com/games/puzzle' | |
} | |
]; | |
// State management | |
let games = JSON.parse(localStorage.getItem('gameDashboard')) || initialGames; | |
let editMode = false; | |
let currentUploadIconId = null; | |
// DOM elements | |
const dashboardContainer = document.getElementById('dashboardContainer'); | |
const toggleEditBtn = document.getElementById('toggleEditBtn'); | |
const addGameBtn = document.getElementById('addGameBtn'); | |
const saveChangesBtn = document.getElementById('saveChangesBtn'); | |
const uploadModal = document.getElementById('uploadModal'); | |
const iconUpload = document.getElementById('iconUpload'); | |
const uploadPreview = document.getElementById('uploadPreview'); | |
const previewContainer = document.getElementById('previewContainer'); | |
const cancelUpload = document.getElementById('cancelUpload'); | |
const confirmUpload = document.getElementById('confirmUpload'); | |
const gameEditModal = document.getElementById('gameEditModal'); | |
const modalTitle = document.getElementById('modalTitle'); | |
const cancelGameEdit = document.getElementById('cancelGameEdit'); | |
const saveGameEdit = document.getElementById('saveGameEdit'); | |
const uploadGameIconBtn = document.getElementById('uploadGameIconBtn'); | |
// Initialize the dashboard | |
function renderDashboard() { | |
dashboardContainer.innerHTML = ''; | |
games.forEach(game => { | |
const gameCard = createGameCard(game); | |
dashboardContainer.appendChild(gameCard); | |
}); | |
} | |
// Create a game card element | |
function createGameCard(game) { | |
const card = document.createElement('div'); | |
card.className = 'game-card bg-white rounded-xl overflow-hidden draggable'; | |
card.dataset.id = game.id; | |
card.innerHTML = ` | |
<div class="p-5"> | |
<div class="flex justify-between items-start mb-4"> | |
<div class="flex items-center"> | |
<img src="${game.icon}" alt="${game.name} icon" class="w-12 h-12 object-contain mr-3"> | |
<div> | |
<h3 class="text-lg font-semibold text-gray-800">${game.name}</h3> | |
</div> | |
</div> | |
<button class="edit-btn bg-gray-100 hover:bg-gray-200 text-gray-700 p-2 rounded-full hide-on-edit"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<button class="delete-btn bg-red-100 hover:bg-red-200 text-red-500 p-2 rounded-full show-on-edit"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
<div class="mb-4"> | |
<h4 class="text-sm font-medium text-gray-700 mb-2">Top Scores</h4> | |
<div class="space-y-2"> | |
${game.scores.map((score, index) => { | |
// Highlight the highest score (first position after sorting) | |
const isFirstPosition = index === 0; | |
return ` | |
<div class="flex justify-between items-center score-tile ${isFirstPosition ? 'led-highlight' : ''} p-2 rounded"> | |
<span class="text-sm font-medium text-black"> | |
${index + 1}. ${score.score.toLocaleString()} | |
</span> | |
<span class="text-xs text-gray-500 mx-auto">${score.mode || ''}</span> | |
<span class="text-xs text-gray-500">${score.date}</span> | |
</div> | |
`; | |
}).join('')} | |
</div> | |
</div> | |
<a href="${game.playLink}" target="_blank" class="block w-full text-center bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-lg mb-3"> | |
Play Now <i class="fas fa-play ml-1"></i> | |
</a> | |
</div> | |
`; | |
// Add event listeners | |
card.querySelector('.edit-btn').addEventListener('click', () => openEditModal(game.id)); | |
card.querySelector('.delete-btn').addEventListener('click', () => deleteGame(game.id)); | |
return card; | |
} | |
// Toggle edit mode | |
function toggleEditMode() { | |
editMode = !editMode; | |
document.body.classList.toggle('edit-mode', editMode); | |
if (editMode) { | |
toggleEditBtn.innerHTML = '<i class="fas fa-times"></i><span>Cancel Edit</span>'; | |
} else { | |
toggleEditBtn.innerHTML = '<i class="fas fa-edit"></i><span>Edit Mode</span>'; | |
} | |
} | |
// Open the game edit modal | |
function openEditModal(gameId = null) { | |
if (gameId) { | |
const game = games.find(g => g.id === gameId); | |
if (game) { | |
document.getElementById('editGameId').value = game.id; | |
document.getElementById('editGameName').value = game.name; | |
document.getElementById('editGameMode').value = game.mode || ''; | |
document.getElementById('editPlayLink').value = game.playLink || ''; | |
// Fill scores | |
document.getElementById('editScore1').value = game.scores[0]?.score || ''; | |
document.getElementById('editMode1').value = game.scores[0]?.mode || ''; | |
document.getElementById('editDate1').value = game.scores[0]?.date || ''; | |
document.getElementById('editScore2').value = game.scores[1]?.score || ''; | |
document.getElementById('editMode2').value = game.scores[1]?.mode || ''; | |
document.getElementById('editDate2').value = game.scores[1]?.date || ''; | |
document.getElementById('editScore3').value = game.scores[2]?.score || ''; | |
document.getElementById('editMode3').value = game.scores[2]?.mode || ''; | |
document.getElementById('editDate3').value = game.scores[2]?.date || ''; | |
modalTitle.textContent = 'Edit Game'; | |
currentUploadIconId = game.id; | |
} | |
} else { | |
// Reset for new game | |
document.getElementById('editGameId').value = ''; | |
document.getElementById('editGameName').value = ''; | |
document.getElementById('editGameMode').value = ''; | |
document.getElementById('editPlayLink').value = ''; | |
document.getElementById('editScore1').value = ''; | |
document.getElementById('editDate1').value = ''; | |
document.getElementById('editScore2').value = ''; | |
document.getElementById('editDate2').value = ''; | |
document.getElementById('editScore3').value = ''; | |
document.getElementById('editDate3').value = ''; | |
modalTitle.textContent = 'Add New Game'; | |
currentUploadIconId = null; | |
} | |
gameEditModal.classList.remove('hidden'); | |
} | |
// Save game changes | |
function saveGameChanges() { | |
const id = document.getElementById('editGameId').value || Date.now().toString(); | |
const name = document.getElementById('editGameName').value; | |
const mode = document.getElementById('editGameMode').value; | |
const playLink = document.getElementById('editPlayLink').value; | |
const scores = [ | |
{ | |
score: parseFloat(document.getElementById('editScore1').value) || 0, | |
date: document.getElementById('editDate1').value || new Date().toISOString().split('T')[0], | |
mode: document.getElementById('editMode1').value || '' | |
}, | |
{ | |
score: parseFloat(document.getElementById('editScore2').value) || 0, | |
date: document.getElementById('editDate2').value || new Date().toISOString().split('T')[0], | |
mode: document.getElementById('editMode2').value || '' | |
}, | |
{ | |
score: parseFloat(document.getElementById('editScore3').value) || 0, | |
date: document.getElementById('editDate3').value || new Date().toISOString().split('T')[0], | |
mode: document.getElementById('editMode3').value || '' | |
} | |
].filter(score => score.score > 0); | |
// Keep scores in their current order (sorted by user preference) | |
// Default icon if no upload | |
const icon = currentUploadIconId | |
? games.find(g => g.id === currentUploadIconId)?.icon | |
: 'https://cdn-icons-png.flaticon.com/512/3522/3522658.png'; | |
if (document.getElementById('editGameId').value) { | |
// Update existing game | |
const index = games.findIndex(g => g.id === id); | |
if (index !== -1) { | |
games[index] = { id, name, mode, icon, scores, playLink }; | |
} | |
} else { | |
// Add new game | |
games.push({ id, name, mode, icon, scores, playLink }); | |
} | |
renderDashboard(); | |
localStorage.setItem('gameDashboard', JSON.stringify(games)); | |
gameEditModal.classList.add('hidden'); | |
} | |
// Delete a game | |
function deleteGame(id) { | |
if (confirm('Are you sure you want to delete this game?')) { | |
games = games.filter(game => game.id !== id); | |
renderDashboard(); | |
} | |
} | |
// Save all changes to localStorage | |
function saveAllChanges() { | |
localStorage.setItem('gameDashboard', JSON.stringify(games)); | |
alert('Changes saved successfully!'); | |
toggleEditMode(); | |
} | |
// Handle icon upload | |
function handleIconUpload() { | |
const file = iconUpload.files[0]; | |
if (file) { | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
uploadPreview.src = e.target.result; | |
previewContainer.classList.remove('hidden'); | |
}; | |
reader.readAsDataURL(file); | |
} | |
} | |
// Confirm icon upload | |
function confirmIconUpload() { | |
const file = iconUpload.files[0]; | |
if (!file) return; | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
if (currentUploadIconId) { | |
const game = games.find(g => g.id === currentUploadIconId); | |
if (game) { | |
game.icon = e.target.result; | |
renderDashboard(); | |
} | |
} | |
uploadModal.classList.add('hidden'); | |
iconUpload.value = ''; | |
previewContainer.classList.add('hidden'); | |
}; | |
reader.readAsDataURL(file); | |
} | |
// Initialize drag and drop for game cards | |
function initDragAndDrop() { | |
let draggedItem = null; | |
let dragOverItem = null; | |
// Add draggable attribute to all game cards | |
document.querySelectorAll('.draggable').forEach(item => { | |
item.setAttribute('draggable', 'true'); | |
}); | |
dashboardContainer.addEventListener('dragstart', (e) => { | |
if (e.target.classList.contains('draggable')) { | |
draggedItem = e.target; | |
e.target.classList.add('dragging'); | |
e.dataTransfer.effectAllowed = 'move'; | |
e.dataTransfer.setData('text/html', e.target.innerHTML); | |
e.dataTransfer.setDragImage(e.target, 20, 20); | |
} | |
}); | |
dashboardContainer.addEventListener('dragend', (e) => { | |
if (draggedItem) { | |
draggedItem.classList.remove('dragging'); | |
document.querySelectorAll('.draggable').forEach(item => { | |
item.classList.remove('over'); | |
}); | |
draggedItem = null; | |
dragOverItem = null; | |
// Update games array order based on new DOM order | |
const newOrder = [...dashboardContainer.children].map(child => child.dataset.id); | |
games.sort((a, b) => newOrder.indexOf(a.id) - newOrder.indexOf(b.id)); | |
// Save the new order | |
localStorage.setItem('gameDashboard', JSON.stringify(games)); | |
} | |
}); | |
dashboardContainer.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
e.dataTransfer.dropEffect = 'move'; | |
const target = e.target.closest('.draggable'); | |
if (!target || target === draggedItem) return; | |
// Remove highlight from previous item | |
if (dragOverItem && dragOverItem !== target) { | |
dragOverItem.classList.remove('over'); | |
} | |
// Highlight new target | |
target.classList.add('over'); | |
dragOverItem = target; | |
const rect = target.getBoundingClientRect(); | |
const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5; | |
if (next) { | |
dashboardContainer.insertBefore(draggedItem, target.nextSibling); | |
} else { | |
dashboardContainer.insertBefore(draggedItem, target); | |
} | |
}); | |
dashboardContainer.addEventListener('dragleave', (e) => { | |
const target = e.target.closest('.draggable'); | |
if (target && target !== draggedItem) { | |
target.classList.remove('over'); | |
} | |
}); | |
} | |
// Event listeners | |
toggleEditBtn.addEventListener('click', toggleEditMode); | |
addGameBtn.addEventListener('click', () => openEditModal()); | |
saveChangesBtn.addEventListener('click', saveAllChanges); | |
iconUpload.addEventListener('change', handleIconUpload); | |
cancelUpload.addEventListener('click', () => { | |
uploadModal.classList.add('hidden'); | |
iconUpload.value = ''; | |
previewContainer.classList.add('hidden'); | |
}); | |
confirmUpload.addEventListener('click', confirmIconUpload); | |
cancelGameEdit.addEventListener('click', () => gameEditModal.classList.add('hidden')); | |
saveGameEdit.addEventListener('click', saveGameChanges); | |
uploadGameIconBtn.addEventListener('click', () => uploadModal.classList.remove('hidden')); | |
// Export data to JSON file | |
function exportData() { | |
const dataStr = JSON.stringify(games, null, 2); | |
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); | |
const exportFileDefaultName = 'high-scores-backup.json'; | |
const linkElement = document.createElement('a'); | |
linkElement.setAttribute('href', dataUri); | |
linkElement.setAttribute('download', exportFileDefaultName); | |
linkElement.click(); | |
} | |
// Import data from JSON file | |
function importData() { | |
document.getElementById('importFile').click(); | |
} | |
// Handle imported file | |
function handleFileImport(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
try { | |
const importedGames = JSON.parse(e.target.result); | |
if (Array.isArray(importedGames)) { | |
if (confirm('This will replace all current games. Continue?')) { | |
games = importedGames; | |
renderDashboard(); | |
localStorage.setItem('gameDashboard', JSON.stringify(games)); | |
alert('Import successful!'); | |
} | |
} else { | |
alert('Invalid file format. Expected an array of games.'); | |
} | |
} catch (error) { | |
alert('Error parsing JSON file: ' + error.message); | |
} | |
e.target.value = ''; // Reset file input | |
}; | |
reader.readAsText(file); | |
} | |
// Dark mode toggle | |
const darkModeToggle = document.getElementById('darkModeToggle'); | |
let darkMode = localStorage.getItem('darkMode') === 'true'; | |
function toggleDarkMode() { | |
darkMode = !darkMode; | |
document.body.classList.toggle('dark-mode', darkMode); | |
localStorage.setItem('darkMode', darkMode); | |
if (darkMode) { | |
darkModeToggle.innerHTML = '<i class="fas fa-sun"></i><span>Light Mode</span>'; | |
} else { | |
darkModeToggle.innerHTML = '<i class="fas fa-moon"></i><span>Dark Mode</span>'; | |
} | |
} | |
// Initialize dark mode | |
if (darkMode) { | |
document.body.classList.add('dark-mode'); | |
darkModeToggle.innerHTML = '<i class="fas fa-sun"></i><span>Light Mode</span>'; | |
} | |
darkModeToggle.addEventListener('click', toggleDarkMode); | |
// Event listeners for export/import | |
document.getElementById('exportBtn').addEventListener('click', exportData); | |
document.getElementById('importBtn').addEventListener('click', importData); | |
document.getElementById('importFile').addEventListener('change', handleFileImport); | |
// Sort buttons event listeners | |
document.getElementById('sortLowToHigh').addEventListener('click', () => { | |
const score1 = document.getElementById('editScore1').value; | |
const score2 = document.getElementById('editScore2').value; | |
const score3 = document.getElementById('editScore3').value; | |
const mode1 = document.getElementById('editMode1').value; | |
const mode2 = document.getElementById('editMode2').value; | |
const mode3 = document.getElementById('editMode3').value; | |
const date1 = document.getElementById('editDate1').value; | |
const date2 = document.getElementById('editDate2').value; | |
const date3 = document.getElementById('editDate3').value; | |
const scores = [ | |
{score: parseFloat(score1) || 0, mode: mode1, date: date1}, | |
{score: parseFloat(score2) || 0, mode: mode2, date: date2}, | |
{score: parseFloat(score3) || 0, mode: mode3, date: date3} | |
].filter(s => s.score > 0); | |
scores.sort((a, b) => a.score - b.score); | |
// Update the fields with sorted values | |
document.getElementById('editScore1').value = scores[0]?.score || ''; | |
document.getElementById('editMode1').value = scores[0]?.mode || ''; | |
document.getElementById('editDate1').value = scores[0]?.date || ''; | |
document.getElementById('editScore2').value = scores[1]?.score || ''; | |
document.getElementById('editMode2').value = scores[1]?.mode || ''; | |
document.getElementById('editDate2').value = scores[1]?.date || ''; | |
document.getElementById('editScore3').value = scores[2]?.score || ''; | |
document.getElementById('editMode3').value = scores[2]?.mode || ''; | |
document.getElementById('editDate3').value = scores[2]?.date || ''; | |
}); | |
document.getElementById('sortHighToLow').addEventListener('click', () => { | |
const score1 = document.getElementById('editScore1').value; | |
const score2 = document.getElementById('editScore2').value; | |
const score3 = document.getElementById('editScore3').value; | |
const mode1 = document.getElementById('editMode1').value; | |
const mode2 = document.getElementById('editMode2').value; | |
const mode3 = document.getElementById('editMode3').value; | |
const date1 = document.getElementById('editDate1').value; | |
const date2 = document.getElementById('editDate2').value; | |
const date3 = document.getElementById('editDate3').value; | |
const scores = [ | |
{score: parseFloat(score1) || 0, mode: mode1, date: date1}, | |
{score: parseFloat(score2) || 0, mode: mode2, date: date2}, | |
{score: parseFloat(score3) || 0, mode: mode3, date: date3} | |
].filter(s => s.score > 0); | |
scores.sort((a, b) => b.score - a.score); | |
// Update the fields with sorted values | |
document.getElementById('editScore1').value = scores[0]?.score || ''; | |
document.getElementById('editMode1').value = scores[0]?.mode || ''; | |
document.getElementById('editDate1').value = scores[0]?.date || ''; | |
document.getElementById('editScore2').value = scores[1]?.score || ''; | |
document.getElementById('editMode2').value = scores[1]?.mode || ''; | |
document.getElementById('editDate2').value = scores[1]?.date || ''; | |
document.getElementById('editScore3').value = scores[2]?.score || ''; | |
document.getElementById('editMode3').value = scores[2]?.mode || ''; | |
document.getElementById('editDate3').value = scores[2]?.date || ''; | |
}); | |
// Initialize the dashboard | |
renderDashboard(); | |
initDragAndDrop(); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Honda219/high-score-dashboard" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |