Compare commits
1 commit
main
...
ft-live-sc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4ecd3888d |
11 changed files with 75 additions and 1025 deletions
|
|
@ -1,16 +1,16 @@
|
||||||
|
|
||||||
# <div align="center"><img src="rosterhash_logo.png" alt="RosterHash Logo" width="200"/></div>
|
# <div align="center"><img src="rosterhash_logo.png" alt="RosterHash Logo" width="200"/></div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](https://quay.io/repository/eddiefigsystems/rosterhash)
|
|
||||||
|
|
||||||
# 🏈 RosterHash
|
# 🏈 RosterHash
|
||||||
|
|
||||||
**Your Fantasy Football Command Center**
|
**Your Fantasy Football Command Center**
|
||||||
|
|
||||||
*Track all your leagues, matchups, and players in one dashboard*
|
*Track all your leagues, matchups, and players in one dashboard*
|
||||||
|
|
||||||
|
[](https://python.org)
|
||||||
|
[](https://flask.palletsprojects.com/)
|
||||||
|
[](https://docker.com)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
4
app.py
4
app.py
|
|
@ -6,7 +6,6 @@ import traceback
|
||||||
from services.sleeper_api import SleeperAPI
|
from services.sleeper_api import SleeperAPI
|
||||||
from services.espn_api import ESPNAPI
|
from services.espn_api import ESPNAPI
|
||||||
from config import Config
|
from config import Config
|
||||||
from faker import Faker
|
|
||||||
|
|
||||||
# Force unbuffered output for Docker logs
|
# Force unbuffered output for Docker logs
|
||||||
os.environ['PYTHONUNBUFFERED'] = '1'
|
os.environ['PYTHONUNBUFFERED'] = '1'
|
||||||
|
|
@ -88,7 +87,6 @@ def index():
|
||||||
print("DEBUG: Index route accessed", flush=True)
|
print("DEBUG: Index route accessed", flush=True)
|
||||||
# Get the last used username from cookie
|
# Get the last used username from cookie
|
||||||
last_username = request.cookies.get('last_username', '')
|
last_username = request.cookies.get('last_username', '')
|
||||||
mySleeperUsername = Faker().user_name()
|
|
||||||
print(f"DEBUG: Last username from cookie: '{last_username}'", flush=True)
|
print(f"DEBUG: Last username from cookie: '{last_username}'", flush=True)
|
||||||
|
|
||||||
# Auto-redirect to dashboard if username cookie exists
|
# Auto-redirect to dashboard if username cookie exists
|
||||||
|
|
@ -96,7 +94,7 @@ def index():
|
||||||
print(f"DEBUG: Auto-redirecting to dashboard for user: '{last_username}'", flush=True)
|
print(f"DEBUG: Auto-redirecting to dashboard for user: '{last_username}'", flush=True)
|
||||||
return redirect(url_for('dashboard_current', username=last_username.strip()))
|
return redirect(url_for('dashboard_current', username=last_username.strip()))
|
||||||
|
|
||||||
return render_template('index.html', last_username=last_username, mySleeperUsername=mySleeperUsername)
|
return render_template('index.html', last_username=last_username)
|
||||||
|
|
||||||
@app.route('/dashboard')
|
@app.route('/dashboard')
|
||||||
def dashboard_form():
|
def dashboard_form():
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
rosterhash:
|
rosterhash:
|
||||||
image: quay.io/eddiefigsystems/rosterhash:latest
|
image: rosterhash:latest
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import os
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
# App version
|
# App version
|
||||||
VERSION = '1.2.1'
|
VERSION = '1.2.0'
|
||||||
|
|
||||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
|
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
Flask==2.3.3
|
Flask==2.3.3
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
Faker==37.12.0
|
|
||||||
|
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
// Easter Eggs for RosterHash
|
|
||||||
// Date-based theme modifications
|
|
||||||
|
|
||||||
class EasterEggs {
|
|
||||||
constructor() {
|
|
||||||
this.today = new Date();
|
|
||||||
this.month = this.today.getMonth() + 1; // 1-12
|
|
||||||
this.day = this.today.getDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if today is Thanksgiving (4th Thursday of November)
|
|
||||||
isThanksgiving() {
|
|
||||||
if (this.month !== 11) return false; // Not November
|
|
||||||
|
|
||||||
const year = this.today.getFullYear();
|
|
||||||
let thursdayCount = 0;
|
|
||||||
|
|
||||||
// Count Thursdays in November
|
|
||||||
for (let day = 1; day <= 30; day++) {
|
|
||||||
const date = new Date(year, 10, day); // Month 10 = November
|
|
||||||
if (date.getDay() === 4) { // Thursday
|
|
||||||
thursdayCount++;
|
|
||||||
if (thursdayCount === 4 && day === this.day) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if today is April Fools' Day
|
|
||||||
isAprilFools() {
|
|
||||||
return this.month === 4 && this.day === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get April Fools team name
|
|
||||||
getAprilFoolsTeamName(abbrev) {
|
|
||||||
const jokeNames = {
|
|
||||||
'SF': '40-Whiners',
|
|
||||||
'LAR': 'Lambs',
|
|
||||||
'LAC': '🔋 Low Battery',
|
|
||||||
'KC': 'Chefs',
|
|
||||||
'BUF': 'Buffalo Wild Wings',
|
|
||||||
'NE': 'Cheaters',
|
|
||||||
'NYJ': 'Just End The Season',
|
|
||||||
'NYG': 'Littles',
|
|
||||||
'DAL': 'America\'s Team (lol)',
|
|
||||||
'PHI': 'Iggles',
|
|
||||||
'WAS': 'Commies',
|
|
||||||
'GB': 'Cheese Heads',
|
|
||||||
'CHI': 'Da Burrs',
|
|
||||||
'MIN': 'Purple People Eaters',
|
|
||||||
'DET': 'Kitties',
|
|
||||||
'TB': 'Tompa Bay',
|
|
||||||
'NO': 'Ain\'ts',
|
|
||||||
'ATL': 'Dirty Birds',
|
|
||||||
'CAR': 'Kittens',
|
|
||||||
'ARI': 'Birdinals',
|
|
||||||
'SEA': 'Rain City Bird Brains',
|
|
||||||
'PIT': 'Stealers',
|
|
||||||
'BAL': 'Purple Birds',
|
|
||||||
'CLE': 'Factory of Sadness',
|
|
||||||
'CIN': 'Bungles',
|
|
||||||
'HOU': 'Texas Cows',
|
|
||||||
'TEN': 'Tacks',
|
|
||||||
'IND': 'Dolts',
|
|
||||||
'JAX': 'Jag-wires',
|
|
||||||
'LV': 'Traitors',
|
|
||||||
'DEN': 'Neigh-bors',
|
|
||||||
'MIA': 'Dolphins (but warmer)',
|
|
||||||
};
|
|
||||||
return jokeNames[abbrev] || abbrev;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply Easter eggs
|
|
||||||
apply() {
|
|
||||||
// Thanksgiving: Replace stars with turkey emoji
|
|
||||||
if (this.isThanksgiving()) {
|
|
||||||
console.log('🦃 Happy Thanksgiving!');
|
|
||||||
this.applyThanksgiving();
|
|
||||||
}
|
|
||||||
|
|
||||||
// April Fools: Replace team names with joke versions
|
|
||||||
if (this.isAprilFools()) {
|
|
||||||
console.log('🤡 April Fools!');
|
|
||||||
this.applyAprilFools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply Thanksgiving theme
|
|
||||||
applyThanksgiving() {
|
|
||||||
// Replace star emoji in Favorite Teams sidebar section
|
|
||||||
const sidebarSummary = document.querySelector('.sidebar-details summary');
|
|
||||||
if (sidebarSummary && sidebarSummary.textContent.includes('⭐')) {
|
|
||||||
sidebarSummary.innerHTML = sidebarSummary.innerHTML.replace('⭐', '🦃');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace star in main Favorite Teams section
|
|
||||||
const mainFavoritesTitle = document.querySelector('.favorites-section-main h2');
|
|
||||||
if (mainFavoritesTitle && mainFavoritesTitle.textContent.includes('⭐')) {
|
|
||||||
mainFavoritesTitle.innerHTML = mainFavoritesTitle.innerHTML.replace('⭐', '🦃');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add turkey emoji after "Week X Games"
|
|
||||||
const scheduleTitle = document.querySelector('.schedule-section h2');
|
|
||||||
if (scheduleTitle) {
|
|
||||||
const text = scheduleTitle.innerHTML;
|
|
||||||
if (!text.includes('🦃')) {
|
|
||||||
scheduleTitle.innerHTML = text.replace(/Week (\d+) Games/, 'Week $1 Games 🦃');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply April Fools theme
|
|
||||||
applyAprilFools() {
|
|
||||||
// Store reference to this for use in setTimeout
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
// Wait for favorites to be rendered, then replace team names
|
|
||||||
setTimeout(() => {
|
|
||||||
// Replace team names in favorite team rows (main dashboard)
|
|
||||||
document.querySelectorAll('.favorite-team-name').forEach(el => {
|
|
||||||
const originalName = el.textContent.trim();
|
|
||||||
// Try to find the abbreviation from TEAM_NAMES
|
|
||||||
let abbrev = null;
|
|
||||||
for (const [key, value] of Object.entries(TEAM_NAMES)) {
|
|
||||||
if (value === originalName) {
|
|
||||||
abbrev = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (abbrev) {
|
|
||||||
el.textContent = self.getAprilFoolsTeamName(abbrev);
|
|
||||||
el.style.fontFamily = '"Comic Sans MS", "Comic Sans", cursive';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace opponent names
|
|
||||||
document.querySelectorAll('.favorite-opponent').forEach(el => {
|
|
||||||
const text = el.textContent.trim();
|
|
||||||
const match = text.match(/^(vs|@) (.+)$/);
|
|
||||||
if (match) {
|
|
||||||
const prefix = match[1];
|
|
||||||
const opponentName = match[2];
|
|
||||||
|
|
||||||
// Find abbreviation
|
|
||||||
let abbrev = null;
|
|
||||||
for (const [key, value] of Object.entries(TEAM_NAMES)) {
|
|
||||||
if (value === opponentName) {
|
|
||||||
abbrev = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (abbrev) {
|
|
||||||
el.textContent = `${prefix} ${self.getAprilFoolsTeamName(abbrev)}`;
|
|
||||||
el.style.fontFamily = '"Comic Sans MS", "Comic Sans", cursive';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace team names in sidebar favorites list
|
|
||||||
document.querySelectorAll('.favorite-game-card .favorite-matchup').forEach(el => {
|
|
||||||
const originalName = el.textContent.trim();
|
|
||||||
let abbrev = null;
|
|
||||||
for (const [key, value] of Object.entries(TEAM_NAMES)) {
|
|
||||||
if (value === originalName) {
|
|
||||||
abbrev = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (abbrev) {
|
|
||||||
el.textContent = self.getAprilFoolsTeamName(abbrev);
|
|
||||||
el.style.fontFamily = '"Comic Sans MS", "Comic Sans", cursive';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add April Fools indicator
|
|
||||||
const header = document.querySelector('.dashboard-header h1');
|
|
||||||
if (header && !header.textContent.includes('🤡')) {
|
|
||||||
header.innerHTML += ' <span style="font-size: 0.8em;">🤡</span>';
|
|
||||||
}
|
|
||||||
}, 500); // Wait for favorites to render
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize and apply Easter eggs when DOM is ready
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const easterEggs = new EasterEggs();
|
|
||||||
easterEggs.apply();
|
|
||||||
|
|
||||||
// Re-apply when favorites are updated (listen for changes)
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
if (easterEggs.isThanksgiving()) {
|
|
||||||
easterEggs.applyThanksgiving();
|
|
||||||
}
|
|
||||||
if (easterEggs.isAprilFools()) {
|
|
||||||
easterEggs.applyAprilFools();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Observe favorites sections for changes
|
|
||||||
const favoritesDisplay = document.getElementById('favorites-display');
|
|
||||||
const favoritesList = document.getElementById('favorites-list');
|
|
||||||
|
|
||||||
if (favoritesDisplay) {
|
|
||||||
observer.observe(favoritesDisplay, { childList: true, subtree: true });
|
|
||||||
}
|
|
||||||
if (favoritesList) {
|
|
||||||
observer.observe(favoritesList, { childList: true, subtree: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,302 +0,0 @@
|
||||||
// Favorites Management for RosterHash
|
|
||||||
// Manages up to 4 favorite NFL teams with localStorage persistence (persists across weeks)
|
|
||||||
|
|
||||||
const MAX_FAVORITES = 4;
|
|
||||||
|
|
||||||
// Team abbreviation to common name mapping
|
|
||||||
const TEAM_NAMES = {
|
|
||||||
'ARI': 'Cardinals',
|
|
||||||
'ATL': 'Falcons',
|
|
||||||
'BAL': 'Ravens',
|
|
||||||
'BUF': 'Bills',
|
|
||||||
'CAR': 'Panthers',
|
|
||||||
'CHI': 'Bears',
|
|
||||||
'CIN': 'Bengals',
|
|
||||||
'CLE': 'Browns',
|
|
||||||
'DAL': 'Cowboys',
|
|
||||||
'DEN': 'Broncos',
|
|
||||||
'DET': 'Lions',
|
|
||||||
'GB': 'Packers',
|
|
||||||
'HOU': 'Texans',
|
|
||||||
'IND': 'Colts',
|
|
||||||
'JAX': 'Jaguars',
|
|
||||||
'KC': 'Chiefs',
|
|
||||||
'LV': 'Raiders',
|
|
||||||
'LAC': 'Chargers',
|
|
||||||
'LAR': 'Rams',
|
|
||||||
'MIA': 'Dolphins',
|
|
||||||
'MIN': 'Vikings',
|
|
||||||
'NE': 'Patriots',
|
|
||||||
'NO': 'Saints',
|
|
||||||
'NYG': 'Giants',
|
|
||||||
'NYJ': 'Jets',
|
|
||||||
'PHI': 'Eagles',
|
|
||||||
'PIT': 'Steelers',
|
|
||||||
'SF': '49ers',
|
|
||||||
'SEA': 'Seahawks',
|
|
||||||
'TB': 'Buccaneers',
|
|
||||||
'TEN': 'Titans',
|
|
||||||
'WAS': 'Commanders'
|
|
||||||
};
|
|
||||||
|
|
||||||
class FavoritesManager {
|
|
||||||
constructor() {
|
|
||||||
this.favorites = this.loadFavorites();
|
|
||||||
this.games = [];
|
|
||||||
this.allTeams = new Set();
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get team common name
|
|
||||||
getTeamName(abbrev) {
|
|
||||||
return TEAM_NAMES[abbrev] || abbrev;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get storage key (no week suffix - persists across weeks)
|
|
||||||
getStorageKey() {
|
|
||||||
return `rosterhash_favorite_teams`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load favorites from localStorage
|
|
||||||
loadFavorites() {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem(this.getStorageKey());
|
|
||||||
return stored ? JSON.parse(stored) : [];
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to load favorites:', e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save favorites to localStorage
|
|
||||||
saveFavorites() {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(this.getStorageKey(), JSON.stringify(this.favorites));
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to save favorites:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize favorites system
|
|
||||||
init() {
|
|
||||||
// Wait for DOM to be ready
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', () => this.setup());
|
|
||||||
} else {
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup favorites after DOM is loaded
|
|
||||||
setup() {
|
|
||||||
this.extractGamesFromPage();
|
|
||||||
this.extractAllTeams();
|
|
||||||
this.populateTeamsPicker();
|
|
||||||
this.renderFavoritesList();
|
|
||||||
this.renderFavoritesDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract all games from the schedule on the page
|
|
||||||
extractGamesFromPage() {
|
|
||||||
this.games = [];
|
|
||||||
this.allTeams = new Set();
|
|
||||||
|
|
||||||
// Find all game cards in the schedule
|
|
||||||
const gameCards = document.querySelectorAll('.game-card');
|
|
||||||
|
|
||||||
gameCards.forEach((card, index) => {
|
|
||||||
const timeElement = card.querySelector('.game-time');
|
|
||||||
const awayTeamElement = card.querySelector('.away-team');
|
|
||||||
const homeTeamElement = card.querySelector('.home-team');
|
|
||||||
const awayScoreElement = card.querySelectorAll('.nfl-score')[0];
|
|
||||||
const homeScoreElement = card.querySelectorAll('.nfl-score')[1];
|
|
||||||
const liveIndicator = card.querySelector('.live-indicator');
|
|
||||||
|
|
||||||
if (awayTeamElement && homeTeamElement) {
|
|
||||||
const awayTeam = awayTeamElement.textContent.trim();
|
|
||||||
const homeTeam = homeTeamElement.textContent.trim();
|
|
||||||
|
|
||||||
// Add teams to the set
|
|
||||||
this.allTeams.add(awayTeam);
|
|
||||||
this.allTeams.add(homeTeam);
|
|
||||||
|
|
||||||
const game = {
|
|
||||||
awayTeam: awayTeam,
|
|
||||||
homeTeam: homeTeam,
|
|
||||||
awayScore: awayScoreElement ? awayScoreElement.textContent.trim() : null,
|
|
||||||
homeScore: homeScoreElement ? homeScoreElement.textContent.trim() : null,
|
|
||||||
time: timeElement ? timeElement.textContent.trim() : 'TBD',
|
|
||||||
isLive: !!liveIndicator,
|
|
||||||
matchup: `${awayTeam} @ ${homeTeam}`
|
|
||||||
};
|
|
||||||
|
|
||||||
this.games.push(game);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract all unique teams
|
|
||||||
extractAllTeams() {
|
|
||||||
// Already done in extractGamesFromPage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the teams picker in the sidebar
|
|
||||||
populateTeamsPicker() {
|
|
||||||
const picker = document.getElementById('games-picker');
|
|
||||||
if (!picker) return;
|
|
||||||
|
|
||||||
picker.innerHTML = '';
|
|
||||||
|
|
||||||
// Use all teams from TEAM_NAMES instead of only teams with games this week
|
|
||||||
const allTeamAbbrevs = Object.keys(TEAM_NAMES);
|
|
||||||
|
|
||||||
// Sort teams alphabetically by common name
|
|
||||||
const sortedTeams = allTeamAbbrevs.sort((a, b) => {
|
|
||||||
const nameA = TEAM_NAMES[a];
|
|
||||||
const nameB = TEAM_NAMES[b];
|
|
||||||
return nameA.localeCompare(nameB);
|
|
||||||
});
|
|
||||||
|
|
||||||
sortedTeams.forEach(team => {
|
|
||||||
const isSelected = this.favorites.includes(team);
|
|
||||||
const isDisabled = !isSelected && this.favorites.length >= MAX_FAVORITES;
|
|
||||||
const teamName = TEAM_NAMES[team];
|
|
||||||
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'game-picker-item';
|
|
||||||
if (isSelected) item.classList.add('selected');
|
|
||||||
if (isDisabled) item.classList.add('disabled');
|
|
||||||
|
|
||||||
item.innerHTML = `
|
|
||||||
<div class="game-picker-matchup">${teamName}</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (!isDisabled || isSelected) {
|
|
||||||
item.addEventListener('click', () => this.toggleFavorite(team));
|
|
||||||
}
|
|
||||||
|
|
||||||
picker.appendChild(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle a team as favorite
|
|
||||||
toggleFavorite(team) {
|
|
||||||
const index = this.favorites.indexOf(team);
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
// Remove from favorites
|
|
||||||
this.favorites.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
// Add to favorites if under limit
|
|
||||||
if (this.favorites.length < MAX_FAVORITES) {
|
|
||||||
this.favorites.push(team);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveFavorites();
|
|
||||||
this.populateTeamsPicker();
|
|
||||||
this.renderFavoritesList();
|
|
||||||
this.renderFavoritesDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render favorites list in sidebar
|
|
||||||
renderFavoritesList() {
|
|
||||||
const list = document.getElementById('favorites-list');
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
if (this.favorites.length === 0) {
|
|
||||||
list.innerHTML = '<p class="favorites-empty">Click teams below to add favorites (max 4)</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.innerHTML = '';
|
|
||||||
|
|
||||||
this.favorites.forEach(team => {
|
|
||||||
const teamName = this.getTeamName(team);
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'favorite-game-card';
|
|
||||||
|
|
||||||
card.innerHTML = `
|
|
||||||
<div class="favorite-game-header">
|
|
||||||
<div class="favorite-matchup">${teamName}</div>
|
|
||||||
<button class="favorite-remove" onclick="favoritesManager.toggleFavorite('${team}')">✕</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
list.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render favorites display on main dashboard (stacked rows like fantasy scores)
|
|
||||||
renderFavoritesDisplay() {
|
|
||||||
const section = document.getElementById('favorites-section-main');
|
|
||||||
const display = document.getElementById('favorites-display');
|
|
||||||
|
|
||||||
if (!section || !display) return;
|
|
||||||
|
|
||||||
if (this.favorites.length === 0) {
|
|
||||||
section.classList.remove('has-favorites');
|
|
||||||
display.innerHTML = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.classList.add('has-favorites');
|
|
||||||
display.innerHTML = '';
|
|
||||||
|
|
||||||
this.favorites.forEach(team => {
|
|
||||||
// Find the game(s) for this team
|
|
||||||
const teamGames = this.games.filter(g => g.awayTeam === team || g.homeTeam === team);
|
|
||||||
|
|
||||||
teamGames.forEach(game => {
|
|
||||||
const row = document.createElement('div');
|
|
||||||
row.className = 'favorite-team-row';
|
|
||||||
|
|
||||||
const isHome = game.homeTeam === team;
|
|
||||||
const opponent = isHome ? game.awayTeam : game.homeTeam;
|
|
||||||
const teamScore = isHome ? game.homeScore : game.awayScore;
|
|
||||||
const oppScore = isHome ? game.awayScore : game.homeScore;
|
|
||||||
|
|
||||||
const teamName = this.getTeamName(team);
|
|
||||||
const opponentName = this.getTeamName(opponent);
|
|
||||||
|
|
||||||
const scoreDisplay = (teamScore && oppScore)
|
|
||||||
? `<span class="favorite-team-score">${teamScore} - ${oppScore}</span>`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const liveBadge = game.isLive
|
|
||||||
? '<span class="favorite-live-indicator">LIVE</span>'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const matchupDisplay = isHome ? `vs ${opponentName}` : `@ ${opponentName}`;
|
|
||||||
|
|
||||||
row.innerHTML = `
|
|
||||||
<div class="favorite-team-info">
|
|
||||||
<span class="favorite-team-name">${teamName}</span>
|
|
||||||
<span class="favorite-opponent">${matchupDisplay}</span>
|
|
||||||
</div>
|
|
||||||
<div class="favorite-team-details">
|
|
||||||
<span class="favorite-game-time">${game.time}</span>
|
|
||||||
${liveBadge}
|
|
||||||
${scoreDisplay}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
display.appendChild(row);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh scores for favorite games (called by page refresh)
|
|
||||||
refreshScores() {
|
|
||||||
this.extractGamesFromPage();
|
|
||||||
this.renderFavoritesList();
|
|
||||||
this.renderFavoritesDisplay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize favorites manager globally
|
|
||||||
let favoritesManager;
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
favoritesManager = new FavoritesManager();
|
|
||||||
});
|
|
||||||
455
static/style.css
455
static/style.css
|
|
@ -1,146 +1,17 @@
|
||||||
.btn-icon {
|
.sidebar-link {
|
||||||
font-size: 18px;
|
display: block;
|
||||||
flex-shrink: 0;
|
padding: 12px 16px;
|
||||||
}
|
|
||||||
|
|
||||||
/* Favorites Section */
|
|
||||||
.favorites-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorites-empty {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: center;
|
|
||||||
padding: 16px;
|
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px dashed var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-game-card {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 2px solid var(--accent);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-game-card:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-game-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-matchup {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 14px;
|
text-decoration: none;
|
||||||
}
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.favorite-remove {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-muted);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-remove:hover {
|
.sidebar-link:hover {
|
||||||
color: var(--error);
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-game-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-score {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-live-badge {
|
|
||||||
background: var(--error);
|
|
||||||
color: white;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Games Picker Section */
|
|
||||||
.games-picker {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-item {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 2px solid var(--border-primary);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-item:hover {
|
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
border-color: var(--accent);
|
transform: translateX(5px);
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-item.selected {
|
|
||||||
background: var(--accent);
|
|
||||||
border-color: var(--accent);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-item.disabled {
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-item.disabled:hover {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-color: var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-matchup {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-time {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.game-picker-item.selected .game-picker-time {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-text {
|
.about-text {
|
||||||
|
|
@ -388,99 +259,6 @@
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Favorites Section Main */
|
|
||||||
.favorites-section-main {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorites-section-main.has-favorites {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorites-section-main h2 {
|
|
||||||
color: var(--accent);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorites-display {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 0;
|
|
||||||
border-bottom: 1px solid var(--border-light);
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-row:hover {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-name {
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--accent);
|
|
||||||
font-size: 14px;
|
|
||||||
min-width: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-opponent {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-details {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-game-time {
|
|
||||||
color: var(--text-muted);
|
|
||||||
min-width: 100px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-live-indicator {
|
|
||||||
background: var(--error);
|
|
||||||
color: white;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-score {
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text-primary);
|
|
||||||
min-width: 60px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-row {
|
.score-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -841,46 +619,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 100%;
|
width: 85%;
|
||||||
max-width: 100%;
|
right: -85%;
|
||||||
right: -100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header {
|
|
||||||
padding: 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-title {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-content {
|
.sidebar-content {
|
||||||
padding: 16px 16px;
|
padding: 50px 20px 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.favorites-display {
|
|
||||||
/* Already stacked, no changes needed */
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-details {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-game-time {
|
|
||||||
min-width: auto;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite-team-score {
|
|
||||||
min-width: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-header {
|
.dashboard-header {
|
||||||
|
|
@ -1113,17 +857,14 @@ body {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: -420px;
|
right: -400px;
|
||||||
width: 420px;
|
width: 400px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--sidebar-bg);
|
background: var(--sidebar-bg);
|
||||||
box-shadow: var(--sidebar-shadow);
|
box-shadow: var(--sidebar-shadow);
|
||||||
transition: right 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: right 0.3s ease;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show sidebar when checkbox is checked */
|
/* Show sidebar when checkbox is checked */
|
||||||
|
|
@ -1137,189 +878,55 @@ body {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar Header */
|
|
||||||
.sidebar-header {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-bottom: 2px solid var(--border-primary);
|
|
||||||
padding: 20px 24px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 10;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-title {
|
|
||||||
color: var(--accent);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
background: var(--bg-secondary);
|
position: absolute;
|
||||||
border: 2px solid var(--border-primary);
|
top: 15px;
|
||||||
border-radius: 8px;
|
right: 15px;
|
||||||
width: 36px;
|
background: transparent;
|
||||||
height: 36px;
|
border: none;
|
||||||
display: flex;
|
font-size: 28px;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 5px;
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn:hover {
|
.close-btn:hover {
|
||||||
background: var(--bg-tertiary);
|
opacity: 0.7;
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-content {
|
.sidebar-content {
|
||||||
padding: 20px 24px;
|
padding: 60px 30px 30px;
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section {
|
.sidebar-section {
|
||||||
margin-bottom: 28px;
|
margin-bottom: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section h3 {
|
.sidebar-section h3 {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
margin-bottom: 12px;
|
margin-bottom: 15px;
|
||||||
font-size: 0.95rem;
|
font-size: 1.1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-button {
|
||||||
/* Sidebar Details (Collapsible) */
|
|
||||||
.sidebar-details {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 2px solid var(--border-primary);
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-details summary {
|
|
||||||
padding: 12px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-details summary::-webkit-details-marker {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-details summary::after {
|
|
||||||
content: '▼';
|
|
||||||
font-size: 12px;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-details[open] summary::after {
|
|
||||||
transform: rotate(-180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-details summary:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-details .about-text {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-top: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-content {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-top: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-content {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom scrollbar for details content */
|
|
||||||
.scrollable-content::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-content::-webkit-scrollbar-track {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-content::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--accent);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-content::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--accent-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Favorites subsection */
|
|
||||||
.favorites-subsection {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.subsection-title {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Actions Section */
|
|
||||||
.sidebar-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-actions form {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-action-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border: 2px solid var(--border-primary);
|
border: 2px solid var(--border-primary);
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
padding: 12px 16px;
|
padding: 12px 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-action-btn:hover {
|
.theme-button:hover {
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
transform: translateX(-4px);
|
transform: translateY(-2px);
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Refresh button */
|
/* Refresh button */
|
||||||
|
|
|
||||||
|
|
@ -21,79 +21,52 @@
|
||||||
|
|
||||||
<!-- Sidebar Menu -->
|
<!-- Sidebar Menu -->
|
||||||
<div class="sidebar" id="sidebar">
|
<div class="sidebar" id="sidebar">
|
||||||
<div class="sidebar-header">
|
<label for="menu-toggle" class="close-btn">✕</label>
|
||||||
<h2 class="sidebar-title">Menu</h2>
|
|
||||||
<label for="menu-toggle" class="close-btn">✕</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<!-- About Section (Collapsible, closed by default) -->
|
<!-- Theme Toggle Section -->
|
||||||
<div class="sidebar-section">
|
<div class="sidebar-section">
|
||||||
<details class="sidebar-details">
|
<h3>Theme</h3>
|
||||||
<summary>About RosterHash</summary>
|
<form method="post" action="/toggle_theme" style="display: inline;">
|
||||||
<div class="about-text">
|
|
||||||
<p>At-a-glance view of your Sleeper leagues and player schedules.</p>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>View all league matchups</li>
|
|
||||||
<li>See player schedules by game</li>
|
|
||||||
<li>Track favorite NFL teams</li>
|
|
||||||
<li>League color-coding</li>
|
|
||||||
</ul>
|
|
||||||
<p class="cron-note">* * * * 4,7,1</p>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Favorite Teams Section (Collapsible, closed by default) -->
|
|
||||||
<div class="sidebar-section">
|
|
||||||
<details class="sidebar-details">
|
|
||||||
<summary>⭐ Favorite Teams</summary>
|
|
||||||
<div class="details-content">
|
|
||||||
<div id="favorites-list" class="favorites-list">
|
|
||||||
<p class="favorites-empty">Click teams below to add favorites (max 4)</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Select Teams subsection -->
|
|
||||||
<div class="favorites-subsection">
|
|
||||||
<h4 class="subsection-title">Select Teams</h4>
|
|
||||||
<div class="scrollable-content">
|
|
||||||
<div id="games-picker" class="games-picker">
|
|
||||||
<!-- Populated by JavaScript -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions and Links (No title) -->
|
|
||||||
<div class="sidebar-section sidebar-actions">
|
|
||||||
<!-- Theme Toggle -->
|
|
||||||
<form method="post" action="/toggle_theme">
|
|
||||||
<input type="hidden" name="return_url" value="{{ request.url }}">
|
<input type="hidden" name="return_url" value="{{ request.url }}">
|
||||||
<button type="submit" class="sidebar-action-btn">
|
<button type="submit" class="theme-button">
|
||||||
{% if current_theme == 'dark' %}
|
{% if current_theme == 'dark' %}
|
||||||
<span class="btn-icon">🌙</span>
|
<span>🌙</span>
|
||||||
<span>Change to Light Theme</span>
|
<span>Dark Mode</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="btn-icon">☀️</span>
|
<span>☀️</span>
|
||||||
<span>Change to Dark Theme</span>
|
<span>Light Mode</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sleeper App Link -->
|
<!-- Links Section -->
|
||||||
<a href="https://sleeper.app" target="_blank" class="sidebar-action-btn">
|
<div class="sidebar-section">
|
||||||
<span class="btn-icon">🏈</span>
|
<h3>Links</h3>
|
||||||
<span>Sleeper App</span>
|
<a href="https://sleeper.app" target="_blank" class="sidebar-link">
|
||||||
|
🏈 Sleeper App
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com" target="_blank" class="sidebar-link">
|
||||||
|
💻 Source Code
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Source Code Link -->
|
<!-- About Section -->
|
||||||
<a href="https://codeberg.org/edfig/RosterHash" target="_blank" class="sidebar-action-btn">
|
<div class="sidebar-section">
|
||||||
<span class="btn-icon">💻</span>
|
<h3>About This WebApp</h3>
|
||||||
<span>Source Code</span>
|
<div class="about-text">
|
||||||
</a>
|
<p>RosterHash gives you an at-a-glance view of your Sleeper leagues player's schedules and status.</p>
|
||||||
|
<p><strong>How to use:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Enter your Sleeper username on the home page</li>
|
||||||
|
<li>View all your leagues' matchup scores at the top</li>
|
||||||
|
<li>See when each of your players plays, grouped by league (indicated by colored borders)</li>
|
||||||
|
<li>Benched players are right-aligned and greyed out</li>
|
||||||
|
<li>Games and Days are collapsed if they're over.</li>
|
||||||
|
</ul>
|
||||||
|
<p class="cron-note">* * * * 4,7,1</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -213,11 +186,5 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Favorites Management Script -->
|
|
||||||
<script src="{{ url_for('static', filename='favorites.js') }}"></script>
|
|
||||||
|
|
||||||
<!-- Easter Eggs Script -->
|
|
||||||
<script src="{{ url_for('static', filename='easter-eggs.js') }}"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -80,14 +80,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Favorites Section (hidden by default, shown by JavaScript if favorites exist) -->
|
|
||||||
<section class="favorites-section-main" id="favorites-section-main">
|
|
||||||
<h2>⭐ Favorite Teams</h2>
|
|
||||||
<div id="favorites-display" class="favorites-display">
|
|
||||||
<!-- Populated by JavaScript -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Main calendar section -->
|
<!-- Main calendar section -->
|
||||||
<section class="schedule-section">
|
<section class="schedule-section">
|
||||||
<h2>Week {{ week }} Games
|
<h2>Week {{ week }} Games
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="example">
|
<div class="example">
|
||||||
<p>Example: <code>{{ mySleeperUsername }}</code></p>
|
<p>Example: <code>mySleeperUsername</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue