nba / index.html
yetarnished's picture
Update index.html
b948cd0 verified
Raw
History Blame Contribute Delete
43.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NBA Dashboard</title>
<!-- CodeMirror -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/python/python.min.js"></script>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-card: #1a1a25;
--bg-card-hover: #222230;
--accent-orange: #f97316;
--accent-blue: #3b82f6;
--accent-purple: #8b5cf6;
--accent-green: #22c55e;
--accent-red: #ef4444;
--accent-yellow: #eab308;
--text-primary: #ffffff;
--text-secondary: #a1a1aa;
--text-muted: #71717a;
--border: rgba(255,255,255,0.08);
--border-light: rgba(255,255,255,0.12);
--glow-orange: rgba(249, 115, 22, 0.3);
--glow-blue: rgba(59, 130, 246, 0.3);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--bg-primary);
color: var(--text-primary);
font-family: 'Inter', -apple-system, sans-serif;
min-height: 100vh;
overflow-x: hidden;
}
/* Background Effects */
.bg-effects {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
overflow: hidden;
}
.bg-orb {
position: absolute;
border-radius: 50%;
filter: blur(100px);
opacity: 0.4;
animation: float 20s ease-in-out infinite;
}
.bg-orb-1 {
width: 600px;
height: 600px;
background: linear-gradient(135deg, var(--accent-orange), var(--accent-red));
top: -200px;
right: -100px;
}
.bg-orb-2 {
width: 500px;
height: 500px;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
bottom: -150px;
left: -100px;
animation-delay: -10s;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(30px, -30px) scale(1.1); }
}
/* Navbar */
nav {
position: sticky;
top: 0;
z-index: 100;
background: rgba(10, 10, 15, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 0 2rem;
}
.nav-container {
max-width: 1400px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
height: 70px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
}
.brand-logo {
width: 45px;
height: 45px;
background: linear-gradient(135deg, var(--accent-orange), var(--accent-red));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4rem;
box-shadow: 0 4px 20px var(--glow-orange);
}
.brand-text {
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -0.5px;
}
.brand-text span {
background: linear-gradient(135deg, var(--accent-orange), var(--accent-red));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-actions {
display: flex;
align-items: center;
gap: 12px;
}
.nav-btn {
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 10px 16px;
border-radius: 10px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.nav-btn:hover {
background: var(--bg-card-hover);
color: var(--text-primary);
border-color: var(--border-light);
transform: translateY(-1px);
}
.nav-btn.primary {
background: linear-gradient(135deg, var(--accent-orange), var(--accent-red));
border: none;
color: white;
box-shadow: 0 4px 15px var(--glow-orange);
}
.nav-btn.primary:hover {
box-shadow: 0 6px 25px var(--glow-orange);
}
/* Main Content */
.main-content {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
position: relative;
z-index: 1;
}
/* Section Tabs */
.section-tabs {
display: flex;
gap: 8px;
margin-bottom: 2rem;
padding: 6px;
background: var(--bg-secondary);
border-radius: 16px;
width: fit-content;
border: 1px solid var(--border);
}
.tab-btn {
padding: 12px 24px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 0.95rem;
font-weight: 600;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
}
.tab-btn:hover {
color: var(--text-primary);
background: rgba(255,255,255,0.05);
}
.tab-btn.active {
background: var(--bg-card);
color: var(--text-primary);
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}
.tab-icon {
font-size: 1rem;
}
/* Section Header */
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.5rem;
}
.section-title {
display: flex;
align-items: center;
gap: 16px;
}
.section-title h2 {
font-size: 1.8rem;
font-weight: 700;
letter-spacing: -0.5px;
}
.live-badge {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
color: var(--accent-red);
}
.live-dot {
width: 8px;
height: 8px;
background: var(--accent-red);
border-radius: 50%;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); }
}
.date-display {
color: var(--text-muted);
font-size: 0.95rem;
font-weight: 500;
}
/* Games Grid */
.games-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 1.25rem;
}
/* Game Card */
.game-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 20px;
padding: 1.5rem;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.game-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--accent-orange), var(--accent-blue));
opacity: 0;
transition: opacity 0.3s ease;
}
.game-card:hover {
border-color: var(--border-light);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
}
.game-card:hover::before {
opacity: 1;
}
.game-card.live {
border-color: rgba(239, 68, 68, 0.3);
}
.game-card.live::before {
background: var(--accent-red);
opacity: 1;
}
.game-status {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.25rem;
}
.status-badge {
padding: 5px 12px;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-badge.live {
background: rgba(239, 68, 68, 0.15);
color: var(--accent-red);
animation: status-pulse 2s ease infinite;
}
@keyframes status-pulse {
0%, 100% { background: rgba(239, 68, 68, 0.15); }
50% { background: rgba(239, 68, 68, 0.25); }
}
.status-badge.upcoming {
background: rgba(59, 130, 246, 0.15);
color: var(--accent-blue);
}
.status-badge.final {
background: rgba(113, 113, 122, 0.15);
color: var(--text-secondary);
}
.game-time {
color: var(--text-muted);
font-size: 0.85rem;
font-weight: 500;
}
.teams-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.team-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
background: rgba(255,255,255,0.02);
border-radius: 12px;
transition: background 0.2s ease;
}
.team-row:hover {
background: rgba(255,255,255,0.05);
}
.team-row.winner {
background: rgba(34, 197, 94, 0.08);
}
.team-info {
display: flex;
align-items: center;
gap: 14px;
}
.team-logo {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 0.8rem;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.team-details {
display: flex;
flex-direction: column;
gap: 2px;
}
.team-name {
font-weight: 700;
font-size: 1rem;
color: var(--text-primary);
}
.team-record {
font-size: 0.8rem;
color: var(--text-muted);
}
.team-score {
font-size: 1.75rem;
font-weight: 800;
color: var(--text-primary);
min-width: 60px;
text-align: right;
}
.team-score.leading {
color: var(--accent-green);
}
.game-footer {
margin-top: 1.25rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.game-channel {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
color: var(--text-muted);
}
.game-channel i {
color: var(--accent-blue);
}
.game-actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 8px 12px;
background: rgba(255,255,255,0.05);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s ease;
}
.action-btn:hover {
background: rgba(255,255,255,0.1);
color: var(--text-primary);
}
/* Loading State */
.loading-container {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem;
gap: 1.5rem;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 3px solid var(--border);
border-top-color: var(--accent-orange);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
color: var(--text-secondary);
font-size: 0.95rem;
}
/* Empty State */
.empty-state {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem;
text-align: center;
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1.5rem;
opacity: 0.5;
}
.empty-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.empty-desc {
color: var(--text-muted);
max-width: 300px;
}
/* Raw Output (fallback) */
.raw-output {
display: none;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 1.5rem;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
line-height: 1.7;
white-space: pre-wrap;
color: var(--text-secondary);
max-height: 600px;
overflow-y: auto;
}
.raw-output.error {
border-color: rgba(239, 68, 68, 0.3);
color: var(--accent-red);
}
/* Settings Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 24px;
padding: 2rem;
width: 90%;
max-width: 420px;
animation: modalIn 0.3s ease;
}
@keyframes modalIn {
from { opacity: 0; transform: scale(0.95) translateY(20px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.5rem;
}
.modal-title {
font-size: 1.25rem;
font-weight: 700;
}
.modal-close {
background: none;
border: none;
color: var(--text-muted);
font-size: 1.25rem;
cursor: pointer;
padding: 8px;
border-radius: 8px;
transition: all 0.2s;
}
.modal-close:hover {
background: rgba(255,255,255,0.1);
color: var(--text-primary);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
.form-input {
width: 100%;
padding: 14px 16px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text-primary);
font-size: 0.95rem;
transition: all 0.2s;
}
.form-input:focus {
outline: none;
border-color: var(--accent-orange);
box-shadow: 0 0 0 3px var(--glow-orange);
}
.form-input::placeholder {
color: var(--text-muted);
}
.modal-btn {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, var(--accent-orange), var(--accent-red));
border: none;
border-radius: 12px;
color: white;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.modal-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px var(--glow-orange);
}
/* Dev Panel */
.dev-panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 450px;
background: var(--bg-secondary);
border-left: 1px solid var(--border);
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 200;
display: flex;
flex-direction: column;
}
.dev-panel.open {
transform: translateX(0);
}
.dev-header {
padding: 1rem 1.5rem;
background: var(--bg-card);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.dev-title {
font-weight: 700;
display: flex;
align-items: center;
gap: 8px;
}
.dev-title i {
color: var(--accent-purple);
}
.dev-actions {
display: flex;
gap: 8px;
}
.dev-btn {
padding: 8px 16px;
border-radius: 8px;
border: none;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.dev-btn.run {
background: var(--accent-green);
color: white;
}
.dev-btn.run:hover {
background: #16a34a;
}
.dev-btn.close {
background: rgba(255,255,255,0.1);
color: var(--text-secondary);
}
.CodeMirror {
flex: 1;
font-size: 13px;
font-family: 'JetBrains Mono', monospace;
}
/* Sidebar Scripts */
.scripts-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid var(--border);
}
.scripts-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 1rem;
}
.scripts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.script-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 1.25rem;
cursor: pointer;
transition: all 0.2s ease;
}
.script-card:hover {
border-color: var(--border-light);
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.script-icon {
width: 42px;
height: 42px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
margin-bottom: 1rem;
}
.script-icon.orange { background: rgba(249, 115, 22, 0.15); color: var(--accent-orange); }
.script-icon.blue { background: rgba(59, 130, 246, 0.15); color: var(--accent-blue); }
.script-icon.purple { background: rgba(139, 92, 246, 0.15); color: var(--accent-purple); }
.script-icon.green { background: rgba(34, 197, 94, 0.15); color: var(--accent-green); }
.script-name {
font-weight: 600;
margin-bottom: 0.25rem;
}
.script-desc {
font-size: 0.8rem;
color: var(--text-muted);
}
/* Team Colors */
.team-ATL { background: linear-gradient(135deg, #E03A3E, #C1D32F); }
.team-BOS { background: linear-gradient(135deg, #007A33, #BA9653); }
.team-BKN { background: linear-gradient(135deg, #000000, #FFFFFF); }
.team-CHA { background: linear-gradient(135deg, #1D1160, #00788C); }
.team-CHI { background: linear-gradient(135deg, #CE1141, #000000); }
.team-CLE { background: linear-gradient(135deg, #860038, #FDBB30); }
.team-DAL { background: linear-gradient(135deg, #00538C, #002B5E); }
.team-DEN { background: linear-gradient(135deg, #0E2240, #FEC524); }
.team-DET { background: linear-gradient(135deg, #C8102E, #1D42BA); }
.team-GSW { background: linear-gradient(135deg, #1D428A, #FFC72C); }
.team-HOU { background: linear-gradient(135deg, #CE1141, #000000); }
.team-IND { background: linear-gradient(135deg, #002D62, #FDBB30); }
.team-LAC { background: linear-gradient(135deg, #C8102E, #1D428A); }
.team-LAL { background: linear-gradient(135deg, #552583, #FDB927); }
.team-MEM { background: linear-gradient(135deg, #5D76A9, #12173F); }
.team-MIA { background: linear-gradient(135deg, #98002E, #F9A01B); }
.team-MIL { background: linear-gradient(135deg, #00471B, #EEE1C6); }
.team-MIN { background: linear-gradient(135deg, #0C2340, #236192); }
.team-NOP { background: linear-gradient(135deg, #0C2340, #C8102E); }
.team-NYK { background: linear-gradient(135deg, #006BB6, #F58426); }
.team-OKC { background: linear-gradient(135deg, #007AC1, #EF3B24); }
.team-ORL { background: linear-gradient(135deg, #0077C0, #000000); }
.team-PHI { background: linear-gradient(135deg, #006BB6, #ED174C); }
.team-PHX { background: linear-gradient(135deg, #1D1160, #E56020); }
.team-POR { background: linear-gradient(135deg, #E03A3E, #000000); }
.team-SAC { background: linear-gradient(135deg, #5A2D81, #63727A); }
.team-SAS { background: linear-gradient(135deg, #C4CED4, #000000); }
.team-TOR { background: linear-gradient(135deg, #CE1141, #000000); }
.team-UTA { background: linear-gradient(135deg, #002B5C, #00471B); }
.team-WAS { background: linear-gradient(135deg, #002B5C, #E31837); }
.team-DEFAULT { background: linear-gradient(135deg, #333, #666); }
/* Responsive */
@media (max-width: 768px) {
.nav-container { padding: 0 1rem; }
.main-content { padding: 1rem; }
.games-grid { grid-template-columns: 1fr; }
.section-tabs { flex-wrap: wrap; width: 100%; }
.tab-btn { flex: 1; justify-content: center; }
.dev-panel { width: 100%; }
}
</style>
</head>
<body>
<!-- Background Effects -->
<div class="bg-effects">
<div class="bg-orb bg-orb-1"></div>
<div class="bg-orb bg-orb-2"></div>
</div>
<!-- Navigation -->
<nav>
<div class="nav-container">
<a href="#" class="brand">
<div class="brand-logo">🏀</div>
<div class="brand-text"><span>NBA</span>Hub</div>
</a>
<div class="nav-actions">
<button class="nav-btn" onclick="toggleDev()" title="Code Editor">
<i class="fas fa-code"></i>
<span>Dev</span>
</button>
<button class="nav-btn" onclick="toggleSettings()" title="Settings">
<i class="fas fa-cog"></i>
</button>
<button class="nav-btn primary" onclick="runScript('nba_schedule.py')" title="Refresh">
<i class="fas fa-sync-alt"></i>
<span>Refresh</span>
</button>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="main-content">
<!-- Section Tabs -->
<div class="section-tabs">
<button class="tab-btn active" onclick="switchTab('games')">
<i class="fas fa-basketball-ball tab-icon"></i>
Games
</button>
<button class="tab-btn" onclick="switchTab('standings')">
<i class="fas fa-trophy tab-icon"></i>
Standings
</button>
<button class="tab-btn" onclick="switchTab('stats')">
<i class="fas fa-chart-bar tab-icon"></i>
Stats
</button>
<button class="tab-btn" onclick="switchTab('players')">
<i class="fas fa-user tab-icon"></i>
Players
</button>
</div>
<!-- Section Header -->
<div class="section-header">
<div class="section-title">
<h2>Today's Games</h2>
<div class="live-badge" id="live-badge" style="display: none;">
<div class="live-dot"></div>
<span>LIVE</span>
</div>
</div>
<div class="date-display" id="current-date"></div>
</div>
<!-- Loading State -->
<div class="loading-container" id="loader">
<div class="loading-spinner"></div>
<div class="loading-text">Fetching latest games...</div>
</div>
<!-- Empty State -->
<div class="empty-state" id="empty-state">
<div class="empty-icon">🏀</div>
<div class="empty-title">No Games Today</div>
<div class="empty-desc">Check back later for upcoming games or view the schedule.</div>
</div>
<!-- Games Grid -->
<div class="games-grid" id="games-grid"></div>
<!-- Raw Output Fallback -->
<div class="raw-output" id="raw-output"></div>
<!-- Available Scripts -->
<div class="scripts-section">
<div class="scripts-title">Available Tools</div>
<div class="scripts-grid">
<div class="script-card" onclick="runScript('nba_schedule.py')">
<div class="script-icon orange">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="script-name">Game Schedule</div>
<div class="script-desc">View today's NBA games</div>
</div>
<div class="script-card" onclick="runScript('nba_standings.py')">
<div class="script-icon blue">
<i class="fas fa-list-ol"></i>
</div>
<div class="script-name">Standings</div>
<div class="script-desc">Conference standings</div>
</div>
<div class="script-card" onclick="runScript('nba_leaders.py')">
<div class="script-icon purple">
<i class="fas fa-star"></i>
</div>
<div class="script-name">League Leaders</div>
<div class="script-desc">Top performers</div>
</div>
<div class="script-card" onclick="runScript('nba_injuries.py')">
<div class="script-icon green">
<i class="fas fa-medkit"></i>
</div>
<div class="script-name">Injury Report</div>
<div class="script-desc">Player injuries & status</div>
</div>
</div>
</div>
</div>
<!-- Settings Modal -->
<div class="modal-overlay" id="settings-modal">
<div class="modal">
<div class="modal-header">
<div class="modal-title">⚙️ Settings</div>
<button class="modal-close" onclick="toggleSettings()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="form-group">
<label class="form-label">Backend URL</label>
<input type="text" class="form-input" id="api-url" placeholder="https://your-app.up.railway.app">
</div>
<button class="modal-btn" onclick="saveSettings()">
Save & Connect
</button>
</div>
</div>
<!-- Dev Panel -->
<div class="dev-panel" id="dev-panel">
<div class="dev-header">
<div class="dev-title">
<i class="fas fa-terminal"></i>
Code Editor
</div>
<div class="dev-actions">
<button class="dev-btn run" onclick="executeCode()">
<i class="fas fa-play"></i> Run
</button>
<button class="dev-btn close" onclick="toggleDev()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<textarea id="editor"></textarea>
</div>
<script>
// Initialize CodeMirror
const editor = CodeMirror.fromTextArea(document.getElementById("editor"), {
mode: "python",
theme: "dracula",
lineNumbers: true,
lineWrapping: true
});
// DOM Elements
const loader = document.getElementById("loader");
const gamesGrid = document.getElementById("games-grid");
const rawOutput = document.getElementById("raw-output");
const emptyState = document.getElementById("empty-state");
const liveBadge = document.getElementById("live-badge");
const modal = document.getElementById("settings-modal");
// Set current date
document.getElementById("current-date").textContent = new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Team abbreviation mapping
const teamAbbreviations = {
'Hawks': 'ATL', 'Celtics': 'BOS', 'Nets': 'BKN', 'Hornets': 'CHA',
'Bulls': 'CHI', 'Cavaliers': 'CLE', 'Mavericks': 'DAL', 'Nuggets': 'DEN',
'Pistons': 'DET', 'Warriors': 'GSW', 'Rockets': 'HOU', 'Pacers': 'IND',
'Clippers': 'LAC', 'Lakers': 'LAL', 'Grizzlies': 'MEM', 'Heat': 'MIA',
'Bucks': 'MIL', 'Timberwolves': 'MIN', 'Pelicans': 'NOP', 'Knicks': 'NYK',
'Thunder': 'OKC', 'Magic': 'ORL', '76ers': 'PHI', 'Suns': 'PHX',
'Trail Blazers': 'POR', 'Blazers': 'POR', 'Kings': 'SAC', 'Spurs': 'SAS',
'Raptors': 'TOR', 'Jazz': 'UTA', 'Wizards': 'WAS'
};
function getTeamAbbr(teamName) {
for (const [name, abbr] of Object.entries(teamAbbreviations)) {
if (teamName.toLowerCase().includes(name.toLowerCase())) {
return abbr;
}
}
return 'DEFAULT';
}
// On Load
window.onload = function() {
const savedUrl = localStorage.getItem("backend_url");
if (!savedUrl) {
toggleSettings();
} else {
document.getElementById("api-url").value = savedUrl;
runScript('nba_schedule.py');
}
};
// Parse game data from output
function parseGames(output) {
// This is a flexible parser - adjust based on your actual output format
const games = [];
const lines = output.split('\n').filter(line => line.trim());
// Try to detect game patterns (customize based on your script output)
let currentGame = null;
for (const line of lines) {
// Pattern: "Team1 vs Team2" or "Team1 @ Team2"
const vsMatch = line.match(/(.+?)\s+(vs\.?|@)\s+(.+)/i);
if (vsMatch) {
currentGame = {
away: { name: vsMatch[1].trim(), score: null },
home: { name: vsMatch[3].trim(), score: null },
status: 'upcoming',
time: '',
channel: ''
};
games.push(currentGame);
continue;
}
// Pattern: Score line "100 - 98" or similar
const scoreMatch = line.match(/(\d+)\s*[-–]\s*(\d+)/);
if (scoreMatch && currentGame) {
currentGame.away.score = parseInt(scoreMatch[1]);
currentGame.home.score = parseInt(scoreMatch[2]);
currentGame.status = 'live';
}
// Pattern: Time like "7:00 PM ET"
const timeMatch = line.match(/(\d{1,2}:\d{2}\s*(AM|PM)\s*(ET|PT|CT)?)/i);
if (timeMatch && currentGame) {
currentGame.time = timeMatch[1];
}
// Pattern: Final
if (line.toLowerCase().includes('final') && currentGame) {
currentGame.status = 'final';
}
// Pattern: LIVE or In Progress
if ((line.toLowerCase().includes('live') || line.toLowerCase().includes('in progress')) && currentGame) {
currentGame.status = 'live';
}
}
return games;
}
// Render game cards
function renderGames(games) {
if (!games || games.length === 0) {
gamesGrid.style.display = 'none';
emptyState.style.display = 'flex';
liveBadge.style.display = 'none';
return;
}
emptyState.style.display = 'none';
gamesGrid.style.display = 'grid';
const hasLive = games.some(g => g.status === 'live');
liveBadge.style.display = hasLive ? 'flex' : 'none';
gamesGrid.innerHTML = games.map(game => {
const awayAbbr = getTeamAbbr(game.away.name);
const homeAbbr = getTeamAbbr(game.home.name);
const awayLeading = game.away.score > game.home.score;
const homeLeading = game.home.score > game.away.score;
return `
<div class="game-card ${game.status === 'live' ? 'live' : ''}">
<div class="game-status">
<span class="status-badge ${game.status}">${
game.status === 'live' ? '🔴 LIVE' :
game.status === 'final' ? 'FINAL' : 'UPCOMING'
}</span>
<span class="game-time">${game.time || ''}</span>
</div>
<div class="teams-container">
<div class="team-row ${game.status === 'final' && awayLeading ? 'winner' : ''}">
<div class="team-info">
<div class="team-logo team-${awayAbbr}">${awayAbbr}</div>
<div class="team-details">
<div class="team-name">${game.away.name}</div>
<div class="team-record">${game.away.record || ''}</div>
</div>
</div>
<div class="team-score ${awayLeading ? 'leading' : ''}">${game.away.score ?? '-'}</div>
</div>
<div class="team-row ${game.status === 'final' && homeLeading ? 'winner' : ''}">
<div class="team-info">
<div class="team-logo team-${homeAbbr}">${homeAbbr}</div>
<div class="team-details">
<div class="team-name">${game.home.name}</div>
<div class="team-record">${game.home.record || ''}</div>
</div>
</div>
<div class="team-score ${homeLeading ? 'leading' : ''}">${game.home.score ?? '-'}</div>
</div>
</div>
<div class="game-footer">
<div class="game-channel">
<i class="fas fa-tv"></i>
${game.channel || 'NBA League Pass'}
</div>
<div class="game-actions">
<button class="action-btn">
<i class="fas fa-chart-line"></i> Stats
</button>
</div>
</div>
</div>
`;
}).join('');
}
// Run Script
async function runScript(filename) {
// Show loading
loader.style.display = 'flex';
gamesGrid.style.display = 'none';
rawOutput.style.display = 'none';
emptyState.style.display = 'none';
try {
// Fetch script
const scriptRes = await fetch(`scripts/${filename}?t=${Date.now()}`);
if (!scriptRes.ok) throw new Error("Could not load script file.");
const pythonCode = await scriptRes.text();
editor.setValue(pythonCode);
// Send to backend
const backendUrl = localStorage.getItem("backend_url").replace(/\/$/, "");
const res = await fetch(`${backendUrl}/run`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: pythonCode })
});
if (!res.ok) throw new Error("Backend connection failed.");
const data = await res.json();
// Try to parse as structured game data
const games = parseGames(data.output);
if (games.length > 0) {
renderGames(games);
rawOutput.style.display = 'none';
} else {
// Fallback to raw output
gamesGrid.style.display = 'none';
rawOutput.style.display = 'block';
rawOutput.className = data.error ? 'raw-output error' : 'raw-output';
rawOutput.textContent = data.output || 'No data returned.';
}
} catch (err) {
gamesGrid.style.display = 'none';
rawOutput.style.display = 'block';
rawOutput.className = 'raw-output error';
rawOutput.textContent = `⚠️ Error: ${err.message}`;
} finally {
loader.style.display = 'none';
}
}
// Execute from editor
async function executeCode() {
const code = editor.getValue();
const backendUrl = localStorage.getItem("backend_url").replace(/\/$/, "");
loader.style.display = 'flex';
gamesGrid.style.display = 'none';
try {
const res = await fetch(`${backendUrl}/run`, {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ code })
});
const data = await res.json();
const games = parseGames(data.output);
if (games.length > 0) {
renderGames(games);
} else {
rawOutput.style.display = 'block';
rawOutput.textContent = data.output;
}
} catch(e) {
rawOutput.style.display = 'block';
rawOutput.className = 'raw-output error';
rawOutput.textContent = e.message;
} finally {
loader.style.display = 'none';
}
}
// Tab switching
function switchTab(tab) {
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
event.target.closest('.tab-btn').classList.add('active');
// You can add different script calls for each tab
const scriptMap = {
'games': 'nba_schedule.py',
'standings': 'nba_standings.py',
'stats': 'nba_leaders.py',
'players': 'nba_players.py'
};
if (scriptMap[tab]) {
runScript(scriptMap[tab]);
}
}
// Toggle functions
function toggleSettings() {
modal.style.display = modal.style.display === 'flex' ? 'none' : 'flex';
}
function saveSettings() {
const url = document.getElementById("api-url").value;
if(url) {
localStorage.setItem("backend_url", url);
toggleSettings();
runScript('nba_schedule.py');
}
}
function toggleDev() {
document.getElementById("dev-panel").classList.toggle("open");
setTimeout(() => editor.refresh(), 300);
}
// Close modal on overlay click
modal.addEventListener('click', (e) => {
if (e.target === modal) toggleSettings();
});
</script>
</body>
</html>