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 Logo
+ +
+ +# 🏈 GameTime + +**Your Fantasy Football Command Center** + +*Track all your leagues, matchups, and players in one sleek dashboard* + +[![Python](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://python.org) +[![Flask](https://img.shields.io/badge/Flask-2.3.3-green.svg)](https://flask.palletsprojects.com/) +[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](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.

+ + + + `, { + headers: { 'Content-Type': 'text/html' } + }); + } + }) + ); + } else { + // For static resources, cache first + event.respondWith( + caches.match(request) + .then(response => { + return response || fetch(request) + .then(response => { + // Cache successful responses + if (response.status === 200) { + const responseClone = response.clone(); + caches.open(CACHE_NAME) + .then(cache => cache.put(request, responseClone)); + } + return response; + }); + }) + ); + } +}); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index c3cfcc8..8377252 100644 --- a/templates/base.html +++ b/templates/base.html @@ -5,6 +5,12 @@ {% block title %}GameTime{% endblock %} + + + + + + @@ -42,13 +48,13 @@ 🏈 Sleeper App - 💻 GitHub + 💻 Source Code @@ -165,6 +172,19 @@ gameContent.style.display = 'none'; }); }); + + // Register service worker for PWA functionality + if ('serviceWorker' in navigator) { + window.addEventListener('load', function() { + navigator.serviceWorker.register('/static/sw.js') + .then(function(registration) { + console.log('GameTime SW: Registered successfully'); + }) + .catch(function(error) { + console.log('GameTime SW: Registration failed:', error); + }); + }); + } diff --git a/templates/dashboard.html b/templates/dashboard.html index fcc69e4..3795df2 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -84,16 +84,22 @@
{% for game in day_info.games %} -
+
-
{{ game.time }}
+
+ {{ game.time }} + {% if game.is_live %} + + LIVE + {% endif %} +
{{ game.away_team }} @ {{ game.home_team }}
- {% if game.is_past %}▶{% else %}▼{% endif %} + {% if game.is_past and not game.is_live %}▶{% else %}▼{% endif %}
diff --git a/templates/index.html b/templates/index.html index 88eac87..0a66fa4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,10 +5,11 @@ {% block content %}
+

GameTime

-
* * * * 0,1,4
+
* * * * 4,7,1
-

Enter your Sleeper username to view your fantasy teams

+

Enter your Sleeper username to get your league schedules.

@@ -17,7 +18,7 @@
-

Example: sleeperuser

+

Example: @mySleeperUsername