added new simple calculator feature.

This commit is contained in:
2026-06-04 17:11:26 +02:00
parent b71a685c58
commit 3269e05f6a
7 changed files with 7440 additions and 4814 deletions
+503
View File
@@ -0,0 +1,503 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Kalkulator</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>
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
height: 100vh;
background: #f0f2f5;
}
.navbar { flex-shrink: 0; z-index: 100; }
/* ── PAGE WRAPPER ── */
.page {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
overflow: hidden;
min-height: 0;
}
/* ── MAIN CARD ── */
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
width: 100%;
max-width: 1100px;
max-height: 100%;
display: flex;
flex-direction: row;
overflow: hidden;
}
/* ── LEFT PANEL: config ── */
.left-panel {
width: 260px;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid #e9ecef;
background: #f8f9fa;
}
.left-header {
padding: 22px 24px 16px;
border-bottom: 1px solid #e9ecef;
}
.left-header h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 700;
color: #2c3e50;
}
.left-body {
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 20px;
flex: 1;
}
.setup-field {
display: flex;
flex-direction: column;
gap: 8px;
}
.setup-field label {
font-size: 0.8rem;
font-weight: 700;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.setup-input {
padding: 13px 14px;
border: 2px solid #dee2e6;
border-radius: 10px;
font-size: 1.15rem;
font-weight: 600;
color: #2c3e50;
text-align: center;
background: white;
transition: border-color 0.2s;
outline: none;
width: 100%;
box-sizing: border-box;
}
.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: 10px;
padding: 14px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
width: 100%;
}
.gen-btn:hover:not(:disabled) { background: #1e7e34; transform: translateY(-1px); }
.gen-btn:disabled { background: #ced4da; cursor: not-allowed; }
/* stats + actions below the fold */
.left-stats {
display: none;
flex-direction: column;
gap: 14px;
padding: 20px 24px;
border-top: 1px solid #e9ecef;
}
.left-stats.visible { display: flex; }
.stat-row {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.stat-label {
font-size: 0.8rem;
font-weight: 600;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.stat-value {
font-size: 1.8rem;
font-weight: 700;
line-height: 1;
}
#totalScore { color: #28a745; }
#tensCount { color: #ffc107; }
.progress-bar-wrap {
height: 6px;
background: #e9ecef;
border-radius: 3px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: #28a745;
border-radius: 3px;
transition: width 0.25s ease;
}
.left-actions {
padding: 0 24px 24px;
display: none;
flex-direction: column;
gap: 10px;
}
.left-actions.visible { display: flex; }
.action-btn {
padding: 11px 12px;
border-radius: 8px;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
width: 100%;
text-align: center;
}
.btn-zeros {
background: white;
border: 2px solid #6c757d;
color: #6c757d;
}
.btn-zeros:hover { background: #6c757d; color: white; }
.btn-reset {
background: white;
border: 2px solid #dc3545;
color: #dc3545;
}
.btn-reset:hover { background: #dc3545; color: white; }
/* ── RIGHT PANEL: grid ── */
.right-panel {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
}
.right-empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #adb5bd;
gap: 10px;
font-size: 1rem;
}
.right-empty .empty-icon { font-size: 3rem; }
.targets-wrapper {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 18px 20px 20px;
min-height: 0;
display: none;
}
.targets-wrapper.visible { display: block; }
.targets-wrapper::-webkit-scrollbar { width: 7px; }
.targets-wrapper::-webkit-scrollbar-track { background: transparent; }
.targets-wrapper::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 4px; }
.targets-grid {
display: grid;
gap: 10px;
}
.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.75rem;
font-weight: 700;
color: #495057;
text-align: center;
padding: 5px 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: 5px;
padding: 6px;
}
.shot-input {
width: 100%;
padding: 6px 2px;
border: 1.5px solid #dee2e6;
border-radius: 4px;
text-align: center;
font-size: 1rem;
font-weight: 700;
color: #333;
background: white;
min-height: 32px;
box-sizing: border-box;
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: #ffc107; }
</style>
<script src="/static/js/i18n.js"></script>
</head>
<body>
<div class="navbar">
<div class="navbar-title">🎯 Kalkulator</div>
<div class="navbar-controls">
<a href="/" class="nav-btn">✕</a>
</div>
</div>
<div class="page">
<div class="main-card">
<!-- LEFT: config + stats -->
<div class="left-panel">
<div class="left-header">
<h3>🎯 Konfiguracija</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-stats" id="leftStats">
<div class="stat-row">
<span class="stat-label">Točke</span>
<span class="stat-value" id="totalScore">0</span>
</div>
<div class="stat-row">
<span class="stat-label">10-ke</span>
<span class="stat-value" id="tensCount">0</span>
</div>
<div class="progress-bar-wrap">
<div class="progress-bar-fill" id="progressBar" style="width:0%"></div>
</div>
<span class="setup-hint" id="calcMeta" style="text-align:center;"></span>
</div>
<div class="left-actions" id="leftActions">
<button class="action-btn btn-zeros" onclick="fillZeros()">Zapolni 0</button>
<button class="action-btn btn-reset" onclick="resetScores()">Pobriši vse</button>
</div>
</div>
<!-- RIGHT: grid -->
<div class="right-panel">
<div class="right-empty" id="rightEmpty">
<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>
</div>
</div>
<script>
let numTargets = 20;
let shotsPerTarget = 2;
let scores = {};
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('calcMeta').textContent =
`${numTargets} × ${shotsPerTarget} = maks. ${numTargets * shotsPerTarget * 10} točk`;
buildGrid();
document.getElementById('rightEmpty').style.display = 'none';
document.getElementById('targetsWrapper').classList.add('visible');
document.getElementById('leftStats').classList.add('visible');
document.getElementById('leftActions').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++; }
}
document.getElementById('totalScore').textContent = total;
document.getElementById('tensCount').textContent = tens;
document.getElementById('progressBar').style.width = `${(filled / totalSlots) * 100}%`;
}
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 resetScores() {
if (!confirm('Pobrisati vse vnose?')) return;
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}`);
inp.value = '';
inp.classList.remove('valid', 'ten');
}
updateTargetGroup(t);
}
updateStats();
}
validateSetup();
</script>
</body>
</html>