diff --git a/README.md b/README.md
new file mode 100644
index 0000000..97ad2ef
--- /dev/null
+++ b/README.md
@@ -0,0 +1,102 @@
+#
+
+
+
+# 🏈 GameTime
+
+**Your Fantasy Football Command Center**
+
+*Track all your leagues, matchups, and players in one sleek dashboard*
+
+[](https://python.org)
+[](https://flask.palletsprojects.com/)
+[](https://docker.com)
+
+
+
+## 🚀 What is GameTime?
+
+GameTime is your all-in-one fantasy football dashboard that connects to Sleeper and ESPN to give you the ultimate game day experience. See all your leagues, matchups, and live scores in one beautiful interface.
+
+### ✨ Features
+
+- 📊 **Multi-League Dashboard** - View all your Sleeper leagues at once
+- 🔴 **Live Score Updates** - Real-time score refreshes during game day
+- 📅 **NFL Schedule Integration** - See when your players are playing
+- 🌙 **Dark/Light Theme** - Switch themes for day or night viewing
+- 📱 **Mobile Responsive** - Looks great on all devices
+- 🐳 **Docker Ready** - Easy deployment with containers
+
+## 🏃♂️ Quick Start
+
+### Option 1: Local Development
+```bash
+# Install dependencies
+pip install -r requirements.txt
+
+# Run the app
+python app.py
+
+# Visit http://localhost:5000
+```
+
+### Option 2: Docker
+```bash
+# Build and run
+docker-compose up
+
+# Or run detached
+docker-compose up -d
+```
+
+## 🎮 How to Use
+
+1. **Enter your Sleeper username** on the homepage
+2. **View your dashboard** - see all leagues and current week matchups
+3. **Navigate weeks** - check past weeks or look ahead
+4. **Live updates** - scores refresh automatically during games
+5. **Theme switching** - toggle between light and dark modes
+
+## 🛠 Tech Stack
+
+- **Backend**: Flask (Python)
+- **APIs**: Sleeper API, ESPN API
+- **Frontend**: HTML, CSS, JavaScript
+- **Deployment**: Docker + Docker Compose
+
+## 📁 Project Structure
+
+```
+gametime/
+├── app.py # Main Flask application
+├── config.py # App configuration
+├── services/ # API integrations
+│ ├── sleeper_api.py # Sleeper API client
+│ └── espn_api.py # ESPN API client
+├── templates/ # HTML templates
+├── static/ # CSS and assets
+└── ContainerFile # Docker build config
+```
+
+## 🤝 Contributing
+
+Got ideas for GameTime? We'd love to hear them!
+
+1. Fork the repo
+2. Create a feature branch
+3. Make your changes
+4. Submit a pull request
+
+## 📜 License
+
+This project is open source and available under the MIT License.
+
+---
+
+
+
+**Ready for GameTime?** 🏈
+
+*Built with ❤️ for fantasy football fanatics*
+
+
\ No newline at end of file
diff --git a/app.py b/app.py
index a5b8689..509751c 100644
--- a/app.py
+++ b/app.py
@@ -1,4 +1,4 @@
-from flask import Flask, render_template, jsonify, request, session, redirect, url_for
+from flask import Flask, render_template, jsonify, request, session, redirect, url_for, send_file
from datetime import datetime, timedelta
import os
import sys
@@ -59,7 +59,7 @@ def inject_apis():
return dict(
sleeper_api=sleeper_api,
app_version=app.config['VERSION'],
- current_theme=session.get('theme', 'light')
+ current_theme=session.get('theme', 'dark')
)
@app.route('/')
@@ -81,7 +81,7 @@ def dashboard_form():
@app.route('/toggle_theme', methods=['POST'])
def toggle_theme():
"""Toggle between light and dark theme"""
- current_theme = session.get('theme', 'light')
+ current_theme = session.get('theme', 'dark')
new_theme = 'dark' if current_theme == 'light' else 'light'
session['theme'] = new_theme
@@ -359,6 +359,17 @@ def refresh_data(username, week):
print(f"ERROR: Refresh data exception: {str(e)}", flush=True)
return jsonify({'error': str(e)}), 500
+# Static file routes
+@app.route('/gametime_logo.png')
+def logo():
+ """Serve the logo file"""
+ return send_file('gametime_logo.png', mimetype='image/png')
+
+@app.route('/favicon.ico')
+def favicon():
+ """Serve the favicon"""
+ return send_file('gametime_logo.png', mimetype='image/png')
+
if __name__ == '__main__':
print("DEBUG: Starting Flask app on 0.0.0.0:5000", flush=True)
print(f"DEBUG: GameTime {app.config['VERSION']} ready!", flush=True)
diff --git a/gametime_logo.png b/gametime_logo.png
new file mode 100644
index 0000000..d5b2d9c
Binary files /dev/null and b/gametime_logo.png differ
diff --git a/services/__pycache__/__init__.cpython-313.pyc b/services/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..04319b7
Binary files /dev/null and b/services/__pycache__/__init__.cpython-313.pyc differ
diff --git a/services/__pycache__/espn_api.cpython-313.pyc b/services/__pycache__/espn_api.cpython-313.pyc
new file mode 100644
index 0000000..cb8be8c
Binary files /dev/null and b/services/__pycache__/espn_api.cpython-313.pyc differ
diff --git a/services/__pycache__/sleeper_api.cpython-313.pyc b/services/__pycache__/sleeper_api.cpython-313.pyc
new file mode 100644
index 0000000..3c7f316
Binary files /dev/null and b/services/__pycache__/sleeper_api.cpython-313.pyc differ
diff --git a/services/espn_api.py b/services/espn_api.py
index 9669a1e..8c92e9d 100644
--- a/services/espn_api.py
+++ b/services/espn_api.py
@@ -76,8 +76,15 @@ class ESPNAPI:
else:
tz_abbr = 'Local'
- # Extract team information
- competitors = event['competitions'][0]['competitors']
+ # Extract team information and game status
+ competition = event['competitions'][0]
+ competitors = competition['competitors']
+
+ # Get game status information
+ status = competition.get('status', {})
+ game_state = status.get('type', {}).get('state', 'pre') # pre, in, post
+ is_live = game_state == 'in'
+ status_display = status.get('type', {}).get('name', 'Scheduled')
home_team = None
away_team = None
@@ -102,7 +109,10 @@ class ESPNAPI:
'home_team': home_team,
'away_team': away_team,
'teams': [home_team, away_team],
- 'is_past': game_date_local < datetime.now(game_date_local.tzinfo)
+ 'is_past': game_date_local < datetime.now(game_date_local.tzinfo),
+ 'is_live': is_live,
+ 'game_state': game_state,
+ 'status_display': status_display
})
except Exception as e:
print(f"ESPN API: Error processing event {i+1}: {str(e)}", flush=True)
diff --git a/static/manifest.json b/static/manifest.json
new file mode 100644
index 0000000..470e50d
--- /dev/null
+++ b/static/manifest.json
@@ -0,0 +1,21 @@
+{
+ "name": "GameTime - Fantasy Football Dashboard",
+ "short_name": "GameTime",
+ "description": "View your Sleeper fantasy football leagues and player schedules",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#1e1e2e",
+ "theme_color": "#8b7ff5",
+ "orientation": "portrait-primary",
+ "icons": [
+ {
+ "src": "/gametime_logo.png",
+ "sizes": "any",
+ "type": "image/png",
+ "purpose": "any maskable"
+ }
+ ],
+ "categories": ["sports", "games"],
+ "scope": "/",
+ "lang": "en"
+}
\ No newline at end of file
diff --git a/static/style.css b/static/style.css
index f46121d..daa2575 100644
--- a/static/style.css
+++ b/static/style.css
@@ -72,6 +72,14 @@
margin-bottom: 20px;
}
+.app-logo {
+ max-width: 120px;
+ height: auto;
+ margin-bottom: 15px;
+ border-radius: 12px;
+ box-shadow: var(--shadow-sm);
+}
+
.app-name {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
color: var(--text-primary);
@@ -1337,4 +1345,34 @@ body {
opacity: 0.8;
}
+/* Live game indicator */
+.live-indicator {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ background-color: #ff0000;
+ border-radius: 50%;
+ margin-left: 8px;
+ margin-right: 6px;
+ animation: pulse-red 1.5s ease-in-out infinite;
+}
+
+.live-text {
+ color: #ff0000;
+ font-weight: bold;
+ font-size: 11px;
+ letter-spacing: 0.5px;
+}
+
+@keyframes pulse-red {
+ 0%, 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.3;
+ transform: scale(1.2);
+ }
+}
+
diff --git a/static/sw.js b/static/sw.js
new file mode 100644
index 0000000..d481f6d
--- /dev/null
+++ b/static/sw.js
@@ -0,0 +1,93 @@
+// GameTime Service Worker
+const CACHE_NAME = 'gametime-v1.0.0';
+const STATIC_CACHE = [
+ '/',
+ '/static/style.css',
+ '/static/manifest.json',
+ '/gametime_logo.png',
+ '/favicon.ico'
+];
+
+// Install event - cache static resources
+self.addEventListener('install', event => {
+ console.log('GameTime SW: Installing...');
+ event.waitUntil(
+ caches.open(CACHE_NAME)
+ .then(cache => {
+ console.log('GameTime SW: Caching static resources');
+ return cache.addAll(STATIC_CACHE);
+ })
+ .then(() => self.skipWaiting())
+ );
+});
+
+// Activate event - clean up old caches
+self.addEventListener('activate', event => {
+ console.log('GameTime SW: Activating...');
+ event.waitUntil(
+ caches.keys().then(cacheNames => {
+ return Promise.all(
+ cacheNames.map(cacheName => {
+ if (cacheName !== CACHE_NAME) {
+ console.log('GameTime SW: Deleting old cache:', cacheName);
+ return caches.delete(cacheName);
+ }
+ })
+ );
+ }).then(() => self.clients.claim())
+ );
+});
+
+// Fetch event - network first, fallback to cache for static resources
+self.addEventListener('fetch', event => {
+ const { request } = event;
+
+ // Skip non-GET requests
+ if (request.method !== 'GET') return;
+
+ // For API calls and dynamic content, use network first
+ if (request.url.includes('/api/') ||
+ request.url.includes('/dashboard') ||
+ request.url.includes('/refresh')) {
+ event.respondWith(
+ fetch(request)
+ .catch(() => {
+ // If offline, show a basic offline page
+ if (request.destination === 'document') {
+ return new Response(`
+
+
+ GameTime - Offline
+
+
+
+
You're offline
+
Please check your internet connection and try again.