Files
Sdk_TV_app/templates/calculator.html
T

867 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="sl">
<head>
<meta charset="UTF-8" />
<title>Nabijanje</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/navbar.css">
<link rel="stylesheet" href="/static/css/buttons.css">
<style>
body { display: flex; flex-direction: column; height: 100vh; overflow: hidden; background: #f5f5f5; }
.navbar { flex-shrink: 0; }
.page {
flex: 1;
display: flex;
align-items: stretch;
justify-content: center;
padding: 16px;
overflow: hidden;
min-height: 0;
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
width: 100%;
max-width: 1400px;
display: flex;
flex-direction: row;
overflow: hidden;
}
/* ── LEFT PANEL: config ── */
.left-panel {
width: 240px;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid #e9ecef;
background: #f8f9fa;
}
.left-header { height: 65px; padding: 0 16px; border-bottom: 1px solid #e9ecef; flex-shrink: 0; display: flex; align-items: center; }
.left-header h3 { margin: 0; font-size: 1.05rem; font-weight: 700; color: #2c3e50; line-height: 1; }
.left-body { padding: 16px 20px; display: flex; flex-direction: column; gap: 14px; flex: 1; }
.setup-field { display: flex; flex-direction: column; gap: 6px; }
.setup-field label {
font-size: 0.75rem; font-weight: 700; color: #6c757d;
text-transform: uppercase; letter-spacing: 0.4px;
}
.setup-input {
padding: 11px 12px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
text-align: center;
background: white;
transition: border-color 0.2s;
outline: none;
width: 100%;
}
.setup-input:focus { border-color: #28a745; box-shadow: 0 0 0 3px rgba(40,167,69,0.12); }
.setup-hint { font-size: 0.72rem; color: #adb5bd; }
.gen-btn {
background: #28a745; color: white; border: none;
border-radius: 8px; padding: 13px;
font-size: 0.95rem; font-weight: 700; cursor: pointer;
transition: background 0.2s, transform 0.1s; width: 100%;
}
.gen-btn:hover:not(:disabled) { background: #1e7e34; }
.gen-btn:disabled { background: #ced4da; cursor: not-allowed; }
.left-utils { padding: 0 20px 18px; display: none; flex-direction: column; gap: 7px; flex-shrink: 0; }
.left-utils.visible { display: flex; }
.util-btn {
padding: 9px 12px; border-radius: 8px; font-size: 0.82rem;
font-weight: 600; cursor: pointer; transition: all 0.15s; width: 100%; text-align: center;
}
.btn-zeros { background: #6c757d; border: 2px solid #6c757d; color: white; }
.btn-zeros:hover { background: #545b62; border-color: #545b62; }
.btn-reset { background: #dc3545; border: 2px solid #dc3545; color: white; }
.btn-reset:hover { background: #b02a37; border-color: #b02a37; }
.btn-print { background: #6f42c1; border: 2px solid #6f42c1; color: white; }
.btn-print:hover { background: #59359a; border-color: #59359a; }
@media print {
/* hide everything except the print layout */
.navbar, .left-panel, .middle-panel, .score-panel,
.page, .main-card, body > *:not(#printLayout) { display: none !important; }
body {
background: white !important;
overflow: visible !important;
height: auto !important;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
}
#printLayout {
display: block !important;
padding: 24px 32px;
color: #333;
}
.pl-header {
display: flex;
align-items: center;
gap: 18px;
padding-bottom: 14px;
border-bottom: 2px solid #dee2e6;
margin-bottom: 18px;
}
.pl-logo {
height: 52px;
width: auto;
object-fit: contain;
}
.pl-titles { display: flex; flex-direction: column; gap: 2px; }
.pl-title {
font-size: 20pt;
font-weight: 800;
color: #2c3e50;
line-height: 1.1;
}
.pl-subtitle {
font-size: 10pt;
color: #6c757d;
}
.pl-meta {
margin-left: auto;
text-align: right;
font-size: 9pt;
color: #6c757d;
line-height: 1.6;
}
.pl-table {
width: 100%;
border-collapse: collapse;
font-size: 11pt;
}
.pl-table thead tr {
background: #f8f9fa;
border-bottom: 2px solid #dee2e6;
}
.pl-table th {
padding: 8px 12px;
text-align: left;
font-size: 8pt;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.4px;
color: #495057;
}
.pl-table th.right { text-align: right; }
.pl-table td {
padding: 9px 12px;
border-bottom: 1px solid #f1f3f4;
color: #333;
}
.pl-table td.right { text-align: right; }
.pl-table tbody tr:nth-child(1) td { background: #fffdf0; }
.pl-table tbody tr:nth-child(2) td { background: #f9f9f9; }
.pl-table tbody tr:nth-child(3) td { background: #fdf7f2; }
.pl-rank { font-weight: 700; font-size: 12pt; }
.pl-rank.r1 { color: #b8860b; }
.pl-rank.r2 { color: #6c757d; }
.pl-rank.r3 { color: #8b4513; }
.pl-name { font-weight: 600; }
.pl-score { font-weight: 800; font-size: 13pt; color: #28a745; }
.pl-tens { font-weight: 700; color: #856404; }
}
/* display settings */
.display-section {
padding: 14px 20px 18px;
border-top: 1px solid #e9ecef;
display: flex;
flex-direction: column;
gap: 12px;
flex-shrink: 0;
}
.display-section-title {
font-size: 0.75rem; font-weight: 700; color: #6c757d;
text-transform: uppercase; letter-spacing: 0.4px;
}
.display-toggle-row {
display: flex; align-items: center; justify-content: space-between;
}
.display-toggle-label { font-size: 0.88rem; font-weight: 600; color: #2c3e50; }
.toggle-switch { position: relative; width: 42px; height: 24px; flex-shrink: 0; }
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-track {
position: absolute; inset: 0; border-radius: 12px;
background: #ced4da; cursor: pointer; transition: background 0.2s;
}
.toggle-track::after {
content: ''; position: absolute; width: 18px; height: 18px;
border-radius: 50%; background: white; top: 3px; left: 3px;
transition: transform 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.toggle-switch input:checked + .toggle-track { background: #28a745; }
.toggle-switch input:checked + .toggle-track::after { transform: translateX(18px); }
.display-camera-row { display: flex; flex-direction: column; gap: 6px; }
.display-camera-row label { font-size: 0.75rem; font-weight: 700; color: #6c757d; text-transform: uppercase; letter-spacing: 0.4px; }
.camera-select {
padding: 9px 12px; border: 2px solid #dee2e6; border-radius: 8px;
font-size: 0.9rem; font-weight: 600; color: #2c3e50; background: white;
outline: none; cursor: pointer; transition: border-color 0.2s; width: 100%;
}
.camera-select:focus { border-color: #28a745; }
.camera-select:disabled { opacity: 0.45; cursor: not-allowed; }
/* ── MIDDLE PANEL ── */
.middle-panel {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
border-right: 1px solid #e9ecef;
}
/* top: name inputs */
.name-bar {
height: 65px;
display: flex;
align-items: center;
gap: 10px;
padding: 0 16px;
border-bottom: 1px solid #e9ecef;
background: #f8f9fa;
flex-shrink: 0;
}
.name-bar-label {
font-size: 0.75rem; font-weight: 700; color: #6c757d;
text-transform: uppercase; letter-spacing: 0.4px;
white-space: nowrap;
}
.name-input {
flex: 1;
padding: 9px 12px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 500;
color: #2c3e50;
background: white;
transition: border-color 0.2s;
outline: none;
min-width: 0;
}
.name-input::placeholder { color: #adb5bd; font-weight: 400; }
.name-input:focus { border-color: #28a745; box-shadow: 0 0 0 3px rgba(40,167,69,0.12); }
/* target grid */
.middle-empty {
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center;
color: #adb5bd; gap: 10px; font-size: 1rem;
}
.middle-empty .empty-icon { font-size: 3rem; }
.targets-wrapper {
flex: 1; overflow-y: auto; overflow-x: hidden;
padding: 14px 16px; min-height: 0; display: none;
}
.targets-wrapper.visible { display: block; }
.targets-wrapper::-webkit-scrollbar { width: 6px; }
.targets-wrapper::-webkit-scrollbar-track { background: transparent; }
.targets-wrapper::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 4px; }
.targets-grid { display: grid; gap: 8px; }
.target-group {
border: 2px solid #e9ecef; border-radius: 8px; background: #f8f9fa;
display: flex; flex-direction: column; overflow: hidden;
transition: border-color 0.15s, background 0.15s;
}
.target-group.completed { border-color: #28a745; background: #f0f8f5; }
.target-num {
background: #e9ecef; border-bottom: 1px solid #dee2e6;
font-size: 0.72rem; font-weight: 700; color: #495057;
text-align: center; padding: 4px; flex-shrink: 0;
transition: background 0.15s, color 0.15s;
}
.target-group.completed .target-num {
background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
border-bottom-color: #28a745; color: #28a745;
}
.shots-col { display: flex; flex-direction: column; gap: 4px; padding: 5px; }
.shot-input {
width: 100%; padding: 5px 2px; border: 1.5px solid #dee2e6;
border-radius: 4px; text-align: center; font-size: 0.95rem;
font-weight: 700; color: #333; background: white;
min-height: 30px; transition: border-color 0.15s, color 0.15s; outline: none;
}
.shot-input:focus { border-color: #28a745; box-shadow: 0 0 0 2px rgba(40,167,69,0.2); }
.shot-input.valid { border-color: #28a745; }
.shot-input.ten { border-color: #ffc107; color: #e6a800; }
/* bottom: add bar */
.add-bar {
display: none;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-top: 2px solid #e9ecef;
background: #f8f9fa;
flex-shrink: 0;
}
.add-bar.visible { display: flex; }
.add-bar-score { display: flex; flex-direction: column; align-items: center; line-height: 1.1; flex-shrink: 0; }
.add-bar-pts { font-size: 1.8rem; font-weight: 800; color: #28a745; line-height: 1; }
.add-bar-tens { font-size: 0.7rem; font-weight: 700; color: #ffc107; }
.add-bar-meta { flex: 1; min-width: 0; }
.add-bar-meta-text { font-size: 0.8rem; color: #6c757d; }
.add-bar-progress { height: 4px; background: #e9ecef; border-radius: 2px; overflow: hidden; margin-top: 4px; }
.add-bar-progress-fill { height: 100%; background: #28a745; border-radius: 2px; transition: width 0.25s ease; }
.btn-add {
background: #007bff; border: 2px solid #007bff; color: white;
padding: 11px 20px; border-radius: 8px; font-size: 0.9rem;
font-weight: 700; cursor: pointer; transition: all 0.15s;
white-space: nowrap; flex-shrink: 0;
}
.btn-add:hover:not(:disabled) { background: #0056b3; border-color: #0056b3; }
.btn-add:disabled { opacity: 0.4; cursor: not-allowed; }
/* ── RIGHT PANEL: scoreboard ── */
.score-panel {
width: 290px;
flex-shrink: 0;
display: flex;
flex-direction: column;
background: #f8f9fa;
overflow: hidden;
}
.score-header {
height: 65px;
padding: 0 16px;
border-bottom: 1px solid #e9ecef;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.score-header h3 { margin: 0; font-size: 1.05rem; font-weight: 700; color: #2c3e50; }
.score-count {
background: #28a745; color: white;
font-size: 0.72rem; font-weight: 700;
padding: 2px 8px; border-radius: 12px;
}
.score-empty {
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center;
color: #adb5bd; gap: 8px; font-size: 0.88rem;
padding: 20px; text-align: center;
}
.score-empty .empty-icon { font-size: 2.4rem; }
.score-list {
flex: 1; overflow-y: auto;
padding: 10px; display: flex; flex-direction: column; gap: 6px;
}
.score-list::-webkit-scrollbar { width: 5px; }
.score-list::-webkit-scrollbar-track { background: transparent; }
.score-list::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 4px; }
.score-row {
display: flex; align-items: center; gap: 7px;
padding: 8px 10px;
background: white; border: 2px solid #e9ecef; border-radius: 10px;
animation: slideIn 0.2s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(10px); }
to { opacity: 1; transform: translateX(0); }
}
.score-row.rank-1 { border-color: #ffc107; background: #fffdf0; }
.score-row.rank-2 { border-color: #adb5bd; background: #f9f9f9; }
.score-row.rank-3 { border-color: #cd7f32; background: #fdf7f2; }
.rank-badge {
width: 26px; height: 26px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 0.72rem; font-weight: 800; flex-shrink: 0;
background: #e9ecef; color: #495057;
}
.score-row.rank-1 .rank-badge { background: #ffc107; color: #7a5800; }
.score-row.rank-2 .rank-badge { background: #adb5bd; color: #343a40; }
.score-row.rank-3 .rank-badge { background: #cd7f32; color: white; }
.score-name {
flex: 1; min-width: 0;
font-size: 0.88rem; font-weight: 600; color: #2c3e50;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
cursor: pointer;
}
.score-name:hover { color: #f97316; }
.score-tens { font-size: 0.7rem; font-weight: 700; color: #ffc107; flex-shrink: 0; white-space: nowrap; }
.score-pts { font-size: 1.05rem; font-weight: 800; color: #28a745; flex-shrink: 0; }
.score-del {
background: none; border: none; cursor: pointer;
color: #ced4da; font-size: 0.95rem; padding: 2px 4px;
border-radius: 4px; transition: color 0.15s, background 0.15s;
flex-shrink: 0; line-height: 1;
}
.score-del:hover { color: #dc3545; background: #fff0f0; }
.score-footer { padding: 10px 10px 0; border-top: 1px solid #e9ecef; flex-shrink: 0; }
.score-print-footer { padding: 8px 10px 10px; flex-shrink: 0; }
.btn-print {
width: 100%; padding: 9px; border-radius: 8px;
border: 2px solid #6f42c1; background: #6f42c1;
color: white; font-size: 0.82rem; font-weight: 600;
cursor: pointer; transition: all 0.15s;
}
.btn-print:hover { background: #59359a; border-color: #59359a; }
.btn-clear-board {
width: 100%; padding: 9px; border-radius: 8px;
border: 2px solid #dc3545; background: #dc3545;
color: white; font-size: 0.82rem; font-weight: 600;
cursor: pointer; transition: all 0.15s;
}
.btn-clear-board:hover { background: #b02a37; border-color: #b02a37; }
</style>
<script src="/static/js/i18n.js"></script>
</head>
<body>
<div class="navbar">
<div class="navbar-title">💥 Nabijanje</div>
<div class="navbar-controls">
<a href="/" class="nav-btn">✕</a>
</div>
</div>
<div class="page">
<div class="main-card">
<!-- ── LEFT: config + stats + utils ── -->
<div class="left-panel">
<div class="left-header">
<h3>⚙️ Nastavitve</h3>
</div>
<div class="left-body">
<div class="setup-field">
<label>Število tarč</label>
<input type="number" id="inputTargets" class="setup-input" min="1" max="100" value="20" oninput="validateSetup()">
</div>
<div class="setup-field">
<label>Strelov na tarčo</label>
<input type="number" id="inputShots" class="setup-input" min="1" max="10" value="2" oninput="validateSetup()">
<span class="setup-hint">Maks. vrednost strela: 10</span>
</div>
<button class="gen-btn" id="startBtn" onclick="startCalculator()">Generiraj</button>
</div>
<div class="left-utils" id="leftUtils">
<button class="util-btn btn-zeros" onclick="fillZeros()">Zapolni 0</button>
<button class="util-btn btn-reset" onclick="resetScores()">Pobriši vse</button>
</div>
<div class="display-section">
<span class="display-section-title">📺 Prikaz na zaslonu</span>
<div class="display-toggle-row">
<span class="display-toggle-label">Omogoči prikaz</span>
<label class="toggle-switch">
<input type="checkbox" id="displayEnabled" onchange="saveDisplaySettings()">
<span class="toggle-track"></span>
</label>
</div>
<div class="display-camera-row">
<label for="displayCamera">Kamera</label>
<select id="displayCamera" class="camera-select" onchange="saveDisplaySettings()" disabled>
<option value="1">Kamera 1</option>
<option value="2">Kamera 2</option>
<option value="3">Kamera 3</option>
<option value="4">Kamera 4</option>
<option value="5">Kamera 5</option>
<option value="6">Kamera 6</option>
</select>
</div>
</div>
</div>
<!-- ── MIDDLE: name bar → targets → add bar ── -->
<div class="middle-panel">
<div class="name-bar">
<span class="name-bar-label">👤</span>
<input type="text" id="inputName" class="name-input" placeholder="Ime in priimek" autocomplete="off">
</div>
<div class="middle-empty" id="middleEmpty">
<span class="empty-icon">🎯</span>
<span>Nastavi konfiguracijo in klikni Generiraj</span>
</div>
<div class="targets-wrapper" id="targetsWrapper">
<div class="targets-grid" id="targetsGrid"></div>
</div>
<div class="add-bar" id="addBar">
<div class="add-bar-score">
<span class="add-bar-pts" id="addBarPts">0</span>
<span class="add-bar-tens" id="addBarTens">0 × 10</span>
</div>
<div class="add-bar-meta">
<div class="add-bar-meta-text" id="addBarMeta"></div>
<div class="add-bar-progress">
<div class="add-bar-progress-fill" id="addBarProgress" style="width:0%"></div>
</div>
</div>
<button class="btn-add" id="addBtn" onclick="addToScoreboard()" disabled>+ Dodaj na lestvico</button>
</div>
</div>
<!-- ── RIGHT: scoreboard ── -->
<div class="score-panel">
<div class="score-header">
<h3>🏆 Lestvica</h3>
<span class="score-count" id="scoreCount">0</span>
</div>
<div class="score-empty" id="scoreEmpty">
<span class="empty-icon">🏅</span>
<span>Dodaj tekmovalce z gumbom<br><em>Dodaj na lestvico</em></span>
</div>
<div class="score-list" id="scoreList" style="display:none;"></div>
<div class="score-footer" id="scoreFooter" style="display:none;">
<button class="btn-clear-board" onclick="clearScoreboard()">Počisti lestvico</button>
</div>
<div class="score-print-footer">
<button class="btn-print" onclick="printLeaderboard()">🖨️ Natisni lestvico</button>
</div>
</div>
</div>
</div>
<!-- Print-only layout — hidden on screen, shown on print -->
<div id="printLayout" style="display:none;">
<div class="pl-header">
<img src="/static/logo.png" class="pl-logo" alt="Logo" onerror="this.style.display='none'">
<div class="pl-titles">
<div class="pl-title">🏆 Lestvica</div>
<div class="pl-subtitle">Rezultati nabijanja</div>
</div>
<div class="pl-meta" id="plMeta"></div>
</div>
<table class="pl-table">
<thead>
<tr>
<th style="width:50px;">Mesto</th>
<th>Tekmovalec</th>
<th class="right" style="width:80px;">Točke</th>
<th class="right" style="width:70px;">10-ke</th>
</tr>
</thead>
<tbody id="plBody"></tbody>
</table>
</div>
<script>
const STORAGE_KEY = 'kalkulator_lestvica';
let numTargets = 20;
let shotsPerTarget = 2;
let scores = {};
let scoreboard = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
function saveScoreboard() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(scoreboard));
fetch('/api/calculator/leaderboard', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(scoreboard)
}).catch(() => {});
}
// ── Display settings ───────────────────────────────────────────────────
function saveDisplaySettings() {
const enabled = document.getElementById('displayEnabled').checked;
const camera = parseInt(document.getElementById('displayCamera').value);
document.getElementById('displayCamera').disabled = !enabled;
fetch('/api/calculator/display', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled, camera })
}).catch(() => {});
}
function loadDisplaySettings() {
fetch('/api/calculator/display')
.then(r => r.json())
.then(data => {
const enabled = !!data.enabled;
document.getElementById('displayEnabled').checked = enabled;
document.getElementById('displayCamera').value = data.camera || 1;
document.getElementById('displayCamera').disabled = !enabled;
}).catch(() => {});
}
function validateSetup() {
const t = parseInt(document.getElementById('inputTargets').value);
const s = parseInt(document.getElementById('inputShots').value);
document.getElementById('startBtn').disabled = !(t >= 1 && t <= 100 && s >= 1 && s <= 10);
}
function startCalculator() {
numTargets = parseInt(document.getElementById('inputTargets').value);
shotsPerTarget = parseInt(document.getElementById('inputShots').value);
scores = {};
for (let t = 1; t <= numTargets; t++) {
scores[t] = {};
for (let s = 1; s <= shotsPerTarget; s++) scores[t][s] = null;
}
document.getElementById('addBarMeta').textContent =
`${numTargets} tarč × ${shotsPerTarget} strel`;
buildGrid();
document.getElementById('middleEmpty').style.display = 'none';
document.getElementById('targetsWrapper').classList.add('visible');
document.getElementById('leftUtils').classList.add('visible');
document.getElementById('addBar').classList.add('visible');
updateStats();
const first = document.querySelector('.shot-input');
if (first) { first.focus(); first.select(); }
}
function buildGrid() {
const grid = document.getElementById('targetsGrid');
let cols;
if (numTargets <= 4) cols = numTargets;
else if (numTargets <= 10) cols = 5;
else if (numTargets <= 20) cols = 5;
else if (numTargets <= 40) cols = 8;
else cols = 10;
grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
let html = '';
for (let t = 1; t <= numTargets; t++) {
html += `<div class="target-group" id="tg-${t}">
<div class="target-num">${t}</div>
<div class="shots-col">`;
for (let s = 1; s <= shotsPerTarget; s++) {
html += `<input type="number" class="shot-input" id="si-${t}-${s}"
min="0" max="10" data-t="${t}" data-s="${s}"
oninput="onShot(${t},${s},this.value)"
onkeydown="onShotKey(event,${t},${s})">`;
}
html += `</div></div>`;
}
grid.innerHTML = html;
}
function onShot(t, s, raw) {
const input = document.getElementById(`si-${t}-${s}`);
let val = null;
if (raw !== '' && raw !== null) {
val = Math.max(0, Math.min(10, parseInt(raw) || 0));
if (input.value !== val.toString()) input.value = val;
}
scores[t][s] = val;
input.classList.remove('valid', 'ten');
if (val !== null) input.classList.add(val === 10 ? 'ten' : 'valid');
updateTargetGroup(t);
updateStats();
}
function onShotKey(e, t, s) {
if (e.key === 'Enter' || e.key === 'Tab') {
e.preventDefault();
const inputs = Array.from(document.querySelectorAll('.shot-input'));
const idx = inputs.indexOf(document.getElementById(`si-${t}-${s}`));
const next = inputs[idx + 1];
if (next) { next.focus(); next.select(); }
}
}
function updateTargetGroup(t) {
const allFilled = Object.values(scores[t]).every(v => v !== null);
document.getElementById(`tg-${t}`).classList.toggle('completed', allFilled);
}
function updateStats() {
let total = 0, tens = 0, filled = 0;
const totalSlots = numTargets * shotsPerTarget;
for (let t = 1; t <= numTargets; t++)
for (let s = 1; s <= shotsPerTarget; s++) {
const v = scores[t][s];
if (v !== null) { total += v; filled++; if (v === 10) tens++; }
}
const pct = totalSlots ? (filled / totalSlots) * 100 : 0;
document.getElementById('addBarPts').textContent = total;
document.getElementById('addBarTens').textContent = `${tens} × 10`;
document.getElementById('addBarProgress').style.width = `${pct}%`;
document.getElementById('addBtn').disabled = (filled < totalSlots);
}
function addToScoreboard() {
const name = document.getElementById('inputName').value.trim() || 'Neznan';
let total = 0, tens = 0;
for (let t = 1; t <= numTargets; t++)
for (let s = 1; s <= shotsPerTarget; s++) {
const v = scores[t][s];
if (v !== null) { total += v; if (v === 10) tens++; }
}
const existing = scoreboard.find(e => e.name.toLowerCase() === name.toLowerCase());
if (existing) {
if (total > existing.score || (total === existing.score && tens > existing.tens)) {
existing.score = total;
existing.tens = tens;
}
} else {
scoreboard.push({ name, score: total, tens, id: Date.now() });
}
scoreboard.sort((a, b) => b.score - a.score || b.tens - a.tens);
saveScoreboard();
renderScoreboard();
document.getElementById('inputName').value = '';
resetScoresQuiet();
document.getElementById('inputName').focus();
}
function removeFromScoreboard(id) {
scoreboard = scoreboard.filter(e => e.id !== id);
saveScoreboard();
renderScoreboard();
}
function clearScoreboard() {
if (!confirm('Počistiti celotno lestvico?')) return;
scoreboard = [];
saveScoreboard();
renderScoreboard();
}
function renderScoreboard() {
const list = document.getElementById('scoreList');
const empty = document.getElementById('scoreEmpty');
const footer = document.getElementById('scoreFooter');
const counter = document.getElementById('scoreCount');
counter.textContent = scoreboard.length;
if (scoreboard.length === 0) {
empty.style.display = '';
list.style.display = 'none';
footer.style.display = 'none';
return;
}
empty.style.display = 'none';
list.style.display = '';
footer.style.display = '';
list.innerHTML = scoreboard.map((entry, idx) => {
const rank = computeRank(idx);
const rankClass = rank <= 3 ? `rank-${rank}` : '';
const medal = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : rank;
return `
<div class="score-row ${rankClass}">
<div class="rank-badge">${medal}</div>
<div class="score-name" title="Klikni za izbiro" onclick="document.getElementById('inputName').value='${entry.name.replace(/'/g, "\\'")}'">${entry.name}</div>
<div class="score-tens">${entry.tens}×10</div>
<div class="score-pts">${entry.score}</div>
<button class="score-del" onclick="removeFromScoreboard(${entry.id})" title="Odstrani">✕</button>
</div>`;
}).join('');
}
function fillZeros() {
for (let t = 1; t <= numTargets; t++) {
for (let s = 1; s <= shotsPerTarget; s++) {
if (scores[t][s] === null) {
scores[t][s] = 0;
const inp = document.getElementById(`si-${t}-${s}`);
inp.value = 0;
inp.classList.remove('ten');
inp.classList.add('valid');
}
}
updateTargetGroup(t);
}
updateStats();
}
function resetScoresQuiet() {
for (let t = 1; t <= numTargets; t++) {
for (let s = 1; s <= shotsPerTarget; s++) {
scores[t][s] = null;
const inp = document.getElementById(`si-${t}-${s}`);
if (inp) { inp.value = ''; inp.classList.remove('valid', 'ten'); }
}
const tg = document.getElementById(`tg-${t}`);
if (tg) tg.classList.remove('completed');
}
updateStats();
}
function resetScores() {
if (!confirm('Pobrisati vse vnose?')) return;
resetScoresQuiet();
}
function computeRank(idx) {
const e = scoreboard[idx];
return scoreboard.filter(o =>
o.score > e.score || (o.score === e.score && o.tens > e.tens)
).length + 1;
}
function printLeaderboard() {
const medals = ['🥇','🥈','🥉'];
const rows = scoreboard.map((e, idx) => {
const rank = computeRank(idx);
const rc = rank <= 3 ? `r${rank}` : '';
const med = rank <= 3 ? medals[rank - 1] : rank;
return `<tr>
<td><span class="pl-rank ${rc}">${med}</span></td>
<td class="pl-name">${e.name}</td>
<td class="right pl-score">${e.score}</td>
<td class="right pl-tens">${e.tens}</td>
</tr>`;
}).join('');
document.getElementById('plBody').innerHTML = rows || '<tr><td colspan="4" style="text-align:center;color:#aaa;padding:20px;">Ni rezultatov</td></tr>';
const now = new Date();
document.getElementById('plMeta').innerHTML =
`${now.toLocaleDateString('sl-SI')}<br>${now.toLocaleTimeString('sl-SI', {hour:'2-digit',minute:'2-digit'})}<br>${scoreboard.length} tekmovalcev`;
window.print();
}
validateSetup();
renderScoreboard();
loadDisplaySettings();
</script>
</body>
</html>