Added new calculator with embedded simple tournament
This commit is contained in:
+192
-1
@@ -130,6 +130,81 @@
|
||||
align-content: stretch;
|
||||
}
|
||||
|
||||
/* ── Leaderboard display mode ── */
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
.main-wrapper .grid {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
min-width: 0;
|
||||
}
|
||||
.lb-sidebar {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #141414;
|
||||
border-left: 2px solid #222;
|
||||
overflow: hidden;
|
||||
}
|
||||
.lb-header {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #222;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.lb-title { font-size: 1.1rem; font-weight: 800; color: #fff; }
|
||||
.lb-count {
|
||||
background: #28a745; color: white;
|
||||
font-size: 0.72rem; font-weight: 700;
|
||||
padding: 2px 9px; border-radius: 12px;
|
||||
}
|
||||
.lb-empty {
|
||||
flex: 1; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
color: #444; gap: 10px; font-size: 0.9rem;
|
||||
}
|
||||
.lb-empty .icon { font-size: 2.5rem; }
|
||||
.lb-list {
|
||||
flex: 1; overflow-y: auto;
|
||||
padding: 10px 12px;
|
||||
display: flex; flex-direction: column; gap: 7px;
|
||||
}
|
||||
.lb-list::-webkit-scrollbar { width: 4px; }
|
||||
.lb-list::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
|
||||
.lb-row {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
padding: 9px 12px;
|
||||
border-radius: 10px;
|
||||
background: #1e1e1e; border: 2px solid #2a2a2a;
|
||||
}
|
||||
.lb-row.r1 { border-color: #ffc107; background: #1c1900; }
|
||||
.lb-row.r2 { border-color: #888; background: #181818; }
|
||||
.lb-row.r3 { border-color: #a0522d; background: #1a1210; }
|
||||
.lb-badge {
|
||||
width: 30px; height: 30px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.78rem; font-weight: 800; flex-shrink: 0;
|
||||
background: #2a2a2a; color: #aaa;
|
||||
}
|
||||
.lb-row.r1 .lb-badge { background: #ffc107; color: #7a5800; font-size: 1rem; }
|
||||
.lb-row.r2 .lb-badge { background: #888; color: #fff; font-size: 1rem; }
|
||||
.lb-row.r3 .lb-badge { background: #a0522d; color: #fff; font-size: 1rem; }
|
||||
.lb-name {
|
||||
flex: 1; min-width: 0;
|
||||
font-size: 0.95rem; font-weight: 700; color: #eee;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.lb-tens { font-size: 0.7rem; font-weight: 700; color: #ffc107; flex-shrink: 0; }
|
||||
.lb-pts { font-size: 1.2rem; font-weight: 900; color: #28a745; flex-shrink: 0; min-width: 36px; text-align: right; }
|
||||
|
||||
.camera-card {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -884,7 +959,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="main-wrapper" id="mainWrapper">
|
||||
<div class="grid" id="cameraGrid">
|
||||
{% for i in range(1, 7) %}
|
||||
<div class="camera-card" data-camera-id="{{ i }}"
|
||||
{% if i in settings.get('disabled_cameras', []) %}style="display:none;"{% endif %}>
|
||||
@@ -916,6 +992,17 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Leaderboard sidebar — injected by JS when calc display is enabled -->
|
||||
<div class="lb-sidebar" id="lbSidebar" style="display:none;">
|
||||
<div class="lb-header">
|
||||
<span class="lb-title">🏆 Lestvica</span>
|
||||
<span class="lb-count" id="lbCount">0</span>
|
||||
</div>
|
||||
<div class="lb-empty" id="lbEmpty"><span class="icon">🏅</span><span>Čakamo na rezultate…</span></div>
|
||||
<div class="lb-list" id="lbList" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Global settings and tournament state
|
||||
let currentSettings = {{ settings|tojson }};
|
||||
@@ -1618,6 +1705,9 @@
|
||||
window.addEventListener('resize', reshapeGrid);
|
||||
addTouchFeedback();
|
||||
|
||||
// Apply leaderboard display mode if enabled
|
||||
initLeaderboardDisplay();
|
||||
|
||||
// Initialize language selector after i18n is ready
|
||||
window.addEventListener('i18nReady', function() {
|
||||
createLanguageSelector('languageSelectorContainer');
|
||||
@@ -1698,6 +1788,107 @@
|
||||
clearInterval(dashboardPollingInterval);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Calculator leaderboard display mode ────────────────────────────────
|
||||
|
||||
let lbPollingInterval = null;
|
||||
let lbDisplayEnabled = false;
|
||||
|
||||
function initLeaderboardDisplay() {
|
||||
fetch('/api/calculator/display')
|
||||
.then(r => r.json())
|
||||
.then(d => applyLeaderboardMode(d.enabled, d.camera || 1))
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function applyLeaderboardMode(enabled, camera) {
|
||||
lbDisplayEnabled = enabled;
|
||||
const sidebar = document.getElementById('lbSidebar');
|
||||
const grid = document.getElementById('cameraGrid');
|
||||
|
||||
if (!enabled) {
|
||||
sidebar.style.display = 'none';
|
||||
// restore camera visibility to match settings
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const card = document.querySelector(`.camera-card[data-camera-id="${i}"]`);
|
||||
if (!card) continue;
|
||||
const isDisabled = currentSettings.disabled_cameras && currentSettings.disabled_cameras.includes(i);
|
||||
card.style.display = isDisabled ? 'none' : '';
|
||||
}
|
||||
grid.style.gridTemplateColumns = '';
|
||||
if (lbPollingInterval) { clearInterval(lbPollingInterval); lbPollingInterval = null; }
|
||||
reshapeGrid();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show only selected camera
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const card = document.querySelector(`.camera-card[data-camera-id="${i}"]`);
|
||||
if (!card) continue;
|
||||
card.style.display = (i === camera) ? '' : 'none';
|
||||
}
|
||||
grid.style.gridTemplateColumns = '1fr';
|
||||
grid.style.gridTemplateRows = '1fr';
|
||||
|
||||
sidebar.style.display = '';
|
||||
reshapeGrid();
|
||||
|
||||
// Poll leaderboard
|
||||
pollLeaderboard();
|
||||
if (!lbPollingInterval) {
|
||||
lbPollingInterval = setInterval(pollLeaderboard, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function pollLeaderboard() {
|
||||
fetch('/api/calculator/leaderboard')
|
||||
.then(r => r.json())
|
||||
.then(data => { if (Array.isArray(data)) renderLbSidebar(data); })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function renderLbSidebar(data) {
|
||||
const list = document.getElementById('lbList');
|
||||
const empty = document.getElementById('lbEmpty');
|
||||
const count = document.getElementById('lbCount');
|
||||
|
||||
count.textContent = data.length;
|
||||
|
||||
if (data.length === 0) {
|
||||
empty.style.display = '';
|
||||
list.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
list.style.display = '';
|
||||
|
||||
list.innerHTML = data.map((e, idx) => {
|
||||
const rank = idx + 1;
|
||||
const rc = rank <= 3 ? `r${rank}` : '';
|
||||
const med = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : rank;
|
||||
return `<div class="lb-row ${rc}">
|
||||
<div class="lb-badge">${med}</div>
|
||||
<div class="lb-name" title="${e.name}">${e.name}</div>
|
||||
<div class="lb-tens">${e.tens}×10</div>
|
||||
<div class="lb-pts">${e.score}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Poll display setting every 5s so enabling/disabling from calculator
|
||||
// takes effect on the main screen automatically
|
||||
setInterval(() => {
|
||||
fetch('/api/calculator/display')
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
const enabled = !!d.enabled;
|
||||
const camera = d.camera || 1;
|
||||
if (enabled !== lbDisplayEnabled) {
|
||||
applyLeaderboardMode(enabled, camera);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user