country + status added and other changes
This commit is contained in:
@@ -78,13 +78,27 @@
|
||||
.bar i { display:block; height:100%; background:linear-gradient(90deg,#0566e9,#0ab4ff,#0640c3); transition:width .5s ease; }
|
||||
.rscore { color:var(--cyan); font-size:clamp(14px,1.9vw,26px); font-weight:900; text-align:right; }
|
||||
.pts { font-size:clamp(8px,.9vw,13px); font-family:Arial,sans-serif; }
|
||||
.next-box { text-align:center; cursor:default; justify-content:center; align-items:center; gap:1vh; padding:1.5vh 0; border:none; background:rgba(1,17,35,.85); border:1.5px solid var(--cyan); }
|
||||
.next-ball { width:9.5vw; height:9.5vw; border-radius:50%; display:grid; place-items:center; font-size:clamp(32px,5.5vw,80px); background:radial-gradient(circle,rgba(0,174,239,.3),transparent 62%); box-shadow:0 0 24px rgba(0,174,239,.55); }
|
||||
.waiting-text { font-family:Arial,sans-serif; font-size:clamp(11px,1.4vw,20px); font-weight:900; text-transform:uppercase; letter-spacing:.5px; color:var(--cyan); }
|
||||
|
||||
/* HISTORY BOX */
|
||||
.next-box { padding:.8vh 1.2vw; align-items:stretch; justify-content:flex-start; gap:0; }
|
||||
.history-wrap { flex:1; min-height:0; overflow-y:auto; display:flex; flex-direction:column; gap:.5vh; padding-top:.3vh; justify-content:flex-start; }
|
||||
.history-wrap::-webkit-scrollbar { width:3px; }
|
||||
.history-wrap::-webkit-scrollbar-thumb { background:rgba(0,174,239,.4); border-radius:99px; }
|
||||
.history-row { display:grid; grid-template-columns:6vw 1fr auto; align-items:center; gap:.6vw; padding:.55vh .5vw; border-radius:7px; background:rgba(0,174,239,.06); border:1px solid rgba(0,174,239,.15); flex-shrink:0; }
|
||||
.history-date { font-family:Arial,sans-serif; font-size:clamp(9px,1vw,13px); font-weight:900; color:var(--cyan); line-height:1.25; }
|
||||
.history-shots { display:flex; gap:.35vw; align-items:center; }
|
||||
.hshot { font-size:clamp(11px,1.2vw,17px); line-height:1; }
|
||||
.hshot.goal { filter:drop-shadow(0 0 4px rgba(0,255,70,.6)); }
|
||||
.hshot.miss { color:var(--red); }
|
||||
.hshot.pending { opacity:.25; }
|
||||
.history-score { font-family:Arial,sans-serif; font-size:clamp(13px,1.5vw,22px); font-weight:900; color:#fff; white-space:nowrap; }
|
||||
.history-score span { color:var(--cyan); font-size:clamp(8px,.9vw,12px); }
|
||||
.history-today { border-color:rgba(0,174,239,.5); background:rgba(0,174,239,.12); }
|
||||
.no-history { font-family:Arial,sans-serif; font-size:clamp(10px,1.1vw,15px); color:rgba(255,255,255,.3); text-align:center; margin:auto; text-transform:uppercase; letter-spacing:.5px; }
|
||||
|
||||
.footer { flex-shrink:0; width:50vw; margin:0 auto; padding:.65vh 2vw; display:flex; justify-content:center; gap:3vw; background:rgba(1,17,35,.82); border:1px solid rgba(0,174,239,.5); border-radius:12px 12px 0 0; font-family:Arial,sans-serif; font-size:clamp(9px,1vw,14px); font-weight:900; text-transform:uppercase; }
|
||||
.footer span:first-child { color:var(--cyan); }
|
||||
|
||||
/* pulse on score update */
|
||||
@keyframes scorePop { 0%{transform:scale(1)} 50%{transform:scale(1.2)} 100%{transform:scale(1)} }
|
||||
.score-pop { animation:scorePop .35s ease; }
|
||||
</style>
|
||||
@@ -138,15 +152,15 @@
|
||||
<section class="bottom-row">
|
||||
<div class="leaderboard">
|
||||
<div class="section-title"><span>Top 5 Leaderboard</span></div>
|
||||
<div class="ranks-wrap" id="leaderboardRows">
|
||||
<!-- filled by JS -->
|
||||
</div>
|
||||
<div class="ranks-wrap" id="leaderboardRows"></div>
|
||||
</div>
|
||||
|
||||
<!-- CHANGED: was next-box with ball, now session history -->
|
||||
<div class="next-box">
|
||||
<div class="section-title"><span>Now Playing</span></div>
|
||||
<div class="next-ball">⚽</div>
|
||||
<div class="waiting-text" id="waitingText">Select a player</div>
|
||||
<div class="section-title"><span>Player History</span></div>
|
||||
<div class="history-wrap" id="historyWrap">
|
||||
<div class="no-history">Select a player</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -159,67 +173,84 @@
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const POLL_URL = "{{ route('scoreboard.state') }}";
|
||||
const MEDALS = ['🏆','🥈','🥉'];
|
||||
let lastPlayerId = null;
|
||||
let lastShots = [];
|
||||
let lastTotal = null;
|
||||
const POLL_URL = "{{ route('scoreboard.state') }}";
|
||||
const MEDALS = ['🏆','🥈','🥉'];
|
||||
const TODAY = new Date().toISOString().slice(0, 10);
|
||||
let lastPlayerId = null;
|
||||
let lastShots = [];
|
||||
let lastTotal = null;
|
||||
let lastLeaderStr = '';
|
||||
let lastHistoryStr = '';
|
||||
|
||||
function renderShot(index, result) {
|
||||
// index 0-based
|
||||
const n = index + 1;
|
||||
const n = index + 1;
|
||||
const icon = document.getElementById('shotIcon' + n);
|
||||
const res = document.getElementById('shotResult' + n);
|
||||
const pts = document.getElementById('shotPts' + n);
|
||||
|
||||
if (result === true) {
|
||||
icon.className = 'shot-icon goal';
|
||||
icon.textContent = '⚽';
|
||||
res.className = 'result goal-badge';
|
||||
res.textContent = 'Goal';
|
||||
icon.className = 'shot-icon goal'; icon.textContent = '⚽';
|
||||
res.className = 'result goal-badge'; res.textContent = 'Goal';
|
||||
pts.textContent = '+1 PT';
|
||||
} else if (result === false) {
|
||||
icon.className = 'shot-icon miss';
|
||||
icon.textContent = '✕';
|
||||
res.className = 'result miss-badge';
|
||||
res.textContent = 'Miss';
|
||||
icon.className = 'shot-icon miss'; icon.textContent = '✕';
|
||||
res.className = 'result miss-badge'; res.textContent = 'Miss';
|
||||
pts.textContent = '0 PTS';
|
||||
} else {
|
||||
icon.className = 'shot-icon pending';
|
||||
icon.textContent = '⚽';
|
||||
res.className = 'result pending-badge';
|
||||
res.textContent = '—';
|
||||
icon.className = 'shot-icon pending'; icon.textContent = '⚽';
|
||||
res.className = 'result pending-badge'; res.textContent = '—';
|
||||
pts.textContent = '0 PTS';
|
||||
}
|
||||
}
|
||||
|
||||
function resetShots() {
|
||||
for (let i = 0; i < 3; i++) renderShot(i, null);
|
||||
}
|
||||
function resetShots() { for (let i = 0; i < 3; i++) renderShot(i, null); }
|
||||
|
||||
function popScore(el) {
|
||||
el.classList.remove('score-pop');
|
||||
void el.offsetWidth; // reflow
|
||||
void el.offsetWidth;
|
||||
el.classList.add('score-pop');
|
||||
}
|
||||
|
||||
function renderLeaderboard(rows, activeId) {
|
||||
const wrap = document.getElementById('leaderboardRows');
|
||||
const maxScore = rows[0]?.total_score || 1;
|
||||
|
||||
wrap.innerHTML = rows.map((r, i) => {
|
||||
const medal = i < 3 ? `<div class="medal">${MEDALS[i]}</div>` : `<div class="rank-num">${r.rank}</div>`;
|
||||
const pct = Math.round((r.total_score / maxScore) * 100);
|
||||
const medal = i < 3 ? `<div class="medal">${MEDALS[i]}</div>` : `<div class="rank-num">${r.rank}</div>`;
|
||||
const pct = Math.round((r.total_score / maxScore) * 100);
|
||||
const active = r.id === activeId ? 'active-player' : '';
|
||||
return `
|
||||
<div class="rank ${active}">
|
||||
${medal}
|
||||
<div class="rname">${r.name}</div>
|
||||
<div class="bar"><i style="width:${pct}%"></i></div>
|
||||
<div class="rscore">${r.total_score}</div>
|
||||
<div class="pts">PTS</div>
|
||||
</div>`;
|
||||
return `<div class="rank ${active}">
|
||||
${medal}
|
||||
<div class="rname">${r.name}</div>
|
||||
<div class="bar"><i style="width:${pct}%"></i></div>
|
||||
<div class="rscore">${r.total_score}</div>
|
||||
<div class="pts">PTS</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderHistory(history) {
|
||||
const wrap = document.getElementById('historyWrap');
|
||||
const str = JSON.stringify(history);
|
||||
if (str === lastHistoryStr) return;
|
||||
lastHistoryStr = str;
|
||||
|
||||
if (!history || history.length === 0) {
|
||||
wrap.innerHTML = '<div class="no-history">No sessions yet</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
wrap.innerHTML = history.map(s => {
|
||||
const isToday = s.date === TODAY;
|
||||
const shots = [0, 1, 2].map(i => {
|
||||
if (s.shots[i] === undefined) return `<span class="hshot pending">⚽</span>`;
|
||||
return s.shots[i]
|
||||
? `<span class="hshot goal">⚽</span>`
|
||||
: `<span class="hshot miss">✕</span>`;
|
||||
}).join('');
|
||||
return `<div class="history-row ${isToday ? 'history-today' : ''}">
|
||||
<div class="history-date">${s.date}</div>
|
||||
<div class="history-shots">${shots}</div>
|
||||
<div class="history-score">${s.score} <span>PTS</span></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
@@ -228,41 +259,34 @@
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
|
||||
// ── Leaderboard ──────────────────────────────
|
||||
const lbStr = JSON.stringify(data.leaderboard);
|
||||
if (lbStr !== lastLeaderStr) {
|
||||
renderLeaderboard(data.leaderboard, data.player?.id);
|
||||
lastLeaderStr = lbStr;
|
||||
}
|
||||
|
||||
// ── Player ───────────────────────────────────
|
||||
if (!data.player) {
|
||||
if (lastPlayerId !== null) {
|
||||
document.getElementById('playerName').textContent = 'Waiting for player…';
|
||||
document.getElementById('totalScore').textContent = '—';
|
||||
document.getElementById('waitingText').textContent = 'Select a player';
|
||||
document.getElementById('historyWrap').innerHTML = '<div class="no-history">Select a player</div>';
|
||||
lastHistoryStr = '';
|
||||
resetShots();
|
||||
lastPlayerId = null;
|
||||
lastShots = [];
|
||||
lastTotal = null;
|
||||
lastPlayerId = null; lastShots = []; lastTotal = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const p = data.player;
|
||||
|
||||
// New player selected
|
||||
if (p.id !== lastPlayerId) {
|
||||
document.getElementById('playerName').textContent = p.name.toUpperCase();
|
||||
document.getElementById('waitingText').textContent = p.name;
|
||||
resetShots();
|
||||
lastPlayerId = p.id;
|
||||
lastShots = [];
|
||||
lastTotal = null;
|
||||
lastPlayerId = p.id; lastShots = []; lastTotal = null;
|
||||
lastHistoryStr = '';
|
||||
renderLeaderboard(data.leaderboard, p.id);
|
||||
}
|
||||
|
||||
// Update total score
|
||||
if (p.total_score !== lastTotal) {
|
||||
const el = document.getElementById('totalScore');
|
||||
el.textContent = p.total_score;
|
||||
@@ -270,20 +294,18 @@
|
||||
lastTotal = p.total_score;
|
||||
}
|
||||
|
||||
// Update shots one by one
|
||||
p.shots.forEach((result, i) => {
|
||||
if (lastShots[i] === undefined) {
|
||||
renderShot(i, result);
|
||||
}
|
||||
if (lastShots[i] === undefined) renderShot(i, result);
|
||||
});
|
||||
lastShots = [...p.shots];
|
||||
|
||||
renderHistory(p.history);
|
||||
})
|
||||
.catch(() => {}); // silently ignore network errors
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// Poll every 3 seconds
|
||||
poll();
|
||||
setInterval(poll, 3000);
|
||||
setInterval(poll, 2000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user