diff --git a/static/easter-eggs.js b/static/easter-eggs.js
new file mode 100644
index 0000000..abe634b
--- /dev/null
+++ b/static/easter-eggs.js
@@ -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 += ' 🤡';
+ }
+ }, 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 });
+ }
+});
diff --git a/static/favorites.js b/static/favorites.js
new file mode 100644
index 0000000..eb1b6ce
--- /dev/null
+++ b/static/favorites.js
@@ -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 = '
No teams available
';
+ 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 = `
+ ${teamName}
+ `;
+
+ 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 = 'Click teams below to add favorites (max 4)
';
+ return;
+ }
+
+ list.innerHTML = '';
+
+ this.favorites.forEach(team => {
+ const teamName = this.getTeamName(team);
+ const card = document.createElement('div');
+ card.className = 'favorite-game-card';
+
+ card.innerHTML = `
+
+ `;
+
+ 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)
+ ? `${teamScore} - ${oppScore}`
+ : '';
+
+ const liveBadge = game.isLive
+ ? 'LIVE'
+ : '';
+
+ const matchupDisplay = isHome ? `vs ${opponentName}` : `@ ${opponentName}`;
+
+ row.innerHTML = `
+
+ ${teamName}
+ ${matchupDisplay}
+
+
+ ${game.time}
+ ${liveBadge}
+ ${scoreDisplay}
+
+ `;
+
+ 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();
+});
diff --git a/static/style.css b/static/style.css
index 7ab4e2c..69e82de 100644
--- a/static/style.css
+++ b/static/style.css
@@ -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 */
diff --git a/templates/base.html b/templates/base.html
index 5a7a02f..34ffafc 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -21,53 +21,80 @@
@@ -186,5 +213,11 @@
});
}
+
+
+
+
+
+