Favorites feature

This commit is contained in:
Eduardo Figueroa 2025-10-31 10:10:47 -07:00
parent 2ae44f64b5
commit 6d21444746
No known key found for this signature in database
GPG key ID: E4B7BBE6F7D53330
5 changed files with 1024 additions and 75 deletions

211
static/easter-eggs.js Normal file
View file

@ -0,0 +1,211 @@
// 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 });
}
});

304
static/favorites.js Normal file
View file

@ -0,0 +1,304 @@
// 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;
if (this.allTeams.size === 0) {
picker.innerHTML = '<p style="color: var(--text-muted); font-size: 13px; text-align: center; padding: 16px;">No teams available</p>';
return;
}
picker.innerHTML = '';
// Sort teams alphabetically by common name
const sortedTeams = Array.from(this.allTeams).sort((a, b) => {
const nameA = this.getTeamName(a);
const nameB = this.getTeamName(b);
return nameA.localeCompare(nameB);
});
sortedTeams.forEach(team => {
const isSelected = this.favorites.includes(team);
const isDisabled = !isSelected && this.favorites.length >= MAX_FAVORITES;
const teamName = this.getTeamName(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();
});

View file

@ -1,17 +1,146 @@
.sidebar-link {
display: block;
padding: 12px 16px;
background: var(--bg-secondary);
border-radius: 8px;
color: var(--text-primary);
text-decoration: none;
margin-bottom: 10px;
transition: all 0.2s ease;
.btn-icon {
font-size: 18px;
flex-shrink: 0;
}
.sidebar-link:hover {
/* 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);
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: translateX(5px);
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);
font-size: 14px;
}
.favorite-remove {
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 4px;
font-size: 16px;
transition: all 0.2s ease;
line-height: 1;
}
.favorite-remove: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);
border-color: var(--accent);
}
.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 {
@ -259,6 +388,99 @@
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 {
display: flex;
justify-content: space-between;
@ -619,12 +841,46 @@
}
.sidebar {
width: 85%;
right: -85%;
width: 100%;
max-width: 100%;
right: -100%;
}
.sidebar-header {
padding: 16px 16px;
}
.sidebar-title {
font-size: 1.3rem;
}
.sidebar-content {
padding: 50px 20px 20px;
padding: 16px 16px;
}
.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 {
@ -857,14 +1113,17 @@ body {
.sidebar {
position: fixed;
top: 0;
right: -400px;
width: 400px;
right: -420px;
width: 420px;
height: 100%;
background: var(--sidebar-bg);
box-shadow: var(--sidebar-shadow);
transition: right 0.3s ease;
transition: right 0.25s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 1001;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
/* Show sidebar when checkbox is checked */
@ -878,55 +1137,189 @@ body {
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 {
position: absolute;
top: 15px;
right: 15px;
background: transparent;
border: none;
font-size: 28px;
background: var(--bg-secondary);
border: 2px solid var(--border-primary);
border-radius: 8px;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: var(--text-primary);
cursor: pointer;
padding: 5px;
padding: 0;
transition: all 0.2s ease;
}
.close-btn:hover {
opacity: 0.7;
background: var(--bg-tertiary);
transform: rotate(90deg);
}
.sidebar-content {
padding: 60px 30px 30px;
padding: 20px 24px;
flex: 1;
overflow-y: auto;
}
.sidebar-section {
margin-bottom: 35px;
margin-bottom: 28px;
}
.sidebar-section h3 {
color: var(--accent);
margin-bottom: 15px;
font-size: 1.1rem;
margin-bottom: 12px;
font-size: 0.95rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.theme-button {
display: flex;
align-items: center;
gap: 10px;
/* Sidebar Details (Collapsible) */
.sidebar-details {
background: var(--bg-secondary);
border: 2px solid var(--border-primary);
border-radius: 8px;
padding: 12px 20px;
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;
justify-content: space-between;
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;
}
.sidebar-actions form {
width: 100%;
}
.sidebar-action-btn {
display: flex;
align-items: center;
gap: 12px;
background: var(--bg-secondary);
border: 2px solid var(--border-primary);
border-radius: 10px;
padding: 12px 16px;
cursor: pointer;
width: 100%;
transition: all 0.2s ease;
color: var(--text-primary);
font-size: 15px;
font-size: 14px;
font-weight: 500;
text-decoration: none;
}
.theme-button:hover {
.sidebar-action-btn:hover {
background: var(--bg-tertiary);
transform: translateY(-2px);
transform: translateX(-4px);
border-color: var(--accent);
}
/* Refresh button */

View file

@ -21,52 +21,79 @@
<!-- Sidebar Menu -->
<div class="sidebar" id="sidebar">
<label for="menu-toggle" class="close-btn"></label>
<div class="sidebar-header">
<h2 class="sidebar-title">Menu</h2>
<label for="menu-toggle" class="close-btn"></label>
</div>
<div class="sidebar-content">
<!-- Theme Toggle Section -->
<!-- About Section (Collapsible, closed by default) -->
<div class="sidebar-section">
<h3>Theme</h3>
<form method="post" action="/toggle_theme" style="display: inline;">
<details class="sidebar-details">
<summary>About RosterHash</summary>
<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 }}">
<button type="submit" class="theme-button">
<button type="submit" class="sidebar-action-btn">
{% if current_theme == 'dark' %}
<span>🌙</span>
<span>Dark Mode</span>
<span class="btn-icon">🌙</span>
<span>Change to Light Theme</span>
{% else %}
<span>☀️</span>
<span>Light Mode</span>
<span class="btn-icon">☀️</span>
<span>Change to Dark Theme</span>
{% endif %}
</button>
</form>
</div>
<!-- Links Section -->
<div class="sidebar-section">
<h3>Links</h3>
<a href="https://sleeper.app" target="_blank" class="sidebar-link">
🏈 Sleeper App
<!-- Sleeper App Link -->
<a href="https://sleeper.app" target="_blank" class="sidebar-action-btn">
<span class="btn-icon">🏈</span>
<span>Sleeper App</span>
</a>
<a href="https://github.com" target="_blank" class="sidebar-link">
💻 Source Code
</a>
</div>
<!-- About Section -->
<div class="sidebar-section">
<h3>About This WebApp</h3>
<div class="about-text">
<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>
<!-- Source Code Link -->
<a href="https://codeberg.org/edfig/RosterHash" target="_blank" class="sidebar-action-btn">
<span class="btn-icon">💻</span>
<span>Source Code</span>
</a>
</div>
</div>
</div>
@ -186,5 +213,11 @@
});
}
</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>
</html>

View file

@ -80,6 +80,14 @@
{% endfor %}
</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 -->
<section class="schedule-section">
<h2>Week {{ week }} Games