diff --git a/app/static/script.js b/app/static/script.js
index dcf9ab3..19e87c5 100644
--- a/app/static/script.js
+++ b/app/static/script.js
@@ -1,246 +1,152 @@
-// Function to fetch scoreboard data using AJAX
-function fetchScoreboardData() {
- var xhr = new XMLHttpRequest();
- xhr.open("GET", "/scoreboard", true);
- xhr.onreadystatechange = function () {
- if (xhr.readyState === XMLHttpRequest.DONE) {
- if (xhr.status === 200) {
- updateScoreboard(JSON.parse(xhr.responseText));
- } else {
- console.error("Failed to fetch scoreboard data.");
- }
- }
- };
- xhr.send();
+async function fetchScoreboardData() {
+ try {
+ const res = await fetch('/scoreboard');
+ if (!res.ok) throw new Error(res.status);
+ updateScoreboard(await res.json());
+ } catch (e) {
+ console.error('Failed to fetch scoreboard data:', e);
+ }
}
-// Function to update scoreboard with fetched data
function updateScoreboard(data) {
- var liveGamesSection = document.getElementById('live-games-section');
- var preGamesSection = document.getElementById('pre-games-section');
- var finalGamesSection = document.getElementById('final-games-section');
+ const sections = [
+ { sectionId: 'live-section', gridId: 'live-games-section', games: data.live_games, render: renderLiveGame },
+ { sectionId: 'pre-section', gridId: 'pre-games-section', games: data.pre_games, render: renderPreGame },
+ { sectionId: 'final-section', gridId: 'final-games-section', games: data.final_games, render: renderFinalGame },
+ ];
- if (liveGamesSection) {
- var liveGamesExist = data && data.live_games && data.live_games.length > 0;
- if (liveGamesExist) {
- if (!document.getElementById('live-games')) {
- var targetElement = document.getElementById('live-games-section');
- var newElement = document.createElement('h1');
- newElement.setAttribute('id', 'live-games');
- newElement.innerText = 'Live Games';
- targetElement.parentNode.insertBefore(newElement, targetElement);
- }
- liveGamesSection.innerHTML = generateGameBoxes(data.live_games, 'LIVE');
- } else {
- var liveGamesElement = document.getElementById('live-games');
- if (liveGamesElement) {
- liveGamesElement.remove();
- }
- liveGamesSection.innerHTML = '';
- }
+ for (const { sectionId, gridId, games, render } of sections) {
+ const section = document.getElementById(sectionId);
+ const grid = document.getElementById(gridId);
+ const hasGames = games && games.length > 0;
+ section.classList.toggle('hidden', !hasGames);
+ grid.innerHTML = hasGames ? games.map(render).join('') : '';
}
- if (preGamesSection) {
- var preGamesExist = data && data.pre_games && data.pre_games.length > 0;
- if (preGamesExist) {
- if (!document.getElementById('on-later')) {
- var targetElement = document.getElementById('pre-games-section');
- var newElement = document.createElement('h1');
- newElement.setAttribute('id', 'on-later');
- newElement.innerText = 'Scheduled Games';
- targetElement.parentNode.insertBefore(newElement, targetElement);
- }
- preGamesSection.innerHTML = generateGameBoxes(data.pre_games, 'PRE');
- } else {
- var onLaterElement = document.getElementById('on-later');
- if (onLaterElement) {
- onLaterElement.remove();
- }
- preGamesSection.innerHTML = '';
- }
- }
-
- if (finalGamesSection) {
- var finalGamesExist = data && data.final_games && data.final_games.length > 0;
-
- // Check if final games exist
- if (finalGamesExist) {
- // Create or update "Game Over" heading
- if (!document.getElementById('game-over')) {
- var targetElement = document.getElementById('final-games-section');
- var newElement = document.createElement('h1');
- newElement.setAttribute('id', 'game-over');
- newElement.innerText = 'Game Over';
- targetElement.parentNode.insertBefore(newElement, targetElement);
- }
-
- // Update final games section with generated game boxes
- finalGamesSection.innerHTML = generateGameBoxes(data.final_games, 'FINAL');
- } else {
- // Remove "Game Over" heading if it exists and clear final games section
- var gameOverElement = document.getElementById('game-over');
- if (gameOverElement) {
- gameOverElement.remove();
- }
- finalGamesSection.innerHTML = '';
- }
- }
-
- updateGauge()
+ updateGauges();
}
-function updateGauge() {
- document.querySelectorAll('.gauge').forEach(function(gauge) {
- // Get the score value from the data-score attribute
- var score = parseInt(gauge.getAttribute('data-score'));
+// ── Renderers ────────────────────────────────────────
- // Clamp the score value between 0 and 700
- score = Math.min(700, Math.max(0, score));
+function renderLiveGame(game) {
+ const intermission = game['Intermission'];
+ const period = game['Period'];
+ const time = game['Time Remaining'];
+ const running = game['Time Running'];
- // Calculate the gauge width as a percentage
- var gaugeWidth = (score / 700) * 100;
+ const periodLabel = intermission
+ ? `${intermissionLabel(period)}`
+ : `${ordinalPeriod(period)}`;
- // Set the width of the gauge
- gauge.style.width = gaugeWidth + '%';
+ const dot = running ? `` : '';
- if (score <=300) {
- gauge.style.backgroundColor = '#4A90E2'
- } else if (score <= 550) {
- gauge.style.backgroundColor = '#FF4500'
- } else {
- gauge.style.backgroundColor = '#FF0033'
- }
+ const hype = !intermission ? `
+
` : '';
+
+ return `
+
+
+ ${teamRow(game, 'Away', 'live')}
+ ${teamRow(game, 'Home', 'live')}
+ ${hype}
+
`;
+}
+
+function renderPreGame(game) {
+ return `
+
+
+ ${teamRow(game, 'Away', 'pre')}
+ ${teamRow(game, 'Home', 'pre')}
+
`;
+}
+
+function renderFinalGame(game) {
+ const labels = { REG: 'Final', OT: 'Final/OT', SO: 'Final/SO' };
+ const label = labels[game['Last Period Type']] ?? 'Final';
+ return `
+
+
+ ${teamRow(game, 'Away', 'final')}
+ ${teamRow(game, 'Home', 'final')}
+
`;
+}
+
+// ── Team Row ─────────────────────────────────────────
+
+function teamRow(game, side, state) {
+ const name = game[`${side} Team`];
+ const logo = game[`${side} Logo`];
+ const score = game[`${side} Score`];
+ const sog = game[`${side} Shots`];
+ const pp = game[`${side} Power Play`];
+ const record = game[`${side} Record`];
+
+ const sogHtml = (state !== 'pre' && sog !== undefined)
+ ? `${sog} SOG` : '';
+ const ppHtml = pp ? `${pp}` : '';
+
+ const right = state === 'pre'
+ ? `${record}`
+ : `${score}`;
+
+ return `
+
+

+
+ ${name}
+ ${sogHtml}${ppHtml}
+
+ ${right}
+
`;
+}
+
+// ── Gauge ────────────────────────────────────────────
+
+function updateGauges() {
+ document.querySelectorAll('.gauge').forEach(el => {
+ const score = Math.min(700, Math.max(0, parseInt(el.dataset.score, 10)));
+ el.style.width = `${(score / 700) * 100}%`;
+ el.style.backgroundColor = score <= 300 ? '#4a90e2'
+ : score <= 550 ? '#f97316'
+ : '#ef4444';
});
}
-// Function to generate HTML for game boxes
-function generateGameBoxes(games, state) {
- var html = '';
- games.forEach(function(game) {
- if (game['Game State'] === state) {
- html += '';
- if (state === 'LIVE') {
- if (game['Time Running']) {
- html += '
'; // Display the red dot if the game is live
- }
- html += '
';
- html += '
![' + game['Away Team'] + ' Logo](' + game['Away Logo'] + ')
';
- html += '
';
- html += '' + game['Away Team'] + '';
- html += 'SOG: ' + game['Away Shots'] + '';
- html += '' + game['Away Power Play'] + '';
- html += '
';
- html += '
' + game['Away Score'] + '';
- html += '
';
- html += '
';
- html += '
![' + game['Home Team'] + ' Logo](' + game['Home Logo'] + ')
';
- html += '
';
- html += '' + game['Home Team'] + '';
- html += 'SOG: ' + game['Home Shots'] + '';
- html += '' + game['Home Power Play'] + '';
- html += '
';
- html += '
' + game['Home Score'] + '';
- html += '
';
- html += '
';
- if (game['Intermission']) {
- html += '
'
- if (game['Period'] == 1 ) {
- html += '1st Int';
- }
- if (game['Period'] == 2 ) {
- html += '2nd Int';
- }
- if (game['Period'] == 3 ) {
- html += '3rd Int';
- }
- html += '
';
- html += '
' + game['Time Remaining'] + '
';
- } else {
- html += '
';
- if (game['Period'] == 1 ) {
- html += '1st';
- }
- else if (game['Period'] == 2 ) {
- html += '2nd';
- }
- else if (game['Period'] == 3 ) {
- html += '3rd';
- }
- else if (game['Period'] == 4 ) {
- html += 'OT';
- }
- else {
- html += 'SO';
- }
- html += '
';
- html += '
' + game['Time Remaining'] + '
';
- }
- html += '
';
- if (!game['Intermission']) {
- html += '
';
- html += 'Hype Meter';
- html += '
';
+// ── Helpers ──────────────────────────────────────────
- html += '
';
- html += '
';
- html += '
';
- html += '
';
- }
-
-
- html += '';
- } else if (state === 'PRE') {
- html += '' + game['Start Time'] + '
';
- html += '';
- html += '
![' + game['Away Team'] + ' Logo](' + game['Away Logo'] + ')
';
- html += '
' + game['Away Team'] + '';
- html += '
' + game['Away Record'] + '';
- html += '
';
- html += '';
- html += '
![' + game['Home Team'] + ' Logo](' + game['Home Logo'] + ')
';
- html += '
' + game['Home Team'] + '';
- html += '
' + game['Home Record'] + '';
- html += '
';
- } else if (state === 'FINAL') {
- html += '';
- if (game['Last Period Type'] === 'REG') {
- html += 'FINAL';
- } else if (game['Last Period Type'] === 'OT') {
- html += 'FINAL/OT';
- } else {
- html += 'FINAL/SO';
- }
- html += '
';
- html += '';
- html += '
![' + game['Away Team'] + ' Logo](' + game['Away Logo'] + ')
';
- html += '
';
- html += '' + game['Away Team'] + '';
- html += 'SOG: ' + game['Away Shots'] + '';
- html += '
';
- html += '
' + game['Away Score'] + '';
- html += '
';
- html += '';
- html += '
![' + game['Home Team'] + ' Logo](' + game['Home Logo'] + ')
';
- html += '
';
- html += '' + game['Home Team'] + '';
- html += 'SOG: ' + game['Home Shots'] + '';
- html += '
';
- html += '
' + game['Home Score'] + '';
- html += '
';
- }
- html += '';
- }
- });
- return html;
+function ordinalPeriod(period) {
+ return ['1st', '2nd', '3rd', 'OT'][period - 1] ?? 'SO';
}
-// Function to reload the scoreboard every 20 seconds
+function intermissionLabel(period) {
+ return ['1st Int', '2nd Int', '3rd Int'][period - 1] ?? 'Int';
+}
+
+// ── Init ─────────────────────────────────────────────
+
function autoRefresh() {
fetchScoreboardData();
- setTimeout(autoRefresh, 5000); // 20 seconds
+ setTimeout(autoRefresh, 5000);
}
-// Call the autoRefresh function when the page loads
-window.onload = function() {
- autoRefresh();
-};
\ No newline at end of file
+window.addEventListener('load', autoRefresh);
diff --git a/app/static/styles.css b/app/static/styles.css
index b234ca6..1aeae95 100644
--- a/app/static/styles.css
+++ b/app/static/styles.css
@@ -1,381 +1,239 @@
-body {
- background-color: #121212;
- font-family: Arial, sans-serif;
- color: #fff;
+:root {
+ --bg: #111;
+ --card: #1c1c1c;
+ --card-border: #2a2a2a;
+ --badge-bg: #2a2a2a;
+ --text: #f0f0f0;
+ --text-muted: #777;
+ --green-bg: #14532d;
+ --green-text: #86efac;
+ --red: #ef4444;
+ --gap: 0.875rem;
+ --radius: 10px;
+ --card-w: 250px;
+}
+
+*, *::before, *::after {
+ box-sizing: border-box;
margin: 0;
+ padding: 0;
}
-h1 {
- text-align: center;
- margin-top: 0.8%;
- margin-bottom: 1.5%;
- color: #f2f2f2;
- font-size: 2.2em;
+body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ min-height: 100vh;
}
-.scoreboard {
+/* ── Header ─────────────────────────────────────── */
+
+header {
+ padding: 1rem 1.25rem 0.5rem;
+}
+
+.header-title {
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.15em;
+ color: var(--text-muted);
+}
+
+/* ── Layout ─────────────────────────────────────── */
+
+main {
+ padding: 0.75rem 1.25rem 2rem;
+}
+
+.section {
+ margin-bottom: 2rem;
+}
+
+.section.hidden {
+ display: none;
+}
+
+.section-heading {
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ color: var(--text-muted);
+ margin-bottom: 0.75rem;
+}
+
+.games-grid {
display: flex;
flex-wrap: wrap;
- justify-content: space-around;
- margin-top: 20px;
+ gap: var(--gap);
}
+/* ── Game Card ──────────────────────────────────── */
+
.game-box {
- background-color: #333;
- border-radius: 12px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- padding: 1%;
- width: 16%;
- max-width: 350px;
- position: relative;
- margin-left: 1%;
- margin-right: 1%;
- margin-bottom: 1.15%;
+ background: var(--card);
+ border: 1px solid var(--card-border);
+ border-radius: var(--radius);
+ padding: 0.875rem;
+ width: var(--card-w);
+ flex-shrink: 0;
}
-.team-info {
+/* ── Card Header (badges + live dot) ───────────── */
+
+.card-header {
display: flex;
align-items: center;
- margin-bottom: 2%;
- margin-top: 9%;
+ justify-content: space-between;
+ margin-bottom: 0.5rem;
+ min-height: 1.25rem;
}
-.team-info-column {
+.badges {
display: flex;
- flex-direction: column;
+ gap: 0.3rem;
+ align-items: center;
+ flex-wrap: wrap;
}
-.team-logo {
- width: 18%;
- height: auto;
- margin-right: 2.25%;
+.badge {
+ font-size: 0.65rem;
+ font-weight: 700;
+ padding: 0.2rem 0.45rem;
+ border-radius: 4px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: var(--badge-bg);
+ color: var(--text);
+ white-space: nowrap;
}
-.team-name {
- font-size: 1rem;
- font-weight: bold;
+.badge-live {
+ background: var(--green-bg);
+ color: var(--green-text);
}
-/* Add a media query for screens between 769px and 900px */
-@media only screen and (max-width: 950px) and (min-width: 769px) {
- .team-name {
- font-size: 0.55rem; /* Adjusted font size for screens between 769px and 900px */
- }
-}
-
-.team-score {
- font-size: 1.35rem;
- font-weight: bold;
- margin-left: auto;
-}
-
-.team-record {
- font-size: 0.8rem;
- font-weight: bold;
- margin-left: auto;
-}
-
-/* Add a media query for screens between 769px and 900px */
-@media only screen and (max-width: 950px) and (min-width: 769px) {
- .team-record {
- font-size: 0.45rem; /* Adjusted font size for screens between 769px and 900px */
- }
-}
-
-.team-sog {
- font-size: 0.75rem;
- color: #ddd;
-}
-
-.team-power-play {
- font-size: 12px;
- color: red;
- margin-left: 10px;
-}
-
-.game-info {
- margin-top: 12px;
- color: #aaa;
- text-align: center;
- font-size: 80%;
-}
-
-.hype-meter-label {
- margin-top: 3%;
- color: #aaa;
- text-align: center;
- font-size: 80%;
- margin-bottom: 3%;
+.badge-muted {
+ color: var(--text-muted);
}
.live-dot {
- position: absolute;
- top: 5px;
- right: 5px;
- width: 10px;
- height: 10px;
- background-color: red;
+ width: 7px;
+ height: 7px;
+ background: var(--red);
border-radius: 50%;
+ flex-shrink: 0;
+ animation: pulse 1.8s ease-in-out infinite;
}
-.pre-state {
- position: absolute;
- top: 5%;
- left: 3%;
- background-color: #444;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 0.75rem;
- color: #fff;
- font-weight: bolder;
- z-index: 1;
- width: auto;
- height: 7%;
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.4; }
+}
+
+/* ── Team Rows ──────────────────────────────────── */
+
+.team-row {
display: flex;
- justify-content: space-evenly;
align-items: center;
+ gap: 0.5rem;
+ padding: 0.45rem 0;
}
-/* Add a media query for screens between 769px and 900px */
-@media only screen and (max-width: 950px) and (min-width: 769px) {
- .pre-state {
- font-size: 0.55rem; /* Adjusted font size for screens between 769px and 900px */
- }
+.team-row + .team-row {
+ border-top: 1px solid var(--card-border);
}
-.final-state {
- position: absolute;
- top: 5%;
- left: 3%;
- background-color: #444;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 0.7rem;
- color: #ddd;
- z-index: 1;
- font-weight: bold;
- width: auto;
- height: 7%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
+.team-logo {
+ width: 30px;
+ height: 30px;
+ object-fit: contain;
+ flex-shrink: 0;
}
-.live-state {
- position: absolute;
- top: 4%;
- left: 4%;
- background-color: #0b6e31;
- padding: 1.5%;
- border-radius: 5px;
+.team-meta {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+}
+
+.team-name {
+ font-size: 0.825rem;
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.team-sog {
+ font-size: 0.68rem;
+ color: var(--text-muted);
+}
+
+.team-pp {
+ font-size: 0.68rem;
+ color: var(--red);
+ font-weight: 600;
+}
+
+.team-score {
+ font-size: 1.2rem;
+ font-weight: 700;
+ margin-left: auto;
+ flex-shrink: 0;
+ min-width: 1.5rem;
+ text-align: right;
+}
+
+.team-record {
font-size: 0.72rem;
- color: #fff;
- font-weight: bolder;
- z-index: 1;
- width: 7%;
- height: 7%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
+ color: var(--text-muted);
+ margin-left: auto;
+ flex-shrink: 0;
+ white-space: nowrap;
}
-.live-time {
- position: absolute;
- top: 4%;
- left: 15%;
- background-color: #444;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 0.75rem;
- color: #ddd;
- z-index: 1;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- width: 10%;
- height: 7%;
+/* ── Hype Meter ─────────────────────────────────── */
+
+.hype-meter {
+ margin-top: 0.75rem;
}
-.live-state-intermission {
- position: absolute;
- top: 4%;
- left: 4%;
- background-color: #444;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 80%;
- color: #fff;
- font-weight: bolder;
- z-index: 1;
- width: 11%;
- height: 8.5%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
+.hype-label {
+ display: block;
+ font-size: 0.6rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--text-muted);
+ margin-bottom: 0.3rem;
}
-.live-time-intermission {
- position: absolute;
- top: 4%;
- left: 19%;
- background-color: #444;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 0.75rem;
- color: #ddd;
- z-index: 1;
- width: 10%;
- height: 8.5%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
-}
-
-#live-games-section {
- display: flex;
- align-items: start;
- flex-wrap: wrap;
- justify-content: flex-start;
-}
-
-#pre-games-section {
- display: flex;
- align-items: start;
- flex-wrap: wrap;
- justify-content: flex-start;
-}
-
-#final-games-section {
- display: flex;
- align-items: start;
- flex-wrap: wrap;
- justify-content: flex-start;
-}
-
-/* Add styles for the game score gauge */
-.game-score-gauge {
- height: 1%;
- background-color: #ccc;
- border-radius: 5px;
- overflow: hidden;
+.gauge-track {
+ height: 4px;
+ background: var(--badge-bg);
+ border-radius: 99px;
+ overflow: hidden;
}
.gauge {
- height: 10px; /* Adjust height as needed */
- /*#8A2BE2*/
- /*#6699CC*/
+ height: 100%;
+ border-radius: 99px;
+ width: 0;
+ transition: width 0.5s ease;
}
-/* Add media query for smaller screens */
-@media only screen and (max-width: 768px) {
- .scoreboard {
- flex-direction: column; /* Change direction to column for smaller screens */
- align-items: center; /* Center align items */
+/* ── Mobile ─────────────────────────────────────── */
+
+@media (max-width: 640px) {
+ :root {
+ --card-w: 100%;
}
- .game-box {
- width: 90%;
- padding: 4%;
- margin-bottom: 4%;
- margin-left: 2%;
- margin-right: 2%;
- max-width: 1000px;
+ .games-grid {
+ flex-direction: column;
}
-
- .team-info {
- align-items: center;
- margin-top: 10%;
- margin-bottom: 2%;
- }
-
- .team-logo {
- width: 12%;
- height: auto;
- }
-
- .team-name {
- font-size: 100%;
- font-weight: bold;
- }
-
- .team-score {
- font-size: 140%;
- font-weight: bold;
- }
-
- .team-sog {
- font-size: 70%;
- }
-
- .game-info {
- font-size: 90%;
- }
-
- .live-state {
- top: 5%;
- left: 3.5%;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 80%;
- width: 5.5%;
- height: 7.2%;
- }
-
- .live-time {
- top: 5%;
- left: 13%;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 80%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- width: 7%;
- height: 7.2%;
- }
-
- .live-state-intermission {
- top: 5%;
- left: 3.5%;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 80%;
- width: 11%;
- height: 7.5%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- }
-
- .live-time-intermission {
- top: 5%;
- left: 18.5%;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 80%;
- width: 8%;
- height: 7.5%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- }
-
- .final-state {
- top: 5%;
- left: 3.5%;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 72%;
- width: auto;
- height: 7.5%;
- }
-
- .pre-state {
- top: 5%;
- left: 3.5%;
- padding: 1.5%;
- border-radius: 5px;
- font-size: 80%;
- }
-
-}
\ No newline at end of file
+}
diff --git a/app/templates/index.html b/app/templates/index.html
index f0b7068..815a3e3 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -6,9 +6,23 @@
-
-
-
+
+
+
+
+
+