Enhance print layouts with branded headers and fix navigation consistency

- Replace plain print headers with full branded headers including logo
  - Add dynamic tournament-type styling (🎯 4-target,  20-target, 💪 40-target)
  - Remove border lines and optimize spacing for clean print appearance
  - Fix emoji positioning in league championship headers
  - Standardize navigation with proper active button indicators
  - Add missing translation keys for calculator instructions
  - Update print media queries for professional document output

  Print improvements:
  - Logo and branding now appear on printed results
  - Consistent 20px spacing between header and table
  - Clean white background with subtle borders
  - Optimized typography for print readability

  Navigation fixes:
  - Added active button highlighting across all PC pages
  - Consistent navigation order: Dashboard → Tournament → Player Analysis → Archive → Draft →
  Calculator
  - Fixed draft page active indicator

  🤖 Generated with Claude Code

  Co-Authored-By: Claude <noreply@anthropic.com>

  This commit message covers all the major improvements we made:
  - Print layout enhancements with branded headers
  - Navigation standardization and active indicators
  - Translation fixes
  - Visual styling improvements
  - Professional document output optimization
This commit is contained in:
2025-09-20 20:03:44 +02:00
parent 33758e7340
commit c61c1448e4
62 changed files with 45554 additions and 11528 deletions
+279 -114
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Results Calculator</title>
<title data-i18n="scoring.results_calculator">Results Calculator</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
@@ -62,28 +62,17 @@
color: #007bff;
}
.nav-btn.primary {
.nav-btn.active {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.primary:hover {
.nav-btn.active:hover {
background: #0056b3;
color: white;
}
.nav-btn.success {
background: #28a745;
border-color: #1e7e34;
color: white;
}
.nav-btn.success:hover {
background: #1e7e34;
color: white;
}
.nav-btn.danger {
background: #dc3545;
border-color: #c82333;
@@ -102,25 +91,75 @@
}
.header-section {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 25px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 20px;
margin-bottom: 30px;
text-align: center;
color: white;
position: relative;
overflow: hidden;
}
/* Dynamic header colors based on tournament type */
.header-section.tournament-4_targets {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.header-section.tournament-20_targets {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
}
.header-section.tournament-40_targets {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.header-section.tournament-default {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
}
.header-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
pointer-events: none;
}
.header-section * {
position: relative;
z-index: 2;
}
.header-logo {
height: 50px;
max-width: 140px;
object-fit: contain;
margin-bottom: 15px;
filter: brightness(1.2) contrast(1.1);
background-color: white;
padding: 8px;
border-radius: 6px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.header-title {
font-size: 2rem;
font-weight: bold;
color: #333;
margin-bottom: 10px;
font-size: 1.6rem;
font-weight: 700;
color: rgb(255, 255, 255);
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.header-subtitle {
color: #666;
font-size: 1.1rem;
margin-bottom: 20px;
font-size: 1rem;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 15px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.progress-info {
@@ -128,7 +167,7 @@
justify-content: center;
gap: 30px;
flex-wrap: wrap;
margin-bottom: 20px;
margin-bottom: 15px;
}
.progress-item {
@@ -136,15 +175,19 @@
}
.progress-number {
font-size: 1.5rem;
font-weight: bold;
color: #007bff;
font-size: 1.4rem;
font-weight: 700;
color: #ffffff;
display: block;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.progress-label {
font-size: 0.9rem;
color: #666;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.9);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.global-actions {
@@ -272,6 +315,19 @@
gap: 15px;
}
.score-display {
text-align: right;
display: flex;
flex-direction: column;
gap: 5px;
}
.score-row {
display: flex;
align-items: center;
gap: 15px;
}
.total-score {
text-align: right;
}
@@ -288,6 +344,22 @@
text-transform: uppercase;
}
.tens-count {
text-align: right;
}
.tens-number {
font-size: 1.4rem;
font-weight: bold;
color: #ffc107;
}
.tens-label {
font-size: 0.8rem;
color: #666;
text-transform: uppercase;
}
.completion-badge {
padding: 6px 15px;
border-radius: 15px;
@@ -389,6 +461,23 @@
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.score-row {
flex-direction: column;
gap: 8px;
}
.participant-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.participant-status {
width: 100%;
justify-content: space-between;
flex-wrap: wrap;
}
}
.target-group {
@@ -447,6 +536,12 @@
background: #f8fff9;
}
.shot-input.ten {
border-color: #ffc107;
background: #fff9e6;
font-weight: bold;
}
.participant-actions {
display: flex;
justify-content: space-between;
@@ -548,33 +643,6 @@
margin: 15px 0;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.participant-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.participant-status {
width: 100%;
justify-content: space-between;
}
.progress-info {
gap: 20px;
}
.global-actions {
flex-direction: column;
align-items: center;
}
.global-btn {
min-width: 200px;
}
}
/* Loading and saving indicators */
.saving {
opacity: 0.7;
@@ -603,44 +671,61 @@
</head>
<body>
<div class="navbar">
<div class="navbar-title">🎯 Results Calculator</div>
<div class="navbar-title">🎯 <span data-i18n="scoring.results_calculator">Results Calculator</span></div>
<div class="navbar-controls">
<a href="/" class="nav-btn">← Dashboard</a>
<a href="/tournament/draft" class="nav-btn">📋 Draft</a>
<a href="/" class="nav-btn">📺 <span data-i18n="navigation.dashboard">Dashboard</span></a>
<a href="/tournament" class="nav-btn">🏆 <span data-i18n="navigation.tournament">Tournament</span></a>
<a href="/archive/player-analysis" class="nav-btn">👤 <span data-i18n="players.player_analysis">Player Analysis</span></a>
<a href="/archive" class="nav-btn">📚 <span data-i18n="navigation.archive">Archive</span></a>
<a href="/tournament/draft" class="nav-btn">📋 <span data-i18n="tournament.view_draft">Draft</span></a>
<a href="/results/calculator" class="nav-btn active">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
</div>
</div>
<div class="container">
<div class="header-section">
<div class="header-title">🎯 Tournament Scoring</div>
<div class="header-section tournament-{{ results.tournament_type if results and results.tournament_type else 'default' }}">
<img src="/static/logo.png" alt="Logo" class="header-logo" onerror="this.style.display='none'" />
<div class="header-title">
{% if results and results.tournament_type == '4_targets' %}
🎯 <span data-i18n="scoring.tournament_scoring">Tournament Scoring</span>
{% elif results and results.tournament_type == '40_targets' %}
💪 <span data-i18n="scoring.tournament_scoring">Tournament Scoring</span>
{% else %}
<span data-i18n="scoring.tournament_scoring">Tournament Scoring</span>
{% endif %}
</div>
<div class="header-subtitle" id="tournamentSubtitle">
{% if results.tournament_type == '40_targets' %}
Enter scores for each participant (40 targets, 2 shots each). Score 0 = miss.
<span data-i18n="scoring.enter_scores_40_targets">Enter scores for each participant (40 targets, 2 shots each). Score 0 = miss.</span>
{% elif results.tournament_type == '4_targets' %}
Enter scores for each participant (4 targets, 5 shots each). Score 0 = miss.
<span data-i18n="scoring.enter_scores_4_targets">Enter scores for each participant (4 targets, 5 shots each). Score 0 = miss.</span>
{% else %}
Enter scores for each participant (20 targets, 2 shots each). Score 0 = miss.
<span data-i18n="scoring.enter_scores_20_targets">Enter scores for each participant (20 targets, 2 shots each). Score 0 = miss.</span>
{% endif %}
</div>
<div class="progress-info">
<div class="progress-item">
<div class="progress-number" id="completedCount">0</div>
<div class="progress-label">Completed</div>
<div class="progress-label" data-i18n="scoring.completed">Zaključeno</div>
</div>
<div class="progress-item">
<div class="progress-number" id="totalParticipants">{{ results.participants|length }}</div>
<div class="progress-label">Total</div>
<div class="progress-label" data-i18n="scoring.total">Skupaj</div>
</div>
<div class="progress-item">
<div class="progress-number" id="totalShots">0</div>
<div class="progress-label">Total Shots</div>
<div class="progress-label" data-i18n="scoring.total_shots">Skupaj Strelov</div>
</div>
<div class="progress-item">
<div class="progress-number" id="totalTens">0</div>
<div class="progress-label" data-i18n="results.tens">Tens</div>
</div>
</div>
<div class="global-actions">
<button class="global-btn fill-all-btn" onclick="fillAllPlayersRandom()">
🎲 Fill All Players Random
🎲 <span data-i18n="scoring.fill_all_players_random">Napolni Vse Igralce Naključno</span>
</button>
</div>
</div>
@@ -654,15 +739,23 @@
<div class="participant-id">ID: {{ player_id }}</div>
</div>
<div class="participant-status">
<div class="total-score">
<div class="score-number" id="total-{{ player_id }}">{{ participant.total_score }}</div>
<div class="score-label">Points</div>
<div class="score-display">
<div class="score-row">
<div class="total-score">
<div class="score-number" id="total-{{ player_id }}">{{ participant.total_score }}</div>
<div class="score-label" data-i18n="scoring.points">Točke</div>
</div>
<div class="tens-count">
<div class="tens-number" id="tens-{{ player_id }}">0</div>
<div class="tens-label">10s</div>
</div>
</div>
</div>
<div class="completion-badge" id="badge-{{ player_id }}">
{% if participant.completed %}
<span class="badge-completed">Completed</span>
<span class="badge-completed" data-i18n="scoring.completed">Zaključeno</span>
{% else %}
<span class="badge-not-started">Not Started</span>
<span class="badge-not-started" data-i18n="scoring.not_started">Ni Začeto</span>
{% endif %}
</div>
<div class="collapse-icon"></div>
@@ -677,10 +770,10 @@
<div class="participant-actions">
<button class="action-btn clear-btn" onclick="clearParticipant({{ player_id }})">
🗑️ Clear All
🗑️ <span data-i18n="scoring.clear">Počisti</span>
</button>
<button class="action-btn save-btn" onclick="saveParticipant({{ player_id }})">
💾 Save Progress
💾 <span data-i18n="scoring.save">Shrani</span>
</button>
</div>
</div>
@@ -690,14 +783,11 @@
</div>
<div class="finish-section">
<div class="finish-title">🏁 Finish Tournament</div>
<div class="finish-description">
Complete scoring for all participants and finalize tournament results.
</div>
<div class="finish-title">🏁 <span data-i18n="scoring.finish_tournament">Zaključi Turnir</span></div>
<div class="warning-message" id="finishWarning" style="display: none;">
<strong>Warning:</strong> Not all participants have completed scores. Please ensure all scoring is complete before finishing.
<strong data-i18n="scoring.warning">Opozorilo:</strong> <span data-i18n="scoring.finish_warning">Niso vsi udeleženci zaključili s točkovanjem. Prosimo, da poskrbite, da je vse točkovanje zaključeno pred zaključkom.</span>
</div>
<button class="finish-btn" id="finishBtn" onclick="finishTournament()">
<button class="finish-btn" id="finishBtn" onclick="finishTournament()" data-i18n="scoring.finish_tournament_button">🏁 Finish Tournament & Show Results
🏆 Finish Tournament & Show Results
</button>
</div>
@@ -723,7 +813,51 @@
shotsPerTarget = 2;
}
console.log(`Tournament config: ${numTargets} targets, ${shotsPerTarget} shots each`);
// Calculate 10s count for a player
function calculateTensCount(playerId) {
const playerIdStr = playerId.toString();
const targets = results.participants[playerIdStr].targets;
let tensCount = 0;
for (let target in targets) {
const targetData = targets[target];
for (let i = 1; i <= shotsPerTarget; i++) {
const shotValue = targetData[`shot${i}`];
if (shotValue === 10) {
tensCount++;
}
}
}
return tensCount;
}
// Update participant's 10s display
function updateParticipantTens(playerId) {
const tensCount = calculateTensCount(playerId);
const tensElement = document.getElementById(`tens-${playerId}`);
if (tensElement) {
tensElement.textContent = tensCount;
// Add visual feedback
tensElement.style.color = '#ffc107';
setTimeout(() => {
tensElement.style.color = '';
}, 300);
}
}
// Update overall 10s count
function updateOverallTens() {
let totalTens = 0;
for (let playerId in results.participants) {
totalTens += calculateTensCount(parseInt(playerId));
}
document.getElementById('totalTens').textContent = totalTens;
}
// Debounce function for auto-saving
function debounce(func, wait, immediate) {
@@ -761,7 +895,7 @@
html += `
<input type="number"
class="shot-input"
class="shot-input ${value === 10 ? 'ten' : ''}"
id="shot${shot}-${playerId}-${i}"
data-player="${playerId}"
data-target="${i}"
@@ -833,16 +967,23 @@
// Update target group styling
updateTargetGroupStyling(playerId, targetNum);
// Recalculate total
// Recalculate totals
updateParticipantTotal(playerId);
updateParticipantTens(playerId);
updateParticipantStatus(playerId);
updateOverallProgress();
updateOverallTens();
// Visual feedback for input
if (numValue !== null) {
input.classList.add('valid');
} else {
input.classList.remove('valid');
// Visual feedback for input - highlight 10s
if (input) {
input.classList.remove('valid', 'ten');
if (numValue !== null) {
if (numValue === 10) {
input.classList.add('ten');
} else {
input.classList.add('valid');
}
}
}
// Auto-save with debounce
@@ -944,12 +1085,12 @@
card.classList.remove('completed', 'in-progress');
if (isCompleted) {
card.classList.add('completed');
badge.innerHTML = '<span class="badge-completed">Completed</span>';
badge.innerHTML = `<span class="badge-completed">${t('scoring.completed')}</span>`;
} else if (isInProgress) {
card.classList.add('in-progress');
badge.innerHTML = '<span class="badge-in-progress">In Progress</span>';
badge.innerHTML = `<span class="badge-in-progress">${t('scoring.in_progress')}</span>`;
} else {
badge.innerHTML = '<span class="badge-not-started">Not Started</span>';
badge.innerHTML = `<span class="badge-not-started">${t('scoring.not_started')}</span>`;
}
}
@@ -1012,12 +1153,12 @@
if (!silent) {
// Show success feedback
const saveBtn = card.querySelector('.save-btn');
const originalText = saveBtn.textContent;
saveBtn.textContent = '✅ Saved';
const originalText = saveBtn.innerHTML;
saveBtn.innerHTML = `${t('scoring.all_saved')}`;
saveBtn.style.background = '#28a745';
setTimeout(() => {
saveBtn.textContent = originalText;
saveBtn.innerHTML = originalText;
saveBtn.style.background = '';
}, 2000);
}
@@ -1037,19 +1178,19 @@
async function saveAllPlayers() {
const saveAllBtn = document.querySelector('.save-all-btn');
const originalText = saveAllBtn.textContent;
saveAllBtn.textContent = '💾 Saving All...';
saveAllBtn.textContent = `💾 ${t('scoring.saving_all')}`;
saveAllBtn.disabled = true;
try {
const playerIds = Object.keys(results.participants);
for (const playerId of playerIds) {
await saveParticipant(parseInt(playerId), true);
}
// Show success feedback
saveAllBtn.textContent = '✅ All Saved!';
saveAllBtn.textContent = `${t('scoring.all_saved')}`;
saveAllBtn.style.background = '#28a745';
setTimeout(() => {
saveAllBtn.textContent = originalText;
saveAllBtn.style.background = '';
@@ -1075,7 +1216,10 @@
for (let i = 1; i <= numTargets; i++) {
for (let shot = 1; shot <= shotsPerTarget; shot++) {
const shotInput = document.getElementById(`shot${shot}-${playerId}-${i}`);
if (shotInput) shotInput.value = '';
if (shotInput) {
shotInput.value = '';
shotInput.classList.remove('valid', 'ten');
}
}
// Update data
@@ -1090,8 +1234,10 @@
}
updateParticipantTotal(playerId);
updateParticipantTens(playerId);
updateParticipantStatus(playerId);
updateOverallProgress();
updateOverallTens();
// Save changes
saveParticipant(playerId);
@@ -1117,7 +1263,15 @@
const randomScore = Math.floor(Math.random() * 11);
const shotInput = document.getElementById(`shot${shot}-${playerId}-${i}`);
if (shotInput) shotInput.value = randomScore;
if (shotInput) {
shotInput.value = randomScore;
shotInput.classList.remove('valid', 'ten');
if (randomScore === 10) {
shotInput.classList.add('ten');
} else {
shotInput.classList.add('valid');
}
}
target[`shot${shot}`] = randomScore;
}
@@ -1129,8 +1283,10 @@
}
updateParticipantTotal(playerId);
updateParticipantTens(playerId);
updateParticipantStatus(playerId);
updateOverallProgress();
updateOverallTens();
}
async function fillAllPlayersRandom() {
@@ -1140,7 +1296,7 @@
const fillAllBtn = document.querySelector('.fill-all-btn');
const originalText = fillAllBtn.textContent;
fillAllBtn.textContent = '🎲 Filling All...';
fillAllBtn.textContent = `🎲 ${t('scoring.filling_all')}`;
fillAllBtn.disabled = true;
try {
@@ -1156,7 +1312,7 @@
}
// Show success feedback
fillAllBtn.textContent = '✅ All Filled!';
fillAllBtn.textContent = `${t('scoring.all_filled')}`;
fillAllBtn.style.background = '#28a745';
// Show notification
@@ -1173,8 +1329,6 @@
font-weight: bold;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
`;
notification.textContent = '🎯 All players filled with random scores! Tournament ready to finish.';
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
@@ -1243,9 +1397,7 @@
// Initialize
document.addEventListener('DOMContentLoaded', function() {
console.log('Initial results structure:', results);
console.log(`Tournament type: ${tournamentType}, Targets: ${numTargets}, Shots per target: ${shotsPerTarget}`);
// Initialize targets for all players
Object.keys(results.participants).forEach(playerId => {
initializeTargetsForPlayer(parseInt(playerId));
@@ -1255,9 +1407,11 @@
updateTargetGroupStyling(parseInt(playerId), i);
}
updateParticipantStatus(parseInt(playerId));
updateParticipantTens(parseInt(playerId));
});
updateOverallProgress();
updateOverallTens();
// Add input validation and navigation
document.addEventListener('input', function(e) {
@@ -1283,9 +1437,20 @@
}
}
});
console.log('🎯 Results Calculator initialized');
console.log('📊 Participants:', Object.keys(results.participants).length);
});
</script>
<!-- Internationalization Support -->
<script src="/static/js/i18n.js"></script>
<script>
// Initialize translations with server data
if (typeof {{ translations|tojson }} !== 'undefined') {
currentTranslations = {{ translations|tojson }};
currentLanguage = '{{ current_language }}';
}
// Apply translations when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
</script>
</body>