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
+43 -19
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tournament Draft</title>
<title data-i18n="draft.tournament_draft">Tournament Draft</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
@@ -76,6 +76,17 @@
color: white;
}
.nav-btn.active {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.active:hover {
background: #0056b3;
color: white;
}
.main-container {
height: calc(100vh - 70px);
display: flex;
@@ -353,32 +364,36 @@
</head>
<body>
<div class="navbar">
<div class="navbar-title">🏆 Tournament Draft</div>
<div class="navbar-title">🏆 <span data-i18n="draft.tournament_draft">Tournament Draft</span></div>
<div class="navbar-controls">
<a href="/" class="nav-btn">← Dashboard</a>
<a href="/tournament" class="nav-btn">⚙️ Manage</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 active">📋 <span data-i18n="tournament.view_draft">Draft</span></a>
<a href="/results/calculator" class="nav-btn">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
</div>
</div>
<div class="main-container">
{% if tournament %}
<div class="tournament-header">
<div class="tournament-title">🎯 Shooting Tournament</div>
<div class="tournament-title">🎯 <span data-i18n="draft.shooting_tournament">Shooting Tournament</span></div>
<div class="tournament-stats">
{{ tournament.total_players }} players • {{ tournament.total_rounds }} rounds
{{ tournament.total_players }} <span data-i18n="draft.players">players</span> • {{ tournament.total_rounds }} <span data-i18n="draft.rounds">rounds</span>
{% if tournament.current_round %}
• Currently on Round {{ tournament.current_round }}
<span data-i18n="draft.currently_on_round">Currently on Round</span> {{ tournament.current_round }}
{% endif %}
</div>
{% if tournament.current_round %}
<div class="tournament-controls">
<button class="round-nav-btn" id="prevRoundBtn" onclick="changeRound(-1)" title="Previous Round">
Previous
<span data-i18n="draft.previous">Previous</span>
</button>
<span class="current-round-display">Round {{ tournament.current_round }} of {{ tournament.total_rounds }}</span>
<span class="current-round-display"><span data-i18n="draft.round">Round</span> {{ tournament.current_round }} <span data-i18n="draft.of">of</span> {{ tournament.total_rounds }}</span>
<button class="round-nav-btn" id="nextRoundBtn" onclick="changeRound(1)" title="Next Round">
Next
<span data-i18n="draft.next">Next</span>
</button>
</div>
{% endif %}
@@ -390,13 +405,13 @@
{% set is_completed = tournament.current_round > round.round_number %}
<div class="round-row {{ 'current' if is_current else ('completed' if is_completed else 'waiting') }}">
<div class="round-header">
<div class="round-title">Round {{ round.round_number }}</div>
<div class="round-title"><span data-i18n="draft.round">Round</span> {{ round.round_number }}</div>
{% if is_current %}
<div class="round-badge current-badge">Current</div>
<div class="round-badge current-badge" data-i18n="draft.current">Current</div>
{% elif is_completed %}
<div class="round-badge completed-badge">Done</div>
<div class="round-badge completed-badge" data-i18n="draft.done">Done</div>
{% else %}
<div class="round-badge waiting-badge">Wait</div>
<div class="round-badge waiting-badge" data-i18n="draft.wait">Wait</div>
{% endif %}
</div>
@@ -409,7 +424,7 @@
<div class="player-name">{{ player.name }}</div>
<div class="player-id">ID: {{ player.id }}</div>
{% else %}
<div class="player-name">Empty</div>
<div class="player-name" data-i18n="draft.empty">Empty</div>
{% endif %}
</div>
{% endfor %}
@@ -420,16 +435,16 @@
{% else %}
<div class="no-tournament">
<h2>No Active Tournament</h2>
<p>Go to Tournament Management to set up players and start a tournament.</p>
<a href="/tournament" class="nav-btn primary">🏆 Set Up Tournament</a>
<h2 data-i18n="draft.no_active_tournament">No Active Tournament</h2>
<p data-i18n="draft.setup_tournament_message">Go to Tournament Management to set up players and start a tournament.</p>
<a href="/tournament" class="nav-btn primary">🏆 <span data-i18n="draft.set_up_tournament">Set Up Tournament</span></a>
</div>
{% endif %}
</div>
<!-- Auto-refresh indicator -->
<div class="refresh-indicator" id="refreshIndicator">
🔄 Updating...
🔄 <span data-i18n="draft.updating">Updating...</span>
</div>
<script>
@@ -543,5 +558,14 @@
console.log(`📊 Displaying ${totalRounds} rounds in vertical layout`);
});
</script>
<!-- Include i18n support -->
<script src="/static/js/i18n.js"></script>
<script>
// Initialize language selector and i18n
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
</script>
</body>
</html>
+14 -2
View File
@@ -385,7 +385,7 @@
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
</svg>
</button>
<a href="/" class="control-btn close-btn" title="Close Fullscreen"></a>
<a href="#" onclick="goBack()" class="control-btn close-btn" title="Close Fullscreen"></a>
</div>
</div>
@@ -666,7 +666,7 @@
document.addEventListener('keydown', function(event) {
switch(event.key) {
case 'Escape':
window.location.href = '/';
goBack();
break;
case 'f':
case 'F':
@@ -761,6 +761,18 @@
document.getElementById('fullscreenStream').addEventListener('dragstart', function(e) {
e.preventDefault();
});
function goBack() {
// Check if user came from mobile by looking at referrer or user agent
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const referrer = document.referrer;
if (isMobile || referrer.includes('/mobile/')) {
window.location.href = '/mobile/streams';
} else {
window.location.href = '/';
}
}
</script>
</body>
</html>
+786 -329
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-769
View File
@@ -1,769 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>📱 Tournament Draft</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui, viewport-fit=cover">
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: #f5f5f5;
font-family: Arial, sans-serif;
min-height: 100vh;
min-height: 100dvh;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.mobile-navbar {
background: white;
color: black;
padding: 15px 20px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.navbar-title {
font-size: 1.4rem;
font-weight: bold;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.nav-btn {
background: #f8f9fa;
border: 2px solid #e9ecef;
cursor: pointer;
padding: 8px 12px;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #333;
text-decoration: none;
font-weight: bold;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 4px;
min-width: 44px;
justify-content: center;
}
.nav-btn:hover {
background: #e9ecef;
border-color: #007bff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
color: #007bff;
}
.nav-btn.active {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.active:hover {
background: #0056b3;
color: white;
}
.nav-btn.disabled {
opacity: 0.5;
pointer-events: none;
background: #f1f3f4;
border-color: #dadce0;
}
.container {
padding: 20px;
max-width: 100%;
}
.tournament-header {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 25px 20px;
margin-bottom: 25px;
text-align: center;
}
.tournament-title {
font-size: 1.8rem;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.tournament-stats {
color: #666;
font-size: 1rem;
margin-bottom: 15px;
}
.view-only-badge {
background: #e3f2fd;
border: 2px solid #2196f3;
color: #1976d2;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: bold;
display: inline-block;
}
.rounds-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.round-card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: all 0.2s ease;
}
.round-card.current {
border-left: 5px solid #007bff;
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.2);
}
.round-card.completed {
border-left: 5px solid #28a745;
opacity: 0.9;
}
.round-card.waiting {
opacity: 0.7;
}
.round-header {
padding: 20px 20px;
display: flex;
align-items: center;
justify-content: space-between;
background: #f8f9fa;
}
.round-card.current .round-header {
background: #f0f8ff;
}
.round-card.completed .round-header {
background: #f8fff9;
}
.round-title {
font-size: 1.3rem;
font-weight: bold;
color: #333;
}
.round-badge {
padding: 8px 15px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: bold;
text-transform: uppercase;
}
.current-badge {
background: #007bff;
color: white;
}
.completed-badge {
background: #28a745;
color: white;
}
.waiting-badge {
background: #6c757d;
color: white;
}
.players-grid {
padding: 20px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.position-card {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 12px;
padding: 18px 15px;
text-align: center;
min-height: 90px;
display: flex;
flex-direction: column;
justify-content: center;
transition: all 0.2s ease;
}
.position-card.filled {
border-color: #28a745;
background: #f8fff9;
}
.position-card.empty {
border-color: #6c757d;
background: #f1f3f4;
opacity: 0.6;
}
.position-number {
font-size: 1.3rem;
font-weight: bold;
color: #007bff;
margin-bottom: 8px;
}
.position-card.empty .position-number {
color: #6c757d;
}
.player-name {
font-size: 1.1rem;
font-weight: bold;
color: #333;
line-height: 1.2;
word-break: keep-all;
overflow-wrap: break-word;
hyphens: none;
}
.position-card.empty .player-name {
color: #6c757d;
font-style: italic;
font-weight: normal;
}
.player-id {
background: #28a745;
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
margin-top: 8px;
display: inline-block;
}
/* Mobile landscape optimization */
@media (orientation: landscape) and (max-height: 500px) {
.mobile-navbar {
padding: 10px 15px;
}
.navbar-title {
font-size: 1.2rem;
}
.container {
padding: 15px;
}
.tournament-header {
padding: 20px 15px;
margin-bottom: 20px;
}
.tournament-title {
font-size: 1.5rem;
}
.tournament-stats {
font-size: 1rem;
}
.players-grid {
grid-template-columns: repeat(3, 1fr);
gap: 12px;
padding: 15px;
}
.position-card {
padding: 12px 10px;
min-height: 70px;
}
.position-number {
font-size: 1.1rem;
margin-bottom: 5px;
}
.player-name {
font-size: 0.95rem;
}
.player-id {
font-size: 0.7rem;
padding: 3px 8px;
margin-top: 5px;
}
.navbar-controls {
gap: 6px;
}
.nav-btn {
padding: 6px 10px;
font-size: 0.75rem;
}
}
@media (max-width: 400px) {
.container {
padding: 15px;
}
.tournament-header {
padding: 20px 15px;
}
.tournament-title {
font-size: 1.6rem;
}
.tournament-stats {
font-size: 1rem;
}
.round-header {
padding: 15px;
}
.round-title {
font-size: 1.2rem;
}
.players-grid {
padding: 15px;
gap: 12px;
}
.position-card {
padding: 15px 12px;
min-height: 85px;
}
.position-number {
font-size: 1.2rem;
}
.player-name {
font-size: 1rem;
}
.player-id {
font-size: 0.75rem;
padding: 3px 8px;
}
.navbar-controls {
gap: 6px;
}
.nav-btn {
padding: 6px 8px;
font-size: 0.75rem;
}
}
.round-change-indicator {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(40, 167, 69, 0.9);
color: white;
padding: 10px 15px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: bold;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 999;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
}
.round-change-indicator.show {
opacity: 1;
}
.tournament-summary {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-top: 25px;
}
.summary-title {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 15px;
text-align: center;
}
.summary-grid {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
text-align: left;
}
.summary-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.summary-item:last-child {
border-bottom: none;
}
.summary-label {
font-weight: bold;
color: #333;
}
.summary-value {
color: #666;
}
.smart-refresh-badge {
background: #28a745;
color: white;
padding: 4px 8px;
border-radius: 10px;
font-size: 0.7rem;
font-weight: bold;
margin-left: 10px;
display: inline-block;
}
/* Tournament status indicator */
.tournament-indicator {
display: inline-flex;
align-items: center;
background: #007bff;
color: white;
padding: 2px 6px;
border-radius: 8px;
font-size: 0.7rem;
font-weight: bold;
margin-left: 5px;
animation: pulse 2s infinite;
}
/* Results available indicator */
.results-indicator {
display: inline-flex;
align-items: center;
background: #28a745;
color: white;
padding: 2px 6px;
border-radius: 8px;
font-size: 0.7rem;
font-weight: bold;
margin-left: 5px;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
/* No tournament message */
.no-tournament-message {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 40px 20px;
text-align: center;
color: #666;
}
.no-tournament-message h2 {
color: #333;
margin-bottom: 15px;
font-size: 1.5rem;
}
.no-tournament-message p {
margin-bottom: 20px;
font-size: 1rem;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="mobile-navbar">
<div class="navbar-title">
📋 Draft
{% if tournament %}
{% if tournament.current_round %}
<span class="tournament-indicator">R{{ tournament.current_round }}</span>
{% endif %}
{% endif %}
</div>
<div class="navbar-controls">
<!-- Always show all 3 navigation buttons -->
<a href="/mobile/streams" class="nav-btn">📷</a>
<a href="/mobile/draft" class="nav-btn active">📋</a>
<a href="/mobile/results" class="nav-btn">🏆</a>
</div>
</div>
<div class="container">
{% if tournament %}
<div class="tournament-header">
<div class="tournament-title">🎯 Shooting Tournament</div>
<div class="tournament-stats">
{{ tournament.total_players }} players • {{ tournament.total_rounds }} rounds
{% if tournament.current_round %}
• Currently on Round {{ tournament.current_round }}
{% endif %}
</div>
</div>
<div class="rounds-container">
{% for round in tournament.rounds %}
{% set is_current = tournament.current_round == round.round_number %}
{% set is_completed = tournament.current_round > round.round_number %}
<div class="round-card {{ 'current' if is_current else ('completed' if is_completed else 'waiting') }}">
<div class="round-header">
<div class="round-title">Round {{ round.round_number }}</div>
{% if is_current %}
<div class="round-badge current-badge">Current</div>
{% elif is_completed %}
<div class="round-badge completed-badge">Completed</div>
{% else %}
<div class="round-badge waiting-badge">Waiting</div>
{% endif %}
</div>
<div class="players-grid">
{% for position in range(1, 7) %}
{% set player = round.players[position-1] if position <= round.players|length else none %}
<div class="position-card {{ 'filled' if player else 'empty' }}">
<div class="position-number">{{ position }}</div>
{% if player %}
<div class="player-name">{{ player.name }}</div>
<div class="player-id">ID: {{ player.id }}</div>
{% else %}
<div class="player-name">Empty</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Tournament Summary -->
<div class="tournament-summary">
<div class="summary-title">📊 Tournament Summary</div>
<div class="summary-grid">
<div class="summary-item">
<span class="summary-label">Total Players:</span>
<span class="summary-value">{{ tournament.total_players }}</span>
</div>
<div class="summary-item">
<span class="summary-label">Total Rounds:</span>
<span class="summary-value">{{ tournament.total_rounds }}</span>
</div>
<div class="summary-item">
<span class="summary-label">Current Round:</span>
<span class="summary-value">{{ tournament.current_round or 'Not set' }}</span>
</div>
<div class="summary-item">
<span class="summary-label">Updates:</span>
<span class="summary-value" style="color: #28a745; font-weight: bold;">On Round Change</span>
</div>
<div class="summary-item">
<span class="summary-label">Created:</span>
<span class="summary-value">{{ tournament.created_at[:10] if tournament.created_at else 'Unknown' }}</span>
</div>
<div class="summary-item">
<span class="summary-label">Status:</span>
<span class="summary-value" style="color: #28a745; font-weight: bold;">Active</span>
</div>
</div>
</div>
{% else %}
<!-- No tournament available -->
<div class="no-tournament-message">
<h2>📋 No Active Tournament</h2>
<p>No tournament is currently running. Check the Results tab for completed tournaments or use the dashboard to start a new tournament.</p>
</div>
{% endif %}
</div>
<!-- Round change indicator -->
<div class="round-change-indicator" id="roundChangeIndicator">
🆕 New Round Starting...
</div>
<script>
// Tournament state
const tournamentActive = {{ 'true' if tournament else 'false' }};
let currentRound = {{ tournament.current_round if tournament else 1 }};
const totalRounds = {{ tournament.total_rounds if tournament else 1 }};
// Manual refresh function
function manualRefresh() {
const indicator = document.getElementById('roundChangeIndicator');
indicator.textContent = '🔄 Refreshing...';
indicator.classList.add('show');
setTimeout(() => {
window.location.reload();
}, 500);
}
function scrollToCurrentRound() {
const currentRound = document.querySelector('.round-card.current');
if (currentRound) {
setTimeout(() => {
currentRound.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}, 500);
}
}
function addMobileTouchFeedback() {
const touchElements = document.querySelectorAll('.nav-btn:not(.disabled), .round-card');
touchElements.forEach(element => {
element.addEventListener('touchstart', function(e) {
if (!this.disabled) {
this.style.transform = 'scale(0.98)';
this.style.transition = 'transform 0.1s ease';
}
});
element.addEventListener('touchend', function(e) {
if (!this.disabled) {
setTimeout(() => {
this.style.transform = '';
this.style.transition = 'transform 0.2s ease';
}, 100);
}
});
element.addEventListener('touchcancel', function(e) {
if (!this.disabled) {
this.style.transform = '';
this.style.transition = 'transform 0.2s ease';
}
});
});
}
function setupRoundChangeDetection() {
if (!tournamentActive) return;
setInterval(async () => {
if (document.visibilityState !== 'visible') return;
try {
const response = await fetch(window.location.pathname + '?check=1');
if (response.ok) {
const pageContent = await response.text();
const roundMatch = pageContent.match(/Currently on Round\s*(\d+)/);
if (roundMatch) {
const serverCurrentRound = parseInt(roundMatch[1]);
if (serverCurrentRound !== currentRound) {
console.log(`📱 Round changed from ${currentRound} to ${serverCurrentRound}`);
const indicator = document.getElementById('roundChangeIndicator');
indicator.textContent = '🆕 New Round Starting...';
indicator.classList.add('show');
setTimeout(() => {
window.location.reload();
}, 1500);
}
}
}
} catch (error) {
console.log('Round check failed:', error);
}
}, 5000);
}
document.addEventListener('keydown', function(event) {
if (event.key === 'r' || event.key === 'R') {
event.preventDefault();
manualRefresh();
}
});
document.addEventListener('DOMContentLoaded', function() {
addMobileTouchFeedback();
scrollToCurrentRound();
setupRoundChangeDetection();
console.log('📱 Mobile Draft with Fixed Navigation loaded');
console.log('🏆 Tournament active:', tournamentActive);
console.log('🔍 Round change detection active');
});
window.addEventListener('focus', function() {
console.log('📱 App regained focus - checking for round changes');
setTimeout(async () => {
try {
const response = await fetch(window.location.pathname + '?check=1');
if (response.ok) {
const pageContent = await response.text();
const roundMatch = pageContent.match(/Currently on Round\s*(\d+)/);
if (roundMatch) {
const serverCurrentRound = parseInt(roundMatch[1]);
if (serverCurrentRound !== currentRound) {
manualRefresh();
}
}
}
} catch (error) {
console.log('Focus round check failed:', error);
}
}, 500);
});
</script>
</body>
</html>
-934
View File
@@ -1,934 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>📱 League Results</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: #f5f5f5;
font-family: Arial, sans-serif;
min-height: 100vh;
overflow-x: hidden;
}
.mobile-navbar {
background: white;
color: black;
padding: 15px 20px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.navbar-title {
font-size: 1.4rem;
font-weight: bold;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.nav-btn {
background: #f8f9fa;
border: 2px solid #e9ecef;
cursor: pointer;
padding: 8px 12px;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #333;
text-decoration: none;
font-weight: bold;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 4px;
min-width: 44px;
justify-content: center;
}
.nav-btn:hover {
background: #e9ecef;
border-color: #007bff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
color: #007bff;
}
.nav-btn.active {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.active:hover {
background: #0056b3;
color: white;
}
.container {
padding: 20px;
max-width: 100%;
}
.league-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 25px 20px;
margin-bottom: 25px;
text-align: center;
color: white;
}
.league-title {
font-size: 1.8rem;
font-weight: bold;
margin-bottom: 10px;
}
.league-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin-bottom: 15px;
}
.league-badge {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: bold;
display: inline-block;
}
.league-stats {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 25px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.stat-item {
text-align: center;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: #007bff;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.9rem;
color: #666;
text-transform: uppercase;
font-weight: bold;
}
.explanation-title {
font-size: 1.2rem;
font-weight: bold;
color: #856404;
margin-bottom: 10px;
}
.explanation-text {
color: #856404;
line-height: 1.4;
}
.results-list {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 25px;
}
.result-card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
align-items: center;
gap: 20px;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.result-card.podium {
border-left: 5px solid;
}
.result-card.rank-1 {
border-left-color: #ffd700;
background: linear-gradient(135deg, #fff9e6 0%, #ffffff 100%);
}
.result-card.rank-2 {
border-left-color: #c0c0c0;
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
}
.result-card.rank-3 {
border-left-color: #cd7f32;
background: linear-gradient(135deg, #fdf6f0 0%, #ffffff 100%);
}
.result-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.rank-display {
display: flex;
flex-direction: column;
align-items: center;
min-width: 60px;
}
.rank-number {
font-size: 1.8rem;
font-weight: bold;
color: #333;
line-height: 1;
}
.result-card.rank-1 .rank-number { color: #b8860b; }
.result-card.rank-2 .rank-number { color: #696969; }
.result-card.rank-3 .rank-number { color: #8b4513; }
.rank-suffix {
font-size: 0.7rem;
color: #666;
text-transform: uppercase;
font-weight: bold;
}
.medal {
font-size: 1.5rem;
margin-top: 3px;
}
.participant-info {
flex: 1;
min-width: 0;
}
.participant-name {
font-size: 1.3rem;
font-weight: bold;
color: #333;
margin-bottom: 8px;
word-wrap: break-word;
}
.participant-details {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 5px;
}
.participant-id {
background: #007bff;
color: white;
padding: 3px 10px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
display: inline-block;
}
.score-display {
text-align: right;
min-width: 90px;
}
.final-score {
font-size: 2rem;
font-weight: bold;
color: #28a745;
line-height: 1;
}
.total-score {
font-size: 0.9rem;
color: #666;
margin-top: 2px;
}
.score-label {
font-size: 0.8rem;
color: #666;
text-transform: uppercase;
font-weight: bold;
}
.tournament-breakdown {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
.breakdown-title {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 10px;
text-align: center;
}
.breakdown-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.tournament-item {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 12px;
text-align: center;
}
.tournament-number {
font-size: 0.8rem;
font-weight: bold;
color: #007bff;
margin-bottom: 5px;
}
.tournament-score {
font-size: 1rem;
font-weight: bold;
color: #333;
}
.tournament-item.joker {
background: #fff3cd;
border-color: #ffc107;
}
.tournament-item.joker .tournament-score {
color: #856404;
}
.tournament-item.excluded {
background: #f8d7da;
border-color: #dc3545;
}
.tournament-item.excluded .tournament-score {
color: #721c24;
}
.tournament-item.counted {
background: #d1edf1;
border-color: #28a745;
}
.tournament-item.counted .tournament-score {
color: #155724;
}
.scoring-legend {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 25px;
}
.legend-title {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 15px;
text-align: center;
}
.legend-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.legend-rule {
background: #f8f9fa;
border-left: 4px solid #007bff;
padding: 12px;
border-radius: 4px;
font-size: 0.9rem;
line-height: 1.4;
}
.legend-items-mobile {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item-mobile {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9rem;
color: #495057;
}
.legend-dot {
font-size: 1.2rem;
width: 24px;
text-align: center;
}
.congratulations {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 25px 20px;
margin-bottom: 25px;
text-align: center;
color: white;
}
.congrats-title {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 10px;
}
.congrats-message {
font-size: 1.1rem;
opacity: 0.9;
}
/* Mobile landscape optimization */
@media (orientation: landscape) and (max-height: 500px) {
.mobile-navbar {
padding: 10px 15px;
}
.navbar-title {
font-size: 1.2rem;
}
.container {
padding: 15px;
}
.league-header {
padding: 20px 15px;
margin-bottom: 20px;
}
.league-title {
font-size: 1.5rem;
}
.league-subtitle {
font-size: 1rem;
}
.result-card {
padding: 15px;
gap: 15px;
}
.rank-number {
font-size: 1.5rem;
}
.participant-name {
font-size: 1.1rem;
}
.final-score {
font-size: 1.7rem;
}
.navbar-controls {
gap: 6px;
}
.nav-btn {
padding: 6px 10px;
font-size: 0.75rem;
}
}
@media (max-width: 400px) {
.container {
padding: 15px;
}
.league-header {
padding: 20px 15px;
}
.league-title {
font-size: 1.6rem;
}
.league-subtitle {
font-size: 1rem;
}
.result-card {
padding: 15px;
gap: 15px;
}
.rank-display {
min-width: 50px;
}
.rank-number {
font-size: 1.6rem;
}
.participant-name {
font-size: 1.2rem;
}
.score-display {
min-width: 80px;
}
.final-score {
font-size: 1.8rem;
}
.congratulations {
padding: 20px 15px;
}
.congrats-title {
font-size: 1.3rem;
}
.congrats-message {
font-size: 1rem;
}
.breakdown-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.tournament-item {
padding: 10px 8px;
}
.tournament-score {
font-size: 0.9rem;
}
.legend-rule {
padding: 10px;
font-size: 0.85rem;
}
.legend-item-mobile {
font-size: 0.85rem;
}
.league-stats {
grid-template-columns: 1fr;
gap: 10px;
}
.navbar-controls {
gap: 6px;
}
.nav-btn {
padding: 6px 8px;
font-size: 0.75rem;
}
}
/* Animation for results appearance */
.result-card {
animation: slideInUp 0.5s ease forwards;
opacity: 0;
transform: translateY(20px);
}
.result-card:nth-child(1) { animation-delay: 0.1s; }
.result-card:nth-child(2) { animation-delay: 0.2s; }
.result-card:nth-child(3) { animation-delay: 0.3s; }
.result-card:nth-child(4) { animation-delay: 0.4s; }
.result-card:nth-child(5) { animation-delay: 0.5s; }
.result-card:nth-child(6) { animation-delay: 0.6s; }
@keyframes slideInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Celebration confetti effect */
.confetti {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 999;
}
.confetti-piece {
position: absolute;
width: 10px;
height: 10px;
background: #ffd700;
animation: confetti-fall 3s linear infinite;
}
@keyframes confetti-fall {
0% {
transform: translateY(-100vh) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(720deg);
opacity: 0;
}
}
/* League status indicator */
.league-indicator {
display: inline-flex;
align-items: center;
background: #28a745;
color: white;
padding: 2px 6px;
border-radius: 8px;
font-size: 0.7rem;
font-weight: bold;
margin-left: 5px;
}
</style>
</head>
<body>
<div class="mobile-navbar">
<div class="navbar-title">
🏆 League
<span class="league-indicator">FINAL</span>
</div>
<div class="navbar-controls">
<a href="/mobile/streams" class="nav-btn">📷</a>
<a href="/mobile/results" class="nav-btn active">🏆</a>
</div>
</div>
<div class="container">
<div class="league-header">
<div class="league-title">🏆 League Championship</div>
<div class="league-subtitle">
Final Results - Best 5 of 6 Tournaments
</div>
<div class="league-badge">
{% if league.tournament_type == '40_targets' %}
40 Targets Format
{% else %}
20 Targets Format
{% endif %}
</div>
</div>
<div class="league-stats">
<div class="stat-item">
<div class="stat-value">{{ participants|length if participants else 0 }}</div>
<div class="stat-label">Participants</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ league.total_tournaments }}</div>
<div class="stat-label">Tournaments</div>
</div>
<div class="stat-item">
<div class="stat-value">{% if participants and participants|length > 0 %}{{ participants[0].final_score }}{% else %}0{% endif %}</div>
<div class="stat-label">Highest Score</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ league.finished_at[:10] if league.finished_at else 'Today' }}</div>
<div class="stat-label">Completed</div>
</div>
</div>
{% if participants and participants|length > 0 %}
<div class="congratulations">
<div class="congrats-title">🎉 League Complete!</div>
<div class="congrats-message">
Congratulations to {{ participants[0].name }} for winning the league!
</div>
</div>
<div class="results-list">
{% for participant in participants %}
<div class="result-card {{ 'podium rank-' + participant.rank|string if participant.rank <= 3 else '' }}">
<div class="rank-display">
<div class="rank-number">{{ participant.rank }}</div>
<div class="rank-suffix">
{% if participant.rank == 1 %}st
{% elif participant.rank == 2 %}nd
{% elif participant.rank == 3 %}rd
{% else %}th
{% endif %}
</div>
{% if participant.rank == 1 %}
<div class="medal">🥇</div>
{% elif participant.rank == 2 %}
<div class="medal">🥈</div>
{% elif participant.rank == 3 %}
<div class="medal">🥉</div>
{% endif %}
</div>
<div class="participant-info">
<div class="participant-name">{{ participant.name }}</div>
<div class="participant-details">
<div class="participant-id">ID: {{ participant.id }}</div>
</div>
</div>
<div class="score-display">
<div class="final-score">{{ participant.final_score }}</div>
<div class="total-score">Total: {{ participant.total_score }}</div>
<div class="score-label">Final Score</div>
</div>
</div>
{% endfor %}
</div>
<!-- Tournament Breakdown for Top Players -->
<!-- Fixed: Replace min(3, participants|length) with proper Jinja2 logic -->
{% set max_breakdowns = 3 %}
{% if participants|length < 3 %}
{% set max_breakdowns = participants|length %}
{% endif %}
{% for i in range(max_breakdowns) %}
{% set participant = participants[i] %}
<div class="tournament-breakdown">
<div class="breakdown-title">
{% if participant.rank == 1 %}🥇{% elif participant.rank == 2 %}🥈{% elif participant.rank == 3 %}🥉{% endif %}
{{ participant.name }}'s Tournament History
</div>
<div class="breakdown-grid">
{% for tournament_num in range(1, 7) %}
{% set result = participant.tournament_results
| selectattr("tournament", "equalto", tournament_num)
| list
| first %}
{% if result %}
{% if (result.joker is defined and result.joker) or (result.participated is defined and not result.participated) %}
<div class="tournament-item joker">
<div class="tournament-number">T{{ tournament_num }}</div>
<div class="tournament-score">🃏 Joker</div>
</div>
{% else %}
{% set all_participated_scores = participant.tournament_results
| selectattr("participated", "defined")
| selectattr("participated")
| selectattr("score", "defined")
| map(attribute="score")
| list %}
{% set sorted_scores = all_participated_scores | sort(reverse=true) %}
{% set is_excluded = sorted_scores | length > 5 and result.score == sorted_scores[-1] %}
{% if is_excluded %}
<div class="tournament-item excluded">
<div class="tournament-number">T{{ tournament_num }}</div>
<div class="tournament-score">{{ result.score }} ❌</div>
</div>
{% else %}
<div class="tournament-item counted">
<div class="tournament-number">T{{ tournament_num }}</div>
<div class="tournament-score">{{ result.score }} ✅</div>
</div>
{% endif %}
{% endif %}
{% else %}
<div class="tournament-item">
<div class="tournament-number">T{{ tournament_num }}</div>
<div class="tournament-score">-</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
<!-- Scoring Legend -->
<div class="scoring-legend">
<div class="legend-title">📖 How Final Scores Are Calculated</div>
<div class="legend-content">
<div class="legend-rule">
<strong>🏆 League Rule:</strong> Best 5 out of 6 tournaments count toward final ranking
</div>
<div class="legend-rule">
<strong>🃏 Joker System:</strong> Each player can skip 1 tournament without penalty
</div>
<div class="legend-items-mobile">
<div class="legend-item-mobile">
<span class="legend-dot counted"></span>
<span>Counted toward final score</span>
</div>
<div class="legend-item-mobile">
<span class="legend-dot excluded"></span>
<span>Excluded (worst score)</span>
</div>
<div class="legend-item-mobile">
<span class="legend-dot joker">🃏</span>
<span>Joker used (skipped)</span>
</div>
</div>
</div>
</div>
{% else %}
<div class="result-card">
<div style="text-align: center; width: 100%; color: #666;">
<h3>No League Results Available</h3>
<p>League results will appear here when the league is complete.</p>
</div>
</div>
{% endif %}
</div>
<!-- Confetti container -->
<div class="confetti" id="confetti"></div>
<script>
function createConfetti() {
const confettiContainer = document.getElementById('confetti');
const colors = ['#ffd700', '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7'];
for (let i = 0; i < 50; i++) {
const confettiPiece = document.createElement('div');
confettiPiece.className = 'confetti-piece';
confettiPiece.style.left = Math.random() * 100 + '%';
confettiPiece.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confettiPiece.style.animationDelay = Math.random() * 3 + 's';
confettiPiece.style.animationDuration = (Math.random() * 2 + 2) + 's';
confettiContainer.appendChild(confettiPiece);
}
setTimeout(() => {
confettiContainer.innerHTML = '';
}, 5000);
}
function addMobileTouchFeedback() {
const touchElements = document.querySelectorAll('.nav-btn:not(.disabled), .result-card');
touchElements.forEach(element => {
element.addEventListener('touchstart', function(e) {
if (!this.disabled) {
this.style.transform = 'scale(0.98) translateY(0)';
this.style.transition = 'transform 0.1s ease';
}
});
element.addEventListener('touchend', function(e) {
if (!this.disabled) {
setTimeout(() => {
this.style.transform = '';
this.style.transition = 'transform 0.2s ease';
}, 100);
}
});
element.addEventListener('touchcancel', function(e) {
if (!this.disabled) {
this.style.transform = '';
this.style.transition = 'transform 0.2s ease';
}
});
});
}
function showChampionCelebration() {
{% if participants and participants|length > 0 %}
createConfetti();
setTimeout(() => {
const firstCard = document.querySelector('.result-card.rank-1');
if (firstCard) {
firstCard.style.boxShadow = '0 0 30px rgba(255, 215, 0, 0.5)';
setTimeout(() => {
firstCard.style.boxShadow = '';
}, 2000);
}
}, 1000);
{% endif %}
}
document.addEventListener('keydown', function(event) {
if (event.key === 'r' || event.key === 'R') {
event.preventDefault();
window.location.reload();
}
});
document.addEventListener('DOMContentLoaded', function() {
addMobileTouchFeedback();
setTimeout(() => {
showChampionCelebration();
}, 1500);
console.log('📱 Mobile League Results loaded');
console.log('🏆 League participants:', {{ participants|length if participants else 0 }});
console.log('🎯 Tournament type:', '{{ league.tournament_type }}');
{% if participants and participants|length > 0 %}
console.log('🏆 League Champion:', '{{ participants[0].name }}');
console.log('🏆 Winning score:', {{ participants[0].final_score }});
{% endif %}
});
</script>
</body>
</html>
-382
View File
@@ -1,382 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>📱 Camera Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: Arial, sans-serif;
min-height: 100vh;
overflow: hidden;
}
.loader-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
color: white;
padding: 20px;
}
.logo {
font-size: 4rem;
margin-bottom: 20px;
animation: pulse 2s infinite;
}
.title {
font-size: 2rem;
font-weight: bold;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 30px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 20px auto;
}
.status-text {
font-size: 1rem;
opacity: 0.8;
margin-top: 15px;
}
.quick-nav {
display: flex;
gap: 15px;
margin-top: 30px;
flex-wrap: wrap;
justify-content: center;
}
.quick-btn {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 12px 20px;
border-radius: 12px;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
}
.quick-btn:hover {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
}
.quick-btn.disabled {
opacity: 0.5;
pointer-events: none;
}
.quick-btn.priority {
background: rgba(40, 167, 69, 0.8);
border-color: rgba(40, 167, 69, 1);
animation: priorityPulse 2s infinite;
}
.quick-btn.priority:hover {
background: rgba(40, 167, 69, 0.9);
border-color: rgba(40, 167, 69, 1);
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes priorityPulse {
0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); }
100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
}
.status-indicator {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
padding: 10px 20px;
margin: 20px 0;
font-size: 0.9rem;
color: white;
}
.status-indicator.tournament-active {
background: rgba(0, 123, 255, 0.8);
border-color: rgba(0, 123, 255, 1);
}
.status-indicator.results-available {
background: rgba(40, 167, 69, 0.8);
border-color: rgba(40, 167, 69, 1);
}
@media (max-width: 480px) {
.logo {
font-size: 3rem;
}
.title {
font-size: 1.7rem;
}
.subtitle {
font-size: 1.1rem;
}
.quick-nav {
flex-direction: column;
align-items: center;
}
.quick-btn {
width: 200px;
justify-content: center;
}
}
</style>
</head>
<body>
<div class="loader-container">
<div class="logo">📱</div>
<div class="title">Camera Dashboard</div>
<div class="subtitle">Loading mobile interface...</div>
<div class="loading-spinner"></div>
<div class="status-text" id="statusText">Detecting tournament state...</div>
<!-- Status Indicator -->
<div class="status-indicator" id="statusIndicator" style="display: none;">
<span id="statusMessage">Checking status...</span>
</div>
<!-- Quick navigation fallback -->
<div class="quick-nav" id="quickNav" style="display: none;">
<a href="/mobile/streams" class="quick-btn">
<span>📷</span>
<span>Camera Streams</span>
</a>
{% if tournament_active %}
<a href="/mobile/draft" class="quick-btn">
<span>📋</span>
<span>Tournament Draft</span>
</a>
{% endif %}
{% if results_available %}
<a href="/mobile/results" class="quick-btn priority">
<span>🏆</span>
<span>View Results</span>
</a>
{% elif tournament_active %}
<div class="quick-btn disabled">
<span>🏆</span>
<span>Results (Soon)</span>
</div>
{% endif %}
</div>
</div>
<script>
// Tournament state from server
const tournamentActive = {{ 'true' if tournament_active else 'false' }};
const resultsAvailable = {{ 'true' if results_available else 'false' }};
function updateStatus(message) {
const statusText = document.getElementById('statusText');
if (statusText) {
statusText.textContent = message;
}
}
function showStatusIndicator(message, type = '') {
const statusIndicator = document.getElementById('statusIndicator');
const statusMessage = document.getElementById('statusMessage');
if (statusIndicator && statusMessage) {
statusMessage.textContent = message;
statusIndicator.className = `status-indicator ${type}`;
statusIndicator.style.display = 'block';
}
}
function redirectToOptimalPage() {
// Determine the best page to redirect to based on current state
let targetUrl = '/mobile/streams'; // Default to streams
let reason = 'Camera streams';
let statusType = '';
if (resultsAvailable) {
targetUrl = '/mobile/results';
reason = 'Tournament results are ready!';
statusType = 'results-available';
showStatusIndicator('🏆 ' + reason, statusType);
} else if (tournamentActive) {
targetUrl = '/mobile/draft';
reason = 'Tournament is active';
statusType = 'tournament-active';
showStatusIndicator('🏆 ' + reason, statusType);
} else {
reason = 'Camera streams (default)';
showStatusIndicator('📷 ' + reason);
}
updateStatus(`Redirecting to ${reason.toLowerCase()}...`);
// Add a small delay for smooth UX
setTimeout(() => {
window.location.href = targetUrl;
}, 1800);
}
function showQuickNav() {
const quickNav = document.getElementById('quickNav');
const statusText = document.getElementById('statusText');
if (quickNav && statusText) {
statusText.textContent = 'Choose your destination:';
quickNav.style.display = 'flex';
// Show status indicator based on current state
if (resultsAvailable) {
showStatusIndicator('🏆 Tournament completed - results ready!', 'results-available');
} else if (tournamentActive) {
showStatusIndicator('🏆 Tournament in progress', 'tournament-active');
} else {
showStatusIndicator('📷 Ready to view camera streams');
}
}
}
// Auto-redirect logic
function initializeRedirect() {
console.log('📱 Mobile entry point loaded');
console.log('🏆 Tournament active:', tournamentActive);
console.log('📊 Results available:', resultsAvailable);
// Show status first
setTimeout(() => {
if (resultsAvailable) {
showStatusIndicator('🏆 Tournament completed - results ready!', 'results-available');
} else if (tournamentActive) {
showStatusIndicator('🏆 Tournament in progress', 'tournament-active');
} else {
showStatusIndicator('📷 Ready to view camera streams');
}
}, 800);
// Wait a moment, then redirect
setTimeout(() => {
redirectToOptimalPage();
}, 1500);
// Show quick nav as fallback after 4 seconds if redirect fails
setTimeout(() => {
showQuickNav();
}, 4000);
}
// Handle errors gracefully
function handleRedirectError() {
updateStatus('Connection issue detected');
setTimeout(() => {
showQuickNav();
}, 1000);
}
// Keyboard shortcuts
document.addEventListener('keydown', function(event) {
switch(event.key) {
case '1':
window.location.href = '/mobile/streams';
break;
case '2':
if (tournamentActive) {
window.location.href = '/mobile/draft';
}
break;
case '3':
if (resultsAvailable) {
window.location.href = '/mobile/results';
}
break;
case 'Escape':
showQuickNav();
break;
case 'r':
case 'R':
if (resultsAvailable) {
window.location.href = '/mobile/results';
}
break;
}
});
// Initialize when page loads
document.addEventListener('DOMContentLoaded', initializeRedirect);
// Handle network errors
window.addEventListener('error', handleRedirectError);
// Handle orientation changes gracefully
window.addEventListener('orientationchange', function() {
setTimeout(() => {
// Trigger a reflow to handle orientation change
document.body.style.display = 'none';
document.body.offsetHeight; // Trigger reflow
document.body.style.display = '';
}, 100);
});
// Handle visibility change (when user returns to app)
document.addEventListener('visibilitychange', function() {
if (!document.hidden && resultsAvailable) {
// If results are available and user returns to app, prioritize results
updateStatus('🏆 Redirecting to results...');
setTimeout(() => {
window.location.href = '/mobile/results';
}, 1000);
}
});
</script>
</body>
</html>
-696
View File
@@ -1,696 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>📱 Tournament Results</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: #f5f5f5;
font-family: Arial, sans-serif;
min-height: 100vh;
overflow-x: hidden;
}
.mobile-navbar {
background: white;
color: black;
padding: 15px 20px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.navbar-title {
font-size: 1.4rem;
font-weight: bold;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.nav-btn {
background: #f8f9fa;
border: 2px solid #e9ecef;
cursor: pointer;
padding: 8px 12px;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #333;
text-decoration: none;
font-weight: bold;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 4px;
min-width: 44px;
justify-content: center;
}
.nav-btn:hover {
background: #e9ecef;
border-color: #007bff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
color: #007bff;
}
.nav-btn.active {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.active:hover {
background: #0056b3;
color: white;
}
.nav-btn.disabled {
opacity: 0.5;
pointer-events: none;
background: #f1f3f4;
border-color: #dadce0;
}
.container {
padding: 20px;
max-width: 100%;
}
.results-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 25px 20px;
margin-bottom: 25px;
text-align: center;
color: white;
}
.results-title {
font-size: 1.8rem;
font-weight: bold;
margin-bottom: 10px;
}
.results-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin-bottom: 15px;
}
.results-badge {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: bold;
display: inline-block;
}
.results-list {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 25px;
}
.result-card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
align-items: center;
gap: 20px;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.result-card.podium {
border-left: 5px solid;
}
.result-card.rank-1 {
border-left-color: #ffd700;
background: linear-gradient(135deg, #fff9e6 0%, #ffffff 100%);
}
.result-card.rank-2 {
border-left-color: #c0c0c0;
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
}
.result-card.rank-3 {
border-left-color: #cd7f32;
background: linear-gradient(135deg, #fdf6f0 0%, #ffffff 100%);
}
.result-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.rank-display {
display: flex;
flex-direction: column;
align-items: center;
min-width: 60px;
}
.rank-number {
font-size: 1.8rem;
font-weight: bold;
color: #333;
line-height: 1;
}
.result-card.rank-1 .rank-number { color: #b8860b; }
.result-card.rank-2 .rank-number { color: #696969; }
.result-card.rank-3 .rank-number { color: #8b4513; }
.rank-suffix {
font-size: 0.7rem;
color: #666;
text-transform: uppercase;
font-weight: bold;
}
.medal {
font-size: 1.5rem;
margin-top: 3px;
}
.participant-info {
flex: 1;
min-width: 0;
}
.participant-name {
font-size: 1.3rem;
font-weight: bold;
color: #333;
margin-bottom: 5px;
word-wrap: break-word;
}
.participant-id {
background: #007bff;
color: white;
padding: 3px 10px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
display: inline-block;
}
.score-display {
text-align: right;
min-width: 80px;
}
.score-number {
font-size: 2rem;
font-weight: bold;
color: #28a745;
line-height: 1;
}
.score-label {
font-size: 0.8rem;
color: #666;
text-transform: uppercase;
font-weight: bold;
}
.tournament-info {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 25px;
}
.info-title {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 15px;
text-align: center;
}
.info-grid {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-weight: bold;
color: #333;
}
.info-value {
color: #666;
}
.congratulations {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 25px 20px;
margin-bottom: 25px;
text-align: center;
color: white;
}
.congrats-title {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 10px;
}
.congrats-message {
font-size: 1.1rem;
opacity: 0.9;
}
/* Mobile landscape optimization */
@media (orientation: landscape) and (max-height: 500px) {
.mobile-navbar {
padding: 10px 15px;
}
.navbar-title {
font-size: 1.2rem;
}
.container {
padding: 15px;
}
.results-header {
padding: 20px 15px;
margin-bottom: 20px;
}
.results-title {
font-size: 1.5rem;
}
.results-subtitle {
font-size: 1rem;
}
.result-card {
padding: 15px;
gap: 15px;
}
.rank-number {
font-size: 1.5rem;
}
.participant-name {
font-size: 1.1rem;
}
.score-number {
font-size: 1.7rem;
}
.navbar-controls {
gap: 6px;
}
.nav-btn {
padding: 6px 10px;
font-size: 0.75rem;
}
}
@media (max-width: 400px) {
.container {
padding: 15px;
}
.results-header {
padding: 20px 15px;
}
.results-title {
font-size: 1.6rem;
}
.results-subtitle {
font-size: 1rem;
}
.result-card {
padding: 15px;
gap: 15px;
}
.rank-display {
min-width: 50px;
}
.rank-number {
font-size: 1.6rem;
}
.participant-name {
font-size: 1.2rem;
}
.score-display {
min-width: 70px;
}
.score-number {
font-size: 1.8rem;
}
.congratulations {
padding: 20px 15px;
}
.congrats-title {
font-size: 1.3rem;
}
.congrats-message {
font-size: 1rem;
}
.navbar-controls {
gap: 6px;
}
.nav-btn {
padding: 6px 8px;
font-size: 0.75rem;
}
}
/* Animation for results appearance */
.result-card {
animation: slideInUp 0.5s ease forwards;
opacity: 0;
transform: translateY(20px);
}
.result-card:nth-child(1) { animation-delay: 0.1s; }
.result-card:nth-child(2) { animation-delay: 0.2s; }
.result-card:nth-child(3) { animation-delay: 0.3s; }
.result-card:nth-child(4) { animation-delay: 0.4s; }
.result-card:nth-child(5) { animation-delay: 0.5s; }
.result-card:nth-child(6) { animation-delay: 0.6s; }
@keyframes slideInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Celebration confetti effect */
.confetti {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 999;
}
.confetti-piece {
position: absolute;
width: 10px;
height: 10px;
background: #ffd700;
animation: confetti-fall 3s linear infinite;
}
@keyframes confetti-fall {
0% {
transform: translateY(-100vh) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(720deg);
opacity: 0;
}
}
/* Results status indicator */
.results-indicator {
display: inline-flex;
align-items: center;
background: #28a745;
color: white;
padding: 2px 6px;
border-radius: 8px;
font-size: 0.7rem;
font-weight: bold;
margin-left: 5px;
}
</style>
</head>
<body>
<div class="mobile-navbar">
<div class="navbar-title">
🏆 Results
<span class="results-indicator">FINAL</span>
</div>
<div class="navbar-controls">
<a href="/mobile/streams" class="nav-btn">📷</a>
{% if tournament_active %}
<a href="/mobile/draft" class="nav-btn">📋</a>
{% endif %}
<a href="/mobile/results" class="nav-btn active">🏆</a>
</div>
</div>
<div class="container">
{% if participants %}
{% if participants|length > 0 %}
<div class="congratulations">
<div class="congrats-title">🎉 Tournament Complete!</div>
<div class="congrats-message">
Congratulations to {{ participants[0].name }} for winning!
</div>
</div>
{% endif %}
<div class="results-list">
{% for participant in participants %}
<div class="result-card {{ 'podium rank-' + participant.rank|string if participant.rank <= 3 else '' }}">
<div class="rank-display">
<div class="rank-number">{{ participant.rank }}</div>
<div class="rank-suffix">
{% if participant.rank == 1 %}st
{% elif participant.rank == 2 %}nd
{% elif participant.rank == 3 %}rd
{% else %}th
{% endif %}
</div>
{% if participant.rank == 1 %}
<div class="medal">🥇</div>
{% elif participant.rank == 2 %}
<div class="medal">🥈</div>
{% elif participant.rank == 3 %}
<div class="medal">🥉</div>
{% endif %}
</div>
<div class="participant-info">
<div class="participant-name">{{ participant.name }}</div>
<div class="participant-id">ID: {{ participant.id }}</div>
</div>
<div class="score-display">
<div class="score-number">{{ participant.total_score }}</div>
<div class="score-label">Points</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="result-card">
<div style="text-align: center; width: 100%; color: #666;">
<h3>No Results Available</h3>
<p>Tournament results will appear here when scoring is complete.</p>
</div>
</div>
{% endif %}
<!-- Tournament Info -->
<div class="tournament-info">
<div class="info-title">📊 Tournament Information</div>
<div class="info-grid">
<div class="info-item">
<span class="info-label">Total Participants:</span>
<span class="info-value">{{ participants|length if participants else 0 }}</span>
</div>
<div class="info-item">
<span class="info-label">Tournament ID:</span>
<span class="info-value">{{ results.tournament_id[:10] if results.tournament_id else 'Unknown' }}...</span>
</div>
<div class="info-item">
<span class="info-label">Created:</span>
<span class="info-value">{{ results.created_at[:10] if results.created_at else 'Unknown' }}</span>
</div>
<div class="info-item">
<span class="info-label">Status:</span>
<span class="info-value" style="color: #28a745; font-weight: bold;">Completed</span>
</div>
{% if participants and participants|length > 0 %}
<div class="info-item">
<span class="info-label">Highest Score:</span>
<span class="info-value" style="color: #28a745; font-weight: bold;">{{ participants[0].total_score }} points</span>
</div>
<div class="info-item">
<span class="info-label">Winner:</span>
<span class="info-value" style="color: #ffd700; font-weight: bold;">{{ participants[0].name }}</span>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Confetti container -->
<div class="confetti" id="confetti"></div>
<script>
function createConfetti() {
const confettiContainer = document.getElementById('confetti');
const colors = ['#ffd700', '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7'];
for (let i = 0; i < 50; i++) {
const confettiPiece = document.createElement('div');
confettiPiece.className = 'confetti-piece';
confettiPiece.style.left = Math.random() * 100 + '%';
confettiPiece.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confettiPiece.style.animationDelay = Math.random() * 3 + 's';
confettiPiece.style.animationDuration = (Math.random() * 2 + 2) + 's';
confettiContainer.appendChild(confettiPiece);
}
setTimeout(() => {
confettiContainer.innerHTML = '';
}, 5000);
}
function addMobileTouchFeedback() {
const touchElements = document.querySelectorAll('.nav-btn:not(.disabled), .result-card');
touchElements.forEach(element => {
element.addEventListener('touchstart', function(e) {
if (!this.disabled) {
this.style.transform = 'scale(0.98) translateY(0)';
this.style.transition = 'transform 0.1s ease';
}
});
element.addEventListener('touchend', function(e) {
if (!this.disabled) {
setTimeout(() => {
this.style.transform = '';
this.style.transition = 'transform 0.2s ease';
}, 100);
}
});
element.addEventListener('touchcancel', function(e) {
if (!this.disabled) {
this.style.transform = '';
this.style.transition = 'transform 0.2s ease';
}
});
});
}
function showWinnerCelebration() {
{% if participants and participants|length > 0 %}
createConfetti();
setTimeout(() => {
const firstCard = document.querySelector('.result-card.rank-1');
if (firstCard) {
firstCard.style.boxShadow = '0 0 30px rgba(255, 215, 0, 0.5)';
setTimeout(() => {
firstCard.style.boxShadow = '';
}, 2000);
}
}, 1000);
{% endif %}
}
document.addEventListener('keydown', function(event) {
if (event.key === 'r' || event.key === 'R') {
event.preventDefault();
window.location.reload();
}
});
document.addEventListener('DOMContentLoaded', function() {
addMobileTouchFeedback();
setTimeout(() => {
showWinnerCelebration();
}, 1500);
console.log('📱 Mobile Results with Smart Navbar loaded');
console.log('🏆 Tournament Results:', {
participants: {{ participants|length if participants else 0 }},
{% if participants and participants|length > 0 %}
winner: '{{ participants[0].name }}',
winning_score: {{ participants[0].total_score }}
{% endif %}
});
});
</script>
</body>
</html>
+81 -137
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>📱 Camera Streams</title>
<title data-i18n="mobile.camera_streams">📱 Camera Streams</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui, viewport-fit=cover">
<style>
* {
@@ -294,31 +294,32 @@
}
}
/* Floating Fullscreen Button */
/* Floating fullscreen button */
.floating-fullscreen-btn {
position: fixed;
bottom: 20px;
right: 20px;
width: 56px;
height: 56px;
background: #007bff;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
background: rgba(0, 123, 255, 0.9);
border: none;
border-radius: 50%;
color: white;
font-size: 1.2rem;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.4);
font-size: 1.5rem;
cursor: pointer;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
}
.floating-fullscreen-btn:hover {
background: #0056b3;
background: rgba(0, 123, 255, 1);
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.5);
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);
}
.floating-fullscreen-btn:active {
@@ -327,59 +328,8 @@
.floating-fullscreen-btn.hidden {
opacity: 0;
transform: scale(0);
pointer-events: none;
}
/* Hide floating button when in fullscreen */
:fullscreen .floating-fullscreen-btn,
:-webkit-full-screen .floating-fullscreen-btn,
:-moz-full-screen .floating-fullscreen-btn,
:-ms-fullscreen .floating-fullscreen-btn {
opacity: 0;
transform: scale(0);
pointer-events: none;
}
/* Fullscreen adjustments */
:fullscreen .mobile-navbar,
:-webkit-full-screen .mobile-navbar,
:-moz-full-screen .mobile-navbar,
:-ms-fullscreen .mobile-navbar {
display: none !important;
}
:fullscreen .container,
:-webkit-full-screen .container,
:-moz-full-screen .container,
:-ms-fullscreen .container {
padding: 10px;
min-height: 100vh;
min-height: 100dvh;
}
html:fullscreen .mobile-navbar,
html:-webkit-full-screen .mobile-navbar,
html:-moz-full-screen .mobile-navbar,
html:-ms-fullscreen .mobile-navbar {
display: none !important;
}
html:fullscreen .container,
html:-webkit-full-screen .container,
html:-moz-full-screen .container,
html:-ms-fullscreen .container {
padding: 10px;
min-height: 100vh;
min-height: 100dvh;
}
@media (max-width: 480px) {
.floating-fullscreen-btn {
width: 60px;
height: 60px;
font-size: 1.3rem;
}
transform: scale(0.8);
}
/* Tournament status indicator */
@@ -412,7 +362,7 @@
<body>
<div class="mobile-navbar">
<div class="navbar-title">
📷 Streams
<span data-i18n="mobile.camera_streams">📷 Prenosi Kamer</span>
{% if tournament_active %}
<span class="tournament-indicator">LIVE</span>
{% elif results_available %}
@@ -420,10 +370,7 @@
{% endif %}
</div>
<div class="navbar-controls">
<!-- Always show all 3 navigation buttons -->
<a href="/mobile/streams" class="nav-btn active">📷</a>
<a href="/mobile/draft" class="nav-btn">📋</a>
<a href="/mobile/results" class="nav-btn">🏆</a>
<!-- Mobile users stay on mobile - no desktop access -->
</div>
</div>
@@ -433,7 +380,7 @@
{% set camera_id = i %}
<div class="camera-option" onclick="openCameraFullscreen({{ camera_id }})">
<div class="stream-preview">
<div class="stream-loading">Loading...</div>
<div class="stream-loading" data-i18n="general.loading">Nalaganje...</div>
<img src="{{ streams[i-1].url }}"
alt="Camera {{ camera_id }}"
class="stream-img"
@@ -454,8 +401,8 @@
</div>
</div>
<!-- Floating Fullscreen Button -->
<button class="floating-fullscreen-btn" id="fullscreenBtn" onclick="toggleFullscreen()" title="Enter Fullscreen">
<!-- Floating fullscreen button -->
<button class="floating-fullscreen-btn" onclick="toggleFullscreen()" id="fullscreenBtn">
</button>
@@ -536,90 +483,69 @@
if (event.key >= '1' && event.key <= '6') {
const cameraId = parseInt(event.key);
openCameraFullscreen(cameraId);
} else if (event.key === 'f' || event.key === 'F') {
event.preventDefault();
toggleFullscreen();
}
});
// Fullscreen functionality
function toggleFullscreen() {
if (isFullscreen()) {
exitFullscreen();
const elem = document.documentElement;
if (!document.fullscreenElement) {
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen();
}
} else {
enterFullscreen();
}
}
function isFullscreen() {
return document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement;
}
function enterFullscreen() {
const element = document.documentElement;
if (element.requestFullscreen) {
element.requestFullscreen().catch(err => console.log('Fullscreen error:', err));
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
function exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen().catch(err => console.log('Exit fullscreen error:', err));
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}
function handleFullscreenChange() {
const fullscreenBtn = document.getElementById('fullscreenBtn');
const navbar = document.querySelector('.mobile-navbar');
const isFS = isFullscreen();
if (fullscreenBtn) {
if (isFS) {
fullscreenBtn.innerHTML = '⛉';
fullscreenBtn.title = 'Exit Fullscreen';
fullscreenBtn.classList.add('hidden');
if (navbar) {
navbar.style.display = 'none';
}
} else {
fullscreenBtn.innerHTML = '⛶';
fullscreenBtn.title = 'Enter Fullscreen';
fullscreenBtn.classList.remove('hidden');
if (navbar) {
navbar.style.display = 'flex';
}
if (!fullscreenBtn) return;
const isFullscreen = document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement;
if (isFullscreen) {
fullscreenBtn.textContent = '⛶';
fullscreenBtn.classList.remove('hidden');
} else {
fullscreenBtn.textContent = '⛶';
fullscreenBtn.classList.remove('hidden');
// If we're coming back from a fullscreen camera view, redirect to streams
if (window.location.pathname.includes('/fullscreen/')) {
window.location.href = '/mobile/streams';
}
}
}
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
// Initialize
document.addEventListener('DOMContentLoaded', function() {
addMobileTouchFeedback();
handleFullscreenChange();
// Add fullscreen event listeners
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('msfullscreenchange', handleFullscreenChange);
console.log('📱 Mobile camera selection loaded with fixed navigation');
console.log('🏆 Tournament active:', {{ 'true' if tournament_active else 'false' }});
console.log('📊 Results available:', {{ 'true' if results_available else 'false' }});
@@ -631,14 +557,17 @@
}
}, 500);
// Only refresh streams if page is visible
setInterval(() => {
if (document.visibilityState !== 'visible') return;
const streams = document.querySelectorAll('.stream-img');
streams.forEach(stream => {
if (stream.complete && stream.naturalHeight === 0) {
stream.src = stream.src.split('?')[0] + '?refresh=' + Date.now();
}
});
}, 30000);
}, 60000); // Reduced from 30s to 60s
if (window.innerHeight > window.innerWidth) {
const hint = document.createElement('div');
@@ -666,5 +595,20 @@
}
});
</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 ready
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
}
</script>
</body>
</html>
+119 -127
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tournament Archive</title>
<title data-i18n="navigation.archive">📚 Arhiv</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
@@ -63,28 +63,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;
@@ -172,36 +161,36 @@
background: #0056b3;
}
/* Enhanced Stats Overview */
.stats-overview {
/* Stats Overview */
.stats-badges {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
.stat-badge {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
padding: 15px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
transition: transform 0.2s ease;
transition: transform 0.3s ease;
}
.stat-card:hover {
.stat-badge:hover {
transform: translateY(-2px);
}
.stat-icon {
font-size: 2rem;
margin-bottom: 10px;
font-size: 1.5rem;
margin-bottom: 8px;
display: block;
}
.stat-value {
font-size: 1.8rem;
.stat-number {
font-size: 1.5rem;
font-weight: bold;
color: #333;
margin-bottom: 5px;
@@ -209,23 +198,10 @@
.stat-label {
color: #666;
font-size: 0.8rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
}
/* Target-specific stat cards */
.stat-card.target-40 {
border-left: 4px solid #dc3545;
}
.stat-card.target-20 {
border-left: 4px solid #ffc107;
}
.stat-card.target-4 {
border-left: 4px solid #28a745;
}
/* Enhanced Archive Grid */
.archive-grid {
@@ -592,7 +568,7 @@
grid-template-columns: 1fr;
}
.stats-overview {
.stats-badges {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
@@ -631,46 +607,47 @@
</head>
<body>
<div class="navbar">
<div class="navbar-title">📚 Tournament Archive</div>
<div class="navbar-title" data-i18n="navigation.archive">📚 Arhiv</div>
<div class="navbar-controls">
<a href="/" class="nav-btn">← Dashboard</a>
<a href="/tournament" class="nav-btn">🏆 Tournament</a>
<a href="/archive/player-analysis" class="nav-btn success">👤 Player Analysis</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 active">📚 <span data-i18n="navigation.archive">Archive</span></a>
</div>
</div>
<div class="container">
<!-- Enhanced Stats Overview -->
<div class="stats-overview">
<div class="stat-card">
<!-- Stats Overview -->
<div class="stats-badges">
<div class="stat-badge">
<span class="stat-icon">🎖️</span>
<div class="stat-value">{{ leagues|length if leagues else 0 }}</div>
<div class="stat-label">Completed Leagues</div>
<div class="stat-number">{{ leagues|length if leagues else 0 }}</div>
<div class="stat-label" data-i18n="league.completed_leagues">Zaključene Lige</div>
</div>
<div class="stat-card">
<div class="stat-badge">
<span class="stat-icon">🏆</span>
<div class="stat-value">{{ tournaments|length if tournaments else 0 }}</div>
<div class="stat-label">Total Tournaments</div>
<div class="stat-number">{{ tournaments|length if tournaments else 0 }}</div>
<div class="stat-label" data-i18n="analysis.total_tournaments">Skupaj Turnirjev</div>
</div>
<div class="stat-card target-40">
<div class="stat-badge">
<span class="stat-icon">💪</span>
<div class="stat-value">{{ tournaments|selectattr('tournament_type', 'equalto', '40_targets')|list|length if tournaments else 0 }}</div>
<div class="stat-label">40-Target Tournaments</div>
<div class="stat-number">{{ tournaments|selectattr('tournament_type', 'equalto', '40_targets')|list|length if tournaments else 0 }}</div>
<div class="stat-label" data-i18n="tournament_types.40_target_tournaments">40-Tarčni Turnirji</div>
</div>
<div class="stat-card target-20">
<div class="stat-badge">
<span class="stat-icon"></span>
<div class="stat-value">{{ tournaments|selectattr('tournament_type', 'equalto', '20_targets')|list|length if tournaments else 0 }}</div>
<div class="stat-label">20-Target Tournaments</div>
<div class="stat-number">{{ tournaments|selectattr('tournament_type', 'equalto', '20_targets')|list|length if tournaments else 0 }}</div>
<div class="stat-label" data-i18n="tournament_types.20_target_tournaments">20-Tarčni Turnirji</div>
</div>
<div class="stat-card target-4">
<div class="stat-badge">
<span class="stat-icon">🎯</span>
<div class="stat-value">{{ tournaments|selectattr('tournament_type', 'equalto', '4_targets')|list|length if tournaments else 0 }}</div>
<div class="stat-label">4-Target <br> Tournaments</div>
<div class="stat-number">{{ tournaments|selectattr('tournament_type', 'equalto', '4_targets')|list|length if tournaments else 0 }}</div>
<div class="stat-label" data-i18n="tournament_types.4_target_tournaments">4-Tarčni Turnirji</div>
</div>
<div class="stat-card">
<div class="stat-badge">
<span class="stat-icon">👥</span>
<div class="stat-value">{{ stats.total_players if stats else 0 }}</div>
<div class="stat-label">Active Players</div>
<div class="stat-number">{{ stats.total_players if stats else 0 }}</div>
<div class="stat-label" data-i18n="players.enabled_players">Omogočeni Igralci</div>
</div>
</div>
@@ -678,23 +655,23 @@
{% if leagues %}
<div class="section">
<div class="section-title">
<span>🎖️ League Championships</span>
<span data-i18n="messages.league_championships">🎖️ Ligaška Prvenstva</span>
</div>
<div class="archive-grid" id="leagues-grid">
{% for league in leagues %}
<div class="archive-card league" data-status="{{ 'completed' if league.completed_tournaments == league.total_tournaments else 'incomplete' }}" data-date="{{ league.archived_at }}" onclick="viewLeague('{{ league.filename }}')">
<div class="archive-header">
<div>
<div class="archive-title">League Championship</div>
<div class="archive-subtitle">{{ league.created_at[:10] if league.created_at != 'Unknown' else 'Unknown Date' }}</div>
<div class="archive-title" data-i18n="messages.league_championship">League Championship</div>
<div class="archive-subtitle">{{ league.created_at[:10] if league.created_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</div>
</div>
<span class="archive-badge badge-league">League</span>
<span class="archive-badge badge-league" data-i18n="league.league">Liga</span>
</div>
<div class="archive-content">
<div class="archive-info">
<div class="info-item">
<span class="info-icon">👥</span>
<span class="info-value">{{ league.participants_count }} players</span>
<span class="info-value">{{ league.participants_count }} <span data-i18n="players.players_label">players</span></span>
</div>
<div class="info-item">
<span class="info-icon">🎯</span>
@@ -702,15 +679,15 @@
</div>
<div class="info-item">
<span class="info-icon">📅</span>
<span class="info-value">{{ league.archived_at[:10] if league.archived_at != 'Unknown' else 'Unknown Date' }}</span>
<span class="info-value">{{ league.archived_at[:10] if league.archived_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</span>
</div>
<div class="info-item">
<span class="info-icon"></span>
<span class="info-value">{{ league.completed_tournaments }}/{{ league.total_tournaments }} tournaments</span>
<span class="info-value">{{ league.completed_tournaments }}/{{ league.total_tournaments }} <span data-i18n="tournament.tournaments">tournaments</span></span>
</div>
</div>
<div class="archive-actions">
<a href="/archive/league/{{ league.filename }}" class="action-btn view-btn">🏆 View</a>
<a href="/archive/league/{{ league.filename }}" class="action-btn view-btn">🏆 <span data-i18n="general.view">View</span></a>
</div>
</div>
</div>
@@ -727,23 +704,23 @@
{% if tournaments_40 %}
<div class="section">
<div class="section-title">
<span>💪 40-Target Tournaments</span>
<span data-i18n="tournament_types.40_target_tournaments">💪 40-Tarčni Turnirji</span>
</div>
<div class="archive-grid" id="tournaments-40-grid">
{% for tournament in tournaments_40 %}
<div class="archive-card tournament-40" data-status="{{ 'finished' if tournament.tournament_finished else 'incomplete' }}" data-date="{{ tournament.archived_at }}" data-targets="40" onclick="viewTournament('{{ tournament.filename }}')">
<div class="archive-header">
<div>
<div class="archive-title">40-Target Tournament</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else 'Unknown Date' }}</div>
<div class="archive-title" data-i18n="messages.40_target_tournament">40-Tarčni Turnir</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</div>
</div>
<span class="archive-badge badge-40-targets">40 Targets</span>
<span class="archive-badge badge-40-targets" data-i18n="tournament_types.40_targets">40 Targets</span>
</div>
<div class="archive-content">
<div class="archive-info">
<div class="info-item">
<span class="info-icon">👥</span>
<span class="info-value">{{ tournament.participants_count }} players</span>
<span class="info-value">{{ tournament.participants_count }} <span data-i18n="players.players_label">players</span></span>
</div>
<div class="info-item">
<span class="info-icon">🎯</span>
@@ -751,15 +728,15 @@
</div>
<div class="info-item">
<span class="info-icon">📅</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else 'Unknown Date' }}</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</span>
</div>
<div class="info-item">
<span class="info-icon">🏁</span>
<span class="info-value">{{ 'Finished' if tournament.tournament_finished else 'Incomplete' }}</span>
<span class="info-value">{{ (translations.tournament.finished if translations else 'Finished') if tournament.tournament_finished else (translations.scoring.in_progress if translations else 'Incomplete') }}</span>
</div>
</div>
<div class="archive-actions">
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 View</a>
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 <span data-i18n="general.view">View</span></a>
</div>
</div>
</div>
@@ -773,23 +750,23 @@
{% if tournaments_20 %}
<div class="section">
<div class="section-title">
<span>⚡ 20-Target Tournaments</span>
<span data-i18n="tournament_types.20_target_tournaments">⚡ 20-Tarčni Turnirji</span>
</div>
<div class="archive-grid" id="tournaments-20-grid">
{% for tournament in tournaments_20 %}
<div class="archive-card tournament-20" data-status="{{ 'finished' if tournament.tournament_finished else 'incomplete' }}" data-date="{{ tournament.archived_at }}" data-targets="20" onclick="viewTournament('{{ tournament.filename }}')">
<div class="archive-header">
<div>
<div class="archive-title">20-Target Tournament</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else 'Unknown Date' }}</div>
<div class="archive-title" data-i18n="messages.20_target_tournament">20-Tarčni Turnir</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</div>
</div>
<span class="archive-badge badge-20-targets">20 Targets</span>
<span class="archive-badge badge-20-targets" data-i18n="tournament_types.20_targets">20 Targets</span>
</div>
<div class="archive-content">
<div class="archive-info">
<div class="info-item">
<span class="info-icon">👥</span>
<span class="info-value">{{ tournament.participants_count }} players</span>
<span class="info-value">{{ tournament.participants_count }} <span data-i18n="players.players_label">players</span></span>
</div>
<div class="info-item">
<span class="info-icon">🎯</span>
@@ -797,15 +774,15 @@
</div>
<div class="info-item">
<span class="info-icon">📅</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else 'Unknown Date' }}</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</span>
</div>
<div class="info-item">
<span class="info-icon">🏁</span>
<span class="info-value">{{ 'Finished' if tournament.tournament_finished else 'Incomplete' }}</span>
<span class="info-value">{{ (translations.tournament.finished if translations else 'Finished') if tournament.tournament_finished else (translations.scoring.in_progress if translations else 'Incomplete') }}</span>
</div>
</div>
<div class="archive-actions">
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 View</a>
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 <span data-i18n="general.view">View</span></a>
</div>
</div>
</div>
@@ -819,23 +796,23 @@
{% if tournaments_4 %}
<div class="section">
<div class="section-title">
<span>🎯 4-Target Tournaments</span>
<span data-i18n="tournament_types.4_target_tournaments">🎯 4-Tarčni Turnirji</span>
</div>
<div class="archive-grid" id="tournaments-4-grid">
{% for tournament in tournaments_4 %}
<div class="archive-card tournament-4" data-status="{{ 'finished' if tournament.tournament_finished else 'incomplete' }}" data-date="{{ tournament.archived_at }}" data-targets="4" onclick="viewTournament('{{ tournament.filename }}')">
<div class="archive-header">
<div>
<div class="archive-title">4-Target Tournament</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else 'Unknown Date' }}</div>
<div class="archive-title" data-i18n="messages.4_target_tournament">4-Tarčni Turnir</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</div>
</div>
<span class="archive-badge badge-4-targets">4 Targets</span>
<span class="archive-badge badge-4-targets" data-i18n="tournament_types.4_targets">4 Targets</span>
</div>
<div class="archive-content">
<div class="archive-info">
<div class="info-item">
<span class="info-icon">👥</span>
<span class="info-value">{{ tournament.participants_count }} players</span>
<span class="info-value">{{ tournament.participants_count }} <span data-i18n="players.players_label">players</span></span>
</div>
<div class="info-item">
<span class="info-icon">🎯</span>
@@ -843,15 +820,15 @@
</div>
<div class="info-item">
<span class="info-icon">📅</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else 'Unknown Date' }}</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</span>
</div>
<div class="info-item">
<span class="info-icon">🏁</span>
<span class="info-value">{{ 'Finished' if tournament.tournament_finished else 'Incomplete' }}</span>
<span class="info-value">{{ (translations.tournament.finished if translations else 'Finished') if tournament.tournament_finished else (translations.scoring.in_progress if translations else 'Incomplete') }}</span>
</div>
</div>
<div class="archive-actions">
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 View</a>
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 <span data-i18n="general.view">View</span></a>
</div>
</div>
</div>
@@ -865,23 +842,23 @@
{% if tournaments_other %}
<div class="section">
<div class="section-title">
<span>🏆 Other Tournaments</span>
<span data-i18n="messages.other_tournaments">🏆 Drugi Turnirji</span>
</div>
<div class="archive-grid" id="tournaments-other-grid">
{% for tournament in tournaments_other %}
<div class="archive-card tournament-other" data-status="{{ 'finished' if tournament.tournament_finished else 'incomplete' }}" data-date="{{ tournament.archived_at }}" data-targets="other" onclick="viewTournament('{{ tournament.filename }}')">
<div class="archive-header">
<div>
<div class="archive-title">Tournament ({{ tournament.tournament_type or 'Unknown' }})</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else 'Unknown Date' }}</div>
<div class="archive-title">{{ translations.tournament.tournament if translations else 'Turnir' }} ({{ tournament.tournament_type or (translations.messages.unknown if translations else 'Neznano') }})</div>
<div class="archive-subtitle">{{ tournament.created_at[:10] if tournament.created_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</div>
</div>
<span class="archive-badge badge-tournament">Tournament</span>
<span class="archive-badge badge-tournament" data-i18n="tournament.tournament">Turnir</span>
</div>
<div class="archive-content">
<div class="archive-info">
<div class="info-item">
<span class="info-icon">👥</span>
<span class="info-value">{{ tournament.participants_count }} players</span>
<span class="info-value">{{ tournament.participants_count }} <span data-i18n="players.players_label">players</span></span>
</div>
<div class="info-item">
<span class="info-icon">🎯</span>
@@ -889,15 +866,15 @@
</div>
<div class="info-item">
<span class="info-icon">📅</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else 'Unknown Date' }}</span>
<span class="info-value">{{ tournament.archived_at[:10] if tournament.archived_at != 'Unknown' else translations.messages.unknown_date if translations else 'Neznan Datum' }}</span>
</div>
<div class="info-item">
<span class="info-icon">🏁</span>
<span class="info-value">{{ 'Finished' if tournament.tournament_finished else 'Incomplete' }}</span>
<span class="info-value">{{ (translations.tournament.finished if translations else 'Finished') if tournament.tournament_finished else (translations.scoring.in_progress if translations else 'Incomplete') }}</span>
</div>
</div>
<div class="archive-actions">
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 View</a>
<a href="/archive/tournament/{{ tournament.filename }}" class="action-btn view-btn">📊 <span data-i18n="general.view">View</span></a>
</div>
</div>
</div>
@@ -913,9 +890,9 @@
<div class="section">
<div class="empty-state">
<div class="empty-icon">📚</div>
<h3>No Archives Found</h3>
<p>Complete some tournaments or leagues to see them archived here</p>
<a href="/tournament" class="nav-btn primary" style="margin-top: 15px;">🏆 Start Tournament</a>
<h3 data-i18n="messages.no_archives_found">Ni Najdenih Arhivov</h3>
<p data-i18n="messages.complete_tournaments_msg">Zaključi nekaj turnirjev ali lig, da jih vidiš arhivirane tukaj</p>
<a href="/tournament" class="nav-btn primary" style="margin-top: 15px;" data-i18n="messages.start_tournament">🏆 Začni Turnir</a>
</div>
</div>
{% endif %}
@@ -924,11 +901,11 @@
<!-- Confirmation Modal -->
<div class="modal-overlay" id="confirmationModal">
<div class="modal">
<div class="modal-title" id="modalTitle">Confirm Action</div>
<div class="modal-message" id="modalMessage">Are you sure you want to proceed?</div>
<div class="modal-title" id="modalTitle" data-i18n="messages.confirm_action">Potrdi Dejanje</div>
<div class="modal-message" id="modalMessage" data-i18n="messages.are_you_sure_proceed">Ali ste prepričani, da želite nadaljevati?</div>
<div class="modal-buttons">
<button class="modal-btn cancel" onclick="closeModal()">Cancel</button>
<button class="modal-btn confirm" id="confirmBtn" onclick="confirmAction()">Confirm</button>
<button class="modal-btn cancel" onclick="closeModal()" data-i18n="general.cancel">Prekliči</button>
<button class="modal-btn confirm" id="confirmBtn" onclick="confirmAction()" data-i18n="general.confirm">Potrdi</button>
</div>
</div>
</div>
@@ -936,24 +913,24 @@
<!-- Edit Modal -->
<div class="modal-overlay" id="editModal">
<div class="modal">
<div class="modal-title" id="editModalTitle">Edit Archive</div>
<div class="modal-title" id="editModalTitle" data-i18n="messages.edit_archive">Uredi Arhiv</div>
<form class="edit-form" id="editForm">
<div class="form-group">
<label class="form-label">Archive Name</label>
<input type="text" class="form-input" id="editName" placeholder="Enter archive name">
<label class="form-label" data-i18n="messages.archive_name">Ime Arhiva</label>
<input type="text" class="form-input" id="editName" data-i18n="[placeholder]messages.enter_archive_name" placeholder="Vnesite ime arhiva">
</div>
<div class="form-group">
<label class="form-label">Format Type</label>
<label class="form-label" data-i18n="messages.format_type">Tip Formata</label>
<select class="form-select" id="editType">
<option value="single_elimination">Single Elimination</option>
<option value="double_elimination">Double Elimination</option>
<option value="round_robin">Round Robin</option>
<option value="swiss">Swiss System</option>
<option value="league">League Championship</option>
<option value="single_elimination" data-i18n="messages.single_elimination">Ena Eliminacija</option>
<option value="double_elimination" data-i18n="messages.double_elimination">Dvojna Eliminacija</option>
<option value="round_robin" data-i18n="messages.round_robin">Vsak z Vsakim</option>
<option value="swiss" data-i18n="messages.swiss_system">Švicarski Sistem</option>
<option value="league" data-i18n="messages.league_championship">League Championship</option>
</select>
</div>
<div class="form-group" id="targetCountGroup">
<label class="form-label">Target Format</label>
<label class="form-label" data-i18n="messages.target_format">Format Tarč</label>
<select class="form-select" id="editTargetCount">
<option value="4_targets">4 Targets (5 shots each)</option>
<option value="20_targets">20 Targets (2 shots each)</option>
@@ -966,8 +943,8 @@
</div>
</form>
<div class="modal-buttons">
<button class="modal-btn cancel" onclick="closeEditModal()">Cancel</button>
<button class="modal-btn primary" onclick="saveEdit()">Save Changes</button>
<button class="modal-btn cancel" onclick="closeEditModal()" data-i18n="general.cancel">Prekliči</button>
<button class="modal-btn primary" onclick="saveEdit()" data-i18n="messages.save_changes">Shrani Spremembe</button>
</div>
</div>
</div>
@@ -988,8 +965,8 @@
// Delete function
async function deleteArchive(type, filename) {
showModal(
'Delete Archive',
`Are you sure you want to delete this ${type}? This action cannot be undone.`,
t('messages.delete_archive'),
`${t('messages.are_you_sure_delete')} ${type}? ${t('messages.action_cannot_undone')}.`,
async () => {
try {
const response = await fetch(`/api/archive/delete/${type}/${filename}`, {
@@ -999,7 +976,7 @@
const result = await response.json();
if (result.status === 'success') {
alert('Archive deleted successfully');
alert(t('messages.archive_deleted'));
location.reload();
} else {
alert('Error deleting archive: ' + result.message);
@@ -1072,5 +1049,20 @@
}
});
</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 ready
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
}
</script>
</body>
</html>
+334 -126
View File
@@ -3,8 +3,25 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Player Analysis - Camera Dashboard</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<title data-i18n="players.player_analysis">👤 Player Analysis - Camera Dashboard</title>
<script src="/static/js/chart.min.js"></script>
<script>
// Fallback: if Chart.js fails to load, create a simple notification
if (typeof Chart === 'undefined') {
console.warn('Chart.js failed to load - charts will be unavailable');
window.Chart = {
register: () => {},
Chart: function() {
return {
update: () => {},
destroy: () => {},
data: {},
options: {}
};
}
};
}
</script>
<style>
* {
margin: 0;
@@ -65,14 +82,14 @@
color: #007bff;
}
.nav-btn.secondary {
background: #6c757d;
border-color: #495057;
.nav-btn.active {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.secondary:hover {
background: #495057;
.nav-btn.active:hover {
background: #0056b3;
color: white;
}
@@ -165,106 +182,223 @@
/* Tournament Leaders Section */
.tournament-leaders {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 30px;
margin-bottom: 30px;
}
.tournament-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
border-radius: 20px;
padding: 35px;
box-shadow:
0 12px 40px rgba(0, 0, 0, 0.15),
0 6px 20px rgba(0, 0, 0, 0.1);
border: none;
position: relative;
overflow: hidden;
transition: box-shadow 0.3s ease;
width: 100%;
margin: 0;
}
.tournament-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #ffc107, #fd7e14);
.tournament-card:hover {
box-shadow:
0 15px 45px rgba(0, 0, 0, 0.15),
0 8px 25px rgba(0, 0, 0, 0.1);
}
/* Different color themes for each tournament type */
.tournament-card[data-type="20_targets"] {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
}
.tournament-card[data-type="40_targets"] {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.tournament-card[data-type="4_targets"] {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.tournament-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
margin-bottom: 28px;
position: relative;
z-index: 3;
}
.tournament-name {
font-size: 1.2rem;
font-weight: bold;
color: #333;
font-size: 1.6rem;
font-weight: 800;
color: white;
text-shadow:
0 2px 4px rgba(0, 0, 0, 0.3),
0 1px 2px rgba(0, 0, 0, 0.2);
position: relative;
display: flex;
align-items: center;
gap: 12px;
}
.tournament-card[data-type="20_targets"] .tournament-name::before {
content: '⚡';
font-size: 1.8rem;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.tournament-card[data-type="40_targets"] .tournament-name::before {
content: '💪';
font-size: 1.8rem;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.tournament-card[data-type="4_targets"] .tournament-name::before {
content: '🎯';
font-size: 1.8rem;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.tournament-date {
background: #f8f9fa;
color: #666;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
color: white;
padding: 8px 16px;
border-radius: 25px;
font-size: 0.75rem;
font-weight: 700;
box-shadow:
0 4px 15px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
text-transform: uppercase;
letter-spacing: 0.8px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.leaders-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
gap: 25px;
position: relative;
z-index: 3;
}
.leader-category {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
background: #ffffff;
border-radius: 16px;
padding: 28px;
text-align: center;
border: 2px solid #e9ecef;
transition: box-shadow 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
}
.leader-category:hover {
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1);
}
.leader-category {
border-color: #e9ecef;
}
/* Dynamic colors based on tournament type */
.tournament-card[data-type="20_targets"] .leader-category::before {
background: linear-gradient(90deg, #ff6b6b 0%, #ff8e53 100%);
}
.tournament-card[data-type="40_targets"] .leader-category::before {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
}
.tournament-card[data-type="4_targets"] .leader-category::before {
background: linear-gradient(90deg, #11998e 0%, #38ef7d 100%);
}
.leader-category::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
border-radius: 16px 16px 0 0;
}
.category-title {
font-size: 0.85rem;
font-weight: 600;
color: #666;
margin-bottom: 8px;
font-size: 0.8rem;
font-weight: 700;
color: #6c757d;
margin-bottom: 18px;
text-transform: uppercase;
letter-spacing: 0.5px;
letter-spacing: 1px;
position: relative;
}
.leader-info {
display: flex;
flex-direction: column;
gap: 4px;
gap: 10px;
}
.leader-name {
font-size: 1.1rem;
font-weight: bold;
color: #333;
font-weight: 600;
color: #2c3e50;
margin-bottom: 8px;
line-height: 1.3;
}
.leader-score {
font-size: 1.3rem;
font-weight: bold;
color: #007bff;
font-size: 1.8rem;
font-weight: 800;
padding: 12px 20px;
border-radius: 12px;
border: 2px solid;
position: relative;
}
.leader-score.tens {
color: #28a745;
/* Score colors based on tournament type */
.tournament-card[data-type="20_targets"] .leader-score {
border-color: #ff6b6b;
background: linear-gradient(135deg, #fff5f5 0%, #ffe8e8 100%);
color: #e53e3e;
}
/* Compact Stats Overview */
.stats-overview {
.tournament-card[data-type="40_targets"] .leader-score {
border-color: #667eea;
background: linear-gradient(135deg, #f8f9ff 0%, #e8ebff 100%);
color: #667eea;
}
.tournament-card[data-type="4_targets"] .leader-score {
border-color: #11998e;
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
color: #059669;
}
/* Yellow styling for 10s (Most 10s category) */
.leader-category:nth-child(2) .leader-score {
border-color: #fbbf24 !important;
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%) !important;
color: #d97706 !important;
}
.leader-category:nth-child(2)::before {
background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%) !important;
}
/* Stats Overview */
.stats-badges {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
.stat-badge {
background: white;
border-radius: 12px;
padding: 15px;
@@ -274,7 +408,7 @@
transition: transform 0.3s ease;
}
.stat-card:hover {
.stat-badge:hover {
transform: translateY(-2px);
}
@@ -284,7 +418,7 @@
display: block;
}
.stat-value {
.stat-number {
font-size: 1.5rem;
font-weight: bold;
color: #333;
@@ -562,16 +696,44 @@
gap: 8px;
}
.stats-overview {
.stats-badges {
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
}
.tournament-leaders {
grid-template-columns: 1fr;
gap: 20px;
}
.tournament-card {
padding: 25px;
margin: 0;
}
.tournament-name {
font-size: 1.3rem;
gap: 8px;
}
.tournament-header {
flex-direction: column;
align-items: center;
gap: 12px;
text-align: center;
}
.leaders-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.leader-category {
padding: 24px;
}
.leader-score {
font-size: 1.6rem;
padding: 10px 16px;
}
.tab-navigation {
@@ -589,53 +751,55 @@
<body>
<!-- Navigation Bar -->
<div class="navbar">
<div class="navbar-title">🎯 Player Analysis</div>
<div class="navbar-title" data-i18n="players.player_analysis">🎯 👤 Player Analysis</div>
<div class="navbar-controls">
<a href="/" class="nav-btn primary">Dashboard</a>
<a href="/archive" class="nav-btn secondary">📚 Archive</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 active">👤 <span data-i18n="players.player_analysis">Player Analysis</span></a>
<a href="/archive" class="nav-btn">📚 <span data-i18n="navigation.archive">Archive</span></a>
</div>
</div>
<div class="container">
<!-- Stats Overview -->
<div class="stats-overview">
<div class="stat-card">
<div class="stats-badges">
<div class="stat-badge">
<span class="stat-icon">👥</span>
<div class="stat-value" id="totalPlayers">Loading...</div>
<div class="stat-label">Total Players</div>
<div class="stat-number" id="totalPlayers">Loading...</div>
<div class="stat-label" data-i18n="players.total_players">Total Players</div>
</div>
<div class="stat-card">
<div class="stat-badge">
<span class="stat-icon">🎯</span>
<div class="stat-value" id="totalTournaments">Loading...</div>
<div class="stat-label">Total Tournaments</div>
<div class="stat-number" id="totalTournaments">Loading...</div>
<div class="stat-label" data-i18n="analysis.total_tournaments">Total Tournaments</div>
</div>
<div class="stat-card">
<div class="stat-badge">
<span class="stat-icon">🏆</span>
<div class="stat-value" id="score20Targets">Loading...</div>
<div class="stat-label">Best 20 Targets</div>
<div class="stat-number" id="score20Targets">Loading...</div>
<div class="stat-label" data-i18n="tournament_types.20_targets">20 Targets</div>
</div>
<div class="stat-card">
<div class="stat-badge">
<span class="stat-icon">🎖️</span>
<div class="stat-value" id="score40Targets">Loading...</div>
<div class="stat-label">Best 40 Targets</div>
<div class="stat-number" id="score40Targets">Loading...</div>
<div class="stat-label" data-i18n="tournament_types.40_targets">40 Targets</div>
</div>
<div class="stat-card">
<div class="stat-badge">
<span class="stat-icon">🥇</span>
<div class="stat-value" id="score4Targets">Loading...</div>
<div class="stat-label">Best 4 Targets</div>
<div class="stat-number" id="score4Targets">Loading...</div>
<div class="stat-label" data-i18n="tournament_types.4_targets">4 Targets</div>
</div>
</div>
<!-- Tab Navigation -->
<div class="section">
<div class="tab-navigation">
<button class="tab-btn active" onclick="switchTab('tournament-leaders')">🏆 Overall Champions</button>
<button class="tab-btn" onclick="switchTab('players')">👥 All Players</button>
<button class="tab-btn active" onclick="switchTab('tournament-leaders')" data-i18n="analysis.overall_champions">🏆 Overall Champions</button>
<button class="tab-btn" onclick="switchTab('players')" data-i18n="analysis.all_players">👥 All Players</button>
</div>
<!-- Tournament Leaders Tab -->
<div id="tournament-leaders" class="tab-content active">
<div class="section-title">Overall Champions by Tournament Type</div>
<div class="section-title" data-i18n="analysis.overview_champions">Overall Champions by Tournament Type</div>
<div class="tournament-leaders" id="tournamentLeaders">
<div class="loading">
@@ -647,19 +811,19 @@
<!-- Players Tab -->
<div id="players" class="tab-content">
<div class="section-title">Select a Player to Analyze</div>
<div class="section-title" data-i18n="analysis.select_player">Izberi igralca za analizo</div>
<!-- Controls -->
<div class="controls">
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search players by name...">
<input type="text" class="search-box" id="searchBox" data-i18n-placeholder="league.search_players_placeholder" placeholder="🔍 Search players by name...">
<select class="sort-select" id="sortSelect">
<option value="name">Sort by Name</option>
<option value="best_score">Sort by Best Score</option>
<option value="average_score">Sort by Average Score</option>
<option value="total_tournaments">Sort by Total Tournaments</option>
<option value="total_leagues">Sort by Total Leagues</option>
<option value="total_shots">Sort by Total Shots</option>
<option value="name" data-i18n="analysis.sort_by_name">Sort by Name</option>
<option value="best_score" data-i18n="analysis.sort_by_best_score">Sort by Best Score</option>
<option value="average_score" data-i18n="analysis.sort_by_average_score">Sort by Average Score</option>
<option value="total_tournaments" data-i18n="analysis.sort_by_total_tournaments">Sort by Total Tournaments</option>
<option value="total_leagues" data-i18n="analysis.sort_by_total_leagues">Sort by Total Leagues</option>
<option value="total_shots" data-i18n="analysis.sort_by_total_shots">Sort by Total Shots</option>
</select>
</div>
@@ -724,7 +888,7 @@
document.getElementById('tournamentLeaders').innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎯</div>
<h3>Unable to Load Tournament Data</h3>
<h3 data-i18n="analysis.unable_load_data">Ne morem naložiti podatkov turnirja</h3>
<p>${result.message || 'Please try refreshing the page'}</p>
</div>
`;
@@ -745,48 +909,83 @@
// Render tournament leaders
function renderTournamentLeaders() {
const leadersContainer = document.getElementById('tournamentLeaders');
if (tournamentData.length === 0) {
leadersContainer.innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎯</div>
<h3>No Tournament Data Available</h3>
<h3 data-i18n="analysis.no_tournament_data">Ni podatkov o turnirju</h3>
<p>Tournament results will appear here once available</p>
</div>
`;
return;
}
leadersContainer.innerHTML = tournamentData.map(tournamentType => `
<div class="tournament-card">
// Sort tournament types by target count: 4, 20, 40
const sortOrder = ['4_targets', '20_targets', '40_targets'];
const sortedTournamentData = [...tournamentData].sort((a, b) => {
const indexA = sortOrder.indexOf(a.id);
const indexB = sortOrder.indexOf(b.id);
// If not in sortOrder, put at end
if (indexA === -1 && indexB === -1) return 0;
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
});
leadersContainer.innerHTML = sortedTournamentData.map(tournamentType => {
// Get translated tournament name and description based on tournament type
let translatedName = tournamentType.name;
let translatedDescription = tournamentType.description;
switch(tournamentType.id) {
case '4_targets':
translatedName = t('tournament_types.4_targets');
translatedDescription = t('tournament_types.4_targets_desc');
break;
case '20_targets':
translatedName = t('tournament_types.20_targets');
translatedDescription = t('tournament_types.20_targets_desc');
break;
case '40_targets':
translatedName = t('tournament_types.40_targets');
translatedDescription = t('tournament_types.40_targets_desc');
break;
}
return `
<div class="tournament-card" data-type="${tournamentType.id}">
<div class="tournament-header">
<div class="tournament-name">${tournamentType.name}</div>
<div class="tournament-date">${tournamentType.total_tournaments} tournament${tournamentType.total_tournaments !== 1 ? 's' : ''}</div>
<div class="tournament-name">${translatedName}</div>
<div class="tournament-date">${tournamentType.total_tournaments} ${tournamentType.total_tournaments === 1 ? t('tournament.tournament') : t('tournament.tournaments')}</div>
</div>
<div style="text-align: center; margin-bottom: 15px; color: #666; font-size: 0.9rem; font-weight: 500;">
${tournamentType.description}
<div style="text-align: center; margin-bottom: 28px; color: rgba(255, 255, 255, 0.95); font-size: 1rem; font-weight: 500; position: relative; z-index: 3; background: rgba(0, 0, 0, 0.15); padding: 14px 24px; border-radius: 14px; backdrop-filter: blur(15px); border: 1px solid rgba(255, 255, 255, 0.25); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);">
${translatedDescription}
</div>
<div class="leaders-grid">
<div class="leader-category">
<div class="category-title">🏆 Best Score</div>
<div class="category-title">🏆 ${t('analysis.best_score')}</div>
<div class="leader-info">
<div class="leader-name">${tournamentType.best_score.player_name || 'No data'}</div>
<div class="leader-name">${tournamentType.best_score.player_name || t('messages.no_data')}</div>
<div class="leader-score">${tournamentType.best_score.score || 0}</div>
</div>
</div>
<div class="leader-category">
<div class="category-title">🎪 Most 10s</div>
<div class="category-title">🎯 ${t('analysis.most_tens')}</div>
<div class="leader-info">
<div class="leader-name">${tournamentType.most_tens.player_name || 'No data'}</div>
<div class="leader-score tens">${tournamentType.most_tens.tens || 0} 10s</div>
<div class="leader-name">${tournamentType.most_tens.player_name || t('messages.no_data')}</div>
<div class="leader-score tens">${tournamentType.most_tens.tens || 0}x 🎯</div>
</div>
</div>
</div>
</div>
`).join('');
`;
}).join('');
}
// Update overall statistics
@@ -795,12 +994,12 @@
let score20Targets = 0;
let score40Targets = 0;
let score4Targets = 0;
// Calculate statistics across all tournament types
tournamentData.forEach(tournamentType => {
totalTournaments += tournamentType.total_tournaments || 0;
// Get best scores for each tournament type
// Get best scores for each tournament type using the id field
if (tournamentType.id === '20_targets' && tournamentType.best_score) {
score20Targets = tournamentType.best_score.score || 0;
} else if (tournamentType.id === '40_targets' && tournamentType.best_score) {
@@ -809,7 +1008,7 @@
score4Targets = tournamentType.best_score.score || 0;
}
});
document.getElementById('totalTournaments').textContent = totalTournaments;
document.getElementById('score20Targets').textContent = score20Targets || '-';
document.getElementById('score40Targets').textContent = score40Targets || '-';
@@ -819,16 +1018,16 @@
// Get tournament type display name
function getTournamentTypeDisplay(tournamentType) {
const typeMap = {
'20_targets': '20 Targets (2 shots each)',
'40_targets': '40 Targets (2 shots each)',
'4_targets': '4 Targets (5 shots each)'
'20_targets': t('tournament_types.20_targets_full'),
'40_targets': t('tournament_types.40_targets_full'),
'4_targets': t('tournament_types.4_targets_full')
};
return typeMap[tournamentType] || tournamentType;
}
// Format date for display
function formatDate(dateString) {
if (!dateString) return 'Unknown Date';
if (!dateString) return t('analysis.unknown_date');
try {
const date = new Date(dateString);
@@ -847,7 +1046,7 @@
});
}
}
return 'Unknown Date';
return t('analysis.unknown_date');
}
return date.toLocaleDateString('en-US', {
@@ -954,30 +1153,27 @@
onclick="viewPlayerStats(${player.id})">
<div class="player-header">
<div class="player-name">${player.name}</div>
<div class="player-badge ${player.current_player ? 'current' : 'archived'}">
${player.current_player ? 'Current' : 'Archived'}
</div>
</div>
<div class="player-stats">
<div class="stat-item">
<span class="icon">🎯</span>
<span class="label">Tournaments:</span>
<span class="label">${t('tournament.tournaments')}:</span>
<span class="value">${player.stats?.total_tournaments || 0}</span>
</div>
<div class="stat-item">
<span class="icon">🏆</span>
<span class="label">Leagues:</span>
<span class="label">${t('league.league')}:</span>
<span class="value">${player.stats?.total_leagues || 0}</span>
</div>
<div class="stat-item">
<span class="icon">📈</span>
<span class="label">Best Score:</span>
<span class="label">${t('analysis.best_score_label')}</span>
<span class="value">${player.stats?.best_tournament_score || 0}</span>
</div>
<div class="stat-item">
<span class="icon">📊</span>
<span class="label">Average:</span>
<span class="label">${t('results.average_score')}</span>
<span class="value">${Math.round(player.stats?.average_tournament_score || 0)}</span>
</div>
</div>
@@ -1023,30 +1219,27 @@
onclick="viewPlayerStats(${player.id})">
<div class="player-header">
<div class="player-name">${player.name}</div>
<div class="player-badge ${player.enabled ? 'current' : 'archived'}">
${player.enabled ? 'Current' : 'Archived'}
</div>
</div>
<div class="player-stats">
<div class="stat-item">
<span class="icon">🎯</span>
<span class="label">Tournaments:</span>
<span class="label">${t('tournament.tournaments')}:</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">🏆</span>
<span class="label">Leagues:</span>
<span class="label">${t('league.league')}:</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">📈</span>
<span class="label">Best Score:</span>
<span class="label">${t('analysis.best_score_label')}</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">📊</span>
<span class="label">Average:</span>
<span class="label">${t('results.average_score')}</span>
<span class="value">Loading...</span>
</div>
</div>
@@ -1125,5 +1318,20 @@
// Initialize page when DOM is loaded
document.addEventListener('DOMContentLoaded', initializePage);
</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 ready
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
}
</script>
</body>
</html>
+226 -95
View File
@@ -15,9 +15,8 @@
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
height: 100vh;
min-height: 100vh;
color: #333;
overflow: hidden;
}
/* Enhanced Navigation Bar */
@@ -33,14 +32,14 @@
}
.navbar-title {
font-size: 1.5rem;
font-size: 1.8rem;
font-weight: bold;
color: #333;
}
.navbar-controls {
display: flex;
gap: 10px;
gap: 12px;
align-items: center;
}
@@ -66,34 +65,69 @@
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;
}
/* Main Container */
/* Standardized Container */
.container {
height: calc(100vh - 60px);
padding: 15px;
display: flex;
flex-direction: column;
gap: 15px;
overflow: hidden;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Top Section - Stats and Charts */
.top-section {
/* Stats Overview */
.stats-badges {
display: grid;
grid-template-columns: 320px 1fr;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
height: 50%;
min-height: 280px;
margin-bottom: 20px;
}
.stat-badge {
background: white;
border-radius: 12px;
padding: 15px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
transition: transform 0.3s ease;
}
.stat-badge:hover {
transform: translateY(-2px);
}
.stat-icon {
font-size: 1.5rem;
margin-bottom: 8px;
display: block;
}
.stat-number {
font-size: 1.5rem;
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 0.75rem;
font-weight: 500;
}
/* Charts Section */
.charts-section {
min-height: 450px;
flex-shrink: 0;
}
/* Stats Panel */
@@ -154,6 +188,7 @@
padding: 18px;
display: flex;
flex-direction: column;
width: 100%;
}
/* Tournament Type Buttons */
@@ -283,11 +318,24 @@
.chart-container {
position: relative;
flex: 1;
min-height: 200px;
min-height: 400px;
max-height: 500px;
background: white;
border-radius: 6px;
border: 1px solid #e9ecef;
padding: 10px;
padding: 15px;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.chart-container canvas {
max-width: calc(100% - 10px) !important;
max-height: calc(100% - 10px) !important;
width: 100% !important;
height: auto !important;
}
.no-data {
@@ -308,25 +356,31 @@
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
flex: 1;
min-height: 0;
min-height: 350px;
max-height: 450px;
flex-shrink: 0;
margin-top: 30px;
}
.history-section {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 18px;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
padding: 24px;
display: flex;
flex-direction: column;
min-height: 0;
min-height: 300px;
max-height: 100%;
border: 1px solid rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
.history-list {
flex: 1;
overflow-y: auto;
padding-right: 8px;
min-height: 0;
min-height: 200px;
max-height: 320px;
}
/* Custom scrollbar */
@@ -350,21 +404,41 @@
/* Tournament History Items */
.history-item {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%);
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
background: linear-gradient(135deg, #f8f9fa 0%, #e8f4fd 100%);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
transition: all 0.3s ease;
border-left: 3px solid #2196f3;
border-left: 4px solid #2196f3;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid rgba(33, 150, 243, 0.1);
position: relative;
overflow: hidden;
}
.history-item::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 3px;
height: 100%;
background: linear-gradient(135deg, #2196f3 0%, #64b5f6 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.history-item:hover::before {
opacity: 1;
}
.history-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.2);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(33, 150, 243, 0.15);
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border-color: rgba(33, 150, 243, 0.2);
}
.history-info {
@@ -393,18 +467,38 @@
/* League History Items */
.league-history-item {
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
background: linear-gradient(135deg, #f3e5f5 0%, #f0e6ff 100%);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
transition: all 0.3s ease;
border-left: 3px solid #9c27b0;
border-left: 4px solid #9c27b0;
border: 1px solid rgba(156, 39, 176, 0.1);
position: relative;
overflow: hidden;
}
.league-history-item::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 3px;
height: 100%;
background: linear-gradient(135deg, #9c27b0 0%, #ba68c8 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.league-history-item:hover::before {
opacity: 1;
}
.league-history-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(156, 39, 176, 0.2);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(156, 39, 176, 0.15);
background: linear-gradient(135deg, #e1bee7 0%, #ce93d8 100%);
border-color: rgba(156, 39, 176, 0.2);
}
.league-header {
@@ -504,7 +598,7 @@
@media (max-width: 1200px) {
.top-section {
grid-template-columns: 1fr;
height: 55%;
min-height: 320px;
}
}
@@ -522,7 +616,7 @@
}
.container {
height: calc(100vh - 80px);
min-height: calc(100vh - 100px);
padding: 10px;
}
@@ -535,8 +629,27 @@
}
.top-section {
height: auto;
min-height: 200px;
min-height: 250px;
}
.bottom-section {
min-height: 500px;
max-height: none;
}
.history-section {
min-height: 220px;
}
.history-list {
min-height: 150px;
max-height: 200px;
}
.chart-container {
max-height: 350px;
min-height: 300px;
padding: 10px;
}
}
</style>
@@ -544,62 +657,65 @@
<body>
<!-- Navigation Bar -->
<div class="navbar">
<div class="navbar-title">📊 {{ player.name }} - Stats</div>
<div class="navbar-title">📊 {{ player.name }} - <span data-i18n="players.player_stats">Stats</span></div>
<div class="navbar-controls">
<a href="/archive/player-analysis" class="nav-btn">👤 All Players</a>
<a href="/archive" class="nav-btn">📚 Archive</a>
<a href="/" class="nav-btn primary">🏠 Dashboard</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 active">👤 <span data-i18n="players.player_analysis">Player Analysis</span></a>
<a href="/archive" class="nav-btn">📚 <span data-i18n="navigation.archive">Archive</span></a>
</div>
</div>
<div class="container">
<!-- Top Section - Stats and Charts -->
<div class="top-section">
<!-- Stats Panel -->
<div class="stats-panel">
<div class="panel-title">📊 Statistics</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value">{{ stats.total_tournaments }}</div>
<div class="stat-label">Tournaments</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.total_leagues }}</div>
<div class="stat-label">Leagues</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.best_tournament_score }}</div>
<div class="stat-label">Best Score</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.average_tournament_score|round|int if stats.average_tournament_score > 0 else 0 }}</div>
<div class="stat-label">Average</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.total_shots_fired|default(0) }}</div>
<div class="stat-label">Total Shots</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ stats.worst_tournament_score if stats.worst_tournament_score > 0 else 0 }}</div>
<div class="stat-label">Worst Score</div>
</div>
</div>
<!-- Stats Overview -->
<div class="stats-badges">
<div class="stat-badge">
<span class="stat-icon">🏆</span>
<div class="stat-number">{{ stats.total_tournaments }}</div>
<div class="stat-label" data-i18n="tournament.tournaments">Tournaments</div>
</div>
<div class="stat-badge">
<span class="stat-icon">🎖️</span>
<div class="stat-number">{{ stats.total_leagues }}</div>
<div class="stat-label" data-i18n="league.league">Leagues</div>
</div>
<div class="stat-badge">
<span class="stat-icon"></span>
<div class="stat-number">{{ stats.best_tournament_score }}</div>
<div class="stat-label" data-i18n="results.best_score">Best Score</div>
</div>
<div class="stat-badge">
<span class="stat-icon">📊</span>
<div class="stat-number">{{ stats.average_tournament_score|round|int if stats.average_tournament_score > 0 else 0 }}</div>
<div class="stat-label" data-i18n="results.average_score">Average</div>
</div>
<div class="stat-badge">
<span class="stat-icon">🔫</span>
<div class="stat-number">{{ stats.total_shots_fired|default(0) }}</div>
<div class="stat-label" data-i18n="results.total_shots">Total Shots</div>
</div>
<div class="stat-badge">
<span class="stat-icon">📉</span>
<div class="stat-number">{{ stats.worst_tournament_score if stats.worst_tournament_score > 0 else 0 }}</div>
<div class="stat-label" data-i18n="results.worst_score">Worst Score</div>
</div>
</div>
<!-- Performance Charts -->
<!-- Charts Section -->
<div class="charts-section">
<div class="charts-panel">
<div class="panel-title">📈 Performance by Tournament Type</div>
<div class="panel-title">📈 <span data-i18n="analysis.performance">Performance by Tournament Type</span></div>
<!-- Tournament Type Buttons -->
<div class="tournament-type-buttons">
<button class="type-btn active targets-40" data-type="40 Targets">
<span>💪</span> 40 Targets
<span>💪</span> <span data-i18n="tournament_types.40_targets">40 Targets</span>
</button>
<button class="type-btn targets-20" data-type="20 Targets">
<span></span> 20 Targets
<span></span> <span data-i18n="tournament_types.20_targets">20 Targets</span>
</button>
<button class="type-btn targets-4" data-type="4 Targets">
<span>🎯</span> 4 Targets
<span>🎯</span> <span data-i18n="tournament_types.4_targets">4 Targets</span>
</button>
</div>
@@ -608,15 +724,15 @@
<div class="chart-stats">
<div class="chart-stat">
<div class="chart-stat-value" id="gameCount">0</div>
<div class="chart-stat-label">Games</div>
<div class="chart-stat-label" data-i18n="tournament.tournaments">Games</div>
</div>
<div class="chart-stat">
<div class="chart-stat-value" id="avgScore">0</div>
<div class="chart-stat-label">Average</div>
<div class="chart-stat-label" data-i18n="results.average_score">Average</div>
</div>
<div class="chart-stat">
<div class="chart-stat-value" id="bestScore">0</div>
<div class="chart-stat-label">Best</div>
<div class="chart-stat-label" data-i18n="results.best_score">Best</div>
</div>
</div>
@@ -680,14 +796,14 @@
<div class="bottom-section">
<!-- Tournament History -->
<div class="history-section">
<div class="panel-title">🎯 Tournament History</div>
<div class="panel-title">🎯 <span data-i18n="analysis.tournament_history">Tournament History</span></div>
{% if stats.tournament_history %}
<div class="history-list">
{% for tournament in stats.tournament_history[:10] %}
<div class="history-item">
<div class="history-info">
<div class="history-date">{{ tournament.date[:10] if tournament.date != 'Unknown' else 'Unknown Date' }}</div>
<div class="history-type">{{ tournament.tournament_type.replace('_', ' ')|title }} • {{ tournament.shots_fired }} shots</div>
<div class="history-date">{{ tournament.date[:10] if tournament.date != 'Unknown' else (translations.analysis.unknown_date if translations else 'Unknown Date') }}</div>
<div class="history-type">{{ tournament.tournament_type.replace('_', ' ')|title }} • {{ tournament.shots_fired }} {{ translations.results.shots if translations else 'shots' }}</div>
</div>
<div class="history-score">{{ tournament.score }}</div>
</div>
@@ -718,7 +834,7 @@
<div class="history-date">{{ league.date[:10] if league.date != 'Unknown' else 'Unknown Date' }}</div>
<div class="history-type">
League Championship • {{ league.tournaments_participated }}/6 tournaments
{% if league.joker_used %} • 🃏 Joker Used{% endif %}
{% if league.joker_used %} • 🃏 {{ translations.league.joker_used if translations else 'Joker Used' }}{% endif %}
</div>
</div>
<div class="league-score-display">{{ league.final_score }}</div>
@@ -732,7 +848,7 @@
<div class="tournament-results">
{% for result in league.tournament_results %}
<span class="tournament-result {{ 'participated' if result.participated else 'joker' }}">
T{{ result.tournament }}: {{ result.score if result.participated else 'Joker' }}
T{{ result.tournament }}: {{ result.score if result.participated else (translations.league.joker_used if translations else 'Joker') }}
</span>
{% endfor %}
</div>
@@ -1122,5 +1238,20 @@
// Initialize page when DOM is loaded
document.addEventListener('DOMContentLoaded', initializePage);
</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 ready
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
}
</script>
</body>
</html>
+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>
File diff suppressed because it is too large Load Diff
+186 -114
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tournament Management</title>
<title data-i18n="tournament.tournament_management">Tournament Management</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;
@@ -193,21 +182,85 @@
.type-option {
background: white;
border: 2px solid #dee2e6;
border-radius: 8px;
padding: 20px;
border-radius: 12px;
padding: 25px;
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.3s ease;
text-align: center;
position: relative;
overflow: hidden;
}
.type-option:hover {
border-color: #007bff;
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.15);
.type-option::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
border-radius: 12px 12px 0 0;
background: #dee2e6;
transition: all 0.3s ease;
}
.type-option.selected {
border-color: #007bff;
background: #f0f8ff;
/* 4 Targets - Green theme */
.type-option[data-type="4_targets"] {
border-color: #11998e;
}
.type-option[data-type="4_targets"]:hover {
border-color: #0f766e;
box-shadow: 0 4px 15px rgba(17, 153, 142, 0.25);
transform: translateY(-2px);
}
.type-option[data-type="4_targets"].selected {
border-color: #0f766e;
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
}
.type-option[data-type="4_targets"]::before {
background: linear-gradient(90deg, #11998e 0%, #38ef7d 100%);
}
/* 20 Targets - Red theme */
.type-option[data-type="20_targets"] {
border-color: #ff6b6b;
}
.type-option[data-type="20_targets"]:hover {
border-color: #e53e3e;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.25);
transform: translateY(-2px);
}
.type-option[data-type="20_targets"].selected {
border-color: #e53e3e;
background: linear-gradient(135deg, #fff5f5 0%, #ffe8e8 100%);
}
.type-option[data-type="20_targets"]::before {
background: linear-gradient(90deg, #ff6b6b 0%, #ff8e53 100%);
}
/* 40 Targets - Blue theme */
.type-option[data-type="40_targets"] {
border-color: #667eea;
}
.type-option[data-type="40_targets"]:hover {
border-color: #5a67d8;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.25);
transform: translateY(-2px);
}
.type-option[data-type="40_targets"].selected {
border-color: #5a67d8;
background: linear-gradient(135deg, #f8f9ff 0%, #e8ebff 100%);
}
.type-option[data-type="40_targets"]::before {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
}
.type-option input[type="radio"] {
@@ -294,11 +347,12 @@
padding: 15px 30px;
border-radius: 8px;
cursor: pointer;
font-size: 1.1rem;
font-size: 0.9rem;
font-weight: bold;
transition: all 0.2s ease;
min-width: 200px;
text-decoration: none;
font-family: Arial, sans-serif;
}
.action-btn:hover {
@@ -909,16 +963,19 @@
</head>
<body>
<div class="navbar">
<div class="navbar-title">🏆 Tournament Management</div>
<div class="navbar-title">🏆 <span data-i18n="tournament.tournament_management">Tournament Management</span></div>
<div class="navbar-controls">
<a href="/" class="nav-btn">← Dashboard</a>
<a href="/" class="nav-btn">📺 <span data-i18n="navigation.dashboard">Dashboard</span></a>
<a href="/tournament" class="nav-btn active">🏆 <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>
{% if league_state and not league_state.league_finished %}
<a href="/results/calculator" class="nav-btn primary">🎯 Results Calculator</a>
<button class="nav-btn danger" onclick="resetLeague()">🗑️ Reset League</button>
<a href="/results/calculator" class="nav-btn">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
<button class="nav-btn danger" onclick="resetLeague()"> <span data-i18n="league.reset_league">Reset League</span></button>
{% elif tournament_state %}
<a href="/tournament/draft" class="nav-btn">📋 View Draft</a>
<a href="/results/calculator" class="nav-btn primary">🎯 Results Calculator</a>
<button class="nav-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
<a href="/tournament/draft" class="nav-btn">📋 <span data-i18n="tournament.view_draft">Draft</span></a>
<a href="/results/calculator" class="nav-btn">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
<button class="nav-btn danger" onclick="resetTournament()">🗑️ <span data-i18n="tournament.reset_tournament">Reset Tournament</span></button>
{% endif %}
</div>
</div>
@@ -927,12 +984,12 @@
<!-- League/Tournament Management Section -->
<div class="section">
{% if league_state and not league_state.league_finished %}
<h2 class="section-title">🏆 League Management</h2>
<h2 class="section-title" data-i18n="league.league_management">🏆 Upravljanje Lige</h2>
<div class="league-status">
<div class="league-active">🏆 League Active</div>
<div class="league-active" data-i18n="league.league_active">🏆 League Active</div>
<div class="league-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-label" data-i18n="tournament.tournament_type">Tip Turnirja</div>
<div class="info-value">
{% if league_state.tournament_type == '40_targets' %}
40 Targets
@@ -944,19 +1001,19 @@
</div>
</div>
<div class="info-item">
<div class="info-label">Current Tournament</div>
<div class="info-label" data-i18n="tournament.current_tournament">Trenutni Turnir</div>
<div class="info-value">{{ league_state.current_tournament }} / {{ league_state.total_tournaments }}</div>
</div>
<div class="info-item">
<div class="info-label">Participants</div>
<div class="info-label" data-i18n="tournament.participants">Udeleženci</div>
<div class="info-value">{{ league_state.participants|length }}</div>
</div>
<div class="info-item">
<div class="info-label">Completed</div>
<div class="info-label" data-i18n="tournament.completed">Zaključeno</div>
<div class="info-value">{{ league_state.completed_tournaments|length }}</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-label" data-i18n="tournament.created">Ustvarjeno</div>
<div class="info-value">{{ league_state.created_at[:10] }}</div>
</div>
</div>
@@ -966,8 +1023,8 @@
{% if league_state.current_tournament < league_state.total_tournaments %}
<!-- Joker selection -->
<div class="joker-section" id="jokerSection">
<div class="joker-title">🃏 Joker Selection for Tournament {{ league_state.current_tournament + 1 }}</div>
<p style="margin-bottom: 15px; color: #856404;">Select players who will use their joker (skip this tournament). Each player can only use their joker once per league.</p>
<div class="joker-title">🃏 <span data-i18n="league.joker_selection_for_tournament">Izbira Jokerja za Turnir</span> {{ league_state.current_tournament + 1 }}</div>
<p style="margin-bottom: 15px; color: #856404;" data-i18n="league.joker_instructions">Izberite igralce, ki bodo uporabili svojega Jokerja (preskočili ta turnir). Vsak igralec lahko uporabi svojega Žolna samo enkrat na ligo.</p>
<div class="joker-grid">
{% for player_id, participant in league_state.participants.items() %}
@@ -985,12 +1042,12 @@
<div class="action-buttons">
<button class="action-btn success" onclick="startNextTournament()">
🚀 Start Tournament {{ league_state.current_tournament + 1 }}
🚀 <span data-i18n="league.start_tournament_number">Začni Turnir</span> {{ league_state.current_tournament + 1 }}
</button>
</div>
{% else %}
<div class="warning">
<strong>League Complete!</strong> All tournaments scheduled. Finish current one to see final results.
<strong data-i18n="league.league_complete">League Complete!</strong> <span data-i18n="league.league_complete_info">All tournaments planned. Finish current for final results.</span>
</div>
{% endif %}
{% endif %}
@@ -1002,7 +1059,7 @@
<div class="league-active">🏆 League Completed!</div>
<div class="league-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-label" data-i18n="tournament.tournament_type">Tip Turnirja</div>
<div class="info-value">
{% if league_state.tournament_type == '40_targets' %}
40 Targets
@@ -1014,32 +1071,32 @@
</div>
</div>
<div class="info-item">
<div class="info-label">Participants</div>
<div class="info-label" data-i18n="tournament.participants">Udeleženci</div>
<div class="info-value">{{ league_state.participants|length }}</div>
</div>
<div class="info-item">
<div class="info-label">Tournaments</div>
<div class="info-label" data-i18n="tournament.tournaments">Turnirji</div>
<div class="info-value">{{ league_state.total_tournaments }}</div>
</div>
<div class="info-item">
<div class="info-label">Finished</div>
<div class="info-label" data-i18n="tournament.finished">Zaključeno</div>
<div class="info-value">{{ league_state.finished_at[:10] if league_state.finished_at else 'Today' }}</div>
</div>
</div>
<div class="action-buttons">
<a href="/results" class="action-btn success">🏆 View League Results</a>
<button class="action-btn danger" onclick="resetLeague()">🗑️ Reset League</button>
<a href="/results" class="action-btn success">🏆 <span data-i18n="league.view_league_results">View League Results</span></a>
<button class="action-btn danger" onclick="resetLeague()">🗑️ <span data-i18n="league.reset_league">Reset League</span></button>
</div>
</div>
{% elif not league_state and tournament_state %}
<h2 class="section-title">🎯 Single Tournament Management</h2>
<h2 class="section-title" data-i18n="tournament.tournament_management">🎯 Upravljanje Turnirja</h2>
<div class="tournament-status">
<div class="tournament-active">🎯 Tournament Active</div>
<div class="tournament-active" data-i18n="tournament.active_tournament">🎯 Aktiven Turnir</div>
<div class="league-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-label" data-i18n="tournament.tournament_type">Tip Turnirja</div>
<div class="info-value">
{% if tournament_state.tournament_type == '40_targets' %}
40 Targets
@@ -1051,53 +1108,53 @@
</div>
</div>
<div class="info-item">
<div class="info-label">Players</div>
<div class="info-label" data-i18n="players.players_label">Igralci</div>
<div class="info-value">{{ tournament_state.total_players }}</div>
</div>
<div class="info-item">
<div class="info-label">Rounds</div>
<div class="info-label" data-i18n="tournament.total_rounds">Skupaj Krogov</div>
<div class="info-value">{{ tournament_state.total_rounds }}</div>
</div>
<div class="info-item">
<div class="info-label">Current Round</div>
<div class="info-label" data-i18n="tournament.current_round">Trenutni Krog</div>
<div class="info-value">{{ tournament_state.current_round }}</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-label" data-i18n="tournament.created">Ustvarjeno</div>
<div class="info-value">{{ tournament_state.created_at[:10] if tournament_state.created_at else 'Today' }}</div>
</div>
</div>
<div class="action-buttons">
<a href="/tournament/draft" class="action-btn">📋 View Draft</a>
<a href="/results/calculator" class="action-btn success">🎯 Score Tournament</a>
<a href="/" class="action-btn">📺 Dashboard</a>
<button class="action-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
<a href="/tournament/draft" class="action-btn">📋 <span data-i18n="navigation.draft">Žreb</span></a>
<a href="/results/calculator" class="action-btn success">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
<a href="/" class="action-btn">📺 <span data-i18n="navigation.dashboard">Nadzorna Plošča</span></a>
<button class="action-btn danger" onclick="resetTournament()">🗑️ <span data-i18n="tournament.reset_tournament">Reset Tournament</span></button>
</div>
</div>
{% else %}
<h2 class="section-title">🏁 Setup</h2>
<div class="league-inactive">No Active League or Tournament</div>
<h2 class="section-title" data-i18n="league.setup">🏁 Nastavitev</h2>
<div class="league-inactive" data-i18n="league.no_active_league_tournament">Ni Aktivne Lige ali Turnirja</div>
<!-- Tournament Type Selection -->
<div class="tournament-type-selection">
<div class="type-title">Select Tournament Type</div>
<div class="type-title" data-i18n="league.select_tournament_type">Izberi Tip Turnirja</div>
<div class="type-options">
<div class="type-option" onclick="selectTournamentType('4_targets')">
<div class="type-option" data-type="4_targets" onclick="selectTournamentType('4_targets')">
<input type="radio" name="tournament_type" value="4_targets">
<div class="type-name">🎯 4 Targets</div>
<div class="type-description">Quick format with 4 targets, 5 shots each <br> (20 shots total)</div>
<div class="type-description" data-i18n="tournament_types.4_targets_desc">Hitri format s 4 tarčami, 5 strelov na tarčo (20 strelov skupaj)</div>
</div>
<div class="type-option selected" onclick="selectTournamentType('20_targets')">
<div class="type-option selected" data-type="20_targets" onclick="selectTournamentType('20_targets')">
<input type="radio" name="tournament_type" value="20_targets" checked>
<div class="type-name">⚡ 20 Targets</div>
<div class="type-description">Standard format with 20 targets, 2 shots each (40 shots total)</div>
<div class="type-description" data-i18n="tournament_types.20_targets_desc">Standardni format z 20 tarčami, 2 strela na tarčo (40 strelov skupaj)</div>
</div>
<div class="type-option" onclick="selectTournamentType('40_targets')">
<div class="type-option" data-type="40_targets" onclick="selectTournamentType('40_targets')">
<input type="radio" name="tournament_type" value="40_targets">
<div class="type-name">💪 40 Targets</div>
<div class="type-description">Extended format with 40 targets, 2 shots each (80 shots total)</div>
<div class="type-description" data-i18n="tournament_types.40_targets_desc">Razširjeni format s 40 tarčami, 2 strela na tarčo (80 strelov skupaj)</div>
</div>
</div>
</div>
@@ -1107,8 +1164,8 @@
</div>
<div class="action-buttons">
<button class="action-btn success" id="startLeagueBtn" onclick="startLeague()">🏆 Start League (6 Tournaments)</button>
<button class="action-btn" id="startSingleBtn" onclick="startSingleTournament()">🏅 Start Single Tournament</button>
<button class="action-btn success" id="startLeagueBtn" onclick="startLeague()">🏆 <span data-i18n="league.start_league_5_tournaments">Začni Ligo (5 Turnirjev)</span></button>
<button class="action-btn" id="startSingleBtn" onclick="startSingleTournament()">🏅 <span data-i18n="league.start_single_tournament">Začni Posamezen Turnir</span></button>
</div>
<div class="warning" id="warningMessage" style="display: none;">
@@ -1123,10 +1180,10 @@
<h2 class="section-title">📋 Current Tournament</h2>
<div class="tournament-status">
<div class="tournament-active">🎯 Tournament Active</div>
<div class="tournament-active">🎯 <span data-i18n="tournament.active_tournament">Aktiven Turnir</span></div>
<div class="tournament-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-label" data-i18n="tournament.tournament_type">Tip Turnirja</div>
<div class="info-value">
{% if tournament_state.tournament_type == '40_targets' %}
40 Targets
@@ -1138,11 +1195,11 @@
</div>
</div>
<div class="info-item">
<div class="info-label">Total Players</div>
<div class="info-label" data-i18n="players.total_players">Skupaj Igralcev</div>
<div class="info-value">{{ tournament_state.total_players }}</div>
</div>
<div class="info-item">
<div class="info-label">Total Rounds</div>
<div class="info-label" data-i18n="tournament.total_rounds">Skupaj Krogov</div>
<div class="info-value">{{ tournament_state.total_rounds }}</div>
</div>
<div class="info-item">
@@ -1151,17 +1208,17 @@
</div>
{% if league_state %}
<div class="info-item">
<div class="info-label">League Tournament</div>
<div class="info-label" data-i18n="tournament.league_tournament">League Tournament</div>
<div class="info-value">{{ tournament_state.league_tournament_number or 'N/A' }}</div>
</div>
{% endif %}
</div>
<div class="action-buttons">
<a href="/tournament/draft" class="action-btn">📋 View Draft</a>
<a href="/results/calculator" class="action-btn success">🎯 Score Tournament</a>
<a href="/" class="action-btn">📺 Dashboard</a>
<button class="action-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
<a href="/tournament/draft" class="action-btn">📋 <span data-i18n="navigation.draft">Žreb</span></a>
<a href="/results/calculator" class="action-btn success">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
<a href="/" class="action-btn">📺 <span data-i18n="navigation.dashboard">Nadzorna Plošča</span></a>
<button class="action-btn danger" onclick="resetTournament()">🗑️ <span data-i18n="tournament.reset_tournament">Ponastavi Turnir</span></button>
</div>
</div>
</div>
@@ -1170,19 +1227,19 @@
<!-- Player Management Section - NEW LIST FORMAT -->
{% if not league_state and not tournament_state %}
<div class="section">
<h2 class="section-title">👥 Player Management</h2>
<h2 class="section-title" data-i18n="players.player_management">👥 Upravljanje Igralcev</h2>
<!-- Add Player Section -->
<div class="add-player-section">
<h3 style="margin: 0 0 15px 0; color: #333;">Add New Player</h3>
<h3 style="margin: 0 0 15px 0; color: #333;" data-i18n="league.add_new_player">Dodaj Novega Igralca</h3>
<div class="add-player-form">
<input type="text"
id="newPlayerName"
placeholder="Enter player name..."
data-i18n="[placeholder]league.enter_player_name" placeholder="Vnesite ime igralca..."
maxlength="50"
onkeypress="handleAddPlayerKeypress(event)">
<button class="add-btn" id="addPlayerBtn" onclick="addPlayer()">
Add Player
<span data-i18n="players.add_player"> Dodaj Igralca</span>
</button>
</div>
</div>
@@ -1193,21 +1250,21 @@
<input type="text"
class="search-input"
id="playerSearch"
placeholder="Search players by name..."
data-i18n="[placeholder]league.search_players_placeholder" placeholder="Išči igralce po imenu..."
oninput="filterPlayers()">
<span class="search-icon">🔍</span>
</div>
<div class="player-controls">
<button class="filter-btn active" data-filter="all" onclick="setFilter('all')">All</button>
<button class="filter-btn" data-filter="enabled" onclick="setFilter('enabled')">Enabled</button>
<button class="filter-btn" data-filter="disabled" onclick="setFilter('disabled')">Disabled</button>
<button class="filter-btn active" data-filter="all" onclick="setFilter('all')" data-i18n="general.all">Vse</button>
<button class="filter-btn" data-filter="enabled" onclick="setFilter('enabled')" data-i18n="players.enabled">Omogočen</button>
<button class="filter-btn" data-filter="disabled" onclick="setFilter('disabled')" data-i18n="players.disabled">Onemogočen</button>
</div>
<div class="bulk-actions">
<button class="bulk-btn" onclick="selectAllVisible()">Select All</button>
<button class="bulk-btn" onclick="enableSelected()">Enable Selected</button>
<button class="bulk-btn danger" onclick="disableSelected()">Disable Selected</button>
<button class="bulk-btn" onclick="selectAllVisible()" data-i18n="general.select_all">Izberi Vse</button>
<button class="bulk-btn" onclick="enableSelected()" data-i18n="general.enable_selected">Omogoči Izbrane</button>
<button class="bulk-btn danger" onclick="disableSelected()" data-i18n="general.disable_selected">Onemogoči Izbrane</button>
</div>
</div>
@@ -1215,19 +1272,19 @@
<div class="stats-summary">
<div class="stat-item">
<span class="stat-number stat-total" id="totalPlayersCount">0</span>
<span>Total Players</span>
<span data-i18n="players.total_players">Skupaj Igralcev</span>
</div>
<div class="stat-item">
<span class="stat-number stat-enabled" id="enabledPlayersCount">0</span>
<span>Enabled</span>
<span data-i18n="players.enabled">Omogočeni</span>
</div>
<div class="stat-item">
<span class="stat-number stat-disabled" id="disabledPlayersCount">0</span>
<span>Disabled</span>
<span data-i18n="players.disabled">Onemogočeni</span>
</div>
<div class="stat-item">
<span class="stat-number" id="visiblePlayersCount">0</span>
<span>Visible</span>
<span data-i18n="general.visible">Vidni</span>
</div>
</div>
@@ -1239,10 +1296,10 @@
<th style="width: 40px;">
<input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">
</th>
<th style="width: 60px;">ID</th>
<th>Name</th>
<th class="status-col" style="width: 120px;">Status</th>
<th class="actions-col" style="width: 200px;">Actions</th>
<th style="width: 60px;" data-i18n="league.id">ID</th>
<th data-i18n="league.name">Ime</th>
<th class="status-col" style="width: 120px;" data-i18n="general.status">Status</th>
<th class="actions-col" style="width: 200px;" data-i18n="general.actions">Dejanja</th>
</tr>
</thead>
<tbody id="playerTableBody">
@@ -1251,7 +1308,7 @@
</table>
<div class="no-results" id="noResults" style="display: none;">
No players found matching your search criteria.
<span data-i18n="league.no_players_found">Ni najdenih igralcev, ki bi ustrezali kriterijem iskanja.</span>
</div>
</div>
</div>
@@ -1392,7 +1449,7 @@
// Bulk actions
function enableSelected() {
if (selectedPlayers.size === 0) {
alert('No players selected');
alert(t('league.no_players_selected'));
return;
}
@@ -1405,7 +1462,7 @@
function disableSelected() {
if (selectedPlayers.size === 0) {
alert('No players selected');
alert(t('league.no_players_selected'));
return;
}
@@ -1506,7 +1563,7 @@
</td>
<td class="player-status status-col">
<span class="status-badge ${player.enabled ? 'status-enabled' : 'status-disabled'}">
${player.enabled ? '✓ Enabled' : '✗ Disabled'}
${player.enabled ? '✓' : '✗'}
</span>
</td>
<td class="player-actions actions-col">
@@ -1521,7 +1578,7 @@
<button class="action-btn-small edit-btn-small"
onclick="editPlayer(${player.id})"
title="Edit Player Name">
✏️ Edit
✏️ <span data-i18n="league.edit">Uredi</span>
</button>
<button class="action-btn-small delete-btn-small"
onclick="confirmDeletePlayer(${player.id})"
@@ -1576,7 +1633,8 @@
alert('Error adding player. Please try again.');
} finally {
addBtn.disabled = false;
addBtn.textContent = ' Add Player';
addBtn.innerHTML = '<span data-i18n="players.add_player"> Dodaj Igralca</span>';
translatePage();
}
}
@@ -1737,11 +1795,11 @@
return;
}
const formatText = selectedTournamentType === '40_targets' ? '40 targets (80 shots)' :
selectedTournamentType === '4_targets' ? '4 targets (20 shots)' :
'20 targets (40 shots)';
const formatText = selectedTournamentType === '40_targets' ? t('tournament_types.40_targets_full') :
selectedTournamentType === '4_targets' ? t('tournament_types.4_targets_full') :
t('tournament_types.20_targets_full');
if (!confirm(`Start league with ${enabledPlayers.length} players using ${formatText} format?`)) {
if (!confirm(t('messages.confirm_start_league', { players: enabledPlayers.length, format: formatText }))) {
return;
}
@@ -1771,7 +1829,9 @@
alert('Error starting league. Please try again.');
} finally {
startBtn.disabled = false;
startBtn.textContent = '🏆 Start League (6 Tournaments)';
startBtn.setAttribute('data-i18n', 'league.start_league_5_tournaments');
startBtn.textContent = '🎖️ Začni Ligo (5 Turnirjev)';
translatePage();
}
}
@@ -1784,11 +1844,11 @@
return;
}
const formatText = selectedTournamentType === '40_targets' ? '40 targets (80 shots)' :
selectedTournamentType === '4_targets' ? '4 targets (20 shots)' :
'20 targets (40 shots)';
const formatText = selectedTournamentType === '40_targets' ? t('tournament_types.40_targets_full') :
selectedTournamentType === '4_targets' ? t('tournament_types.4_targets_full') :
t('tournament_types.20_targets_full');
if (!confirm(`Start single tournament with ${enabledPlayers.length} players using ${formatText} format?`)) {
if (!confirm(t('messages.confirm_start_tournament', { players: enabledPlayers.length, format: formatText }))) {
return;
}
@@ -1828,7 +1888,10 @@
const jokerCheckboxes = document.querySelectorAll('.joker-checkbox:checked:not(:disabled)');
const jokerPlayers = Array.from(jokerCheckboxes).map(cb => parseInt(cb.dataset.playerId));
if (!confirm(`Start next tournament? ${jokerPlayers.length} players will use their joker.`)) {
const confirmMessage = jokerPlayers.length === 1 ?
t('league.start_tournament_confirm_single') :
t('league.start_tournament_confirm_multiple').replace('{count}', jokerPlayers.length);
if (!confirm(confirmMessage)) {
return;
}
@@ -1948,5 +2011,14 @@
console.log('Tournament active:', tournamentActive);
});
</script>
<!-- Include i18n support -->
<script src="/static/js/i18n.js"></script>
<script>
// Initialize language selector and i18n
document.addEventListener('DOMContentLoaded', function() {
translatePage();
});
</script>
</body>
</html>