311 lines
17 KiB
PHP
311 lines
17 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Rosetta Education - Score & Win</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
:root{
|
|
--cyan:#02aeef; --blue:#0647c9; --green:#06c70d; --red:#ef150f;
|
|
}
|
|
html, body { width:100vw; height:100vh; overflow:hidden; font-family:Impact,Haettenschweiler,'Arial Narrow Bold',Arial,sans-serif; color:#fff; background:#020814; }
|
|
|
|
.screen {
|
|
position:relative; width:100vw; height:100vh;
|
|
display:flex; flex-direction:column;
|
|
padding:1.2vh 3.5vw 1vh; gap:0.8vh;
|
|
background:
|
|
linear-gradient(180deg,rgba(0,8,25,.3) 0%,rgba(0,15,35,.2) 46%,rgba(0,12,24,.5) 100%),
|
|
url('assets/stadium-bg.png') center center/cover no-repeat;
|
|
}
|
|
.screen::before {
|
|
content:""; position:absolute; inset:0;
|
|
background:radial-gradient(circle at 50% 34%,rgba(0,174,239,.12),transparent 30%),
|
|
linear-gradient(90deg,rgba(0,0,0,.45),transparent 20%,transparent 80%,rgba(0,0,0,.45));
|
|
pointer-events:none; z-index:0;
|
|
}
|
|
.topbar,.player-panel,.score-row,.bottom-row,.footer { position:relative; z-index:1; }
|
|
|
|
/* TOPBAR */
|
|
.topbar { display:grid; grid-template-columns:22vw 1fr 22vw; align-items:center; gap:1.5vw; flex-shrink:0; }
|
|
.logo-wrap img { width:20vw; max-width:360px; filter:drop-shadow(0 4px 8px rgba(0,0,0,.7)); }
|
|
.title-badge { justify-self:center; text-align:center; padding:.7vh 2vw .9vh; transform:skew(-10deg); background:linear-gradient(180deg,rgba(0,34,73,.85),rgba(0,9,25,.9)); border:1px solid rgba(0,174,239,.4); box-shadow:0 0 18px rgba(0,174,239,.25),inset 0 0 16px rgba(0,174,239,.1); }
|
|
.title-badge .title { transform:skew(10deg); font-size:clamp(28px,3.6vw,58px); line-height:.95; letter-spacing:2px; text-shadow:0 4px 0 rgba(0,0,0,.5),0 0 12px rgba(0,174,239,.5); }
|
|
.title-badge .title span { color:var(--cyan); }
|
|
.title-badge .sub { transform:skew(10deg); display:inline-block; margin-top:.3vh; padding:.2vh 1.5vw; font-size:clamp(11px,1.3vw,20px); letter-spacing:1px; background:linear-gradient(180deg,#147aff,#0643bd); border:1px solid #029bff; box-shadow:0 0 10px rgba(0,168,255,.5); }
|
|
.passion { justify-self:end; padding:.9vh 1.4vw; display:flex; align-items:center; gap:.7vw; border:1.5px solid var(--cyan); border-radius:11px; background:rgba(3,18,39,.8); box-shadow:0 0 16px rgba(0,174,239,.5),inset 0 0 16px rgba(0,174,239,.12); font-family:Arial,sans-serif; font-weight:900; text-transform:uppercase; font-size:clamp(10px,1.25vw,18px); line-height:1.3; text-shadow:0 2px 4px #000; }
|
|
.passion .cup { font-size:clamp(16px,2vw,30px); }
|
|
.passion span { color:var(--cyan); }
|
|
|
|
/* PLAYER PANEL */
|
|
.player-panel { width:50vw; margin:0 auto; flex-shrink:0; text-align:center; padding:.7vh 2vw 1vh; background:rgba(3,15,34,.82); border:1.5px solid rgba(0,174,239,.36); border-radius:24px 24px 12px 12px; clip-path:polygon(6% 0,94% 0,100% 50%,94% 100%,6% 100%,0 50%); box-shadow:0 0 22px rgba(0,174,239,.25),inset 0 0 20px rgba(0,174,239,.08); }
|
|
.player-panel .label { font-family:Arial,sans-serif; color:var(--cyan); font-size:clamp(10px,1.3vw,18px); font-weight:900; letter-spacing:1px; text-transform:uppercase; }
|
|
.player-panel .name { font-size:clamp(28px,3.8vw,60px); letter-spacing:.5vw; margin-top:.2vh; text-shadow:0 4px 8px rgba(0,0,0,.8),0 0 8px rgba(255,255,255,.2); transition:opacity .3s; }
|
|
|
|
/* SCORE ROW */
|
|
.score-row { display:grid; grid-template-columns:1.1fr 1fr 1fr 1fr; gap:1vw; width:88vw; margin:0 auto; flex-shrink:0; }
|
|
.card { border:1.5px solid var(--cyan); border-radius:11px; background:linear-gradient(180deg,rgba(2,23,52,.9),rgba(0,10,24,.85)); box-shadow:0 0 12px rgba(0,174,239,.55),inset 0 0 22px rgba(0,174,239,.1); padding:.7vh .8vw; text-align:center; overflow:hidden; position:relative; display:flex; flex-direction:column; align-items:center; justify-content:space-evenly; }
|
|
.card::before { content:""; position:absolute; inset:0; background:linear-gradient(135deg,rgba(255,255,255,.08),transparent 25%,transparent 70%,rgba(0,174,239,.07)); pointer-events:none; }
|
|
.card-title { position:relative; z-index:1; width:100%; padding:.35vh .4vw; border:1px solid #009dff; border-radius:5px; background:linear-gradient(180deg,#0c7cff,#063cae); font-family:Arial,sans-serif; font-weight:900; font-size:clamp(10px,1.25vw,18px); text-transform:uppercase; }
|
|
.total-number { position:relative; z-index:1; font-size:clamp(48px,6.5vw,110px); line-height:1; text-shadow:0 6px 0 rgba(0,0,0,.5); transition:all .4s; }
|
|
.points { position:relative; z-index:1; color:var(--cyan); font-family:Arial,sans-serif; font-weight:900; font-size:clamp(10px,1.3vw,18px); letter-spacing:1px; }
|
|
.stars { color:var(--cyan); padding:0 .4vw; }
|
|
.shot-icon { position:relative; z-index:1; font-size:clamp(28px,3.8vw,58px); transition:all .3s; }
|
|
.shot-icon.miss { color:var(--red); filter:drop-shadow(0 0 12px rgba(255,0,0,.7)); text-shadow:0 0 14px #ff4a43; }
|
|
.shot-icon.goal { filter:drop-shadow(0 0 15px rgba(0,255,70,.5)); }
|
|
.shot-icon.pending { opacity:.3; }
|
|
.result { position:relative; z-index:1; width:82%; padding:.35vh 0; border-radius:6px; font-family:Arial,sans-serif; font-size:clamp(12px,1.7vw,24px); font-weight:900; text-transform:uppercase; box-shadow:inset 0 0 10px rgba(255,255,255,.15); transition:all .3s; }
|
|
.goal-badge { background:linear-gradient(180deg,#0ddc12,#047706); border:1px solid #2aff38; }
|
|
.miss-badge { background:linear-gradient(180deg,#ff2b21,#8c0806); border:1px solid #ff4b43; }
|
|
.pending-badge { background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.15); color:rgba(255,255,255,.4); }
|
|
.shot-points { position:relative; z-index:1; font-family:Arial,sans-serif; color:var(--cyan); font-size:clamp(10px,1.2vw,16px); font-weight:900; }
|
|
|
|
/* BOTTOM ROW */
|
|
.bottom-row { display:grid; grid-template-columns:1.25fr .85fr; gap:2.5vw; width:88vw; margin:0 auto; flex:1; min-height:0; padding-bottom:.5vh; }
|
|
.leaderboard,.next-box { background:rgba(1,17,35,.85); border:1.5px solid var(--cyan); border-radius:12px; box-shadow:0 0 14px rgba(0,174,239,.5),inset 0 0 22px rgba(0,174,239,.07); overflow:hidden; display:flex; flex-direction:column; }
|
|
.section-title { flex-shrink:0; width:62%; margin:0 auto .6vh; padding:.5vh 1vw; text-align:center; transform:skew(-15deg); background:linear-gradient(180deg,#0b77f2,#043894); border:1px solid #009dff; box-shadow:0 0 13px rgba(0,168,255,.5); font-family:Arial,sans-serif; font-size:clamp(10px,1.25vw,17px); font-weight:900; text-transform:uppercase; }
|
|
.section-title span { display:block; transform:skew(15deg); }
|
|
.leaderboard { padding:.8vh 1.4vw; }
|
|
.ranks-wrap { flex:1; min-height:0; display:flex; flex-direction:column; justify-content:space-evenly; }
|
|
.rank { display:grid; grid-template-columns:3vw 1fr 13vw 3.8vw 2.5vw; align-items:center; gap:.6vw; padding:.3vh 0; border-bottom:1px solid rgba(0,174,239,.15); font-family:Arial,sans-serif; transition:all .3s; }
|
|
.rank:last-child { border-bottom:none; }
|
|
.rank.active-player { background:rgba(0,174,239,.1); border-radius:6px; padding-left:.4vw; }
|
|
.medal { font-size:clamp(12px,1.6vw,22px); text-align:center; }
|
|
.rank-num { width:1.9vw; height:1.9vw; border-radius:4px; display:grid; place-items:center; background:linear-gradient(180deg,#197cff,#06398e); border:1px solid #00aeef; font-size:clamp(9px,1.1vw,15px); font-weight:900; }
|
|
.rname { font-size:clamp(10px,1.2vw,17px); font-weight:900; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
.bar { height:1.2vh; border-radius:99px; background:rgba(0,0,0,.5); overflow:hidden; }
|
|
.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; }
|
|
|
|
/* 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); }
|
|
|
|
@keyframes scorePop { 0%{transform:scale(1)} 50%{transform:scale(1.2)} 100%{transform:scale(1)} }
|
|
.score-pop { animation:scorePop .35s ease; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="screen">
|
|
|
|
<header class="topbar">
|
|
<div class="logo-wrap"><img src="assets/rosetta-logo.png" alt="Rosetta Education International" /></div>
|
|
<div class="title-badge">
|
|
<div class="title">SC⚽RE <span>& WIN</span></div>
|
|
<div class="sub">CHALLENGE 2025</div>
|
|
</div>
|
|
<div class="passion">
|
|
<div class="cup">🏆</div>
|
|
<div>Play with <span>Passion</span>,<br/>Win with <span>Pride!</span></div>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="player-panel">
|
|
<div class="label">★ ★ Player Now Playing ★ ★</div>
|
|
<div class="name" id="playerName">Waiting for player…</div>
|
|
</section>
|
|
|
|
<section class="score-row">
|
|
<div class="card total-card">
|
|
<div class="card-title">Total Score</div>
|
|
<div class="total-number" id="totalScore">—</div>
|
|
<div class="points"><span class="stars">★</span>POINTS<span class="stars">★</span></div>
|
|
</div>
|
|
<div class="card shot-card" id="shotCard1">
|
|
<div class="card-title">Shot 1</div>
|
|
<div class="shot-icon pending" id="shotIcon1">⚽</div>
|
|
<div class="result pending-badge" id="shotResult1">—</div>
|
|
<div class="shot-points" id="shotPts1">0 PTS</div>
|
|
</div>
|
|
<div class="card shot-card" id="shotCard2">
|
|
<div class="card-title">Shot 2</div>
|
|
<div class="shot-icon pending" id="shotIcon2">⚽</div>
|
|
<div class="result pending-badge" id="shotResult2">—</div>
|
|
<div class="shot-points" id="shotPts2">0 PTS</div>
|
|
</div>
|
|
<div class="card shot-card" id="shotCard3">
|
|
<div class="card-title">Shot 3</div>
|
|
<div class="shot-icon pending" id="shotIcon3">⚽</div>
|
|
<div class="result pending-badge" id="shotResult3">—</div>
|
|
<div class="shot-points" id="shotPts3">0 PTS</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="bottom-row">
|
|
<div class="leaderboard">
|
|
<div class="section-title"><span>Top 5 Leaderboard</span></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>Player History</span></div>
|
|
<div class="history-wrap" id="historyWrap">
|
|
<div class="no-history">Select a player</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<footer class="footer">
|
|
<span>◷ Event In Progress</span>
|
|
<span>|</span>
|
|
<span>👥 Cheer. Support. Inspire.</span>
|
|
</footer>
|
|
|
|
</main>
|
|
|
|
<script>
|
|
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) {
|
|
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';
|
|
pts.textContent = '+1 PT';
|
|
} else if (result === false) {
|
|
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 = '—';
|
|
pts.textContent = '0 PTS';
|
|
}
|
|
}
|
|
|
|
function resetShots() { for (let i = 0; i < 3; i++) renderShot(i, null); }
|
|
|
|
function popScore(el) {
|
|
el.classList.remove('score-pop');
|
|
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 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>`;
|
|
}).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('');
|
|
}
|
|
|
|
function poll() {
|
|
fetch(POLL_URL)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
|
|
const lbStr = JSON.stringify(data.leaderboard);
|
|
if (lbStr !== lastLeaderStr) {
|
|
renderLeaderboard(data.leaderboard, data.player?.id);
|
|
lastLeaderStr = lbStr;
|
|
}
|
|
|
|
if (!data.player) {
|
|
if (lastPlayerId !== null) {
|
|
document.getElementById('playerName').textContent = 'Waiting for player…';
|
|
document.getElementById('totalScore').textContent = '—';
|
|
document.getElementById('historyWrap').innerHTML = '<div class="no-history">Select a player</div>';
|
|
lastHistoryStr = '';
|
|
resetShots();
|
|
lastPlayerId = null; lastShots = []; lastTotal = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const p = data.player;
|
|
|
|
if (p.id !== lastPlayerId) {
|
|
document.getElementById('playerName').textContent = p.name.toUpperCase();
|
|
resetShots();
|
|
lastPlayerId = p.id; lastShots = []; lastTotal = null;
|
|
lastHistoryStr = '';
|
|
renderLeaderboard(data.leaderboard, p.id);
|
|
}
|
|
|
|
if (p.total_score !== lastTotal) {
|
|
const el = document.getElementById('totalScore');
|
|
el.textContent = p.total_score;
|
|
if (lastTotal !== null) popScore(el);
|
|
lastTotal = p.total_score;
|
|
}
|
|
|
|
p.shots.forEach((result, i) => {
|
|
if (lastShots[i] === undefined) renderShot(i, result);
|
|
});
|
|
lastShots = [...p.shots];
|
|
|
|
renderHistory(p.history);
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
poll();
|
|
setInterval(poll, 2000);
|
|
</script>
|
|
</body>
|
|
</html> |