initial commit

This commit is contained in:
Nicola
2026-06-24 13:00:51 +02:00
commit 2d4c3864ef
11 changed files with 2555 additions and 0 deletions
+254
View File
@@ -0,0 +1,254 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Scoreboard</title>
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
background: transparent;
color: #fff;
user-select: none;
}
/* Contenitore responsive centra il widget */
.widget {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/* Scoreboard si adatta al contenuto */
.scoreboard {
display: inline-block;
min-width: 380px;
max-width: 100%;
background: linear-gradient(160deg, rgba(0,0,0,0.88), rgba(10,10,20,0.88));
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
border-radius: calc(min(16px, 1.2vw));
overflow: hidden;
box-shadow: 0 8px 40px rgba(0,0,0,0.6);
}
/* Header (Sets) */
.sb-header {
display: grid;
grid-template-columns: minmax(0, 1fr) 48px 48px 48px 60px;
background: rgba(255,255,255,0.06);
border-bottom: 1px solid rgba(255,255,255,0.06);
padding: 0.5em 1.2em;
font-size: clamp(10px, 1.2vw, 14px);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: rgba(255,255,255,0.5);
}
.sb-header .col-player { text-align: left; }
.sb-header .col-set,
.sb-header .col-points { text-align: center; }
/* Player Row */
.player-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 48px 48px 48px 60px;
align-items: center;
padding: 0.6em 1.2em;
border-bottom: 1px solid rgba(255,255,255,0.04);
transition: background 0.3s;
position: relative;
font-size: clamp(14px, 1.8vw, 22px);
}
.player-row:last-child { border-bottom: none; }
.player-row.serving {
background: rgba(250, 204, 21, 0.06);
}
.player-row.serving::before {
content: '●';
position: absolute;
left: 0.3em;
top: 50%;
transform: translateY(-50%);
font-size: 0.5em;
color: #facc15;
text-shadow: 0 0 8px rgba(250,204,21,0.6);
}
.player-row .player-name {
font-weight: 700;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 0.5em;
}
.player-row .player-name .flag {
display: inline-block;
width: 1.6em;
height: 1.1em;
border-radius: 2px;
background-size: cover;
background-position: center;
flex-shrink: 0;
}
/* Bandiere inline SVG base64 */
.flag-it { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="1" height="2" fill="%23009246"/><rect x="1" width="1" height="2" fill="%23fff"/><rect x="2" width="1" height="2" fill="%23ce2b37"/></svg>'); }
.flag-es { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23c60b1e"/><rect y="0.5" width="3" height="1" fill="%23ffc400"/></svg>'); }
.flag-us { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23fff"/><rect width="3" height="1" fill="%23b22234"/><rect y="1" width="3" height="1" fill="%233b3b6d"/></svg>'); }
.flag-fr { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="1" height="2" fill="%23005240"/><rect x="1" width="1" height="2" fill="%23fff"/><rect x="2" width="1" height="2" fill="%23ce2b37"/></svg>'); }
.flag-gb { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23012369"/><rect width="3" height="1" fill="%23c8102e"/><rect y="0.33" width="3" height="0.33" fill="%23fff"/><rect y="1.33" width="3" height="0.33" fill="%23fff"/></svg>'); }
.flag-au { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%2300008b"/><rect width="1.5" height="1" fill="%23fff"/><path d="M0 0L1.5 1M0 1L1.5 0" stroke="%23c8102e" stroke-width="0.15"/></svg>'); }
.flag-de { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="0.67" fill="%23000"/><rect y="0.67" width="3" height="0.67" fill="%23dd0000"/><rect y="1.33" width="3" height="0.67" fill="%23ffce00"/></svg>'); }
.flag-ch { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23d52b1e"/><rect x="1.2" y="0.4" width="0.6" height="1.2" fill="%23fff"/><rect x="0.8" y="0.8" width="1.4" height="0.4" fill="%23fff"/></svg>'); }
.flag-rs { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23fff"/><rect y="0.67" width="3" height="0.67" fill="%23005240"/><rect y="1.33" width="3" height="0.67" fill="%23c8102e"/></svg>'); }
.flag-gr { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23005b94"/><rect x="0.5" y="0.5" width="1" height="1" fill="%23fff"/><rect y="0.5" width="3" height="0.2" fill="%23fff"/></svg>'); }
.flag-br { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23009246"/><circle cx="1.5" cy="1" r="0.6" fill="%23ffcc00"/><path d="M1.5 0.6l0.3 0.5h-0.6z" fill="%23005240"/></svg>'); }
.flag-ar { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="%23fff"/><rect y="0.33" width="3" height="0.67" fill="%2375aadb"/><rect y="1" width="3" height="0.67" fill="%2375aadb"/></svg>'); }
.player-row .set-score {
text-align: center;
font-weight: 700;
}
.player-row .set-score.current-set {
color: #facc15;
}
.player-row .points {
text-align: center;
font-weight: 800;
letter-spacing: 1px;
font-size: 1.2em;
}
.player-row .points.deuce {
font-size: 0.7em;
font-weight: 600;
text-transform: uppercase;
color: #facc15;
}
.player-row .points.advantage {
color: #4ade80;
}
/* Animazioni */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.scoreboard { animation: fadeInUp 0.5s ease-out; }
@keyframes pointFlash {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.25); }
}
.points.flash {
animation: pointFlash 0.25s ease;
}
</style>
</head>
<body>
<div class="widget">
<div class="scoreboard" id="scoreboard">
<div class="sb-header">
<span class="col-player">Giocatore</span>
<span class="col-set">S1</span>
<span class="col-set">S2</span>
<span class="col-set">S3</span>
<span class="col-points">Punti</span>
</div>
<div class="player-row" id="player1Row">
<div class="player-name">
<span class="flag" id="flag1"></span>
<span id="player1Name">J. Sinner</span>
</div>
<span class="set-score" id="p1s1">0</span>
<span class="set-score" id="p1s2">0</span>
<span class="set-score" id="p1s3">0</span>
<span class="points" id="p1points">0</span>
</div>
<div class="player-row" id="player2Row">
<div class="player-name">
<span class="flag" id="flag2"></span>
<span id="player2Name">C. Alcaraz</span>
</div>
<span class="set-score" id="p2s1">0</span>
<span class="set-score" id="p2s2">0</span>
<span class="set-score" id="p2s3">0</span>
<span class="points" id="p2points">0</span>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
let prevPoints = [0, 0];
let prevDeuce = false;
let prevAdv = 0;
function updateScoreboard(state) {
// Nomi e bandiere
document.getElementById('player1Name').textContent = state.player1;
document.getElementById('player2Name').textContent = state.player2;
document.getElementById('flag1').className = 'flag flag-' + (state.flag1 || 'it');
document.getElementById('flag2').className = 'flag flag-' + (state.flag2 || 'es');
// Set
for (let s = 0; s < 3; s++) {
const e1 = document.getElementById('p1s' + (s + 1));
const e2 = document.getElementById('p2s' + (s + 1));
if (state.sets[s]) {
e1.textContent = state.sets[s][0] ?? '0';
e2.textContent = state.sets[s][1] ?? '0';
} else {
e1.textContent = '0';
e2.textContent = '0';
}
e1.classList.toggle('current-set', s === state.currentSet);
e2.classList.toggle('current-set', s === state.currentSet);
}
// Punti
const labels = pointLabel(state);
const p1el = document.getElementById('p1points');
const p2el = document.getElementById('p2points');
p1el.textContent = labels[0];
p2el.textContent = labels[1];
// Classi punti
p1el.className = 'points';
p2el.className = 'points';
if (state.deuce) {
p1el.classList.add('deuce');
p2el.classList.add('deuce');
if (state.advantage === 1) p1el.classList.add('advantage');
if (state.advantage === 2) p2el.classList.add('advantage');
}
// Flash su cambio punti
const pNow = state.points;
if (pNow[0] !== prevPoints[0] || pNow[1] !== prevPoints[1] ||
state.deuce !== prevDeuce || state.advantage !== prevAdv) {
const changed = pNow[0] !== prevPoints[0] ? 1 : 2;
const el = document.getElementById('p' + changed + 'points');
el.classList.remove('flash');
void el.offsetWidth;
el.classList.add('flash');
}
prevPoints = [...pNow];
prevDeuce = state.deuce;
prevAdv = state.advantage;
// Servizio
document.getElementById('player1Row').classList.toggle('serving', state.server === 1);
document.getElementById('player2Row').classList.toggle('serving', state.server === 2);
}
// Ascolta aggiornamenti
const unsub = onStateUpdate(updateScoreboard);
</script>
</body>
</html>