1343 lines
37 KiB
HTML
1343 lines
37 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title data-i18n="league.league_results">League Results</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="stylesheet" href="/static/css/base.css">
|
|
<link rel="stylesheet" href="/static/css/navbar.css">
|
|
<link rel="stylesheet" href="/static/css/buttons.css">
|
|
<link rel="stylesheet" href="/static/css/components.css">
|
|
<link rel="stylesheet" href="/static/css/responsive.css">
|
|
<style>
|
|
/* League scoreboard specific styles */
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
background: #f5f5f5;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
color: #333;
|
|
}
|
|
|
|
.navbar {
|
|
background: white;
|
|
border-bottom: 1px solid #e1e5e9;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
padding: 15px 25px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.navbar-logo {
|
|
height: 40px;
|
|
max-width: 120px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.navbar-title {
|
|
font-size: 1.8rem;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-left: 15px;
|
|
}
|
|
|
|
.navbar-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.navbar-controls {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.nav-btn {
|
|
background: #f8f9fa;
|
|
border: 2px solid #e9ecef;
|
|
cursor: pointer;
|
|
padding: 12px 20px;
|
|
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.9rem;
|
|
}
|
|
|
|
.nav-btn:hover {
|
|
background: #e9ecef;
|
|
border-color: #28a745;
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
transform: translateY(-1px);
|
|
color: #28a745;
|
|
}
|
|
|
|
.nav-btn.primary {
|
|
background: #28a745;
|
|
border-color: #1e7e34;
|
|
color: white;
|
|
}
|
|
|
|
.nav-btn.primary:hover {
|
|
background: #1e7e34;
|
|
color: white;
|
|
}
|
|
|
|
/* Main League Layout */
|
|
.league-container {
|
|
height: calc(100vh - 90px);
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr;
|
|
gap: 20px;
|
|
padding: 20px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Left Column - Header & Champions */
|
|
.left-column {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.league-header {
|
|
border-radius: 12px 12px 0 0;
|
|
box-shadow: none;
|
|
padding: 20px;
|
|
text-align: center;
|
|
flex-shrink: 0;
|
|
color: white;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Dynamic header colors based on tournament type */
|
|
.league-header.tournament-4_targets {
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
}
|
|
|
|
.league-header.tournament-20_targets {
|
|
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
|
|
}
|
|
|
|
.league-header.tournament-40_targets {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
|
|
.league-header::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;
|
|
}
|
|
|
|
.league-header * {
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.header-logo {
|
|
height: 50px;
|
|
max-width: 150px;
|
|
object-fit: contain;
|
|
margin-bottom: 10px;
|
|
filter: brightness(1.2) contrast(1.1);
|
|
background-color: white;
|
|
padding: 6px;
|
|
border-radius: 6px;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.league-title {
|
|
font-size: 1.8rem;
|
|
font-weight: 700;
|
|
color: rgb(255, 255, 255);
|
|
margin-bottom: 8px;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.league-subtitle {
|
|
font-size: 0.9rem;
|
|
color: rgba(255, 255, 255, 0.95);
|
|
margin-bottom: 12px;
|
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.league-meta {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
gap: 10px;
|
|
}
|
|
|
|
.meta-item {
|
|
text-align: center;
|
|
}
|
|
|
|
.meta-number {
|
|
font-size: 1.2rem;
|
|
font-weight: 700;
|
|
color: #ffffff;
|
|
display: block;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.meta-label {
|
|
font-size: 0.65rem;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* League Champion Section */
|
|
.champion-section {
|
|
background: white;
|
|
border-radius: 0 0 12px 12px;
|
|
box-shadow: none;
|
|
padding: 12px;
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
min-height: 0;
|
|
border: 1px solid #e9ecef;
|
|
}
|
|
|
|
.champion-title {
|
|
text-align: center;
|
|
font-size: 0.9rem;
|
|
font-weight: 700;
|
|
color: #2c3e50;
|
|
margin-bottom: 8px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.champion-container {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
grid-template-rows: 0.75fr 0.75fr 1.5fr;
|
|
gap: 10px;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
min-height: 0;
|
|
}
|
|
|
|
.champion-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: none;
|
|
padding: 12px 12px 18px 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
border: 1px solid #e9ecef;
|
|
border-top-width: 6px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.champion-card::before {
|
|
display: none;
|
|
}
|
|
|
|
.champion-card:hover {
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.champion-card.rank-1 {
|
|
border-top-color: #ffd700;
|
|
border-color: #ffd700;
|
|
background: linear-gradient(135deg, #fffbf0 0%, #fff9e6 100%);
|
|
grid-column: 2;
|
|
grid-row: 1 / 4;
|
|
}
|
|
|
|
.champion-card.rank-2 {
|
|
border-top-color: #c0c0c0;
|
|
border-color: #c0c0c0;
|
|
background: linear-gradient(135deg, #f5f5f5 0%, #f0f0f0 100%);
|
|
grid-column: 1;
|
|
grid-row: 2 / 4;
|
|
}
|
|
|
|
.champion-card.rank-3 {
|
|
border-top-color: #cd7f32;
|
|
border-color: #cd7f32;
|
|
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 100%);
|
|
grid-column: 3;
|
|
grid-row: 3;
|
|
}
|
|
|
|
.rank-display {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 2px;
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.medal {
|
|
font-size: 4.5rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.rank-number {
|
|
font-size: 1.9rem;
|
|
font-weight: 900;
|
|
color: #333;
|
|
line-height: 1;
|
|
}
|
|
|
|
.champion-card.rank-1 .rank-number { color: #b8860b; }
|
|
.champion-card.rank-2 .rank-number { color: #696969; }
|
|
.champion-card.rank-3 .rank-number { color: #8b4513; }
|
|
|
|
|
|
.participant-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
text-align: center;
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.participant-name {
|
|
font-size: 1rem;
|
|
font-weight: 700;
|
|
color: #2c3e50;
|
|
margin: 0;
|
|
word-wrap: break-word;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.participant-details {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.participant-id {
|
|
background: #28a745;
|
|
color: white;
|
|
padding: 6px 12px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
display: inline-block;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3);
|
|
}
|
|
|
|
.joker-badge {
|
|
background: #ffc107;
|
|
color: #856404;
|
|
padding: 6px 12px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
display: inline-block;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
box-shadow: 0 2px 8px rgba(255, 193, 7, 0.3);
|
|
}
|
|
|
|
.score-display {
|
|
text-align: center;
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1px;
|
|
}
|
|
|
|
.score-number {
|
|
font-size: 1.8rem;
|
|
font-weight: 900;
|
|
color: #28a745;
|
|
line-height: 1;
|
|
}
|
|
|
|
.tens-count {
|
|
font-size: 1.2rem;
|
|
color: #ffc107;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.total-score {
|
|
font-size: 0.8rem;
|
|
color: #ffc107;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.tens-count {
|
|
font-size: 0.8rem;
|
|
color: #ffc107;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.score-label {
|
|
font-size: 0.6rem;
|
|
color: #999;
|
|
text-transform: uppercase;
|
|
font-weight: 600;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
/* Right Column - League Table */
|
|
.right-column {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: none;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border: 1px solid #e9ecef;
|
|
}
|
|
|
|
.table-header {
|
|
background: #f8f9fa;
|
|
padding: 12px 15px;
|
|
border-bottom: 1px solid #dee2e6;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.table-title {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
margin: 0;
|
|
}
|
|
|
|
.table-container {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.league-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.league-table th,
|
|
.league-table td {
|
|
padding: 6px 4px;
|
|
text-align: center;
|
|
border-bottom: 1px solid #f1f3f4;
|
|
border-right: 1px solid #f1f3f4;
|
|
}
|
|
|
|
.league-table th {
|
|
background: #f8f9fa;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
text-transform: uppercase;
|
|
font-size: 0.6rem;
|
|
letter-spacing: 0.5px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.league-table th.player-col {
|
|
text-align: left;
|
|
width: 100px;
|
|
}
|
|
|
|
.league-table th.tournament-col {
|
|
width: 60px;
|
|
}
|
|
|
|
.league-table th.final-col {
|
|
width: 70px;
|
|
background: #e3f2fd;
|
|
}
|
|
|
|
.league-table th.tens-col {
|
|
width: 60px;
|
|
background: #fff3cd;
|
|
}
|
|
|
|
.league-table tbody tr:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.league-table tbody tr:nth-child(1) {
|
|
background: #fff9e6;
|
|
}
|
|
|
|
.league-table tbody tr:nth-child(1):hover {
|
|
background: #fff3cd;
|
|
}
|
|
|
|
.league-table tbody tr:nth-child(2) {
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.league-table tbody tr:nth-child(2):hover {
|
|
background: #e9ecef;
|
|
}
|
|
|
|
.league-table tbody tr:nth-child(3) {
|
|
background: #fdf6f0;
|
|
}
|
|
|
|
.league-table tbody tr:nth-child(3):hover {
|
|
background: #f8f1e6;
|
|
}
|
|
|
|
.rank-cell {
|
|
font-size: 0.9rem;
|
|
font-weight: 700;
|
|
width: 45px;
|
|
text-align: center;
|
|
}
|
|
|
|
.rank-1 { color: #b8860b; }
|
|
.rank-2 { color: #6c757d; }
|
|
.rank-3 { color: #8b4513; }
|
|
|
|
.player-cell {
|
|
text-align: left !important;
|
|
padding-left: 10px !important;
|
|
}
|
|
|
|
.player-name {
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
word-break: keep-all;
|
|
overflow-wrap: break-word;
|
|
hyphens: none;
|
|
}
|
|
|
|
.player-id {
|
|
background: #28a745;
|
|
color: white;
|
|
padding: 2px 5px;
|
|
border-radius: 5px;
|
|
font-size: 0.6rem;
|
|
font-weight: 500;
|
|
display: inline-block;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.tournament-score {
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
color: #333;
|
|
min-width: 35px;
|
|
}
|
|
|
|
.tournament-score.joker {
|
|
background: #ffc107;
|
|
color: #856404;
|
|
padding: 2px 5px;
|
|
border-radius: 3px;
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tournament-score.excluded {
|
|
background: #dc3545;
|
|
color: white;
|
|
padding: 2px 5px;
|
|
border-radius: 3px;
|
|
text-decoration: line-through;
|
|
opacity: 0.8;
|
|
font-size: 0.65rem;
|
|
}
|
|
|
|
.tournament-score.counted {
|
|
background: #28a745;
|
|
color: white;
|
|
padding: 2px 5px;
|
|
border-radius: 3px;
|
|
font-weight: 600;
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.final-score-cell {
|
|
background: #e3f2fd !important;
|
|
font-size: 1rem;
|
|
font-weight: 700;
|
|
color: #1976d2;
|
|
}
|
|
|
|
.tens-count-cell {
|
|
background: #fff3cd !important;
|
|
font-size: 0.9rem;
|
|
font-weight: 700;
|
|
color: #856404;
|
|
}
|
|
|
|
/* Tournament Headers */
|
|
.tournament-header-group {
|
|
background: #e3f2fd;
|
|
border-bottom: 2px solid #1976d2;
|
|
}
|
|
|
|
.tournament-header-group th {
|
|
background: #e3f2fd;
|
|
color: #1976d2;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.tens-header-group {
|
|
background: #fff3cd;
|
|
border-bottom: 2px solid #856404;
|
|
}
|
|
|
|
.tens-header-group th {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Calculation Legend */
|
|
.calculation-legend {
|
|
background: #f8f9fa;
|
|
border-top: 1px solid #dee2e6;
|
|
padding: 15px 20px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.legend-title {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.legend-items {
|
|
display: flex;
|
|
gap: 15px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 0.75rem;
|
|
color: #495057;
|
|
}
|
|
|
|
.legend-box {
|
|
width: 18px;
|
|
height: 14px;
|
|
border-radius: 3px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.legend-joker { background: #ffc107; }
|
|
.legend-excluded { background: #dc3545; }
|
|
.legend-counted { background: #28a745; }
|
|
.legend-final { background: #1976d2; }
|
|
.legend-tens { background: #856404; }
|
|
|
|
/* League Stats Footer */
|
|
.stats-footer {
|
|
background: white;
|
|
border-top: 1px solid #dee2e6;
|
|
padding: 10px 20px;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
font-size: 0.8rem;
|
|
color: #6c757d;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.stat-item {
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-value {
|
|
font-weight: 600;
|
|
color: #28a745;
|
|
}
|
|
|
|
/* Mobile responsive */
|
|
@media (max-width: 768px) {
|
|
.league-container {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: auto 1fr;
|
|
}
|
|
|
|
.league-meta {
|
|
gap: 10px;
|
|
}
|
|
|
|
.meta-number {
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.champion-section {
|
|
padding: 20px 15px;
|
|
}
|
|
|
|
.league-table {
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.league-table th,
|
|
.league-table td {
|
|
padding: 5px 3px;
|
|
}
|
|
}
|
|
|
|
/* PRINT STYLES */
|
|
@media print {
|
|
.navbar {
|
|
display: none !important;
|
|
}
|
|
|
|
html, body {
|
|
height: auto !important;
|
|
overflow: visible !important;
|
|
background: white !important;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
|
|
.league-container {
|
|
display: block !important;
|
|
height: auto !important;
|
|
padding: 0 !important;
|
|
gap: 0 !important;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.left-column {
|
|
display: block !important;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.league-header {
|
|
background: white !important;
|
|
color: #333 !important;
|
|
box-shadow: none !important;
|
|
border: 2px solid #ddd !important;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.league-header::before {
|
|
display: none !important;
|
|
}
|
|
|
|
.header-logo {
|
|
height: 60px !important;
|
|
max-width: 160px !important;
|
|
background-color: transparent !important;
|
|
padding: 0 !important;
|
|
border: none !important;
|
|
filter: none !important;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.league-title {
|
|
font-size: 24pt !important;
|
|
font-weight: bold !important;
|
|
color: #333 !important;
|
|
text-shadow: none !important;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.league-subtitle {
|
|
font-size: 14pt !important;
|
|
color: #666 !important;
|
|
text-shadow: none !important;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.league-meta {
|
|
color: #333 !important;
|
|
}
|
|
|
|
.meta-number {
|
|
color: #333 !important;
|
|
text-shadow: none !important;
|
|
font-size: 16pt !important;
|
|
}
|
|
|
|
.meta-label {
|
|
color: #666 !important;
|
|
font-size: 10pt !important;
|
|
}
|
|
|
|
.champion-section {
|
|
display: none !important;
|
|
}
|
|
|
|
.right-column {
|
|
background: white !important;
|
|
border-radius: 0 !important;
|
|
box-shadow: none !important;
|
|
overflow: visible !important;
|
|
display: block !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
.table-header {
|
|
display: none !important;
|
|
}
|
|
|
|
.league-table {
|
|
margin-top: 0 !important;
|
|
}
|
|
|
|
.print-title {
|
|
display: none !important;
|
|
}
|
|
|
|
.table-title {
|
|
display: none !important;
|
|
}
|
|
}
|
|
</style>
|
|
<script src="/static/js/i18n.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="navbar">
|
|
<div class="navbar-brand">
|
|
<div class="navbar-title">🎖️ <span data-i18n="league.league_results">League Results</span></div>
|
|
</div>
|
|
<div class="navbar-controls">
|
|
<button class="nav-btn" onclick="exportLeagueJSON()">💾 <span data-i18n="general.export">Export JSON</span></button>
|
|
<button class="nav-btn" onclick="window.print()">🖨️ <span data-i18n="general.print">Print</span></button>
|
|
<button class="nav-btn" id="btnTvView" onclick="toggleTvView()" title="Preklopi pogled na TV zaslonu">📺</button>
|
|
<a href="/" class="nav-btn">✕</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="league-container">
|
|
<!-- Left Column -->
|
|
<div class="left-column">
|
|
<!-- League Header -->
|
|
<div class="league-header tournament-{{ league.tournament_type if league and league.tournament_type else '20_targets' }}">
|
|
<img src="/static/logo.png" alt="Logo" class="header-logo" onerror="this.style.display='none'" />
|
|
<div class="league-title">
|
|
{% if league and league.tournament_type == '4_targets' %}
|
|
🎯 <span data-i18n="league.league_championship">League Championship</span>
|
|
{% elif league and league.tournament_type == '40_targets' %}
|
|
💪 <span data-i18n="league.league_championship">League Championship</span>
|
|
{% else %}
|
|
⚡ <span data-i18n="league.league_championship">League Championship</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="league-subtitle" data-i18n="league.final_rankings_best_4_of_5">Final Rankings - Best 4 of 5 Tournaments</div>
|
|
<div class="league-meta">
|
|
<div class="meta-item">
|
|
<span class="meta-number" id="participantCount">0</span>
|
|
<span class="meta-label" data-i18n="players.players">Igralci</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<span class="meta-number" id="tournamentCount">5</span>
|
|
<span class="meta-label" data-i18n="tournament.tournaments">Turnirji</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<span class="meta-number" id="targetCount">0</span>
|
|
<span class="meta-label" data-i18n="tournament.targets">Targets</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<span class="meta-number" id="topScore">0</span>
|
|
<span class="meta-label" data-i18n="results.highest_score">Highest Score</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<span class="meta-number" id="mostTens">0</span>
|
|
<span class="meta-label" data-i18n="results.most_tens">Most 10s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Champion Section -->
|
|
<div class="champion-section" id="championSection">
|
|
<div class="champion-title" data-i18n="league.league_champions">🎖️ League Champions</div>
|
|
<div class="champion-container" id="championContainer">
|
|
<!-- Champion cards will be generated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column -->
|
|
<div class="right-column">
|
|
<div class="table-header">
|
|
<h3 class="table-title" data-i18n="league.5_tournament_league">📊 5 Turnirska Liga - Štejejo Najboljši 4</h3>
|
|
<!-- Print-only title -->
|
|
<div class="print-title" style="display: none;">
|
|
🎖️ <span data-i18n="league.league_results">League Results</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-container">
|
|
<table class="league-table">
|
|
<thead>
|
|
<tr>
|
|
<th rowspan="2" class="rank-cell" data-i18n="results.position">Rank</th>
|
|
<th rowspan="2" class="player-col" data-i18n="league.participant">Udeleženec</th>
|
|
<th colspan="5" class="tournament-header-group" data-i18n="league.tournament_scores">Tournament Scores</th>
|
|
<th rowspan="2" class="final-col"><span data-i18n="league.final">Final</span><br><small>(<span data-i18n="league.best_4">Best 4</span>)</small></th>
|
|
<th rowspan="2" class="tens-col"><span data-i18n="league.total_10s">10k</span></th>
|
|
</tr>
|
|
<tr class="tournament-header-group">
|
|
<th class="tournament-col">T1</th>
|
|
<th class="tournament-col">T2</th>
|
|
<th class="tournament-col">T3</th>
|
|
<th class="tournament-col">T4</th>
|
|
<th class="tournament-col">T5</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="leagueTableBody">
|
|
<!-- Table rows will be generated by JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="calculation-legend">
|
|
<div class="legend-title" data-i18n="league.scoring_legend">📖 Legenda Točkovanja:</div>
|
|
<div class="legend-items">
|
|
<div class="legend-item">
|
|
<span class="legend-box legend-counted"></span>
|
|
<span data-i18n="league.counted_score">Counted Score</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-box legend-excluded"></span>
|
|
<span data-i18n="league.excluded_worst">Excluded (Worst)</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-box legend-joker"></span>
|
|
<span data-i18n="league.joker_used">{{ translations.league.joker_used if translations else 'Joker Used' }}</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-box legend-final"></span>
|
|
<span data-i18n="league.final_score">Final Score</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-box legend-tens"></span>
|
|
<span data-i18n="league.total_10s_tiebreaker">Total 10s (Tiebreaker)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-footer">
|
|
<div class="stat-item">
|
|
<div class="stat-value" id="footerParticipants">0</div>
|
|
<div data-i18n="players.players">Igralci</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value" id="footerTournaments">5</div>
|
|
<div data-i18n="tournament.tournaments">Turnirji</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value" id="footerHighest">0</div>
|
|
<div data-i18n="results.highest_score">Highest Score</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value" id="footerAverage">0</div>
|
|
<div data-i18n="league.average_final">Povprečni Končni</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value" id="footerMostTens">0</div>
|
|
<div data-i18n="results.most_tens">Most Tens</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value" id="footerDate">Today</div>
|
|
<div data-i18n="general.date">Datum</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Get data from template
|
|
const participants = {{ participants|tojson }};
|
|
const league = {{ league|tojson }};
|
|
|
|
console.log('League participants:', participants);
|
|
console.log('League data:', league);
|
|
|
|
// Calculate 10s from targets in tournament results
|
|
function calculateTensFromTargets(targets) {
|
|
let tensCount = 0;
|
|
if (!targets || typeof targets !== 'object') {
|
|
return 0;
|
|
}
|
|
|
|
for (let targetNum in targets) {
|
|
const target = targets[targetNum];
|
|
if (target && typeof target === 'object') {
|
|
for (let shotKey in target) {
|
|
if (shotKey.startsWith('shot') && target[shotKey] === 10) {
|
|
tensCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return tensCount;
|
|
}
|
|
|
|
// Calculate total 10s across all tournaments for a participant
|
|
function calculateTotalTensForParticipant(participant) {
|
|
let totalTens = 0;
|
|
|
|
// Method 1: Check if backend already calculated total_tens
|
|
if (participant.total_tens !== undefined && participant.total_tens !== null) {
|
|
return participant.total_tens;
|
|
}
|
|
|
|
// Method 2: Try to sum up tens_count from tournament_results
|
|
if (participant.tournament_results && Array.isArray(participant.tournament_results)) {
|
|
participant.tournament_results.forEach(result => {
|
|
if (result.participated && result.tens_count !== undefined) {
|
|
totalTens += result.tens_count;
|
|
}
|
|
});
|
|
|
|
if (totalTens > 0) {
|
|
return totalTens;
|
|
}
|
|
}
|
|
|
|
// Method 3: Try to calculate from targets data (usually not available)
|
|
if (participant.tournament_results && Array.isArray(participant.tournament_results)) {
|
|
participant.tournament_results.forEach(result => {
|
|
if (result.targets && result.participated) {
|
|
totalTens += calculateTensFromTargets(result.targets);
|
|
}
|
|
});
|
|
}
|
|
|
|
return totalTens;
|
|
}
|
|
|
|
// Process participant data and add calculated fields
|
|
function processParticipants() {
|
|
if (!participants || !Array.isArray(participants)) {
|
|
console.error('Invalid participants data:', participants);
|
|
return [];
|
|
}
|
|
|
|
return participants.map(participant => {
|
|
const totalTens = calculateTotalTensForParticipant(participant);
|
|
|
|
return {
|
|
...participant,
|
|
total_tens: totalTens
|
|
};
|
|
});
|
|
}
|
|
|
|
// Calculate best 4 scoring logic for 5 tournaments
|
|
function calculateBest4Logic(participant) {
|
|
if (!participant.tournament_results || !Array.isArray(participant.tournament_results)) {
|
|
return { allScores: [], best4: [], excluded: [], excludedIndices: [] };
|
|
}
|
|
|
|
const participatedResults = participant.tournament_results.filter(r => r.participated);
|
|
const allScores = participatedResults.map(r => r.score);
|
|
|
|
if (allScores.length <= 4) {
|
|
return { allScores, best4: allScores, excluded: [], excludedIndices: [] };
|
|
}
|
|
|
|
// Create array of score objects with original indices
|
|
const scoresWithIndices = allScores.map((score, index) => ({ score, originalIndex: index }));
|
|
|
|
// Sort by score (highest first)
|
|
const sortedScoresWithIndices = [...scoresWithIndices].sort((a, b) => b.score - a.score);
|
|
|
|
// Take best 4 and excluded (worst)
|
|
const best4WithIndices = sortedScoresWithIndices.slice(0, 4);
|
|
const excludedWithIndices = sortedScoresWithIndices.slice(4);
|
|
|
|
const best4 = best4WithIndices.map(item => item.score);
|
|
const excluded = excludedWithIndices.map(item => item.score);
|
|
const excludedIndices = excludedWithIndices.map(item => item.originalIndex);
|
|
|
|
return { allScores, best4, excluded, excludedIndices };
|
|
}
|
|
|
|
// Generate champion HTML
|
|
function generateChampionHTML(processedParticipants) {
|
|
const topThree = processedParticipants.slice(0, 3);
|
|
|
|
return topThree.map(participant => {
|
|
const medal = participant.rank === 1 ? '🥇' :
|
|
participant.rank === 2 ? '🥈' :
|
|
participant.rank === 3 ? '🥉' : '';
|
|
|
|
|
|
return `
|
|
<div class="champion-card rank-${participant.rank}">
|
|
<div class="rank-display">
|
|
<div class="medal">${medal}</div>
|
|
</div>
|
|
|
|
<div class="participant-info">
|
|
<div class="participant-name">${participant.name}</div>
|
|
</div>
|
|
|
|
<div class="score-display">
|
|
<div class="score-number">${participant.final_score}</div>
|
|
<div class="tens-count">🎯 ${participant.total_tens}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// Generate table HTML for 5 tournaments only
|
|
function generateTableHTML(processedParticipants) {
|
|
return processedParticipants.map(participant => {
|
|
const rankClass = participant.rank <= 3 ? `rank-${participant.rank}` : 'rank-other';
|
|
const medal = participant.rank === 1 ? '🥇' :
|
|
participant.rank === 2 ? '🥈' :
|
|
participant.rank === 3 ? '🥉' : '';
|
|
|
|
const best4Logic = calculateBest4Logic(participant);
|
|
|
|
// Generate ONLY 5 tournament score cells (T1-T5)
|
|
let tournamentCells = '';
|
|
let participatedTournamentIndex = 0; // Track index in participated tournaments only
|
|
|
|
for (let tournamentNum = 1; tournamentNum <= 5; tournamentNum++) {
|
|
const result = participant.tournament_results?.find(r => r.tournament === tournamentNum);
|
|
|
|
if (!result) {
|
|
tournamentCells += '<td>-</td>';
|
|
} else if (!result.participated || result.joker) {
|
|
tournamentCells += '<td><span class="tournament-score joker">🃏</span></td>';
|
|
} else {
|
|
const score = result.score;
|
|
const tensCount = result.tens_count || 0;
|
|
|
|
// Check if this specific tournament index should be excluded
|
|
const isExcluded = best4Logic.excludedIndices.includes(participatedTournamentIndex) && best4Logic.allScores.length > 4;
|
|
const scoreClass = isExcluded ? 'excluded' : 'counted';
|
|
|
|
tournamentCells += `<td><span class="tournament-score ${scoreClass}">${score}</span></td>`;
|
|
participatedTournamentIndex++; // Only increment for participated tournaments
|
|
}
|
|
}
|
|
|
|
return `
|
|
<tr>
|
|
<td class="rank-cell ${rankClass}">
|
|
${participant.rank} ${medal}
|
|
</td>
|
|
<td class="player-cell">
|
|
<div class="player-name">${participant.name}</div>
|
|
</td>
|
|
${tournamentCells}
|
|
<td class="final-score-cell">${participant.final_score}</td>
|
|
<td class="tens-count-cell">🎯 ${participant.total_tens}</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// Update statistics
|
|
function updateStatistics(processedParticipants) {
|
|
const count = processedParticipants.length;
|
|
const totalTournaments = 5; // Fixed to 5 tournaments
|
|
const tournamentType = league ? league.tournament_type : '20_targets';
|
|
const targetCount = tournamentType === '40_targets' ? 40 :
|
|
tournamentType === '4_targets' ? 4 : 20;
|
|
|
|
const topScore = count > 0 ? processedParticipants[0].final_score : 0;
|
|
const mostTens = count > 0 ? Math.max(...processedParticipants.map(p => p.total_tens)) : 0;
|
|
const averageFinal = count > 0 ? Math.round(processedParticipants.reduce((sum, p) => sum + p.final_score, 0) / count) : 0;
|
|
|
|
// Format date from YYYY-MM-DD to DD-MM-YY
|
|
function formatFooterDate(dateString) {
|
|
if (!dateString) return '';
|
|
const parts = dateString.split('-');
|
|
if (parts.length !== 3) return dateString;
|
|
const year = parts[0].slice(-2);
|
|
const month = parts[1];
|
|
const day = parts[2];
|
|
return `${day}-${month}-${year}`;
|
|
}
|
|
|
|
const currentDate = league && league.finished_at ?
|
|
formatFooterDate(league.finished_at.substring(0, 10)) :
|
|
formatFooterDate(new Date().toISOString().substring(0, 10));
|
|
|
|
// Update header stats
|
|
document.getElementById('participantCount').textContent = count;
|
|
document.getElementById('targetCount').textContent = targetCount;
|
|
document.getElementById('topScore').textContent = topScore;
|
|
document.getElementById('mostTens').textContent = mostTens;
|
|
|
|
// Update footer stats
|
|
document.getElementById('footerParticipants').textContent = count;
|
|
document.getElementById('footerHighest').textContent = topScore;
|
|
document.getElementById('footerAverage').textContent = averageFinal;
|
|
document.getElementById('footerMostTens').textContent = mostTens;
|
|
document.getElementById('footerDate').textContent = currentDate;
|
|
}
|
|
|
|
// Initialize the page
|
|
function initializePage() {
|
|
try {
|
|
const processedParticipants = processParticipants();
|
|
|
|
if (processedParticipants.length === 0) {
|
|
console.error('No participants to display');
|
|
document.getElementById('championSection').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
// Generate champion cards (top 3 only)
|
|
if (processedParticipants.length >= 3) {
|
|
document.getElementById('championContainer').innerHTML = generateChampionHTML(processedParticipants);
|
|
} else {
|
|
document.getElementById('championSection').style.display = 'none';
|
|
}
|
|
|
|
// Generate table
|
|
document.getElementById('leagueTableBody').innerHTML = generateTableHTML(processedParticipants);
|
|
|
|
// Update statistics
|
|
updateStatistics(processedParticipants);
|
|
|
|
console.log('League results initialized successfully');
|
|
|
|
} catch (error) {
|
|
console.error('Error initializing league results:', error);
|
|
}
|
|
}
|
|
|
|
// Initialize when page loads
|
|
document.addEventListener('DOMContentLoaded', initializePage);
|
|
|
|
// Export league data as JSON
|
|
function exportLeagueJSON() {
|
|
const leagueData = {{ league | tojson | safe }};
|
|
|
|
// Wrap in archive format for compatibility
|
|
const archiveData = {
|
|
league: leagueData,
|
|
archived_at: new Date().toISOString()
|
|
};
|
|
|
|
const dataStr = JSON.stringify(archiveData, null, 2);
|
|
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
const url = URL.createObjectURL(dataBlob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
|
|
// Generate filename with league info
|
|
const leagueId = leagueData.league_id || 'league';
|
|
const date = new Date().toISOString().slice(0, 10);
|
|
link.download = `${leagueId}_${date}.json`;
|
|
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
// Keyboard shortcuts
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'r' || event.key === 'R') {
|
|
event.preventDefault();
|
|
window.location.reload();
|
|
} else if (event.key === 'p' || event.key === 'P') {
|
|
event.preventDefault();
|
|
window.print();
|
|
}
|
|
});
|
|
|
|
// ── TV VIEW TOGGLE ────────────────────────────────────────────────────────
|
|
const isTvDisplay = new URLSearchParams(window.location.search).get('tv') === '1';
|
|
let currentTvView = 'results';
|
|
|
|
function updateTvViewBtn(view) {
|
|
currentTvView = view;
|
|
const btn = document.getElementById('btnTvView');
|
|
if (!btn) return;
|
|
btn.style.background = (view === 'results') ? '#28a745' : '';
|
|
btn.style.color = (view === 'results') ? 'white' : '';
|
|
btn.style.borderColor = (view === 'results') ? '#1e7e34' : '';
|
|
}
|
|
|
|
async function toggleTvView() {
|
|
const newView = currentTvView === 'results' ? 'cameras' : 'results';
|
|
try {
|
|
const payload = { view: newView };
|
|
if (newView === 'results') {
|
|
// Pass the current page path so the TV follows to this league archive, not a tournament archive
|
|
payload.tv_url = window.location.pathname;
|
|
}
|
|
await fetch('/api/tv/view', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
updateTvViewBtn(newView);
|
|
if (isTvDisplay && newView === 'cameras') {
|
|
window.location.href = '/';
|
|
}
|
|
} catch (e) { console.error(e); }
|
|
}
|
|
|
|
// Poll to stay in sync and follow back-redirect when in TV mode
|
|
setInterval(async () => {
|
|
if (document.visibilityState !== 'visible') return;
|
|
try {
|
|
const r = await fetch('/api/dashboard/state');
|
|
if (!r.ok) return;
|
|
const d = await r.json();
|
|
updateTvViewBtn(d.tv_view || 'cameras');
|
|
if (isTvDisplay && d.tv_view !== 'results') {
|
|
if (d.tv_view === 'draft' && d.tournament_active) {
|
|
window.location.href = '/tournament/draft?tv=1';
|
|
} else {
|
|
window.location.href = '/';
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
}, 3000);
|
|
|
|
fetch('/api/dashboard/state').then(r => r.json()).then(d => updateTvViewBtn(d.tv_view || 'cameras')).catch(() => {});
|
|
</script>
|
|
</body>
|
|
</html> |