Upload files to "templates"

This commit is contained in:
2026-05-13 12:12:13 +00:00
parent a8deb6dd70
commit 55baa1fadd
3 changed files with 1803 additions and 0 deletions
+324
View File
@@ -0,0 +1,324 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Route Rush · History</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Exo+2:wght@400;600;800;900&display=swap');
*{margin:0;padding:0;box-sizing:border-box;}
:root{
--bg:#070b14;
--surface:#0d1220;
--surface2:#131928;
--accent:#00ffe0;
--accent2:#ff3e6c;
--warn:#ffb800;
--boost:#00ff88;
--text:#ddeeff;
--muted:#3a4a66;
--mono:'Share Tech Mono',monospace;
--sans:'Exo 2',sans-serif;
}
html,body{min-height:100vh;background:var(--bg);color:var(--text);font-family:var(--sans);}
body::before{
content:'';position:fixed;inset:0;
background-image:
linear-gradient(rgba(0,255,224,0.025) 1px,transparent 1px),
linear-gradient(90deg,rgba(0,255,224,0.025) 1px,transparent 1px);
background-size:40px 40px;pointer-events:none;z-index:0;
}
header{
position:relative;z-index:1;
padding:1.75rem 2rem 1.25rem;
border-bottom:1px solid rgba(0,255,224,0.08);
display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:1rem;
}
.header-left h1{
font-family:var(--sans);font-weight:900;font-size:2rem;letter-spacing:-0.03em;
color:var(--accent);text-shadow:0 0 20px rgba(0,255,224,.3);line-height:1;
}
.header-left h1 span{color:var(--accent2);}
.header-left p{font-family:var(--mono);font-size:.55rem;color:var(--muted);letter-spacing:.12em;margin-top:.3rem;}
.nav-links{display:flex;gap:.5rem;}
.nav-links a{
font-family:var(--mono);font-size:.6rem;color:var(--muted);
text-decoration:none;padding:.35rem .8rem;
border:1px solid rgba(0,255,224,.1);border-radius:2px;
transition:all .15s;letter-spacing:.08em;
}
.nav-links a:hover{color:var(--accent);border-color:rgba(0,255,224,.3);}
main{position:relative;z-index:1;max-width:960px;margin:0 auto;padding:2rem;}
/* loading / empty */
#loading{text-align:center;color:var(--muted);padding:4rem;font-family:var(--mono);font-size:.75rem;
letter-spacing:.1em;}
#loading::after{content:'...';animation:dots 1.2s step-end infinite;}
@keyframes dots{0%{content:''}33%{content:'.'}66%{content:'..'}100%{content:'...'}}
#empty{display:none;text-align:center;padding:4rem;font-family:var(--mono);font-size:.7rem;color:var(--muted);}
#empty .icon{font-size:3rem;margin-bottom:1rem;display:block;}
#content{display:none;}
/* section */
.section{margin-bottom:2.5rem;}
.section-title{
display:flex;align-items:center;gap:.75rem;margin-bottom:1.25rem;
}
.section-title h2{
font-family:var(--sans);font-weight:900;font-size:1.1rem;letter-spacing:-0.01em;
}
.section-title .pill{
font-family:var(--mono);font-size:.45rem;letter-spacing:.15em;
padding:.2rem .5rem;border-radius:2px;
background:rgba(0,255,224,.06);border:1px solid rgba(0,255,224,.15);color:var(--accent);
}
/* top cards */
.top-row{display:flex;gap:.75rem;flex-wrap:wrap;}
.top-card{
flex:1;min-width:140px;
background:var(--surface);border:1px solid rgba(0,255,224,.08);border-radius:4px;
padding:.9rem 1.1rem;display:flex;align-items:center;gap:.75rem;
transition:border-color .2s;
}
.top-card:hover{border-color:rgba(0,255,224,.2);}
.top-card.gold{border-color:rgba(255,184,0,.3);background:rgba(255,184,0,.04);}
.top-card.silver{border-color:rgba(180,180,180,.2);}
.top-card.bronze{border-color:rgba(160,90,30,.2);}
.top-medal{font-size:1.6rem;}
.top-info .top-name{font-weight:700;font-size:.9rem;margin-bottom:.15rem;}
.top-info .top-score{
font-family:var(--sans);font-weight:900;font-size:1.2rem;
letter-spacing:-0.02em;color:var(--accent);line-height:1;
}
.top-info .top-meta{font-family:var(--mono);font-size:.45rem;color:var(--muted);margin-top:.15rem;}
/* sessions */
.session-card{
background:var(--surface);border:1px solid rgba(0,255,224,.08);border-radius:4px;
margin-bottom:.75rem;overflow:hidden;
}
.session-header{
display:flex;align-items:center;justify-content:space-between;
padding:.9rem 1.1rem;cursor:pointer;transition:background .15s;
}
.session-header:hover{background:var(--surface2);}
.session-meta{display:flex;align-items:center;gap:.9rem;flex-wrap:wrap;}
.session-id{font-family:var(--mono);font-size:.5rem;color:var(--muted);letter-spacing:.1em;}
.session-date{font-size:.85rem;font-weight:600;}
.session-badge{
font-family:var(--mono);font-size:.5rem;
padding:.2rem .55rem;border-radius:2px;
background:rgba(0,255,224,.06);border:1px solid rgba(0,255,224,.15);color:var(--accent);
}
.session-right{display:flex;align-items:center;gap:.75rem;}
.session-rounds{font-family:var(--mono);font-size:.5rem;color:var(--muted);}
.chevron{color:var(--muted);transition:transform .2s;font-size:.9rem;}
.session-body{display:none;border-top:1px solid rgba(0,255,224,.06);}
.session-body.open{display:block;}
table{width:100%;border-collapse:collapse;}
thead th{
text-align:left;font-family:var(--mono);font-size:.5rem;letter-spacing:.12em;
color:var(--muted);padding:.65rem 1.1rem;
border-bottom:1px solid rgba(0,255,224,.06);
}
tbody td{padding:.6rem 1.1rem;font-size:.85rem;border-bottom:1px solid rgba(255,255,255,.02);}
tbody tr:last-child td{border:none;}
.td-rank{font-family:var(--mono);font-weight:700;color:var(--muted);font-size:.8rem;}
.td-score{font-family:var(--sans);font-weight:800;color:var(--accent);font-size:.95rem;}
.td-medal{font-size:1rem;}
/* global leaderboard table */
.global-table-wrap{
background:var(--surface);border:1px solid rgba(0,255,224,.08);border-radius:4px;overflow:hidden;
}
/* fade-up */
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.fade-up{animation:fadeUp .4s ease both;}
</style>
</head>
<body>
<header>
<div class="header-left">
<h1>ROUTE<span>RUSH</span></h1>
<p>// SCORE HISTORY</p>
</div>
<nav class="nav-links">
<a href="host">← HOST PANEL</a>
<a href=".">PLAY</a>
</nav>
</header>
<main>
<div id="loading">LOADING HISTORY</div>
<div id="empty"><span class="icon">📡</span>No sessions yet.<br>Scores are saved when a game ends.</div>
<div id="content">
<!-- TOP PLAYERS -->
<div class="section" id="section-top">
<div class="section-title">
<h2>🏆 All-Time Top Players</h2>
<span class="pill">BEST SCORE</span>
</div>
<div class="top-row" id="top-row"></div>
</div>
<!-- GLOBAL RANKING -->
<div class="section">
<div class="section-title">
<h2>📊 Global Ranking</h2>
<span class="pill">ALL SESSIONS</span>
</div>
<div class="global-table-wrap">
<table>
<thead>
<tr>
<th></th>
<th>PLAYER</th>
<th>BEST SCORE</th>
<th>SESSIONS</th>
<th>AVG SCORE</th>
</tr>
</thead>
<tbody id="global-tbody"></tbody>
</table>
</div>
</div>
<!-- SESSIONS -->
<div class="section">
<div class="section-title">
<h2>📋 Sessions</h2>
<span class="pill" id="session-count-pill">0 GAMES</span>
</div>
<div id="sessions-list"></div>
</div>
</div>
</main>
<script>
const medals = ['🥇','🥈','🥉'];
const rankClass = ['gold','silver','bronze'];
async function load() {
const res = await fetch('api/scores');
const data = await res.json();
document.getElementById('loading').style.display = 'none';
if (!data.length) {
document.getElementById('empty').style.display = 'block';
return;
}
document.getElementById('content').style.display = 'block';
document.getElementById('session-count-pill').textContent = `${data.length} GAME${data.length>1?'S':''}`;
// ── BUILD PLAYER MAP ──────────────────────────────────────────────────────
const playerMap = {};
data.forEach(s => {
s.players.forEach(p => {
if (!playerMap[p.player]) {
playerMap[p.player] = { best: p.score, total: p.score, sessions: 1 };
} else {
if (p.score > playerMap[p.player].best) playerMap[p.player].best = p.score;
playerMap[p.player].total += p.score;
playerMap[p.player].sessions++;
}
});
});
const sorted = Object.entries(playerMap).sort((a,b) => b[1].best - a[1].best);
// ── TOP CARDS ─────────────────────────────────────────────────────────────
const topRow = document.getElementById('top-row');
sorted.slice(0, 5).forEach(([name, info], i) => {
const card = document.createElement('div');
card.className = `top-card ${rankClass[i]||''} fade-up`;
card.style.animationDelay = `${i*.06}s`;
card.innerHTML = `
<div class="top-medal">${i<3?medals[i]:`#${i+1}`}</div>
<div class="top-info">
<div class="top-name">${name}</div>
<div class="top-score">${info.best.toLocaleString()}</div>
<div class="top-meta">${info.sessions} SESSION${info.sessions>1?'S':''}</div>
</div>`;
topRow.appendChild(card);
});
// ── GLOBAL TABLE ──────────────────────────────────────────────────────────
const tbody = document.getElementById('global-tbody');
sorted.forEach(([name, info], i) => {
const avg = Math.round(info.total / info.sessions);
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="td-medal">${i<3?medals[i]:''}<span class="td-rank" style="${i>=3?'':'display:none'}">#${i+1}</span></td>
<td style="font-weight:600">${name}</td>
<td class="td-score">${info.best.toLocaleString()}</td>
<td style="font-family:var(--mono);font-size:.75rem;color:var(--muted)">${info.sessions}</td>
<td style="font-family:var(--mono);font-size:.75rem;color:var(--muted)">${avg.toLocaleString()}</td>`;
tbody.appendChild(tr);
});
// ── SESSIONS ──────────────────────────────────────────────────────────────
const list = document.getElementById('sessions-list');
data.forEach(session => {
const date = new Date(session.started_at + 'Z').toLocaleString('en-US', {
month:'short', day:'numeric', hour:'2-digit', minute:'2-digit'
});
const winner = session.players[0];
const card = document.createElement('div');
card.className = 'session-card fade-up';
card.innerHTML = `
<div class="session-header" onclick="toggle(this)">
<div class="session-meta">
<span class="session-id">SESSION #${session.id}</span>
<span class="session-date">${date}</span>
${winner ? `<span class="session-badge">🥇 ${winner.player} · ${winner.score.toLocaleString()} PTS</span>` : ''}
</div>
<div class="session-right">
<span class="session-rounds">${session.rounds} RND${session.rounds!==1?'S':''}</span>
<span class="chevron">▼</span>
</div>
</div>
<div class="session-body">
<table>
<thead><tr><th></th><th>PLAYER</th><th>SCORE</th><th>RANK</th></tr></thead>
<tbody>
${session.players.map((p,i) => `
<tr>
<td class="td-medal">${i<3?medals[i]:''}</td>
<td style="font-weight:600">${p.player}</td>
<td class="td-score">${p.score.toLocaleString()}</td>
<td style="font-family:var(--mono);font-size:.7rem;color:var(--muted)">#${p.rank}</td>
</tr>`).join('')}
</tbody>
</table>
</div>`;
list.appendChild(card);
});
}
function toggle(header) {
const body = header.nextElementSibling;
const chevron = header.querySelector('.chevron');
body.classList.toggle('open');
chevron.style.transform = body.classList.contains('open') ? 'rotate(180deg)' : '';
}
load();
</script>
</body>
</html>