// Timezone Detection Module (function() { 'use strict'; /** * Detect and set user's timezone */ function detectAndSetTimezone() { try { // Get user's timezone using modern browser API const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; if (timezone) { // Store in session storage for immediate use sessionStorage.setItem('userTimezone', timezone); // Send to server to store in session fetch('/public/api/set-timezone.php', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify({ timezone: timezone }) }) .then(response => response.json()) .then(data => { if (data.success) { // For logged-in users, also save to database if (typeof serverRendering !== 'undefined' && serverRendering.isLoggedIn) { const formData = new FormData(); formData.append('timezone', timezone); fetch('/public/api/update-timezone.php', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: formData }) .then(response => response.json()) .then(dbData => { // Timezone saved to database }) .catch(error => { // Database timezone update failed }); } // Dispatch custom event for other parts of the app window.dispatchEvent(new CustomEvent('timezoneDetected', { detail: { timezone: timezone } })); } else { // Failed to set timezone - silent handling } }) .catch(error => { // Timezone detection request failed - silent handling }); } } catch (error) { // Timezone detection not supported - silent handling } } /** * Get user's timezone (from session storage or detect) */ window.getUserTimezone = function() { return sessionStorage.getItem('userTimezone') || 'UTC'; }; /** * Convert UTC datetime to user's local time */ window.convertToUserTime = function(utcDatetime) { try { const userTz = getUserTimezone(); const date = new Date(utcDatetime); // Return formatted string in user's timezone return date.toLocaleString('en-US', { timeZone: userTz, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (error) { // Time conversion failed return utcDatetime; } }; /** * Get today's date in user's timezone */ window.getTodayInUserTimezone = function() { try { const userTz = getUserTimezone(); const now = new Date(); return now.toLocaleDateString('en-CA', { timeZone: userTz }); // YYYY-MM-DD format } catch (error) { // Today date conversion failed return new Date().toISOString().split('T')[0]; } }; /** * Debug function to log timezone info to console */ window.debugTimezone = function() { // Debug logging disabled for production }; // Auto-debug on page load setTimeout(() => { // Debug logging disabled for production }, 2000); // Auto-detect timezone when page loads if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', detectAndSetTimezone); } else { detectAndSetTimezone(); } // Add global debug function for easy console access window.timezoneDebug = window.debugTimezone; })(); // Fixtures module functionality - now uses lazy loading system function changeFixturesDate(direction) { // Check if LazyFixturesManager is available if (typeof window.LazyFixturesManager === 'undefined') { console.warn('LazyFixturesManager not available, date navigation disabled'); return; } const fixturesDate = document.getElementById('fixtures-date'); const dateString = fixturesDate.dataset.date; // Parse the date manually to avoid timezone issues const [year, month, day] = dateString.split('-').map(Number); const currentDate = new Date(year, month - 1, day); // Month is 0-indexed if (direction === 'prev') { currentDate.setDate(currentDate.getDate() - 1); } else if (direction === 'next') { currentDate.setDate(currentDate.getDate() + 1); } // Format date in local timezone instead of UTC to avoid day shifts const newYear = currentDate.getFullYear(); const newMonth = String(currentDate.getMonth() + 1).padStart(2, '0'); const newDay = String(currentDate.getDate()).padStart(2, '0'); const formattedDate = `${newYear}-${newMonth}-${newDay}`; // Update the date display const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; document.getElementById('fixtures-date').textContent = currentDate.toLocaleDateString('en-US', options); document.getElementById('fixtures-date').dataset.date = formattedDate; // Update the time display with client timezone (current time, not the selected date) updateLocalTime(); // Use lazy loading system instead of direct fixtures.php call if (window.lazyFixtures && typeof window.lazyFixtures.changeDate === 'function') { window.lazyFixtures.changeDate(formattedDate).catch(error => { console.error('Error changing date:', error); }); } } // Function to load today's fixtures on page load function loadTodaysFixtures() { const fixturesModule = document.getElementById('fixtures-module'); if (!fixturesModule) return; // Only load on homepage // Initialize with client's current date only initializeFixturesDate(); // Note: Actual fixtures loading now handled by LazyFixturesManager // No longer calling fixtures.php directly to avoid cache file creation } // Initialize fixtures filter functionality function initializeFixturesFilters() { const filterButtons = document.querySelectorAll('.filter-btn'); const searchInput = document.getElementById('team-search'); const clearSearchBtn = document.getElementById('clear-search'); const locationFilter = document.getElementById('location-filter'); if (!filterButtons.length || !searchInput || !clearSearchBtn || !locationFilter) return; // Declare all variables first to avoid initialization issues let currentFilter = 'all'; let currentSearchTerm = ''; let currentLocation = 'all'; let userTeamIds = []; // Store user's followed team IDs let isInitializing = true; let initialUrlParams = null; // Store original URL params // Capture initial URL parameters immediately to prevent race conditions initialUrlParams = new URLSearchParams(window.location.search); // Times are now stored in local venue timezone - no conversion needed // Initialize league accordions directly (no timeout needed) initializeLeagueAccordions(); // Initialize accordion expand/collapse all controls initializeAccordionControls(); // Load location options for current date loadLocationOptions(); // Load user's followed teams for My Teams filter function loadUserTeams() { return fetch('/public/ajax/user_teams.php') .then(response => response.json()) .then(data => { if (data.success) { userTeamIds = data.team_ids || []; } else { userTeamIds = []; } // If we're currently on myteams filter, reapply it now that teams are loaded if (currentFilter === 'myteams') { applyFilters(); } return userTeamIds; }) .catch(error => { console.error('Error loading user teams:', error); userTeamIds = []; return userTeamIds; }); } // Load user's teams if logged in, then initialize from URL if (document.querySelector('[data-filter="myteams"]')) { // User is logged in, load teams first loadUserTeams().then(() => { // Initialize from URL parameters after teams are loaded initializeFromUrl(); }); } else { // User not logged in, initialize immediately initializeFromUrl(); } // Listen for lazy loading completion to apply initial filters document.addEventListener('lazyFixturesLoaded', () => { // Re-apply filters after content is loaded, especially for URL parameters if (currentFilter !== 'all') { applyFilters(); } }); // Also listen for when lazy fixtures manager completes initial loading const checkForInitialLoad = () => { if (window.lazyFixtures && window.lazyFixtures.initialized) { // Wait a bit for top leagues to load, then apply filters setTimeout(() => { if (currentFilter !== 'all') { applyFilters(); } }, 1000); } else { // Check again in 100ms setTimeout(checkForInitialLoad, 100); } }; // Start checking for initial load after a brief delay setTimeout(checkForInitialLoad, 200); // Filter button functionality filterButtons.forEach(btn => { btn.addEventListener('click', async () => { filterButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); currentFilter = btn.dataset.filter; updateUrl(); // Ensure lazy fixtures are loaded for the current date before applying filters if (window.lazyFixtures && typeof window.lazyFixtures.getCurrentDate === 'function') { const currentDate = window.lazyFixtures.getCurrentDate(); const activeDate = window.lazyFixtures.getActiveDate(); console.log('Filter clicked:', currentFilter, 'Current date:', currentDate, 'Active date:', activeDate); // Force reload if the active date doesn't match current date if (activeDate !== currentDate) { console.log('Date mismatch detected - reloading fixtures for current date:', currentDate); try { await window.lazyFixtures.changeDate(currentDate); } catch (error) { console.error('Error reloading fixtures for current date:', error); } } } applyFilters(); // This already calls updateLocationOptions() at the end }); }); // Location filter functionality locationFilter.addEventListener('change', (e) => { currentLocation = e.target.value; updateUrl(); applyFilters(); updateLocationOptions(); // Update available options after location change // Blur the select to return to icon view e.target.blur(); }); // Search functionality searchInput.addEventListener('input', (e) => { currentSearchTerm = e.target.value.toLowerCase(); clearSearchBtn.classList.toggle('show', currentSearchTerm.length > 0); updateUrl(); applyFilters(); updateLocationOptions(); // Update available options after search }); // Handle Enter key to blur search input (hide text, show icon) searchInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); searchInput.blur(); // This will trigger the CSS to hide text and show icon } }); // Clear search functionality clearSearchBtn.addEventListener('click', () => { searchInput.value = ''; currentSearchTerm = ''; clearSearchBtn.classList.remove('show'); updateUrl(); applyFilters(); updateLocationOptions(); // Update available options after clearing search }); function initializeFromUrl() { // Use the captured initial URL params to avoid race conditions const urlParams = initialUrlParams; // Set filter from URL const filterParam = urlParams.get('filter'); if (filterParam && ['all', 'live', 'ontv', 'myteams'].includes(filterParam)) { currentFilter = filterParam; filterButtons.forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === currentFilter); }); // Log for debugging console.log('Set filter from URL:', currentFilter); } // Set location from URL const locationParam = urlParams.get('location'); if (locationParam) { // Convert location parameter to the format expected by the dropdown let locationValue = 'all'; if (locationParam !== 'all') { // Try to determine if it's a continent or country by checking both // We'll set this after options are loaded currentLocation = locationParam; } } // Set search from URL const searchParam = urlParams.get('search'); if (searchParam) { currentSearchTerm = searchParam.toLowerCase(); searchInput.value = searchParam; clearSearchBtn.classList.add('show'); } } function updateUrl() { const url = new URL(window.location); // During initialization, preserve original URL parameters if (isInitializing && initialUrlParams) { // Only update if we're not overriding an existing parameter const originalFilter = initialUrlParams.get('filter'); const originalLocation = initialUrlParams.get('location'); const originalSearch = initialUrlParams.get('search'); if (originalFilter && ['all', 'live', 'ontv', 'myteams'].includes(originalFilter)) { currentFilter = originalFilter; url.searchParams.set('filter', originalFilter); // Update button states const filterButtons = document.querySelectorAll('.filter-btn'); filterButtons.forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === originalFilter); }); } else if (currentFilter !== 'all') { url.searchParams.set('filter', currentFilter); } if (originalLocation && originalLocation !== 'all') { url.searchParams.set('location', originalLocation); } else if (currentLocation !== 'all') { const locationValue = currentLocation.includes(':') ? currentLocation.split(':')[1] : currentLocation; url.searchParams.set('location', locationValue); } if (originalSearch) { url.searchParams.set('search', originalSearch); } else if (currentSearchTerm) { url.searchParams.set('search', currentSearchTerm); } } else { // Normal URL updating after initialization if (currentFilter !== 'all') { url.searchParams.set('filter', currentFilter); } else { url.searchParams.delete('filter'); } if (currentLocation !== 'all') { const locationValue = currentLocation.includes(':') ? currentLocation.split(':')[1] : currentLocation; url.searchParams.set('location', locationValue); } else { url.searchParams.delete('location'); } if (currentSearchTerm) { url.searchParams.set('search', currentSearchTerm); } else { url.searchParams.delete('search'); } } // Update the URL if (isInitializing) { window.history.replaceState({}, '', url); isInitializing = false; } else { window.history.pushState({}, '', url); } } function loadLocationOptions() { const todayDate = document.getElementById('fixtures-date').dataset.date; fetch(`/public/fixtures-locations.php?date=${todayDate}`) .then(response => response.json()) .then(data => { populateLocationOptions(data.continents, data.countries); }) .catch(error => { console.error('Error loading location options:', error); }); } // New function to load location options from skeleton data function loadLocationOptionsFromData(leaguesData) { // Extract unique continents and countries from leagues data const continents = [...new Set(leaguesData.map(league => league.continent).filter(continent => continent))]; const countries = [...new Set(leaguesData.map(league => league.country).filter(country => country))]; // Sort alphabetically continents.sort(); countries.sort(); populateLocationOptions(continents, countries); } function populateLocationOptions(continents, countries) { const continentsGroup = document.getElementById('continents-group'); const countriesGroup = document.getElementById('countries-group'); // Clear existing options continentsGroup.innerHTML = ''; countriesGroup.innerHTML = ''; // Add continent options continents.forEach(continent => { const option = document.createElement('option'); option.value = `continent:${continent}`; option.textContent = continent; continentsGroup.appendChild(option); }); // Add country options countries.forEach(country => { const option = document.createElement('option'); option.value = `country:${country}`; option.textContent = country; countriesGroup.appendChild(option); }); // Set location from URL parameter after options are loaded const urlParams = new URLSearchParams(window.location.search); const locationParam = urlParams.get('location'); if (locationParam && locationParam !== 'all') { // Try to find the location in either continents or countries let foundOption = null; // Check continents first for (const continent of continents) { if (continent.toLowerCase() === locationParam.toLowerCase()) { foundOption = `continent:${continent}`; break; } } // If not found in continents, check countries if (!foundOption) { for (const country of countries) { if (country.toLowerCase() === locationParam.toLowerCase()) { foundOption = `country:${country}`; break; } } } if (foundOption) { currentLocation = foundOption; locationFilter.value = foundOption; } else if (locationParam && locationParam !== 'all') { // Location from URL not found in today's fixtures, reset to all currentLocation = 'all'; locationFilter.value = 'all'; // Update URL to remove invalid location const url = new URL(window.location); url.searchParams.delete('location'); window.history.replaceState({}, '', url); } } // Apply filters after setting initial values updateUrl(); // For URL parameters, wait a bit for lazy loading to initialize if (initialUrlParams && initialUrlParams.get('filter')) { setTimeout(() => { applyFilters(); }, 500); } else { applyFilters(); } // Note: updateLocationOptions() will be called by applyFilters() after content loads } // Make functions globally accessible window.loadLocationOptionsFromData = loadLocationOptionsFromData; window.populateLocationOptions = populateLocationOptions; // Listen for live status changes from smart polling system document.addEventListener('matchLiveStatusChanged', (e) => { // Only re-apply filters if we're currently on the Live filter if (currentFilter === 'live') { applyFilters(); } }); function applyFilters() { // Apply filters directly to existing content (no more DOM reload) applyFiltersToContent(); } function applyFiltersToContent() { const leagueFixtures = document.querySelectorAll('.league-fixtures'); leagueFixtures.forEach(league => { let shouldShowLeague = true; // Apply location filter to league if (currentLocation !== 'all') { const [locationType, locationValue] = currentLocation.split(':'); if (locationType === 'continent') { shouldShowLeague = league.dataset.continent === locationValue; } else if (locationType === 'country') { shouldShowLeague = league.dataset.country === locationValue; } } if (!shouldShowLeague) { league.style.display = 'none'; return; } const matches = league.querySelectorAll('.match-item'); let visibleMatches = 0; matches.forEach(match => { let shouldShow = true; // Filter by status if (currentFilter === 'live') { // Check both the 'live' class (added by smart polling) and match status content const hasLiveClass = match.classList.contains('live'); const statusElement = match.querySelector('.match-status'); const statusText = statusElement ? statusElement.textContent.trim() : ''; // Check if status indicates live match (time format like "45'" or specific live statuses) const isLiveByStatus = statusText.match(/^\d+(?:\+\d+)?'$/) || // Time format like "45'" or "90+3'" ['1H', '2H', 'HT', 'ET', 'BT', 'P', 'LIVE'].includes(statusText); shouldShow = hasLiveClass || isLiveByStatus; } else if (currentFilter === 'ontv') { // Show only matches that have TV schedule information const tvChannels = match.querySelector('.tv-channels'); shouldShow = tvChannels && tvChannels.textContent.trim() !== ''; } else if (currentFilter === 'myteams') { // Show only matches involving user's followed teams if (userTeamIds.length === 0) { shouldShow = false; // No teams followed, show nothing } else { const homeTeamId = match.getAttribute('data-home-team-id'); const awayTeamId = match.getAttribute('data-away-team-id'); shouldShow = userTeamIds.includes(homeTeamId) || userTeamIds.includes(awayTeamId); // Only log successful matches occasionally for debugging if (shouldShow && Math.random() < 0.1) { const homeTeamName = match.querySelector('.home-team .team-name')?.textContent.trim() || ''; const awayTeamName = match.querySelector('.away-team .team-name')?.textContent.trim() || ''; console.log('My Teams match found:', homeTeamName, 'vs', awayTeamName); } } } // Filter by search term if (shouldShow && currentSearchTerm) { const homeTeam = match.querySelector('.home-team .team-name')?.textContent.toLowerCase() || ''; const awayTeam = match.querySelector('.away-team .team-name')?.textContent.toLowerCase() || ''; shouldShow = homeTeam.includes(currentSearchTerm) || awayTeam.includes(currentSearchTerm); } match.style.display = shouldShow ? 'flex' : 'none'; if (shouldShow) visibleMatches++; }); // Check if league has loading/placeholder content const hasLoadingContent = league.querySelector('.lazy-loading, .lazy-placeholder'); // For Live filter, be more aggressive about hiding leagues without live matches if (currentFilter === 'live') { // Hide league if no visible matches, regardless of loading state league.style.display = visibleMatches > 0 ? 'block' : 'none'; // If the league is loading but we're filtering for live matches, // we can trigger loading and then re-filter once loaded if (visibleMatches === 0 && hasLoadingContent) { const leagueId = league.dataset.leagueId; if (leagueId && window.lazyFixtures && !window.lazyFixtures.isLeagueLoaded(leagueId)) { // Load the league and then re-filter window.lazyFixtures.loadLeagueFixtures(leagueId).then(() => { // Re-apply filters after loading setTimeout(() => applyFilters(), 100); }).catch(() => { // If loading fails, hide the league league.style.display = 'none'; }); } } } else { // For other filters, show loading content league.style.display = (visibleMatches > 0 || hasLoadingContent) ? 'block' : 'none'; } }); // Check if My Teams filter shows no results and show/hide message const fixturesContent = document.getElementById('fixtures-content'); let noTeamsMessage = document.getElementById('no-teams-message'); if (currentFilter === 'myteams') { const visibleLeagues = document.querySelectorAll('.league-fixtures:not([style*="display: none"])'); if (visibleLeagues.length === 0) { // Hide all fixtures and show no teams message fixturesContent.style.display = 'none'; // Create or show the no teams message if (!noTeamsMessage) { noTeamsMessage = document.createElement('div'); noTeamsMessage.id = 'no-teams-message'; noTeamsMessage.className = 'no-matches'; noTeamsMessage.innerHTML = '

😞 None of your teams play today

'; fixturesContent.parentNode.insertBefore(noTeamsMessage, fixturesContent.nextSibling); } else { noTeamsMessage.style.display = 'block'; } } else { // Show fixtures and hide no teams message fixturesContent.style.display = 'block'; if (noTeamsMessage) { noTeamsMessage.style.display = 'none'; } } } else { // For other filters, always show fixtures and hide no teams message fixturesContent.style.display = 'block'; if (noTeamsMessage) { noTeamsMessage.style.display = 'none'; } } // Update location options after filtering is complete (no delay needed since no DOM reload) updateLocationOptions(); // Reinitialize accordion functionality after content manipulation initializeLeagueAccordions(); // Auto-expand accordions when filtering is active if (currentLocation !== 'all' || currentSearchTerm || currentFilter === 'live' || currentFilter === 'myteams') { expandAllAccordions(); } } function updateLocationOptions() { // Analyze currently visible matches to determine available locations const availableLocations = { continents: new Set(), countries: new Set() }; const leagueFixtures = document.querySelectorAll('.league-fixtures'); leagueFixtures.forEach(league => { // Check if this league would have any visible matches with current filter const matches = league.querySelectorAll('.match-item'); let hasVisibleMatches = false; matches.forEach(match => { let shouldShow = true; // Apply the same filter logic as applyFilters() if (currentFilter === 'live') { // Check both the 'live' class (added by smart polling) and match status content const hasLiveClass = match.classList.contains('live'); const statusElement = match.querySelector('.match-status'); const statusText = statusElement ? statusElement.textContent.trim() : ''; // Check if status indicates live match (time format like "45'" or specific live statuses) const isLiveByStatus = statusText.match(/^\d+(?:\+\d+)?'$/) || // Time format like "45'" or "90+3'" ['1H', '2H', 'HT', 'ET', 'BT', 'P', 'LIVE'].includes(statusText); shouldShow = hasLiveClass || isLiveByStatus; } else if (currentFilter === 'ontv') { // Show only matches that have TV schedule information const tvChannels = match.querySelector('.tv-channels'); shouldShow = tvChannels && tvChannels.textContent.trim() !== ''; } else if (currentFilter === 'myteams') { // Show only matches involving user's followed teams if (userTeamIds.length === 0) { shouldShow = false; // No teams followed, show nothing } else { const homeTeamId = match.getAttribute('data-home-team-id'); const awayTeamId = match.getAttribute('data-away-team-id'); shouldShow = userTeamIds.includes(homeTeamId) || userTeamIds.includes(awayTeamId); } } // For 'all' filter, shouldShow remains true // Apply search filter if (shouldShow && currentSearchTerm) { const homeTeam = match.querySelector('.home-team .team-name')?.textContent.toLowerCase() || ''; const awayTeam = match.querySelector('.away-team .team-name')?.textContent.toLowerCase() || ''; shouldShow = homeTeam.includes(currentSearchTerm) || awayTeam.includes(currentSearchTerm); } if (shouldShow) { hasVisibleMatches = true; } }); // If this league has visible matches, add its location to available options if (hasVisibleMatches) { const continent = league.dataset.continent; const country = league.dataset.country; if (continent) { availableLocations.continents.add(continent); } if (country) { availableLocations.countries.add(country); } } }); // Update dropdown options to show/hide them based on availability const continentsGroup = document.getElementById('continents-group'); const countriesGroup = document.getElementById('countries-group'); if (continentsGroup && countriesGroup) { // Update continent options - show/hide based on availability const continentOptions = continentsGroup.querySelectorAll('option'); continentOptions.forEach(option => { const continent = option.value.replace('continent:', ''); const isAvailable = availableLocations.continents.has(continent); option.style.display = isAvailable ? '' : 'none'; option.disabled = false; // Remove disabled state since we're hiding instead }); // Update country options - show/hide based on availability const countryOptions = countriesGroup.querySelectorAll('option'); countryOptions.forEach(option => { const country = option.value.replace('country:', ''); const isAvailable = availableLocations.countries.has(country); option.style.display = isAvailable ? '' : 'none'; option.disabled = false; // Remove disabled state since we're hiding instead }); // Hide optgroups if they have no visible options const visibleContinentOptions = Array.from(continentOptions).some(option => option.style.display !== 'none'); const visibleCountryOptions = Array.from(countryOptions).some(option => option.style.display !== 'none'); continentsGroup.style.display = visibleContinentOptions ? '' : 'none'; countriesGroup.style.display = visibleCountryOptions ? '' : 'none'; // If current selection is hidden, reset to 'all' const locationFilter = document.getElementById('location-filter'); if (locationFilter.value !== 'all') { const selectedOption = locationFilter.querySelector(`option[value="${locationFilter.value}"]`); if (selectedOption && selectedOption.style.display === 'none') { currentLocation = 'all'; locationFilter.value = 'all'; updateUrl(); applyFilters(); } } } } } // Note: Time conversion removed - times are now stored in local venue timezone // Initialize accordion functionality for leagues with ID > 100 function initializeLeagueAccordions() { const accordionLeagues = document.querySelectorAll('.league-accordion'); accordionLeagues.forEach((league, index) => { const header = league.querySelector('.league-header'); const toggle = league.querySelector('.accordion-toggle'); const matchesList = league.querySelector('.matches-list'); if (!header || !toggle || !matchesList) { return; } // Make entire league clickable league.style.cursor = 'pointer'; // Add click event listener to the entire league container, not just header try { const result = league.addEventListener('click', function(e) { // Check if click is specifically on a match item or interactive element const matchItem = e.target.closest('.match-item'); const interactiveElement = e.target.closest('.recap-link, .match-recap-btn'); if (matchItem || interactiveElement) { // Allow match items and interactive elements to handle their own clicks return; } // This is an accordion click - prevent any other handlers from firing e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); // Prevent other event listeners on the same element const isCollapsed = league.classList.contains('collapsed'); if (isCollapsed) { // Expand league.classList.remove('collapsed'); toggle.textContent = '▼'; // Load fixtures when expanding if using lazy loading system const leagueId = league.dataset.leagueId; if (leagueId && window.lazyFixtures && !window.lazyFixtures.loadedLeagues.has(leagueId)) { window.lazyFixtures.loadLeagueFixtures(leagueId, league); } } else { // Collapse league.classList.add('collapsed'); toggle.textContent = '▲'; } }, true); } catch (error) { console.error('Failed to add event listener to league', index, 'Error:', error); } // Set initial state for collapsed leagues if (league.classList.contains('collapsed')) { toggle.textContent = '▲'; } else { toggle.textContent = '▼'; } }); } // Initialize expand/collapse all functionality function initializeAccordionControls() { const expandAllBtn = document.getElementById('expand-all-btn'); const collapseAllBtn = document.getElementById('collapse-all-btn'); if (!expandAllBtn || !collapseAllBtn) return; // Remove existing event listeners to prevent duplicates expandAllBtn.removeEventListener('click', expandAllAccordions); collapseAllBtn.removeEventListener('click', collapseAllAccordions); // Add event listeners expandAllBtn.addEventListener('click', function() { expandAllAccordions(); }); collapseAllBtn.addEventListener('click', function() { collapseAllAccordions(); }); } // Make function globally accessible window.initializeAccordionControls = initializeAccordionControls; // Function to expand all accordions function expandAllAccordions() { const accordionLeagues = document.querySelectorAll('.league-accordion'); accordionLeagues.forEach(league => { const toggle = league.querySelector('.accordion-toggle'); if (league.classList.contains('collapsed')) { league.classList.remove('collapsed'); if (toggle) toggle.textContent = '▼'; // Load fixtures when expanding if using lazy loading system const leagueId = league.dataset.leagueId; if (leagueId && window.lazyFixtures && !window.lazyFixtures.loadedLeagues.has(leagueId)) { window.lazyFixtures.loadLeagueFixtures(leagueId, league); } } }); } // Function to collapse all accordions function collapseAllAccordions() { const accordionLeagues = document.querySelectorAll('.league-accordion'); accordionLeagues.forEach(league => { const toggle = league.querySelector('.accordion-toggle'); if (!league.classList.contains('collapsed')) { league.classList.add('collapsed'); if (toggle) toggle.textContent = '▲'; } }); } // Add click handlers for match items document.addEventListener('click', function(e) { // Only handle direct clicks on match items, not accordion header clicks that bubble up const matchItem = e.target.closest('.match-item'); const accordion = e.target.closest('.league-accordion'); if (matchItem) { // Don't navigate if clicking on a recap link or other interactive elements if (e.target.closest('.recap-link') || e.target.closest('.match-recap-btn')) { return; // Let the recap link work normally } // Don't navigate if this is an accordion header click (clicking outside the actual match content) if (accordion) { const leagueHeader = accordion.querySelector('.league-header'); const accordionToggle = accordion.querySelector('.accordion-toggle'); // If click target is the header, toggle, or clicked element is not actually inside a match item, don't navigate if (e.target === leagueHeader || e.target === accordionToggle || e.target.closest('.league-header') || e.target.closest('.accordion-toggle')) { return; // This is an accordion control click, not a match click } } // Navigate to the match page const fixtureUrl = matchItem.getAttribute('data-fixture-url'); if (fixtureUrl) { window.location.href = fixtureUrl; } } }); // Function to initialize the fixtures date using client timezone function initializeFixturesDate() { const now = new Date(); // Format date in local timezone instead of UTC to avoid day shifts const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const formattedDate = `${year}-${month}-${day}`; // Update the date display const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; document.getElementById('fixtures-date').textContent = now.toLocaleDateString('en-US', options); document.getElementById('fixtures-date').dataset.date = formattedDate; // Update the time display with client timezone const timeOptions = { hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }; document.getElementById('fixtures-time').textContent = now.toLocaleTimeString('en-US', timeOptions); } // Function to update local time function updateLocalTime() { const timeElement = document.getElementById('fixtures-time'); if (timeElement) { const now = new Date(); const timeOptions = { hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }; timeElement.textContent = now.toLocaleTimeString('en-US', timeOptions); } } // Update time every minute setInterval(updateLocalTime, 60000); // Add keyboard navigation document.addEventListener('keydown', function(e) { if (e.key === 'ArrowLeft') { changeFixturesDate('prev'); } else if (e.key === 'ArrowRight') { changeFixturesDate('next'); } }); // Date picker functionality function openDatePicker() { const currentDate = document.getElementById('fixtures-date').dataset.date; const [year, month, day] = currentDate.split('-').map(Number); // Create modal backdrop const modal = document.createElement('div'); modal.className = 'date-picker-modal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; `; // Create date picker container const picker = document.createElement('div'); picker.className = 'date-picker'; picker.style.cssText = ` background: #2a2a2a; border: 1px solid #444; border-radius: 8px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); max-width: 320px; width: 90%; `; // Create header with month/year navigation const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; color: #fff; `; const prevBtn = document.createElement('button'); prevBtn.innerHTML = '‹'; prevBtn.style.cssText = ` background: none; border: none; color: #fff; font-size: 20px; cursor: pointer; padding: 5px 10px; border-radius: 4px; `; prevBtn.onmouseover = () => prevBtn.style.background = '#444'; prevBtn.onmouseout = () => prevBtn.style.background = 'none'; const nextBtn = document.createElement('button'); nextBtn.innerHTML = '›'; nextBtn.style.cssText = prevBtn.style.cssText; nextBtn.onmouseover = () => nextBtn.style.background = '#444'; nextBtn.onmouseout = () => nextBtn.style.background = 'none'; const monthYear = document.createElement('span'); monthYear.style.cssText = ` font-weight: bold; font-size: 16px; `; let currentMonth = month - 1; // 0-indexed let currentYear = year; // Create calendar grid const calendar = document.createElement('div'); calendar.style.cssText = ` display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 15px; `; // Day labels const dayLabels = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; dayLabels.forEach(label => { const dayLabel = document.createElement('div'); dayLabel.textContent = label; dayLabel.style.cssText = ` text-align: center; padding: 8px 4px; font-size: 12px; color: #999; font-weight: bold; `; calendar.appendChild(dayLabel); }); function renderCalendar() { // Clear existing days const dayElements = calendar.querySelectorAll('.calendar-day'); dayElements.forEach(el => el.remove()); monthYear.textContent = new Date(currentYear, currentMonth).toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); const firstDay = new Date(currentYear, currentMonth, 1).getDay(); const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); const today = new Date(); const isCurrentMonth = today.getFullYear() === currentYear && today.getMonth() === currentMonth; // Add empty cells for days before the first day of the month for (let i = 0; i < firstDay; i++) { const emptyDay = document.createElement('div'); emptyDay.className = 'calendar-day'; calendar.appendChild(emptyDay); } // Add days of the month for (let dayNum = 1; dayNum <= daysInMonth; dayNum++) { const dayElement = document.createElement('div'); dayElement.className = 'calendar-day'; dayElement.textContent = dayNum; const isSelected = currentYear === year && currentMonth === (month - 1) && dayNum === day; const isToday = isCurrentMonth && dayNum === today.getDate(); dayElement.style.cssText = ` text-align: center; padding: 8px 4px; cursor: pointer; border-radius: 4px; color: #fff; transition: background-color 0.2s; ${isSelected ? 'background: #007bff; font-weight: bold;' : ''} ${isToday && !isSelected ? 'background: #333; border: 1px solid #666;' : ''} `; dayElement.onmouseover = () => { if (!isSelected) dayElement.style.background = '#444'; }; dayElement.onmouseout = () => { if (!isSelected) { dayElement.style.background = isToday ? '#333' : 'transparent'; } }; dayElement.onclick = () => { const selectedDate = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(dayNum).padStart(2, '0')}`; jumpToDate(selectedDate); document.body.removeChild(modal); }; calendar.appendChild(dayElement); } } prevBtn.onclick = () => { currentMonth--; if (currentMonth < 0) { currentMonth = 11; currentYear--; } renderCalendar(); }; nextBtn.onclick = () => { currentMonth++; if (currentMonth > 11) { currentMonth = 0; currentYear++; } renderCalendar(); }; // Create close button const closeBtn = document.createElement('button'); closeBtn.textContent = 'Close'; closeBtn.style.cssText = ` width: 100%; padding: 10px; background: #444; border: none; color: #fff; border-radius: 4px; cursor: pointer; font-size: 14px; `; closeBtn.onmouseover = () => closeBtn.style.background = '#555'; closeBtn.onmouseout = () => closeBtn.style.background = '#444'; closeBtn.onclick = () => document.body.removeChild(modal); // Assemble the picker header.appendChild(prevBtn); header.appendChild(monthYear); header.appendChild(nextBtn); picker.appendChild(header); picker.appendChild(calendar); picker.appendChild(closeBtn); modal.appendChild(picker); // Close on backdrop click modal.onclick = (e) => { if (e.target === modal) { document.body.removeChild(modal); } }; // Close on escape key const escapeHandler = (e) => { if (e.key === 'Escape') { document.body.removeChild(modal); document.removeEventListener('keydown', escapeHandler); } }; document.addEventListener('keydown', escapeHandler); document.body.appendChild(modal); renderCalendar(); } // Function to jump to a specific date - now uses lazy loading function jumpToDate(dateString) { // Check if LazyFixturesManager is available if (typeof window.LazyFixturesManager === 'undefined') { console.warn('LazyFixturesManager not available, date jumping disabled'); return; } const [year, month, day] = dateString.split('-').map(Number); const targetDate = new Date(year, month - 1, day); // Update the date display const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; document.getElementById('fixtures-date').textContent = targetDate.toLocaleDateString('en-US', options); document.getElementById('fixtures-date').dataset.date = dateString; // Update the time display with client timezone (current time, not the selected date) updateLocalTime(); // Use lazy loading system instead of direct fixtures.php call if (window.lazyFixtures && typeof window.lazyFixtures.changeDate === 'function') { window.lazyFixtures.changeDate(dateString).catch(error => { console.error('Error changing date:', error); }); } } // Make functions globally available window.openDatePicker = openDatePicker; window.jumpToDate = jumpToDate; // Debug logging to console // Navigation state let currentPath = []; let currentTeam = null; let isSearchActive = false; // Set currentTeam from serverRendering for team pages if (serverRendering.currentView === 'team' && serverRendering.teamData) { currentTeam = serverRendering.teamData; } let searchTimeout = null; let currentSortMode = 'relevance'; // 'relevance' or 'alphabetical' let originalCountriesOrder = []; // Store original order for restoring relevance sort // DOM elements const backButton = document.getElementById('backButton'); const continentsSection = document.getElementById('continents-section'); const countriesSection = document.getElementById('countries-section'); const leaguesSection = document.getElementById('leagues-section'); const teamsSection = document.getElementById('teams-section'); const searchResultsSection = document.getElementById('search-results-section'); const countriesList = document.getElementById('countries-list'); const leaguesList = document.getElementById('leagues-list'); const teamsList = document.getElementById('teams-list'); const searchResultsList = document.getElementById('search-results-list'); const globalSearchInput = document.getElementById('search-input'); const clearSearchButton = document.getElementById('clear-search'); const searchLoading = document.getElementById('search-loading'); const searchNoResults = document.getElementById('search-no-results'); // Mobile menu elements const hamburgerMenu = document.getElementById('hamburger-menu'); const sidebar = document.getElementById('sidebar'); const sidebarOverlay = document.getElementById('sidebar-overlay'); // Main content sections const heroSection = document.querySelector('.hero'); const dynamicContent = document.getElementById('dynamic-content'); const heroSearchInput = document.querySelector('.hero .search-input2'); // Event listeners // Back button now uses anchor tag navigation - no click handler needed // Mobile menu event listeners hamburgerMenu.addEventListener('click', toggleMobileMenu); sidebarOverlay.addEventListener('click', closeMobileMenu); // Search functionality globalSearchInput.addEventListener('input', handleSearchInput); clearSearchButton.addEventListener('click', clearSearch); globalSearchInput.addEventListener('keydown', (e) => { if (e.key === 'Escape') { clearSearch(); } else if (e.key === 'Enter') { e.preventDefault(); const query = globalSearchInput.value.trim(); if (query) { window.location.href = `/search/?q=${encodeURIComponent(query)}`; } } }); // Connect hero search to sidebar search if (heroSearchInput) { heroSearchInput.addEventListener('input', (e) => { const query = e.target.value; globalSearchInput.value = query; handleSearchInput({ target: globalSearchInput }); // On mobile, open sidebar and focus on sidebar search if (window.innerWidth <= 768) { // Add visual feedback globalSearchInput.classList.add('transferring-focus'); openMobileMenu(); // Transfer focus to sidebar search after menu opens setTimeout(() => { heroSearchInput.blur(); // Remove focus from hero search globalSearchInput.focus(); globalSearchInput.classList.remove('transferring-focus'); // Position cursor at end of text globalSearchInput.setSelectionRange(query.length, query.length); }, 300); // Wait for slide animation } else { // On desktop, transfer focus to sidebar search after 3+ characters if (query.length >= 3) { setTimeout(() => { heroSearchInput.blur(); // Remove focus from hero search globalSearchInput.focus(); // Position cursor at end of text globalSearchInput.setSelectionRange(query.length, query.length); }, 50); // Small delay to ensure value is set } } }); heroSearchInput.addEventListener('keydown', (e) => { if (e.key === 'Escape') { clearSearch(); } else if (e.key === 'Enter') { e.preventDefault(); const query = heroSearchInput.value.trim(); if (query) { window.location.href = `/search/?q=${encodeURIComponent(query)}`; } } }); heroSearchInput.addEventListener('focus', () => { // On mobile, immediately open sidebar and transfer focus if (window.innerWidth <= 768) { // Add visual feedback globalSearchInput.classList.add('transferring-focus'); openMobileMenu(); setTimeout(() => { heroSearchInput.blur(); // Remove focus from hero search globalSearchInput.focus(); globalSearchInput.classList.remove('transferring-focus'); }, 300); } else { // On desktop, don't transfer focus immediately - wait for user to type 3+ characters // Add subtle visual indicator that search results will appear in sidebar globalSearchInput.style.borderColor = '#60a5fa'; setTimeout(() => { globalSearchInput.style.borderColor = ''; }, 1000); } }); } // Add click listeners to popular tags document.querySelectorAll('.tag[data-search-term]').forEach(tag => { tag.addEventListener('click', (e) => { e.preventDefault(); const searchTerm = tag.dataset.searchTerm; // Populate both search inputs if (globalSearchInput) { globalSearchInput.value = searchTerm; } if (heroSearchInput) { heroSearchInput.value = searchTerm; } // Trigger the search handleSearchInput({ target: globalSearchInput }); // On mobile, open sidebar and focus on search if (window.innerWidth <= 768) { openMobileMenu(); setTimeout(() => { searchInput.focus(); searchInput.setSelectionRange(searchTerm.length, searchTerm.length); }, 300); } else { // On desktop, focus on sidebar search searchInput.focus(); searchInput.setSelectionRange(searchTerm.length, searchTerm.length); } }); }); // URL and history management function updateURL() { const pathSegments = currentPath.map(segment => { if (typeof segment === 'object' && segment.name) { // Use the database slug - throw error if not available if (!segment.slug) { throw new Error(`Database slug missing for segment: ${segment.name}`); } return encodeURIComponent(segment.slug); } // For string segments (continents and countries), generate slugs if (typeof segment === 'string') { return encodeURIComponent(slugifySync(segment)); } // Unknown segment type throw new Error(`Unknown segment type: ${typeof segment}, value: ${segment}`); }); if (currentTeam) { if (!currentTeam.slug) { throw new Error(`Database slug missing for team: ${currentTeam.name}`); } pathSegments.push(encodeURIComponent(currentTeam.slug)); } // Preserve existing query parameters when updating URL const currentUrl = new URL(window.location); const newPath = '/' + pathSegments.join('/'); const newUrl = newPath + currentUrl.search + currentUrl.hash; history.pushState({ path: currentPath, team: currentTeam }, '', newUrl); } function updatePageTitle() { // Don't update title on homepage - server sets the correct title if (serverRendering.isHomepage && currentPath.length === 0) { return; } // Don't override server-set titles for league pages during navigation restoration if (serverRendering.currentView === 'league' && serverRendering.hasPrerenderedContent) { // Server has already set the correct SEO title for league pages return; } // Only protect team pages from being overridden by client-side navigation // Allow continent/country titles to be updated during navigation const currentTitle = document.title; if (currentTeam && currentTitle.startsWith('Sync ') && currentTitle.includes(' Fixtures to Your Calendar')) { // Server already set the correct team SEO title, don't override it return; } let title = 'Sync Football Matches to Your Calendar'; if (currentTeam) { // Format: Sync [team name] Fixtures to Your Calendar const teamName = currentTeam.name; title = `Sync ${teamName} Fixtures to Your Calendar`; } else if (currentPath.length === 1) { // Continent level: Sync [continent] Football League Matches to Calendar const continentName = currentPath[0]; title = `Sync ${continentName} Football League Matches to Calendar`; } else if (currentPath.length === 2) { // Country level: Sync [country] Football League Matches to Calendar const countryName = currentPath[1]; title = `Sync ${countryName} Football League Matches to Calendar`; } else if (currentPath.length > 0) { const lastSegment = currentPath[currentPath.length - 1]; const segmentName = typeof lastSegment === 'object' ? lastSegment.name : lastSegment; title = `${segmentName} - Sync Football Matches to Your Calendar`; } document.title = title; } // Add staggered animation delays to newly added items function applyStaggeredAnimation(container, itemSelector = '.nav-item, .team-item') { const items = container.querySelectorAll(itemSelector); items.forEach((item, index) => { // Reset any existing animation item.style.animation = 'none'; item.style.opacity = '0'; item.style.transform = 'translateX(-15px)'; // Apply staggered delay setTimeout(() => { if (item.classList.contains('team-item')) { item.style.animation = `slideInTeam 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards`; } else { item.style.animation = `slideInItem 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards`; } }, index * 50); // 50ms delay between each item }); } // Handle browser back/forward window.addEventListener('popstate', (event) => { if (event.state) { currentPath = event.state.path || []; currentTeam = event.state.team || null; if (currentTeam) { showTeamDetails(currentTeam, false); } else { hideTeamDetails(); // Restore navigation state if (currentPath.length === 0) { showSection('continents-section'); } else if (currentPath.length === 1) { navigateToCountries(currentPath[0], false); } else if (currentPath.length === 2) { navigateToLeagues(currentPath[0], currentPath[1], false); } else if (currentPath.length === 3) { navigateToTeams(currentPath[0], currentPath[1], currentPath[2], false); } } updatePageTitle(); } }); function showSection(sectionId) { document.querySelectorAll('.nav-section').forEach(section => { section.classList.remove('active'); }); document.getElementById(sectionId).classList.add('active'); // Note: Analytics tracking is handled by URL changes and server-side page titles // Don't track section changes here to avoid duplicate page views } // Search functions function handleSearchInput(e) { const query = e.target.value.trim(); // Sync with hero search if this came from sidebar search if (e.target === globalSearchInput && heroSearchInput && heroSearchInput.value !== query) { heroSearchInput.value = query; } // Show/hide clear button if (query.length > 0) { clearSearchButton.classList.add('visible'); } else { clearSearchButton.classList.remove('visible'); } // Clear previous timeout if (searchTimeout) { clearTimeout(searchTimeout); } if (query.length < 2) { if (query.length === 0) { exitSearch(); } return; } // Debounce search searchTimeout = setTimeout(() => { performSearch(query); }, 300); } function performSearch(query) { isSearchActive = true; showSection('search-results-section'); // Don't clear page content since we're using an overlay // Dynamic content stays intact under the overlay currentTeam = null; // Add visual connection to sidebar document.body.classList.add('search-active'); // Show loading for sidebar search searchLoading.style.display = 'block'; searchNoResults.style.display = 'none'; searchResultsList.innerHTML = ''; // Create enhanced search results container in overlay createEnhancedSearchInterface(query); // Perform all searches concurrently const searches = [ // Original sidebar search (teams/leagues) fetch(`/api/search.php?q=${encodeURIComponent(query)}&global=1`, { method: 'GET', headers: { 'X-Search-Context': 'global', 'Cache-Control': 'no-cache' } }), // News search fetch(`/api/search_news.php?q=${encodeURIComponent(query)}&limit=8`, { method: 'GET', headers: { 'Cache-Control': 'no-cache' } }), // Players search fetch(`/api/search_players.php?q=${encodeURIComponent(query)}&limit=50`, { method: 'GET', headers: { 'Cache-Control': 'no-cache' } }) ]; Promise.allSettled(searches) .then(async (results) => { // Handle sidebar search (teams/leagues) searchLoading.style.display = 'none'; // Handle teams/leagues search (sidebar only) if (results[0].status === 'fulfilled') { const sidebarData = await results[0].value.json(); if (sidebarData.results && sidebarData.results.length > 0) { displaySearchResults(sidebarData.results); trackSearch(query, sidebarData.results.length); } else { searchNoResults.style.display = 'block'; searchNoResults.textContent = `No teams or leagues found for "${query}"`; } } // Handle news search if (results[1].status === 'fulfilled') { const newsData = await results[1].value.json(); displayNewsResults(newsData.results || [], query); } else { displayNewsResults([], query, 'Error loading news results'); } // Handle players search if (results[2].status === 'fulfilled') { const playersData = await results[2].value.json(); displayPlayersResults(playersData.results || [], query); } else { displayPlayersResults([], query, 'Error loading player results'); } }) .catch(error => { console.error('Search error:', error); searchLoading.style.display = 'none'; searchNoResults.style.display = 'block'; searchNoResults.textContent = 'Search failed. Please try again.'; // Still show the enhanced interface even if main search fails displayNewsResults([], query, 'Search failed'); displayPlayersResults([], query, 'Search failed'); }); } function displaySearchResults(results) { searchResultsList.innerHTML = ''; results.forEach(result => { const item = document.createElement('li'); item.className = 'search-result-item'; // Create icon with logo/crest if available, fallback otherwise let iconContent; if (result.type === 'team' && result.crest) { iconContent = `${result.name} crest`; } else if (result.type === 'league' && result.logo) { iconContent = `${result.name} logo`; } else { iconContent = getFallbackIcon(result.type); } // Generate proper URL for the result const resultUrl = generateResultUrl(result); item.innerHTML = `
${iconContent}
${result.name}
${result.subtitle}
`; searchResultsList.appendChild(item); }); } function getFallbackIcon(type) { switch(type) { case 'league': return '🏆'; case 'team': return '⚽'; case 'country': return '🌍'; case 'continent': return '🌎'; default: return '🔍'; } } function generateResultUrl(result) { // Generate proper URL based on result type and path if (!result.path || result.path.length === 0) { return '/'; } let url = ''; if (result.type === 'continent') { // /continent url = '/' + encodeURIComponent(createSlug(result.path[0])); } else if (result.type === 'country') { // /continent/country if (result.path.length >= 2) { url = '/' + encodeURIComponent(createSlug(result.path[0])) + '/' + encodeURIComponent(createSlug(result.path[1])); } } else if (result.type === 'league') { // /continent/country/league if (result.path.length >= 3) { url = '/' + encodeURIComponent(createSlug(result.path[0])) + '/' + encodeURIComponent(createSlug(result.path[1])) + '/' + encodeURIComponent(result.path[2].slug || createSlug(result.path[2])); } } else if (result.type === 'team') { // /continent/country/league/team if (result.path.length >= 3) { // Create team slug from name and ID if not provided const teamSlug = result.slug || createSlug(result.name) + '-' + result.id; url = '/' + encodeURIComponent(createSlug(result.path[0])) + '/' + encodeURIComponent(createSlug(result.path[1])) + '/' + encodeURIComponent(result.path[2].slug || createSlug(result.path[2])) + '/' + encodeURIComponent(teamSlug); } } return url || '/'; } function createSlug(input) { // Handle both string and object inputs if (typeof input === 'object' && input.slug) { return input.slug; } if (typeof input === 'object' && input.name) { input = input.name; } if (typeof input !== 'string') { return ''; } // Simple URL-safe slug generation - matches server-side PHP function let slug = input.toLowerCase().trim(); slug = slug.replace(/\s+/g, '-'); slug = slug.replace(/[^a-z0-9_\-]/g, ''); slug = slug.replace(/-+/g, '-'); slug = slug.replace(/^-+|-+$/g, ''); return slug; } function createEnhancedSearchInterface() { // Remove existing overlay if it exists const existing = document.getElementById('enhanced-search-overlay'); if (existing) { existing.remove(); } // Create enhanced search overlay const overlay = document.createElement('div'); overlay.id = 'enhanced-search-overlay'; overlay.className = 'enhanced-search-overlay'; overlay.innerHTML = `

Search Results

Players

Articles

`; // Find the target container (main content area) let targetElement = document.querySelector('main'); if (!targetElement) { targetElement = document.getElementById('dynamic-content'); } if (!targetElement) { targetElement = document.querySelector('.main-content'); } if (targetElement) { // Make target element position relative if it isn't already const computedStyle = window.getComputedStyle(targetElement); if (computedStyle.position === 'static') { targetElement.style.position = 'relative'; } // Add overlay to target container, not body targetElement.appendChild(overlay); } else { // Fallback to body if no target found document.body.appendChild(overlay); } // Add close functionality const closeButton = document.getElementById('enhanced-search-close'); const backdrop = overlay.querySelector('.enhanced-search-backdrop'); const closeOverlay = () => { overlay.classList.add('closing'); setTimeout(() => { if (overlay.parentNode) { overlay.remove(); } }, 300); }; closeButton.addEventListener('click', closeOverlay); backdrop.addEventListener('click', closeOverlay); // Close on Escape key const handleEscape = (e) => { if (e.key === 'Escape') { closeOverlay(); document.removeEventListener('keydown', handleEscape); } }; document.addEventListener('keydown', handleEscape); // Show overlay with animation setTimeout(() => { overlay.classList.add('visible'); }, 10); return overlay; } function displayNewsResults(results) { const container = document.getElementById('news-search-results'); if (!container) return; if (!results || results.length === 0) { container.innerHTML = '
No news articles found
'; return; } container.innerHTML = results.map(article => `
${article.featured_image ? `${article.title}` : '' }

${article.title}

${article.excerpt}

${article.published_date} ${article.tags && article.tags.length > 0 ? `${article.tags}` : ''}
`).join(''); } function displayPlayersResults(results) { const container = document.getElementById('players-search-results'); if (!container) return; if (!results || results.length === 0) { container.innerHTML = '
No players found
'; return; } container.innerHTML = results.map(player => `
${player.photo ? `${player.name}` : '
👤
' }

${player.name}

${player.position || 'Position not specified'} ${player.primary_team ? `
${player.primary_team.crest ? `${player.primary_team.name}` : '' } ${player.primary_team.name}
` : '' }
`).join(''); } function clearSearch() { globalSearchInput.value = ''; if (heroSearchInput) { heroSearchInput.value = ''; } clearSearchButton.classList.remove('visible'); exitSearch(); } function exitSearch() { isSearchActive = false; searchLoading.style.display = 'none'; searchNoResults.style.display = 'none'; // Remove visual connection document.body.classList.remove('search-active'); // Return to appropriate navigation state if (currentTeam) { showSection('teams-section'); } else if (currentPath.length === 0) { showSection('continents-section'); } else if (currentPath.length === 1) { showSection('countries-section'); } else if (currentPath.length === 2) { showSection('leagues-section'); } else if (currentPath.length === 3) { showSection('teams-section'); } } // Function to toggle sort mode and re-sort current list function toggleSortMode() { currentSortMode = currentSortMode === 'relevance' ? 'alphabetical' : 'relevance'; // Always try to sort countries list if it exists const countriesList = document.getElementById('countries-list'); if (countriesList) { sortCountriesList(); return; } // Check for leagues list const leaguesList = document.getElementById('leagues-list'); if (leaguesList) { sortLeaguesList(); return; } // Check for teams list const teamsList = document.getElementById('teams-list'); if (teamsList) { sortTeamsList(); return; } // Re-sort the current active list const activeSection = document.querySelector('.section-content.active'); if (activeSection) { if (activeSection.id === 'teams-section') { // Re-sort teams list sortTeamsList(); } else if (activeSection.id === 'leagues-section') { // Re-sort leagues list sortLeaguesList(); } else { // Default to countries if we can't determine sortCountriesList(); } } } // Function to toggle sort mode for a specific list function toggleSortModeForList(listType) { currentSortMode = currentSortMode === 'relevance' ? 'alphabetical' : 'relevance'; if (listType === 'countries') { sortCountriesList(); } else if (listType === 'leagues') { sortLeaguesList(); } else if (listType === 'teams') { sortTeamsList(); } } // Function to sort teams list based on current mode function sortTeamsList() { const teamsList = document.getElementById('teams-list'); if (!teamsList) return; const items = Array.from(teamsList.children); const allHeader = items.find(item => item.querySelector('a')?.textContent?.startsWith('Sort by')); const teamItems = items.filter(item => !item.querySelector('a')?.textContent?.startsWith('Sort by')); if (currentSortMode === 'alphabetical') { teamItems.sort((a, b) => { const linkA = a.querySelector('a'); const linkB = b.querySelector('a'); // Clean up the text by removing extra whitespace and the emoji const nameA = linkA?.textContent?.replace(/\s+/g, ' ').replace('⚽', '').trim() || ''; const nameB = linkB?.textContent?.replace(/\s+/g, ' ').replace('⚽', '').trim() || ''; return nameA.localeCompare(nameB); }); } else { // Relevance sort - restore original order (no change needed, teams already in relevance order) // Could add specific relevance logic here if needed } // Update header text if (allHeader) { const link = allHeader.querySelector('a'); if (link) { link.innerHTML = `Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'}`; } } // Clear and re-append sorted items teamsList.innerHTML = ''; if (allHeader) teamsList.appendChild(allHeader); teamItems.forEach(item => teamsList.appendChild(item)); } // Function to sort leagues list based on current mode function sortLeaguesList() { const leaguesList = document.getElementById('leagues-list'); if (!leaguesList) return; const items = Array.from(leaguesList.children); const allHeader = items.find(item => item.querySelector('a')?.textContent?.startsWith('Sort by')); const leagueItems = items.filter(item => !item.querySelector('a')?.textContent?.startsWith('Sort by')); if (currentSortMode === 'alphabetical') { leagueItems.sort((a, b) => { const linkA = a.querySelector('a'); const linkB = b.querySelector('a'); const nameA = linkA?.textContent?.trim() || ''; const nameB = linkB?.textContent?.trim() || ''; return nameA.localeCompare(nameB); }); } else { // Relevance sort - restore original order (no change needed) } // Update header text if (allHeader) { const link = allHeader.querySelector('a'); if (link) { link.innerHTML = `Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'}`; } } // Clear and re-append sorted items leaguesList.innerHTML = ''; if (allHeader) leaguesList.appendChild(allHeader); leagueItems.forEach(item => leaguesList.appendChild(item)); } // Function to sort countries list based on current mode function sortCountriesList() { const countriesList = document.getElementById('countries-list'); if (!countriesList) return; const items = Array.from(countriesList.children); const allHeader = items[0]; // First item is always the header const countryItems = items.slice(1); // All items except the first // Store original order on first call only if (originalCountriesOrder.length === 0) { originalCountriesOrder = countryItems.map(item => { const link = item.querySelector('a'); return link?.getAttribute('href')?.split('/').pop() || ''; }); } if (currentSortMode === 'alphabetical') { countryItems.sort((a, b) => { // Get country name from the generic inside the link const linkA = a.querySelector('a'); const linkB = b.querySelector('a'); const nameA = linkA?.querySelector('generic')?.textContent?.trim() || linkA?.textContent?.trim() || ''; const nameB = linkB?.querySelector('generic')?.textContent?.trim() || linkB?.textContent?.trim() || ''; return nameA.localeCompare(nameB); }); } else { // Relevance sort - restore original order if (originalCountriesOrder.length > 0) { countryItems.sort((a, b) => { const linkA = a.querySelector('a'); const linkB = b.querySelector('a'); const slugA = linkA?.getAttribute('href')?.split('/').pop() || ''; const slugB = linkB?.getAttribute('href')?.split('/').pop() || ''; const indexA = originalCountriesOrder.indexOf(slugA); const indexB = originalCountriesOrder.indexOf(slugB); // Items not in original order go to the end if (indexA === -1 && indexB === -1) return 0; if (indexA === -1) return 1; if (indexB === -1) return -1; return indexA - indexB; }); } } // Update header text (removed "Alpha" as requested) if (allHeader) { const link = allHeader.querySelector('a'); if (link) { link.textContent = `Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'}`; } } // Clear and re-append sorted items countriesList.innerHTML = ''; if (allHeader) countriesList.appendChild(allHeader); countryItems.forEach(item => countriesList.appendChild(item)); } function updateBackButton() { if (currentPath.length > 0 || currentTeam || isSearchActive) { backButton.classList.add('visible'); // Calculate the parent URL by removing the last segment const currentUrl = window.location.pathname; const segments = currentUrl.split('/').filter(segment => segment !== ''); let parentUrl = '/'; if (segments.length > 1) { // Remove the last segment to go up one level segments.pop(); parentUrl = '/' + segments.join('/'); } // Update the href attribute backButton.href = parentUrl; } else { backButton.classList.remove('visible'); backButton.href = '/'; } } function showTeamDetails(team, updateHistory = true) { currentTeam = team; // Hide hero section and show team details if (heroSection) { heroSection.style.display = 'none'; } // Find team details section in dynamic content (if it exists) const teamDetailsSection = document.querySelector('.team-details'); const teamLoadingSection = document.querySelector('.team-loading'); if (teamDetailsSection) { teamDetailsSection.classList.add('visible'); } if (teamLoadingSection) { teamLoadingSection.classList.remove('visible'); } if (updateHistory) { updateURL(); } updatePageTitle(); updateBackButton(); } function hideTeamDetails() { currentTeam = null; // Check if we're at the teams level (league selected) if (currentPath.length === 3) { // Stay in teams view - reload teams grid navigateToTeams(currentPath[0], currentPath[1], currentPath[2], false); } else if (currentPath.length === 1) { // We're at continent level - don't reset, keep the continent content visible // Do nothing - continent content should remain visible } else if (currentPath.length === 2) { // We're at country level - don't reset, keep the country content visible // Do nothing - country content should remain visible } else { // Show hero section and clear dynamic content if (heroSection) { if (heroSection) { heroSection.style.display = 'flex'; } } dynamicContent.innerHTML = ''; } } function filterMatchesByLeague(leagueName, clickedBadge) { const allBadges = document.querySelectorAll('.league-badge'); const allMatches = document.querySelectorAll('.match-row'); // If clicking the same badge, show all matches (remove filter) if (clickedBadge.classList.contains('active')) { allBadges.forEach(badge => badge.classList.remove('active')); allMatches.forEach(match => match.style.display = ''); return; } // Remove active from all badges allBadges.forEach(badge => badge.classList.remove('active')); // Add active to clicked badge clickedBadge.classList.add('active'); // Filter matches allMatches.forEach(match => { const matchLeague = match.getAttribute('data-league'); if (matchLeague === leagueName) { match.style.display = ''; } else { match.style.display = 'none'; } }); // Check if any matches are visible const visibleMatches = document.querySelectorAll('.match-row[style=""], .match-row:not([style])'); const matchesList = document.querySelector('.matches-list'); const existingNoMatches = matchesList.querySelector('.no-matches-filtered'); if (visibleMatches.length === 0) { // Show "no matches" message if none visible if (!existingNoMatches) { const noMatchesDiv = document.createElement('div'); noMatchesDiv.className = 'no-matches-filtered'; noMatchesDiv.textContent = `No upcoming matches in ${leagueName}`; noMatchesDiv.style.cssText = 'text-align: center; color: #71717a; padding: 20px; font-style: italic;'; matchesList.appendChild(noMatchesDiv); } } else { // Remove "no matches" message if matches are visible if (existingNoMatches) { existingNoMatches.remove(); } } } // Helper function to escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Helper function to escape strings for JavaScript function escapeJs(text) { if (!text) return ''; return text.toString() .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t'); } // Generate continent content (countries grid) function generateContinentContent(continent, countries) { if (!countries || Object.keys(countries).length === 0) { return `

${continent}

No countries found in this continent

`; } let html = `

${continent}

Choose a country to explore leagues and teams

`; // Sort countries by minimum league ID const sortedCountries = sortCountriesByMinLeagueId(Object.keys(countries), countries); sortedCountries.forEach(country => { const leagues = countries[country]; const leagueCount = leagues.length; const continentSlug = slugifySync(continent); const countrySlug = slugifySync(country); const countryDisplayName = country.replace(/-/g, ' '); html += `

${escapeHtml(countryDisplayName)}

${leagueCount} league${leagueCount !== 1 ? 's' : ''}

`; }); html += '
'; return html; } function navigateToCountries(continent, updateHistory = true) { // Temporarily disable popstate handling during navigation const originalPopstateHandler = window.onpopstate; window.onpopstate = null; currentPath = [continent]; const countries = Object.keys(hierarchyData[continent] || {}); const mainContent = document.querySelector('.main-content'); if (mainContent) { mainContent.style.display = 'block'; } if (heroSection) { heroSection.style.display = 'none'; } // Generate and show continent content in main area const continentContent = generateContinentContent(continent, hierarchyData[continent]); const dynamicContentElement = document.getElementById('dynamic-content'); if (dynamicContentElement) { dynamicContentElement.innerHTML = continentContent; } else { console.error('Error: dynamic-content element not found.'); } showSection('countries-section'); countriesList.innerHTML = ''; // Add "All [Continent]" option const allContinentNavItem = document.createElement('li'); allContinentNavItem.className = 'nav-item'; allContinentNavItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allContinentNavItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('countries'); }); countriesList.appendChild(allContinentNavItem); // Sort countries by minimum league ID const sortedCountries = sortCountriesByMinLeagueId(countries, hierarchyData[continent] || {}); sortedCountries.forEach(country => { // Create country slug const continentSlug = slugifySync(continent); const countrySlug = slugifySync(country); const countryDisplayName = country.replace(/-/g, ' '); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${countryDisplayName} `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); countriesList.appendChild(item); }); // Apply staggered animation to the newly added items applyStaggeredAnimation(countriesList); showSection('countries-section'); hideTeamDetails(); if (updateHistory) { updateURL(); } updatePageTitle(); updateBackButton(); // Re-enable popstate handling after a brief delay setTimeout(() => { window.onpopstate = originalPopstateHandler; }, 100); } // Simple synchronous slugify function for JavaScript navigation function slugifySync(text) { return text.toLowerCase() .replace(/[àáâãäå]/g, 'a') .replace(/[èéêë]/g, 'e') .replace(/[ìíîï]/g, 'i') .replace(/[òóôõö]/g, 'o') .replace(/[ùúûü]/g, 'u') .replace(/[ñ]/g, 'n') .replace(/[ç]/g, 'c') .replace(/[^a-z0-9\s-]/g, '') // Remove special characters .replace(/\s+/g, '-') .replace(/-+/g, '-') .trim('-'); } // Sort countries by their lowest league ID function sortCountriesByMinLeagueId(countries, countriesData) { return countries.sort((a, b) => { const leaguesA = countriesData[a] || []; const leaguesB = countriesData[b] || []; // Find minimum ID for each country const minIdA = leaguesA.length > 0 ? Math.min(...leaguesA.map(league => league.id)) : Infinity; const minIdB = leaguesB.length > 0 ? Math.min(...leaguesB.map(league => league.id)) : Infinity; return minIdA - minIdB; }); } function navigateToLeagues(continent, country, updateHistory = true) { currentPath = [continent, country]; const leagues = (hierarchyData[continent]?.[country] || []).slice().sort((a, b) => { return a.id - b.id; }); // Create slugs for SEO URLs - generate for continents/countries, use database for leagues // Handle both string names and objects with .name property const continentName = typeof continent === 'string' ? continent : continent.name; const countryName = typeof country === 'string' ? country : country.name; const continentSlug = slugifySync(continentName); const countrySlug = slugifySync(countryName); // Hide hero section and show main content with leagues grid if (heroSection) { heroSection.style.display = 'none'; } // Generate leagues list HTML for main content let leaguesHTML = `

${countryName} Leagues

Select a league to view teams

`; leagues.forEach(league => { // Create league slug with fallback if missing let leagueSlug = league.slug; if (!leagueSlug) { console.warn(`Database slug missing for league: ${league.name}, generating fallback slug`); leagueSlug = slugifySync(league.name) + (league.id ? `-${league.id}` : ''); } leaguesHTML += `
${league.name}
${league.team_count} team${league.team_count !== 1 ? 's' : ''}
`; }); leaguesHTML += '
'; dynamicContent.innerHTML = leaguesHTML; leaguesList.innerHTML = ''; // Add "All [Country]" option const allCountryNavItem = document.createElement('li'); allCountryNavItem.className = 'nav-item'; const countryDisplayName = country.replace(/-/g, ' '); allCountryNavItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allCountryNavItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('leagues'); }); leaguesList.appendChild(allCountryNavItem); leagues.forEach(league => { // Create league slug with fallback if missing let leagueSlug = league.slug; if (!leagueSlug) { console.warn(`Database slug missing for league: ${league.name}, generating fallback slug`); leagueSlug = slugifySync(league.name) + (league.id ? `-${league.id}` : ''); } const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${league.name} (${league.team_count} teams) `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); leaguesList.appendChild(item); }); // Apply staggered animation to the newly added items applyStaggeredAnimation(leaguesList); showSection('leagues-section'); hideTeamDetails(); if (updateHistory) { updateURL(); } updatePageTitle(); updateBackButton(); } function navigateToTeams(continent, country, league, updateHistory = true, leagueSlug = null) { currentPath = [continent, country, league]; const continentName = typeof continent === 'string' ? continent : continent.name; const countryName = typeof country === 'string' ? country : country.name; const leagueName = typeof league === 'string' ? league : league.name; const continentSlug = slugifySync(continentName); const countrySlug = slugifySync(countryName); const finalLeagueSlug = league.slug || leagueSlug || slugifySync(leagueName); // Show loading state in main content if (heroSection) { heroSection.style.display = 'none'; } dynamicContent.innerHTML = `
Loading standings...
Please wait while we fetch the standings for ${escapeHtml(leagueName)}.
`; // Show loading state in sidebar teamsList.innerHTML = '
  • Loading standings...
  • '; showSection('teams-section'); // Try to load standings first, fallback to teams if no standings available fetch(`/public/ajax/standings.php?league=${encodeURIComponent(league.id)}&theme=dark`, { headers: { 'X-AJAX-TOKEN': window.ajaxToken || '' } }) .then(response => { if (!response.ok) { throw new Error('No standings available'); } return response.text(); }) .then(standingsHtml => { if (!standingsHtml || standingsHtml.trim().length === 0) { throw new Error('No standings data available'); } // Display standings in main content const fullContentHTML = `

    ${escapeHtml(countryName)} - ${escapeHtml(leagueName)}

    League Standings & Teams

    ${standingsHtml}
    `; dynamicContent.innerHTML = fullContentHTML; // Load teams for sidebar return fetch(`/api/teams.php?league_id=${encodeURIComponent(league.id)}`); }) .then(response => { if (!response.ok) { throw new Error('Failed to load teams'); } return response.json(); }) .then(data => { if (data.error) { throw new Error(data.error); } const teams = data.teams || []; // Populate sidebar with teams teamsList.innerHTML = ''; const allLeagueItem = document.createElement('li'); allLeagueItem.className = 'nav-item'; allLeagueItem.innerHTML = `Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'}`; allLeagueItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('teams'); }); teamsList.appendChild(allLeagueItem); teams.forEach(team => { const item = document.createElement('li'); item.className = 'nav-item'; const teamSlug = team.slug || slugifySync(team.name); const logoHtml = team.crest ? `${escapeHtml(team.name)} crest` : '⚽'; item.innerHTML = `
    ${escapeHtml(team.name)}
    `; teamsList.appendChild(item); }); if (updateHistory) { updateURL(); } updatePageTitle(); updateBackButton(); }) .catch(error => { console.error('Standings not available, falling back to teams list:', error); // Fallback to teams list if standings are not available fetch(`/api/teams.php?league_id=${encodeURIComponent(league.id)}`) .then(response => response.json()) .then(data => { if (data.error) { throw new Error(data.error); } const teams = data.teams || []; // Populate sidebar teamsList.innerHTML = ''; const allLeagueItem = document.createElement('li'); allLeagueItem.className = 'nav-item'; allLeagueItem.innerHTML = `Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'}`; allLeagueItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('teams'); }); teamsList.appendChild(allLeagueItem); teams.forEach(team => { const item = document.createElement('li'); item.className = 'nav-item'; const teamSlug = team.slug || slugifySync(team.name); const logoHtml = team.crest ? `${escapeHtml(team.name)} crest` : '⚽'; item.innerHTML = `
    ${escapeHtml(team.name)}
    `; teamsList.appendChild(item); }); // Populate main content list as fallback const teamsListHTML = teams.map(team => { const teamSlug = team.slug || slugifySync(team.name); return `
    ${escapeHtml(team.name)}
    `; }).join(''); const fullContentHTML = `

    ${escapeHtml(countryName)} - ${escapeHtml(leagueName)}

    ${teams.length} teams

    ${teamsListHTML}
    `; dynamicContent.innerHTML = fullContentHTML; if (updateHistory) { updateURL(); } updatePageTitle(); updateBackButton(); }) .catch(fallbackError => { console.error('Error loading teams as fallback:', fallbackError); teamsList.innerHTML = '
  • Error loading league data
  • '; dynamicContent.innerHTML = `
    Error Loading League Data
    Could not load standings or teams for ${escapeHtml(leagueName)}. Please try again.
    `; }); }); } // Helper function to generate team URL using primary location async function generateTeamUrl(teamSlug) { try { const response = await fetch(`/api/team-location.php?team_slug=${encodeURIComponent(teamSlug)}`); const data = await response.json(); if (data.error) { console.warn('Could not get team location, using fallback URL:', data.error); return null; } return data.primary_url; } catch (error) { console.warn('Error getting team location:', error); return null; } } // Helper function to generate fixture URL function generateFixtureUrl(matchDate, homeTeam, awayTeam, matchId) { if (!matchDate || !homeTeam || !awayTeam) { return '#'; } // Always extract date from the raw database datetime string to avoid timezone issues let formattedDate; if (typeof matchDate === 'string') { // Extract YYYY-MM-DD from datetime string (e.g., "2025-09-02T14:00:00.000Z") // Look for the first 10 characters that match YYYY-MM-DD pattern const dateMatch = matchDate.match(/(\d{4}-\d{2}-\d{2})/); if (dateMatch) { formattedDate = dateMatch[1]; } else { console.error('Invalid date format for URL generation:', matchDate); return '#'; } } else { console.error('Expected string date for URL generation, got:', typeof matchDate, matchDate); return '#'; } // Create slugs from team names using same logic as PHP UrlGenerator::createSlug() function createSlug(text) { let slug = text.toLowerCase().trim(); // Convert special characters to ASCII equivalents (matching PHP transliteration) const transliteration = { 'á': 'a', 'à': 'a', 'ã': 'a', 'â': 'a', 'ä': 'a', 'é': 'e', 'è': 'e', 'ê': 'e', 'ë': 'e', 'í': 'i', 'ì': 'i', 'î': 'i', 'ï': 'i', 'ó': 'o', 'ò': 'o', 'õ': 'o', 'ô': 'o', 'ö': 'o', 'ú': 'u', 'ù': 'u', 'û': 'u', 'ü': 'u', 'ç': 'c', 'ñ': 'n', 'š': 's', 'ž': 'z', 'đ': 'd', 'ć': 'c', 'č': 'c' }; // Apply character transliteration for (const [accented, plain] of Object.entries(transliteration)) { slug = slug.replace(new RegExp(accented, 'g'), plain); } // Replace periods with spaces to ensure proper separation (e.g., "S.J." becomes "S J") slug = slug.replace(/\./g, ' '); // Replace spaces with hyphens slug = slug.replace(/\s+/g, '-'); // Remove remaining problematic characters for URLs (keep only a-z, 0-9, _, -) slug = slug.replace(/[^a-z0-9_\-]/g, ''); // Replace multiple consecutive hyphens with single hyphen slug = slug.replace(/-+/g, '-'); // Trim hyphens from start and end slug = slug.replace(/^-+|-+$/g, ''); return slug; } const homeSlug = createSlug(homeTeam); const awaySlug = createSlug(awayTeam); if (matchId !== null && matchId !== undefined) { return `/fixtures/${formattedDate}/${homeSlug}-vs-${awaySlug}-${matchId}`; } return `/fixtures/${formattedDate}/${homeSlug}-vs-${awaySlug}`; } function navigateBack() { if (isSearchActive) { clearSearch(); return; } if (currentTeam) { currentTeam = null; const [continent, country, league] = currentPath; navigateToTeams(continent, country, league); return; } currentPath.pop(); if (currentPath.length === 0) { // When going back to homepage, redirect to root URL to ensure proper server-side rendering window.location.href = '/'; } else if (currentPath.length === 1) { const [continent] = currentPath; navigateToCountries(continent); } else if (currentPath.length === 2) { const [continent, country] = currentPath; navigateToLeagues(continent, country); } } // Search functionality function searchAction() { // Focus on search input searchInput.focus(); } function openGuidedTour() { // Show the guided tour modal const modal = document.getElementById('guided-tour-modal'); modal.style.display = 'block'; document.body.style.overflow = 'hidden'; // Prevent background scrolling } function closeGuidedTour() { // Hide the guided tour modal const modal = document.getElementById('guided-tour-modal'); modal.style.display = 'none'; document.body.style.overflow = 'auto'; // Re-enable background scrolling } function navigateToLeague(leagueId, leagueName) { // This would navigate to a specific league's teams // For now, we'll just show a placeholder alert(`Navigate to ${leagueName} (ID: ${leagueId}) - This will be implemented soon!`); } // Calendar functions function updateMatchesCalendar(matches) { const matchesList = document.getElementById('matches-list'); const calendarMonth = document.getElementById('calendar-month'); // Set current month const now = new Date(); const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; calendarMonth.textContent = `${monthNames[now.getMonth()]} ${now.getFullYear()}`; if (!matches || matches.length === 0) { matchesList.innerHTML = '
    No upcoming matches scheduled
    '; return; } matchesList.innerHTML = ''; matches.forEach(match => { const matchDate = new Date(match.datetime); const day = matchDate.getDate(); const timeString = matchDate.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); // Format the match event text let eventText = ''; if (match.home_team && match.away_team) { eventText = `${match.home_team} - ${match.away_team}`; } const matchRow = document.createElement('div'); matchRow.className = 'match-row'; matchRow.innerHTML = `
    ${day} ${monthNames[matchDate.getMonth()]}
    ${timeString}
    ${eventText}
    ${match.league_name || 'Match'}
    `; matchesList.appendChild(matchRow); }); } function previousMonth() { // Placeholder for previous month navigation } function nextMonth() { // Placeholder for next month navigation } // Mobile menu functions function toggleMobileMenu() { const isActive = sidebar.classList.contains('active'); if (isActive) { closeMobileMenu(); } else { openMobileMenu(); } } function openMobileMenu() { sidebar.classList.add('active'); sidebarOverlay.classList.add('active'); hamburgerMenu.classList.add('active'); document.body.style.overflow = 'hidden'; // Prevent scrolling when menu is open } function closeMobileMenu() { sidebar.classList.remove('active'); sidebarOverlay.classList.remove('active'); hamburgerMenu.classList.remove('active'); document.body.style.overflow = ''; // Restore scrolling } // Close mobile menu when clicking on navigation items function handleNavClick() { if (window.innerWidth <= 768) { closeMobileMenu(); } } // Add click listeners to nav items to close menu on mobile document.addEventListener('click', function(e) { // Only close mobile menu when clicking on team items or when clicking outside sidebar if (e.target.closest('.team-item') || e.target.closest('.team-card')) { if (window.innerWidth <= 768) { closeMobileMenu(); } } }); // Close menu on window resize if becoming desktop window.addEventListener('resize', function() { if (window.innerWidth > 768) { closeMobileMenu(); } }); function loadHomepage() { // Reset navigation state to homepage currentPath = []; currentTeam = null; isSearchActive = false; // Clear search globalSearchInput.value = ''; if (heroSearchInput) { heroSearchInput.value = ''; } clearSearchButton.classList.remove('visible'); // Show hero section and clear dynamic content const mainElement = document.querySelector('main'); if (mainElement) { mainElement.style.display = 'block'; } if (heroSection) { heroSection.style.display = 'flex'; } dynamicContent.innerHTML = ''; // Reset sidebar to show all continents showSection('continents-section'); // Update page elements for homepage updateURL(); updatePageTitle(); updateBackButton(); } // URL Routing functionality function parseCurrentUrl() { const path = window.location.pathname; const segments = path.split('/').filter(segment => segment.length > 0 && segment !== 'index.php'); return { path: path, segments: segments, isHomepage: segments.length === 0 }; } function initializeFromUrl() { const urlInfo = parseCurrentUrl(); // If we have server-rendered content, update navigation state but don't reload content if (serverRendering.hasPrerenderedContent) { // Wait for hierarchy data before restoring navigation state whenHierarchyReady(() => { // Restore navigation state to match URL by calling appropriate navigation functions // without updating history or reloading content if (serverRendering.currentView === 'continent') { restoreNavigationStateForContinent(serverRendering.pageData.continent); } else if (serverRendering.currentView === 'country') { restoreNavigationStateForCountry(serverRendering.pageData.continent, serverRendering.pageData.country); } else if (serverRendering.currentView === 'league') { restoreNavigationStateForLeague(serverRendering.pageData.continent, serverRendering.pageData.country, serverRendering.pageData.league); } else if (serverRendering.currentView === 'team') { restoreNavigationStateForTeam(serverRendering.pageData.continent, serverRendering.pageData.country, serverRendering.pageData.league, serverRendering.pageData.team); } }); // Hide hero section for non-homepage content const heroSection = document.querySelector('.hero'); if (heroSection) { if (heroSection) { heroSection.style.display = 'none'; } } // Add event listeners to pre-rendered content attachEventListenersToPrerenderedContent(); return; } // Use client-side URL parsing to determine routing (fallback when server-side routing fails) if (urlInfo.isHomepage) { // Load homepage content through routing loadHomepage(); return; } const segments = urlInfo.segments; // Skip processing for fixture URLs - these are handled server-side if (segments.length > 0 && segments[0] === 'fixtures') { return; } // Additional fix: Directly handle continent URLs even if server-side detection failed if (segments.length === 1) { const [continentSlug] = segments; // Wait for hierarchy data before restoring whenHierarchyReady(() => { restoreNavigationStateForContinent(continentSlug); }); return; } if (segments.length === 4) { // Pattern: /continent/country/league/team (legacy) const [continentSlug, countrySlug, leagueSlug, teamSlug] = segments; // Redirect to the team URL window.location.href = `/${continentSlug}/${countrySlug}/${leagueSlug}/${teamSlug}`; } else if (segments.length === 3) { // Pattern: /continent/country/league const [continentSlug, countrySlug, leagueSlug] = segments; whenHierarchyReady(() => { loadLeagueFromUrl(continentSlug, countrySlug, leagueSlug); }); } else if (segments.length === 2) { // Pattern: /continent/country - show leagues const [continentSlug, countrySlug] = segments; whenHierarchyReady(() => { loadCountryFromUrl(continentSlug, countrySlug); }); } else if (segments.length === 1) { // Pattern: /continent - show countries const [continentSlug] = segments; whenHierarchyReady(() => { loadContinentFromUrl(continentSlug); }); } } function attachEventListenersToPrerenderedContent() { // JavaScript navigation disabled - use default HTML links instead // This prevents double-loading conflicts between JS and server-side routing // Country cards and league cards now use pure HTML links for navigation // No JavaScript event listeners needed - let the browser handle navigation naturally } // State restoration functions for server-rendered content function restoreNavigationStateForContinent(continentSlug) { // Find the continent object const continent = Object.keys(hierarchyData).find(c => slugifySync(c).toLowerCase() === continentSlug.toLowerCase() ); if (continent) { currentPath = [continent]; // Generate and show continent content in main area const continentContent = generateContinentContent(continent, hierarchyData[continent]); dynamicContent.innerHTML = continentContent; // Hide hero section to show content const heroSection = document.querySelector('.hero'); if (heroSection) { heroSection.style.display = 'none'; } // Show the countries section in sidebar showSection('countries-section'); // Populate countries list without triggering navigation const countries = Object.keys(hierarchyData[continent] || {}); countriesList.innerHTML = ''; // Add "All [Continent]" option const allItem = document.createElement('li'); allItem.className = 'nav-item'; allItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('countries'); }); countriesList.appendChild(allItem); // Sort countries by minimum league ID const sortedCountries = sortCountriesByMinLeagueId(countries, hierarchyData[continent] || {}); sortedCountries.forEach(country => { const continentSlugForLink = slugifySync(typeof continent === 'object' ? (continent.name || continent) : continent); const countrySlugForLink = slugifySync(typeof country === 'object' ? (country.name || country) : country); const countryDisplayName = country.replace(/-/g, ' '); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${countryDisplayName} `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); countriesList.appendChild(item); }); showSection('countries-section'); updateBackButton(); // Update page title after navigation state is restored updatePageTitle(); } } function restoreNavigationStateForCountry(continentSlug, countrySlug) { // Find the continent and country objects const continent = Object.keys(hierarchyData).find(c => slugifySync(c).toLowerCase() === continentSlug.toLowerCase() ); if (continent) { const country = Object.keys(hierarchyData[continent] || {}).find(c => slugifySync(c).toLowerCase() === countrySlug.toLowerCase() ); if (country) { currentPath = [continent, country]; // Populate countries list for the continent const countries = Object.keys(hierarchyData[continent] || {}); countriesList.innerHTML = ''; // Add "All [Continent]" option const allContinentItem = document.createElement('li'); allContinentItem.className = 'nav-item'; allContinentItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allContinentItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('countries'); }); countriesList.appendChild(allContinentItem); // Sort countries by minimum league ID const sortedCountries = sortCountriesByMinLeagueId(countries, hierarchyData[continent] || {}); sortedCountries.forEach(countryName => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = countryName.slug || slugifySync(countryName); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${countryName} `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); countriesList.appendChild(item); }); // Then populate leagues list const leagues = (hierarchyData[continent][country] || []).slice().sort((a, b) => { return a.id - b.id; }); leaguesList.innerHTML = ''; // Add "All [Country]" option const allCountryItem = document.createElement('li'); allCountryItem.className = 'nav-item'; const countryDisplayName = country.replace(/-/g, ' '); allCountryItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allCountryItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('leagues'); }); leaguesList.appendChild(allCountryItem); leagues.forEach(league => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = country.slug || slugifySync(country); const leagueSlugForLink = league.slug || slugifySync(league.name); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${league.name} (${league.team_count} teams) `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); leaguesList.appendChild(item); }); showSection('leagues-section'); updateBackButton(); // Update page title after navigation state is restored updatePageTitle(); } } } function restoreNavigationStateForLeague(continentSlug, countrySlug, leagueSlug) { // Find the continent, country, and league objects const continent = Object.keys(hierarchyData).find(c => slugifySync(c).toLowerCase() === continentSlug.toLowerCase() ); if (continent) { const country = Object.keys(hierarchyData[continent] || {}).find(c => slugifySync(c).toLowerCase() === countrySlug.toLowerCase() ); if (country) { const leagues = (hierarchyData[continent][country] || []).slice().sort((a, b) => { return a.id - b.id; }); const league = leagues.find(l => (l.slug && l.slug === leagueSlug) || slugifySync(l.name) === leagueSlug ); if (league) { currentPath = [continent, country, league]; // Populate countries list for the continent const countries = Object.keys(hierarchyData[continent] || {}); countriesList.innerHTML = ''; // Add "All [Continent]" option const allContinentItem = document.createElement('li'); allContinentItem.className = 'nav-item'; allContinentItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allContinentItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('countries'); }); countriesList.appendChild(allContinentItem); // Sort countries by minimum league ID const sortedCountries = sortCountriesByMinLeagueId(countries, hierarchyData[continent] || {}); sortedCountries.forEach(countryName => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = countryName.slug || slugifySync(countryName); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${countryName} `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); countriesList.appendChild(item); }); // Populate leagues list for the country leaguesList.innerHTML = ''; // Add "Sort by" option instead of "All [Country]" const allCountryItem = document.createElement('li'); allCountryItem.className = 'nav-item'; const countryDisplayName = country.replace(/-/g, ' '); allCountryItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; allCountryItem.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // Note: This "All" button might not have an href, consider adding one toggleSortModeForList('leagues'); }); leaguesList.appendChild(allCountryItem); leagues.forEach(leagueItem => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = country.slug || slugifySync(country); const leagueSlugForLink = leagueItem.slug || slugifySync(leagueItem.name); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${leagueItem.name} (${leagueItem.team_count} teams) `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); leaguesList.appendChild(item); }); // Then populate teams list via API teamsList.innerHTML = '
  • Loading teams...
  • '; fetch(`/api/teams.php?league_id=${encodeURIComponent(league.id)}`) .then(response => response.json()) .then(data => { teamsList.innerHTML = ''; // Add "All [League]" option const allLeagueItem = document.createElement('li'); allLeagueItem.className = 'nav-item'; allLeagueItem.innerHTML = ` Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'} `; teamsList.appendChild(allLeagueItem); if (data.teams && data.teams.length > 0) { data.teams.forEach(team => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = country.slug || slugifySync(country); const leagueSlugForLink = league.slug || slugifySync(league.name); const teamSlugForLink = team.slug || slugifySync(team.name); const item = document.createElement('li'); item.className = 'nav-item'; const logoHtml = team.crest ? `${team.name} crest` : '⚽'; item.innerHTML = `
    ${team.name}
    `; item.addEventListener('click', (e) => { // e.preventDefault(); showTeamDetails(team, true); }); teamsList.appendChild(item); }); } }) .catch(error => { console.error('Error loading teams for navigation:', error); teamsList.innerHTML = '
  • Error loading teams
  • '; }); showSection('teams-section'); updateBackButton(); // Initialize any additional features for league view setTimeout(() => { }, 100); } } } } function restoreNavigationStateForTeam(continentSlug, countrySlug, leagueSlug, teamSlug) { // For team pages, we restore the league state and then highlight the specific team restoreNavigationStateForLeague(continentSlug, countrySlug, leagueSlug); // Set currentTeam from server-rendered data if available if (serverRendering.teamData) { currentTeam = serverRendering.teamData; } // Note: updateBackButton() will be called by restoreNavigationStateForLeague } async function setupSidebarForLeague(continent, country, league) { // Efficiently set up sidebar without content flashing for league pages // This replicates what navigateToCountries/navigateToLeagues do // but only for the sidebar, without affecting the main content area // Step 1: Set up countries in sidebar (like navigateToCountries) const countries = Object.keys(hierarchyData[continent] || {}); countriesList.innerHTML = ''; countries.forEach(countryName => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = countryName.slug || slugifySync(countryName); const countryDisplayName = countryName.replace(/-/g, ' '); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${countryDisplayName} `; item.addEventListener('click', (e) => { // Allow natural navigation - removed preventDefault() // The href attribute will handle navigation }); countriesList.appendChild(item); }); // Step 2: Set up leagues in sidebar (like navigateToLeagues) const leagues = hierarchyData[continent]?.[country] || []; leaguesList.innerHTML = ''; leagues.forEach(leagueData => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = country.slug || slugifySync(country); const leagueSlugForLink = leagueData.slug || slugifySync(leagueData.name); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${leagueData.name} (${leagueData.team_count} teams) `; item.addEventListener('click', (e) => { e.preventDefault(); navigateToTeams(continent, country, leagueData); }); leaguesList.appendChild(item); }); // Show the leagues section in sidebar showSection('leagues-section'); } async function setupSidebarForTeam(continent, country, league, teamsData) { // Efficiently set up sidebar without content flashing or extra API calls // This replicates what navigateToCountries/navigateToLeagues/navigateToTeams do // but only for the sidebar, without affecting the main content area // Step 1: Set up countries in sidebar (like navigateToCountries) const countries = Object.keys(hierarchyData[continent] || {}); countriesList.innerHTML = ''; const allCountriesItem = document.createElement('li'); allCountriesItem.className = 'nav-item'; allCountriesItem.innerHTML = `Sort by ${currentSortMode === 'relevance' ? 'Name' : 'Relevance'}`; allCountriesItem.addEventListener('click', (e) => { e.preventDefault(); toggleSortMode(); }); countriesList.appendChild(allCountriesItem); countries.forEach(countryName => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = countryName.slug || slugifySync(countryName); const countryDisplayName = countryName.replace(/-/g, ' '); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${countryDisplayName} `; item.addEventListener('click', (e) => { e.preventDefault(); navigateToLeagues(continent, countryName); }); countriesList.appendChild(item); }); // Step 2: Set up leagues in sidebar (like navigateToLeagues) const leagues = hierarchyData[continent]?.[country] || []; leaguesList.innerHTML = ''; leagues.forEach(leagueData => { const continentSlugForLink = continent.slug || slugifySync(continent); const countrySlugForLink = country.slug || slugifySync(country); const leagueSlugForLink = leagueData.slug || slugifySync(leagueData.name); const item = document.createElement('li'); item.className = 'nav-item'; item.innerHTML = ` ${leagueData.name} (${leagueData.team_count} teams) `; item.addEventListener('click', (e) => { e.preventDefault(); navigateToTeams(continent, country, leagueData); }); leaguesList.appendChild(item); }); // Step 3: Set up teams in sidebar using the teams data we already have teamsList.innerHTML = ''; if (teamsData && teamsData.length > 0) { teamsData.forEach(team => { const item = document.createElement('li'); item.className = 'nav-item'; const logoHtml = team.crest ? `${team.name} crest` : '⚽'; // Generate 4-segment URL: continent/country/league/team const continentSlug = continent.slug || slugifySync(continent); const countrySlug = country.slug || slugifySync(country); const leagueSlug = league.slug || slugifySync(league.name); const teamSlug = team.slug; if (!teamSlug) { throw new Error(`Database slug missing for team: ${team.name}`); } item.innerHTML = `
    ${team.name}
    `; teamsList.appendChild(item); }); } // Show the teams section in sidebar showSection('teams-section'); } function showError(title, message) { if (heroSection) { heroSection.style.display = 'none'; } dynamicContent.innerHTML = `
    ${title}
    ${message}
    `; } async function loadLeagueFromUrl(continentSlug, countrySlug, leagueSlug) { // Decode URL-encoded characters const decodedContinentSlug = decodeURIComponent(continentSlug); const decodedCountrySlug = decodeURIComponent(countrySlug); const decodedLeagueSlug = decodeURIComponent(leagueSlug); try { // Step 1: Find the continent in hierarchyData const continent = Object.keys(hierarchyData).find(c => slugifySync(c).toLowerCase() === decodedContinentSlug.toLowerCase() ); if (!continent) { showError('Continent Not Found', `Could not find continent "${decodedContinentSlug}"`); return; } // Step 2: Find the country in this continent const country = Object.keys(hierarchyData[continent] || {}).find(c => slugifySync(c).toLowerCase() === decodedCountrySlug.toLowerCase() ); if (!country) { showError('Country Not Found', `Could not find country "${decodedCountrySlug}" in ${continent}`); return; } // Step 3: Find the league in this country const leagues = (hierarchyData[continent][country] || []).slice().sort((a, b) => { return a.id - b.id; }); const league = leagues.find(l => slugifySync(l.name) === decodedLeagueSlug ); if (!league) { showError('League Not Found', `Could not find league "${decodedLeagueSlug}" in ${country}, ${continent}`); return; } // Success! Set up navigation state efficiently and load league directly currentPath = [continent, country, league]; // Set up sidebar navigation state without content flashing await setupSidebarForLeague(continent, country, league); // Load teams content directly via navigateToTeams (this shows the final content) navigateToTeams(continent, country, league, false); } catch (error) { console.error('Error loading league from URL:', error); showError('Error Loading League', 'There was an error loading the league page.'); } } async function loadCountryFromUrl(continentSlug, countrySlug) { // Decode URL-encoded characters const decodedContinentSlug = decodeURIComponent(continentSlug); const decodedCountrySlug = decodeURIComponent(countrySlug); const continent = Object.keys(hierarchyData).find(c => slugifySync(c).toLowerCase() === decodedContinentSlug.toLowerCase() ); if (continent) { const country = Object.keys(hierarchyData[continent] || {}).find(c => slugifySync(c).toLowerCase() === decodedCountrySlug.toLowerCase() ); if (country) { // First navigate to countries (this expands the continent in sidebar) navigateToCountries(continent, false); // Then navigate to leagues for that country setTimeout(() => { navigateToLeagues(continent, country, false); }, 50); } else { showError('Country Not Found', `Could not find country "${decodedCountrySlug}" in ${continent}`); } } else { showError('Continent Not Found', `Could not find continent "${decodedContinentSlug}"`); } } function loadContinentFromUrl(continentSlug) { // Decode URL-encoded characters const decodedContinentSlug = decodeURIComponent(continentSlug); const continent = Object.keys(hierarchyData).find(c => slugifySync(c).toLowerCase() === decodedContinentSlug.toLowerCase() ); if (continent) { navigateToCountries(continent, false); } else { showError('Continent Not Found', `Could not find continent "${decodedContinentSlug}"`); } } async function navigateToTeamPage(continentSlug, countrySlug, teamId, teamName) { // Decode URL segments const decodedContinentSlug = decodeURIComponent(continentSlug); const decodedCountrySlug = decodeURIComponent(countrySlug); // First navigate through the hierarchy const continent = Object.keys(hierarchyData).find(c => slugifySync(c) === decodedContinentSlug ); if (continent) { currentPath = [continent]; const country = Object.keys(hierarchyData[continent] || {}).find(c => slugifySync(c) === decodedCountrySlug ); if (country) { currentPath.push(country); } } // Load the team details await loadTeamDetails(teamId, teamName, false); // false = don't update history since URL is already set updateBackButton(); } async function navigateToTeamsGrid(continentSlug, countrySlug, leagueId, leagueName) { const continent = Object.keys(hierarchyData).find(c => slugifySync(c) === continentSlug ); // Create league slug for URL generation const leagueSlug = slugifySync(leagueName); if (continent) { currentPath = [continent]; const country = Object.keys(hierarchyData[continent] || {}).find(c => slugifySync(c) === countrySlug ); if (country) { currentPath.push(country); currentPath.push({ id: leagueId, name: leagueName }); } } // Load teams for this league try { if (heroSection) { heroSection.style.display = 'none'; } dynamicContent.innerHTML = `
    Loading teams...
    Please wait while we fetch the teams.
    `; const response = await fetch(`/api/teams.php?league_id=${encodeURIComponent(leagueId)}`); const data = await response.json(); if (data.error) { throw new Error(data.error); } // Generate teams grid HTML const teamsHTML = data.teams.map(team => `

    ${team.name}

    `).join(''); // Generate complete teams grid const teamsGridHTML = `

    ${leagueName}

    ${data.teams.length} teams

    📅 ${serverRendering.isLoggedIn ? `Sync All ${escapeHtml(leagueName)} Fixtures to Your Calendar` : `Sign in to sync all ${escapeHtml(leagueName)} fixtures to your calendar`}
    Loading calendars...
    ${teamsHTML}
    `; dynamicContent.innerHTML = teamsGridHTML; updatePageTitle(); updateBackButton(); } catch (error) { console.error('Error loading teams grid:', error); dynamicContent.innerHTML = `
    Error Loading Teams
    Could not load teams. Please try again.
    `; } } // Function to load standings for containers already on the page function loadInitialStandings() { // Find all standings containers on the page const standingsContainers = document.querySelectorAll('[id^="standings-container-"]'); standingsContainers.forEach(container => { const containerId = container.id; const leagueId = containerId.replace('standings-container-', ''); if (leagueId && container.querySelector('.standings-loading')) { let standingsUrl = `/ajax/standings.php?league=${leagueId}&theme=dark&form=true`; // Add team parameter if we're on a team page to highlight the team's row if (currentTeam && currentTeam.id) { standingsUrl += `&team=${currentTeam.id}`; console.log('Adding team parameter to standings URL (loadInitialStandings):', standingsUrl, 'currentTeam:', currentTeam); } else { console.log('No currentTeam or currentTeam.id found in loadInitialStandings. currentTeam:', currentTeam); } const fetchOptions = {}; if (window.ajaxToken) { fetchOptions.headers = { 'X-AJAX-TOKEN': window.ajaxToken }; } fetch(standingsUrl, fetchOptions) .then(response => response.text()) .then(html => { container.innerHTML = html; }) .catch(error => { console.error(`Error loading standings for league ${leagueId}:`, error); container.innerHTML = '

    Unable to load standings

    '; }); } }); } // Function to switch between standings tabs function switchStandingsTab(event, tabId) { event.preventDefault(); // Remove active class from all tab buttons const tabButtons = document.querySelectorAll('.standings-tab-btn'); tabButtons.forEach(btn => btn.classList.remove('active')); // Hide all tab content const tabContents = document.querySelectorAll('.standings-tab-content'); tabContents.forEach(content => content.classList.remove('active')); // Activate clicked tab button event.target.classList.add('active'); // Show corresponding tab content const activeTab = document.getElementById(tabId); if (activeTab) { activeTab.classList.add('active'); // Load standings for this tab if not already loaded const standingsContainer = activeTab.querySelector('[id^="standings-container-"]'); if (standingsContainer && standingsContainer.querySelector('.standings-loading')) { const containerId = standingsContainer.id; const leagueId = containerId.replace('standings-container-', ''); if (leagueId) { let standingsUrl = `/public/ajax/standings.php?league=${leagueId}&theme=dark&form=true`; // Add team parameter if we're on a team page to highlight the team's row if (currentTeam && currentTeam.id) { standingsUrl += `&team=${currentTeam.id}`; } fetch(standingsUrl, { headers: { 'X-AJAX-TOKEN': window.ajaxToken || '' } }) .then(response => response.text()) .then(html => { standingsContainer.innerHTML = html; }) .catch(error => { console.error(`Error loading standings for league ${leagueId}:`, error); standingsContainer.innerHTML = '

    Unable to load standings

    '; }); } } } } // Handle browser back/forward buttons window.addEventListener('popstate', function(event) { if (event.state) { // Restore state from history currentPath = event.state.path || []; currentTeam = event.state.team || null; if (currentTeam) { loadTeamDetails(currentTeam.id, currentTeam.name, false); } else { // Navigate based on current path const pathLength = currentPath.length; if (pathLength === 0) { showHomepage(); } else if (pathLength === 1) { navigateToCountries(currentPath[0], false); } else if (pathLength === 2) { navigateToLeagues(currentPath[0], currentPath[1], false); } else if (pathLength === 3) { const league = currentPath[2]; if (typeof league === 'object') { navigateToTeamsGrid('', '', league.id, league.name); } } } updatePageTitle(); updateBackButton(); } else { // No state, try to initialize from URL initializeFromUrl(); } }); function showHomepage() { currentPath = []; currentTeam = null; if (heroSection) { heroSection.style.display = 'flex'; } dynamicContent.innerHTML = ''; // Reset navigation continentsSection.style.display = 'block'; countriesSection.style.display = 'none'; leaguesSection.style.display = 'none'; teamsSection.style.display = 'none'; searchResultsSection.style.display = 'none'; } // Initialize updateBackButton(); // Initialize routing from URL after DOM is ready document.addEventListener('DOMContentLoaded', function() { initializeFromUrl(); initializeCalendarControl(); initializeUserProfileDropdown(); // Initialize timezone conversion tooltips // Initialize accordion functionality for any fixtures already on page initializeLeagueAccordions(); // Initialize fixtures filtering functionality const fixturesModule = document.getElementById('fixtures-module'); if (fixturesModule) { initializeFixturesFilters(); // Initialize lazy fixtures loading for skeleton system if (typeof initializeLazyFixtures === 'function') { initializeLazyFixtures(); } } // Load today's fixtures on homepage if (serverRendering.isHomepage) { loadTodaysFixtures(); } // Handle browser back/forward navigation for fixtures filters window.addEventListener('popstate', function(event) { if (serverRendering.isHomepage) { // Re-initialize filters from new URL const fixturesModule = document.getElementById('fixtures-module'); if (fixturesModule) { initializeFixturesFilters(); } } }); // Handle sync intents after login if (serverRendering.isLoggedIn) { handleSyncIntentAfterLogin(); } // Auto-trigger sync if user arrived from standings calendar icon const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('sync') === 'true' && serverRendering.currentView === 'team') { // Wait a moment for the page to fully load, then trigger sync setTimeout(function() { const teamAllSyncButton = document.querySelector('.team-all-sync-badge'); if (teamAllSyncButton) { // Auto-click the sync all button teamAllSyncButton.click(); // Remove the sync parameter from URL without reloading const newUrl = window.location.pathname + window.location.hash; window.history.replaceState({}, '', newUrl); } else { console.log('Team sync button not found on page'); } }, 1000); } // Load standings for any standings containers already on the page loadInitialStandings(); // Add event listeners for guided tour buttons const guidedTourButtons = document.querySelectorAll('button[onclick*="openGuidedTour"]'); guidedTourButtons.forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); openGuidedTour(); }); }); // Add event listeners for continent navigation in sidebar const continentLinks = document.querySelectorAll('#continents-section .nav-link'); continentLinks.forEach(link => { link.addEventListener('click', function(e) { // Allow natural navigation to continent pages // No preventDefault() - let the server-side links work // The href="/continent-slug" will handle navigation }); }); // Fallback: If content area is empty after initialization, load homepage setTimeout(function() { const dynamicContent = document.getElementById('dynamic-content'); const heroSection = document.querySelector('.hero'); // Only trigger fallback if we explicitly don't have server-rendered content // and the content area is actually empty const shouldHaveContent = serverRendering.hasPrerenderedContent; // For server-rendered content, trust that it's there - don't check visibility if (shouldHaveContent) { // If we have server-rendered content, don't trigger fallback return; } // Only check for fallback if we don't have server-rendered content if (dynamicContent && (!dynamicContent.innerHTML.trim() || dynamicContent.innerHTML.trim() === '') && heroSection && heroSection.style.display === 'none') { console.log('Content area is empty, loading homepage as fallback'); loadHomepage(); } }, 200); // Increased timeout slightly to allow for rendering }); // Also initialize immediately in case DOMContentLoaded already fired if (document.readyState === 'loading') { // Still loading, wait for DOMContentLoaded } else { // Already loaded, initialize immediately initializeFromUrl(); initializeCalendarControl(); // Add event listeners for guided tour buttons const guidedTourButtons = document.querySelectorAll('button[onclick*="openGuidedTour"]'); guidedTourButtons.forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); openGuidedTour(); }); }); // Add event listeners for continent navigation in sidebar const continentLinks = document.querySelectorAll('#continents-section .nav-link'); continentLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const continentItem = this.closest('[data-continent]'); if (continentItem) { const continent = continentItem.getAttribute('data-continent'); navigateToCountries(continent); } }); }); // Fallback for immediate loading case too setTimeout(function() { const dynamicContent = document.getElementById('dynamic-content'); const heroSection = document.querySelector('.hero'); // Only trigger fallback if we don't have server-rendered content const shouldHaveContent = serverRendering.hasPrerenderedContent; // For server-rendered content, trust that it's there - don't check visibility if (shouldHaveContent) { return; } // Check if both dynamic content is empty and hero is hidden if (dynamicContent && (!dynamicContent.innerHTML.trim() || dynamicContent.innerHTML.trim() === '') && heroSection && heroSection.style.display === 'none') { console.log('Content area is empty, loading homepage as fallback'); loadHomepage(); } }, 100); } // Calendar Management System let userCalendars = []; let currentModal = null; function initializeCalendarControl() { const calendarButton = document.getElementById('calendar-button'); const calendarDropdown = document.getElementById('calendar-dropdown'); const addCalendarBtn = document.getElementById('add-calendar-btn'); if (!calendarButton) return; // Not logged in // Load user calendars loadUserCalendars(); // Calendar button click calendarButton.addEventListener('click', function(e) { e.stopPropagation(); calendarDropdown.classList.toggle('show'); }); // Add calendar button addCalendarBtn.addEventListener('click', function() { showAddCalendarModal(); }); // Close dropdown when clicking outside document.addEventListener('click', function(e) { if (!calendarButton.contains(e.target) && !calendarDropdown.contains(e.target)) { calendarDropdown.classList.remove('show'); } }); } async function loadUserCalendars() { try { const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=list', { headers }); const data = await response.json(); if (response.ok) { userCalendars = data.calendars; updateCalendarUI(); } else { console.error('Failed to load calendars:', data.error); } } catch (error) { console.error('Error loading calendars:', error); } } function updateCalendarUI() { const calendarList = document.getElementById('calendar-list'); const calendarButtonText = document.getElementById('calendar-button-text'); if (userCalendars.length === 0) { calendarList.innerHTML = '

    No calendars yet

    Create your first calendar to get started with syncing your football matches!

    '; calendarButtonText.textContent = 'Add Calendar'; } else { calendarButtonText.textContent = `My Calendars (${userCalendars.length})`; // Add user header if logged in and user email is available let userHeaderHTML = ''; if (serverRendering.isLoggedIn && serverRendering.userEmail) { userHeaderHTML = `
    Logged in as:
    ${escapeHtml(serverRendering.userEmail)}
    `; } calendarList.innerHTML = userHeaderHTML + userCalendars.map(calendar => `
    ${escapeHtml(calendar.name)}
    ${calendar.event_count} events ${calendar.subscription_count} subscriptions
    `).join(''); } } function showAddCalendarModal() { const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); currentModal = document.getElementById('calendar-modal'); document.getElementById('calendar-name').focus(); } async function createCalendar() { const name = document.getElementById('calendar-name').value.trim(); if (!name) { alert('Please enter a calendar name'); return; } try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=create', { method: 'POST', headers, body: JSON.stringify({ name }) }); const data = await response.json(); if (response.ok) { closeModal(); loadUserCalendars(); // Refresh the list showNotification('Calendar created successfully!', 'success'); } else { alert('Error: ' + data.error); } } catch (error) { console.error('Error creating calendar:', error); alert('Failed to create calendar. Please try again.'); } } function editCalendar(calendarId) { const calendar = userCalendars.find(c => c.id == calendarId); if (!calendar) return; const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); currentModal = document.getElementById('calendar-modal'); // Load subscriptions for this calendar loadCalendarSubscriptions(calendarId); } async function loadCalendarSubscriptions(calendarId) { try { const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch(`/api/calendar-management.php?action=subscriptions&calendar_id=${calendarId}`, { headers }); const data = await response.json(); const subscriptionsList = document.getElementById('subscriptions-list'); const subscriptionCount = document.getElementById('subscription-count'); if (!subscriptionsList) return; if (response.ok && data.subscriptions && data.subscriptions.length > 0) { // Update count if (subscriptionCount) { subscriptionCount.textContent = `${data.subscriptions.length} subscription${data.subscriptions.length !== 1 ? 's' : ''}`; } const subscriptionsHTML = data.subscriptions.map(sub => { const displayName = sub.display_name || sub.subscription_name || 'Unknown'; const typeIcon = sub.subscription_type === 'team' ? '👤' : '🏆'; // Create a more detailed display with team and league info let detailText = ''; let metaText = ''; if (sub.subscription_type === 'team') { const teamName = sub.team_name || displayName; const leagueName = sub.league_name || 'All Leagues'; detailText = teamName; metaText = leagueName; } else if (sub.subscription_type === 'league') { detailText = displayName; metaText = 'All Teams'; } else { detailText = displayName; metaText = sub.subscription_type; } return `
    ${typeIcon} ${escapeHtml(detailText)}
    ${escapeHtml(metaText)}
    `; }).join(''); subscriptionsList.innerHTML = subscriptionsHTML; } else { // Update count if (subscriptionCount) { subscriptionCount.textContent = '0 subscriptions'; } subscriptionsList.innerHTML = '
    No subscriptions found
    '; } } catch (error) { console.error('Error loading subscriptions:', error); const subscriptionCount = document.getElementById('subscription-count'); if (subscriptionCount) { subscriptionCount.textContent = 'Error loading'; } if (subscriptionsList) { subscriptionsList.innerHTML = '
    Error loading subscriptions
    '; } } } async function removeSubscription(subscriptionId, calendarId) { try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch(`/api/calendar-management.php?action=remove_subscription&calendar_id=${calendarId}&subscription_id=${subscriptionId}`, { method: 'POST', headers }); const data = await response.json(); if (response.ok) { showNotification('Subscription removed successfully', 'success'); // Reload subscriptions to reflect the change loadCalendarSubscriptions(calendarId); } else { showNotification('Error: ' + data.error, 'error'); } } catch (error) { console.error('Error removing subscription:', error); showNotification('Failed to remove subscription. Please try again.', 'error'); } } async function removeSyncSubscription(subscriptionId, calendarId, calendarName, dropdownSelector, reloadFunction, ...reloadArgs) { try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch(`/api/calendar-management.php?action=remove_subscription&calendar_id=${calendarId}&subscription_id=${subscriptionId}`, { method: 'POST', headers }); const data = await response.json(); if (response.ok) { showCalendarDropdownNotification(`Removed from ${calendarName}`, 'success'); // For team-all dropdowns, hide the dropdown completely so it regenerates fresh on next interaction const dropdown = document.querySelector(dropdownSelector); if (dropdown && dropdownSelector.includes('team-all')) { dropdown.classList.remove('show'); dropdown.innerHTML = '
    Loading calendars...
    '; } else if (dropdown && reloadFunction) { // For regular dropdowns, reload normally await reloadFunction(dropdown, ...reloadArgs); } // Refresh the calendar modal to show updated subscription counts loadUserCalendars(); } else { showNotification('Error: ' + data.error, 'error'); } } catch (error) { console.error('Error removing sync subscription:', error); showNotification('Failed to remove subscription. Please try again.', 'error'); } } async function previewCalendar(uuid) { const calendarUrl = `https://www.followteams.com/calendars/${uuid}.ics`; const previewHTML = ` `; document.body.insertAdjacentHTML('beforeend', previewHTML); // Load and parse the ICS file await loadCalendarPreview(calendarUrl); } async function loadCalendarPreview(icsUrl) { try { const response = await fetch(icsUrl); const icsText = await response.text(); if (!response.ok) { throw new Error('Failed to load calendar file'); } const events = parseICSEvents(icsText); renderCalendarPreview(events); } catch (error) { console.error('Error loading calendar preview:', error); document.getElementById('calendar-container').innerHTML = `
    ⚠️
    Failed to load calendar
    Error: ${error.message}
    `; } } function parseICSEvents(icsText) { const events = []; const lines = icsText.split('\n'); let currentEvent = null; for (let line of lines) { line = line.trim(); if (line === 'BEGIN:VEVENT') { currentEvent = {}; } else if (line === 'END:VEVENT' && currentEvent) { if (currentEvent.dtstart && currentEvent.summary) { events.push(currentEvent); } currentEvent = null; } else if (currentEvent && line.includes(':')) { const [key, ...valueParts] = line.split(':'); const value = valueParts.join(':'); if (key === 'SUMMARY') { currentEvent.summary = value; } else if (key.startsWith('DTSTART')) { currentEvent.dtstart = parseICSDate(value); } else if (key.startsWith('DTEND')) { currentEvent.dtend = parseICSDate(value); } else if (key === 'DESCRIPTION') { currentEvent.description = value; } else if (key === 'LOCATION') { currentEvent.location = value; } } } return events.sort((a, b) => new Date(a.dtstart) - new Date(b.dtstart)); } function parseICSDate(dateStr) { // Handle both YYYYMMDDTHHMMSS and YYYYMMDD formats if (dateStr.length === 8) { // YYYYMMDD format const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); return `${year}-${month}-${day}T00:00:00`; } else if (dateStr.length >= 15) { // YYYYMMDDTHHMMSS format const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); const hour = dateStr.substring(9, 11); const minute = dateStr.substring(11, 13); const second = dateStr.substring(13, 15); return `${year}-${month}-${day}T${hour}:${minute}:${second}`; } return dateStr; } function renderCalendarPreview(events) { const container = document.getElementById('calendar-container'); const today = new Date(); // Detect user's timezone const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; if (events.length === 0) { container.innerHTML = `
    📅
    No events found in calendar
    Add some team or league subscriptions to see events here
    `; return; } // Filter upcoming events (next 30 days) const thirtyDaysFromNow = new Date(); thirtyDaysFromNow.setDate(today.getDate() + 30); const upcomingEvents = events.filter(event => { const eventDate = new Date(event.dtstart); return eventDate >= today && eventDate <= thirtyDaysFromNow; }); if (upcomingEvents.length === 0) { container.innerHTML = `
    📅
    No upcoming events in the next 30 days
    Total events in calendar: ${events.length}
    `; return; } const eventsHTML = upcomingEvents.map(event => { const eventDate = new Date(event.dtstart); const isToday = eventDate.toDateString() === today.toDateString(); const dayClass = isToday ? 'today' : ''; return `
    ${eventDate.getDate()}
    ${eventDate.toLocaleDateString('en-US', { month: 'short' })}
    ${eventDate.toLocaleDateString('en-US', { weekday: 'short' })}
    ${escapeHtml(event.summary)}
    🕒 ${eventDate.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true, timeZone: userTimezone })} ${isToday ? ' TODAY' : ''} (${userTimezone.split('/').pop().replace('_', ' ')})
    ${event.description ? `
    ${escapeHtml(event.description)}
    ` : ''} ${event.location ? `
    📍 ${escapeHtml(event.location)}
    ` : ''}
    `; }).join(''); container.innerHTML = `

    Upcoming Matches

    ${upcomingEvents.length} events in the next 30 days • Times shown in ${userTimezone.split('/').pop().replace('_', ' ')}

    ${eventsHTML}
    `; } function closePreviewModal() { const modal = document.getElementById('calendar-preview-modal'); if (modal) { modal.remove(); } } async function updateCalendar(calendarId) { const name = document.getElementById('edit-calendar-name').value.trim(); if (!name) { alert('Please enter a calendar name'); return; } try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=update', { method: 'PUT', headers, body: JSON.stringify({ calendar_id: calendarId, name }) }); const data = await response.json(); if (response.ok) { closeModal(); loadUserCalendars(); // Refresh the list showNotification('Calendar updated successfully!', 'success'); } else { alert('Error: ' + data.error); } } catch (error) { console.error('Error updating calendar:', error); alert('Failed to update calendar. Please try again.'); } } async function deleteCalendar(calendarId) { const calendar = userCalendars.find(c => c.id == calendarId); if (!calendar) return; if (!confirm(`Are you sure you want to delete "${calendar.name}"? This action cannot be undone.`)) { return; } try { const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch(`/api/calendar-management.php?action=delete&calendar_id=${calendarId}`, { method: 'DELETE', headers }); const data = await response.json(); if (response.ok) { loadUserCalendars(); // Refresh the list showNotification('Calendar deleted successfully!', 'success'); } else { alert('Error: ' + data.error); } } catch (error) { console.error('Error deleting calendar:', error); alert('Failed to delete calendar. Please try again.'); } } function copyCalendarURL(uuid) { const url = `https://www.followteams.com/calendars/${uuid}.ics`; navigator.clipboard.writeText(url).then(() => { showNotification('Calendar URL copied to clipboard!', 'success'); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = url; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showNotification('Calendar URL copied to clipboard!', 'success'); }); } // Calendar sync functions function syncToGoogle(uuid) { const icsUrl = `http://www.followteams.com/calendars/${uuid}.ics`; // Use the addbyurl format for better compatibility // This directly opens the "Add calendar by URL" dialog const encodedUrl = encodeURIComponent(icsUrl); const googleUrl = `https://calendar.google.com/calendar/u/0/r/settings/addbyurl?cid=${encodedUrl}`; window.open(googleUrl, '_blank'); showSyncSuccess('google', 'Opening Google Calendar to add your matches...'); } function syncToApple(uuid) { const icsUrl = `http://www.followteams.com/calendars/${uuid}.ics`; // For Apple Calendar (webcal protocol) const appleUrl = icsUrl.replace('http://', 'webcal://'); // Show detailed instructions modal for Apple since it can be tricky showAppleSyncModal(icsUrl, appleUrl); } function showAppleSyncModal(icsUrl, webcalUrl) { const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); } function closeAppleSyncModal() { const modal = document.getElementById('apple-sync-modal'); if (modal) { modal.remove(); } } function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { // Success handled by caller }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); }); } function syncToOutlook(uuid) { const icsUrl = `http://www.followteams.com/calendars/${uuid}.ics`; const encodedUrl = encodeURIComponent(icsUrl); // Outlook Web App URL for subscribing to calendars const outlookUrl = `https://outlook.live.com/calendar/0/addcalendar?url=${encodedUrl}`; window.open(outlookUrl, '_blank'); showNotification('Opening Outlook Calendar...', 'info'); } // Premium Modal Functions let currentSyncUuid = null; let currentSyncCalendarName = null; function openSyncModal(uuid, calendarName) { currentSyncUuid = uuid; currentSyncCalendarName = calendarName; const modal = document.getElementById('calendarSyncModal'); modal.classList.add('active'); // Update modal title with calendar name const header = modal.querySelector('.calendar-sync-header h3'); header.textContent = `Sync "${calendarName}"`; // No recommendations - let users choose freely } function closeSyncModal() { const modal = document.getElementById('calendarSyncModal'); modal.classList.remove('active'); currentSyncUuid = null; currentSyncCalendarName = null; } function syncModalOption(service) { if (!currentSyncUuid) return; // Save the UUID before closing the modal const uuidToSync = currentSyncUuid; const calendarNameToSync = currentSyncCalendarName; // Track sync analytics trackCalendarSync(service, calendarNameToSync); // Close modal closeSyncModal(); // Call appropriate sync function with saved UUID switch(service) { case 'google': syncToGoogle(uuidToSync); break; case 'apple': syncToApple(uuidToSync); break; case 'outlook': syncToOutlook(uuidToSync); break; case 'yahoo': syncToYahoo(uuidToSync); break; case 'copy-ics': copyIcsUrl(uuidToSync); break; case 'ics': downloadICS(uuidToSync); break; } } // Analytics tracking function trackCalendarSync(service, calendarName) { // Send analytics data fetch('/api/track-sync.php', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'calendar_sync', service: service, calendar: calendarName, timestamp: new Date().toISOString(), user_agent: navigator.userAgent }) }).catch(err => { // Silent fail for analytics console.log('Analytics tracking failed:', err); }); } // Additional sync functions function syncToYahoo(uuid) { const icsUrl = `http://www.followteams.com/calendars/${uuid}.ics`; const encodedUrl = encodeURIComponent(icsUrl); // Yahoo Calendar subscription URL const yahooUrl = `https://calendar.yahoo.com/?v=60&TITLE=${encodeURIComponent('Football Calendar')}&URL=${encodedUrl}`; window.open(yahooUrl, '_blank'); showSyncSuccess('yahoo', 'Opening Yahoo Calendar to add your matches...'); } function downloadICS(uuid) { const icsUrl = `http://www.followteams.com/calendars/${uuid}.ics`; // Create download link const link = document.createElement('a'); link.href = icsUrl; link.download = `${currentSyncCalendarName || 'calendar'}.ics`; document.body.appendChild(link); link.click(); document.body.removeChild(link); showSyncSuccess('ics', 'Calendar file downloaded! Now import it to your calendar app.'); } function copyIcsUrl(uuid) { const icsUrl = `http://www.followteams.com/calendars/${uuid}.ics`; // Copy to clipboard navigator.clipboard.writeText(icsUrl).then(() => { showSyncSuccess('copy-ics', 'ICS URL copied! Paste this into your calendar app to receive automatic updates.'); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = icsUrl; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showSyncSuccess('copy-ics', 'ICS URL copied! Paste this into your calendar app to receive automatic updates.'); }); } function showSyncSuccess(service, message) { // Enhanced success notification with next steps const serviceNames = { 'google': 'Google Calendar', 'apple': 'Apple Calendar', 'outlook': 'Outlook Calendar', 'yahoo': 'Yahoo Calendar', 'copy-ics': 'ICS URL', 'ics': 'Calendar File' }; const serviceName = serviceNames[service] || 'Calendar'; const fullMessage = `✅ ${serviceName}: ${message}`; showNotification(fullMessage, 'success'); // Show follow-up tip after 3 seconds (except for copy-ics which already explains updates) if (service !== 'copy-ics') { setTimeout(() => { showNotification('💡 Tip: Your calendar will auto-update when fixtures change!', 'info'); }, 3000); } } // Close modal when clicking outside document.addEventListener('click', function(event) { const modal = document.getElementById('calendarSyncModal'); if (event.target === modal) { closeSyncModal(); } }); function copyCalendarUrl() { const urlInput = document.getElementById('calendar-url-input'); if (!urlInput) return; const url = urlInput.value; navigator.clipboard.writeText(url).then(() => { showNotification('Calendar URL copied to clipboard!', 'success'); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = url; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showNotification('Calendar URL copied to clipboard!', 'success'); }); } function closeModal() { if (currentModal) { currentModal.remove(); currentModal = null; } } function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${type === 'success' ? '#10b981' : '#3b82f6'}; color: white; padding: 12px 20px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 3000; font-size: 14px; max-width: 400px; `; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 4000); } function showCalendarDropdownNotification(message, type = 'success') { const calendarDropdown = document.getElementById('calendar-dropdown'); const calendarList = document.getElementById('calendar-list'); if (!calendarDropdown || !calendarList) return; // Open the dropdown calendarDropdown.classList.add('show'); // Remove any existing notifications const existingNotification = calendarDropdown.querySelector('.dropdown-notification'); if (existingNotification) { existingNotification.remove(); } // Create notification element const notification = document.createElement('div'); notification.className = 'dropdown-notification'; notification.style.cssText = ` background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'}; color: white; padding: 12px 16px; border-radius: 6px; margin-bottom: 12px; font-size: 14px; font-weight: 500; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); animation: slideInFromTop 0.3s ease-out; `; notification.textContent = message; // Insert at the top of the dropdown, before the calendar list calendarDropdown.insertBefore(notification, calendarList); // Auto-remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.style.animation = 'slideOutToTop 0.3s ease-in'; setTimeout(() => { notification.remove(); }, 300); } }, 5000); } function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text ? text.replace(/[&<>"']/g, function(m) { return map[m]; }) : ''; } function escapeJs(text) { if (!text) return ''; return text.toString() .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t'); } // League sync functionality let syncDropdownTimeout = null; // Function to toggle sync more options accordion function toggleSyncMoreOptions(teamId) { const container = document.getElementById(`league-sync-options-${teamId}`); const chevron = document.getElementById(`sync-chevron-${teamId}`); if (!container || !chevron) return; const isExpanded = container.classList.contains('expanded'); if (isExpanded) { container.classList.remove('expanded'); chevron.classList.remove('rotated'); } else { container.classList.add('expanded'); chevron.classList.add('rotated'); } } function showSyncDropdown(event, leagueId, leagueName, teamId, isClick = false) { clearTimeout(syncDropdownTimeout); // Prevent default action if this is a click event if (event && isClick) { event.preventDefault(); event.stopPropagation(); } // Check if user is logged in first if (!serverRendering.isLoggedIn) { // If it's a click, handle the redirect if (isClick) { const currentUrl = window.location.href; const syncIntent = { action: 'sync_team_league', team_id: teamId, league_id: leagueId, league_name: leagueName }; sessionStorage.setItem('syncIntent', JSON.stringify(syncIntent)); window.location.href = '/login.php?redirect=' + encodeURIComponent(currentUrl); } // Don't show dropdown for not-logged-in users (both click and hover) return; } // Hide all other sync dropdowns first const allDropdowns = document.querySelectorAll('.league-sync-dropdown.show'); allDropdowns.forEach(dropdown => dropdown.classList.remove('show')); const dropdown = document.getElementById(`sync-dropdown-${leagueId}`); if (!dropdown) return; dropdown.classList.add('show'); // Only reload calendars on click or if dropdown is empty if (isClick || dropdown.querySelector('.sync-loading')) { dropdown.innerHTML = '
    Loading calendars...
    '; loadCalendarsForSync(dropdown, leagueId, leagueName, teamId, isClick); } } function showTeamAllSyncDropdown(event, teamId, teamName, isClick = false, suffix = '') { clearTimeout(syncDropdownTimeout); // Prevent default action if this is a click event if (event && isClick) { event.preventDefault(); event.stopPropagation(); } // Check if user is logged in first if (!serverRendering.isLoggedIn) { // Store sync intent and redirect to login only on clicks if (isClick) { const currentUrl = window.location.href; const syncIntent = { action: 'sync_team', team_id: teamId, team_name: teamName }; sessionStorage.setItem('syncIntent', JSON.stringify(syncIntent)); window.location.href = '/login.php?redirect=' + encodeURIComponent(currentUrl); } return; } // Hide all other sync dropdowns first const allDropdowns = document.querySelectorAll('.league-sync-dropdown.show'); allDropdowns.forEach(dropdown => dropdown.classList.remove('show')); // Build dropdown ID with optional suffix const dropdownId = suffix ? `sync-dropdown-team-all-${suffix}-${teamId}` : `sync-dropdown-team-all-${teamId}`; const dropdown = document.getElementById(dropdownId); if (!dropdown) { console.error('Team-all sync dropdown not found:', dropdownId); return; } dropdown.classList.add('show'); // Only reload calendars on click or if dropdown is empty if (isClick || dropdown.querySelector('.sync-loading')) { dropdown.innerHTML = '
    Loading calendars...
    '; loadCalendarsForTeamAllSync(dropdown, teamId, teamName, isClick); } } async function showLeagueAllSyncDropdown(event, leagueId, leagueName, isClick = false) { clearTimeout(syncDropdownTimeout); // Check if user is logged in first if (!serverRendering.isLoggedIn) { // Only redirect to login on click, not hover if (isClick) { const currentUrl = window.location.href; const syncIntent = { action: 'sync_league', league_id: leagueId, league_name: leagueName }; sessionStorage.setItem('syncIntent', JSON.stringify(syncIntent)); window.location.href = '/login.php?redirect=' + encodeURIComponent(currentUrl); } // Don't show dropdown for not-logged-in users (both click and hover) return; } // Check user's calendars to determine behavior only on click if (isClick) { try { const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=list', { headers }); const data = await response.json(); if (response.ok && data.calendars) { if (data.calendars.length === 0) { // No calendars - create one automatically await createCalendarForLeague(leagueId, leagueName); return; } else if (data.calendars.length === 1) { // Only one calendar - sync directly to it const calendar = data.calendars[0]; await syncLeagueAllToCalendar(leagueId, calendar.id, calendar.name, leagueName); return; } // Multiple calendars - fall through to show dropdown for selection } } catch (error) { console.error('Error checking calendars:', error); // Fall through to show dropdown on error } } // Hide all other sync dropdowns first const allDropdowns = document.querySelectorAll('.league-sync-dropdown.show'); allDropdowns.forEach(dropdown => dropdown.classList.remove('show')); const dropdown = document.getElementById(`sync-dropdown-league-all-${leagueId}`); if (!dropdown) { console.error('League-all sync dropdown not found:', `sync-dropdown-league-all-${leagueId}`); return; } dropdown.classList.add('show'); // Load calendars if not already loaded if (dropdown.querySelector('.sync-loading')) { loadCalendarsForLeagueAllSync(dropdown, leagueId, leagueName); } } function hideSyncDropdown(event) { syncDropdownTimeout = setTimeout(() => { const dropdowns = document.querySelectorAll('.league-sync-dropdown.show'); dropdowns.forEach(dropdown => { // Check if mouse is still over the dropdown or its parent const rect = dropdown.getBoundingClientRect(); // Find the parent container - it could be league-sync-container, team-all-sync-container, or league-all-sync-container const container = dropdown.closest('.league-sync-container, .team-all-sync-container, .league-all-sync-container'); if (!container) { // If no container found, just hide the dropdown dropdown.classList.remove('show'); return; } const containerRect = container.getBoundingClientRect(); if (!isMouseOverElement(event, rect) && !isMouseOverElement(event, containerRect)) { dropdown.classList.remove('show'); } }); }, 200); } function isMouseOverElement(event, rect) { if (!rect || !event) return false; return event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom; } async function loadCalendarsForSync(dropdown, leagueId, leagueName, teamId, isClick = false) { try { // Use check_subscriptions to get calendars with current subscription status const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch(`/api/calendar-management.php?action=check_subscriptions&team_id=${teamId}&league_id=${leagueId}`, { headers }); const data = await response.json(); if (response.ok && data.calendars && data.calendars.length > 0) { // If user has only one calendar and it's a click, sync directly without dropdown if (isClick && data.calendars.length === 1) { const calendar = data.calendars[0]; if (!calendar.has_team_league_subscription) { // Auto-sync to their only calendar syncTeamToCalendar(teamId, leagueId, calendar.id, calendar.name, leagueName); return; } } const calendarsHTML = data.calendars.map(calendar => { if (calendar.has_team_league_subscription) { return `
    ✅ Synced to ${escapeHtml(calendar.name)}
    `; } else { return `
    📅 Sync to ${escapeHtml(calendar.name)}
    `; } }).join(''); dropdown.innerHTML = calendarsHTML; } else { if (!serverRendering.isLoggedIn) { // Store the sync intent and redirect to login if (isClick) { const currentUrl = window.location.href; const syncIntent = { action: 'sync_team_league', team_id: teamId, league_id: leagueId, league_name: leagueName }; sessionStorage.setItem('syncIntent', JSON.stringify(syncIntent)); window.location.href = '/login.php?redirect=' + encodeURIComponent(currentUrl); return; } else { // On hover, show hint to click dropdown.innerHTML = '
    Click to login and sync
    '; } } else { // User is logged in but has no calendars if (isClick) { // Only auto-create calendar on actual clicks, not on hover await createCalendarForTeamLeague(teamId, leagueId, leagueName); } else { // On hover, just show a message prompting to click dropdown.innerHTML = '
    Click to create calendar and sync
    '; } } } } catch (error) { console.error('Error loading calendars for sync:', error); dropdown.innerHTML = '
    Error loading calendars
    '; } } async function loadCalendarsForTeamAllSync(dropdown, teamId, teamName, isClick = false) { console.log('Loading calendars for team-all sync:', teamId, teamName, 'isClick:', isClick); try { // Use check_subscriptions to get calendars with current subscription status (no league_id for team-all) const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch(`/api/calendar-management.php?action=check_subscriptions&team_id=${teamId}`, { headers }); const data = await response.json(); if (response.ok && data.calendars && data.calendars.length > 0) { console.log('Calendars loaded:', data.calendars.length); // If user has only one calendar and it's a click, sync directly without dropdown if (isClick && data.calendars.length === 1) { const calendar = data.calendars[0]; if (!calendar.has_team_all_subscription) { // Auto-sync to their only calendar syncTeamAllToCalendar(teamId, calendar.id, calendar.name, teamName); return; } } const calendarsHTML = data.calendars.map(calendar => { if (calendar.has_team_all_subscription) { return `
    ✅ Synced to ${escapeHtml(calendar.name)}
    `; } else { return `
    📅 Sync to ${escapeHtml(calendar.name)}
    `; } }).join(''); // Add "Create New Calendar" option at the top when user has existing calendars const createNewCalendarHTML = `
    🆕 Create New Calendar for ${escapeHtml(teamName)}
    `; dropdown.innerHTML = createNewCalendarHTML + calendarsHTML; // Ensure dropdown stays visible after content is loaded if (isClick) { dropdown.classList.add('show'); console.log('Dropdown content set, should be visible:', dropdown.classList.contains('show')); } } else { console.log('No calendars found or error:', data); if (!serverRendering.isLoggedIn) { // Store the sync intent and redirect to login const currentUrl = window.location.href; const syncIntent = { action: 'sync_team', team_id: teamId, team_name: teamName }; sessionStorage.setItem('syncIntent', JSON.stringify(syncIntent)); window.location.href = '/login.php?redirect=' + encodeURIComponent(currentUrl); return; } else { // User is logged in but has no calendars if (isClick) { // Only auto-create calendar on actual clicks, not on hover await createCalendarForTeam(teamId, teamName); } else { // On hover, just show a message prompting to click dropdown.innerHTML = '
    Click to create calendar and sync
    '; } } } } catch (error) { console.error('Error loading calendars for team all sync:', error); dropdown.innerHTML = '
    Error loading calendars
    '; } } async function loadCalendarsForLeagueAllSync(dropdown, leagueId, leagueName) { console.log('Loading calendars for league-all sync:', leagueId, leagueName); try { const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=list', { headers }); const data = await response.json(); if (response.ok && data.calendars && data.calendars.length > 0) { console.log('Calendars loaded:', data.calendars.length); const calendarsHTML = data.calendars.map(calendar => `
    📅 Sync to ${escapeHtml(calendar.name)}
    ` ).join(''); dropdown.innerHTML = calendarsHTML; } else { console.log('No calendars found or error:', data); dropdown.innerHTML = '
    No calendars found. Create one
    '; } } catch (error) { console.error('Error loading calendars for league all sync:', error); dropdown.innerHTML = '
    Error loading calendars
    '; } } async function syncTeamToCalendar(teamId, leagueId, calendarId, calendarName, leagueName) { try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=add_team', { method: 'POST', headers, body: JSON.stringify({ calendar_id: calendarId, team_id: teamId, league_id: leagueId }) }); const data = await response.json(); if (response.ok) { // Show success notification in the calendar dropdown and auto-open it showCalendarDropdownNotification(`${leagueName} matches synced to ${calendarName}!`, 'success'); // Refresh the dropdown to show updated subscription status const dropdown = document.getElementById(`sync-dropdown-${leagueId}`); if (dropdown) { // Reload the dropdown content to show current status await loadCalendarsForSync(dropdown, leagueId, leagueName, teamId); } // Refresh the calendar modal to show updated subscription counts loadUserCalendars(); } else { showNotification('Error: ' + data.error, 'error'); } } catch (error) { console.error('Error syncing team to calendar:', error); showNotification('Failed to sync. Please try again.', 'error'); } } async function syncTeamAllToCalendar(teamId, calendarId, calendarName, teamName) { try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=add_team', { method: 'POST', headers, body: JSON.stringify({ calendar_id: calendarId, team_id: teamId // Note: league_id is intentionally omitted for team-all sync }) }); const data = await response.json(); if (response.ok) { // Show success notification in the calendar dropdown and auto-open it showCalendarDropdownNotification(`All ${teamName} fixtures synced to ${calendarName}!`, 'success'); // Refresh the dropdown to show updated subscription status const dropdown = document.getElementById(`sync-dropdown-team-all-${teamId}`); if (dropdown) { // Reload the dropdown content to show current status await loadCalendarsForTeamAllSync(dropdown, teamId, teamName, true); } // Refresh the calendar modal to show updated subscription counts loadUserCalendars(); } else { // Check if it's the specific "Calendar ID and Team ID required" error if (data.error && data.error.includes('Calendar ID and Team ID required')) { console.log('Suppressed expected sync error after removal:', data.error); } else { showNotification('Error: ' + data.error, 'error'); } } } catch (error) { console.error('Error syncing team-all to calendar:', error); showNotification('Failed to sync. Please try again.', 'error'); } } async function syncLeagueAllToCalendar(leagueId, calendarId, calendarName, leagueName) { try { const headers = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const response = await fetch('/api/calendar-management.php?action=add_league', { method: 'POST', headers, body: JSON.stringify({ calendar_id: calendarId, league_id: leagueId }) }); const data = await response.json(); if (response.ok) { // Show success notification in the calendar dropdown and auto-open it showCalendarDropdownNotification(`All ${leagueName} fixtures synced to ${calendarName}!`, 'success'); // Hide the dropdown const dropdown = document.getElementById(`sync-dropdown-league-all-${leagueId}`); if (dropdown) { dropdown.classList.remove('show'); } // Refresh the calendar modal to show updated subscription counts loadUserCalendars(); } else { showNotification('Error: ' + data.error, 'error'); } } catch (error) { console.error('Error syncing league-all to calendar:', error); showNotification('Failed to sync. Please try again.', 'error'); } } // Function to auto-create a calendar for a team and sync it async function createCalendarForTeam(teamId, teamName) { try { console.log('Auto-creating calendar for team:', teamName); // Create calendar with team name const createHeaders = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { createHeaders['X-AJAX-TOKEN'] = window.ajaxToken; } const createResponse = await fetch('/api/calendar-management.php?action=create', { method: 'POST', headers: createHeaders, body: JSON.stringify({ name: teamName }) }); const createData = await createResponse.json(); if (!createResponse.ok) { console.error('Failed to create calendar:', createData.error); showNotification('Failed to create calendar: ' + createData.error, 'error'); return; } const calendarId = createData.calendar.id; const calendarUuid = createData.calendar.uuid; // Add team to the new calendar const addTeamHeaders = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { addTeamHeaders['X-AJAX-TOKEN'] = window.ajaxToken; } const addTeamResponse = await fetch('/api/calendar-management.php?action=add_team', { method: 'POST', headers: addTeamHeaders, body: JSON.stringify({ calendar_id: calendarId, team_id: teamId }) }); const addTeamData = await addTeamResponse.json(); if (!addTeamResponse.ok) { console.error('Failed to add team to calendar:', addTeamData.error); // Check if it's the specific "Calendar ID and Team ID required" error if (addTeamData.error && addTeamData.error.includes('Calendar ID and Team ID required')) { console.log('Suppressed expected add team error:', addTeamData.error); } else { showNotification('Calendar created but failed to add team: ' + addTeamData.error, 'error'); } return; } // Show success and open sync modal showNotification(`Calendar "${teamName}" created and team synced!`, 'success'); // Refresh user calendars and open sync modal await loadUserCalendars(); openSyncModal(calendarUuid, teamName); } catch (error) { console.error('Error creating calendar for team:', error); showNotification('Failed to create calendar. Please try again.', 'error'); } } async function createCalendarForTeamLeague(leagueId, leagueName, teamId) { try { console.log('Auto-creating calendar for team-league:', leagueName, 'TeamID:', teamId); // Create calendar with team-league specific name const createHeaders = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { createHeaders['X-AJAX-TOKEN'] = window.ajaxToken; } const createResponse = await fetch('/api/calendar-management.php?action=create', { method: 'POST', headers: createHeaders, body: JSON.stringify({ name: leagueName + ' - Team' }) }); const createData = await createResponse.json(); if (!createResponse.ok) { console.error('Failed to create calendar:', createData.error); showNotification('Failed to create calendar: ' + createData.error, 'error'); return; } const calendarId = createData.calendar.id; const calendarUuid = createData.calendar.uuid; // Add team-league subscription to the new calendar const addTeamHeaders = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { addTeamHeaders['X-AJAX-TOKEN'] = window.ajaxToken; } const addTeamResponse = await fetch('/api/calendar-management.php?action=add_team', { method: 'POST', headers: addTeamHeaders, body: JSON.stringify({ calendar_id: calendarId, team_id: teamId, league_id: leagueId }) }); const addTeamData = await addTeamResponse.json(); if (!addTeamResponse.ok) { console.error('Failed to add team to calendar:', addTeamData.error); showNotification('Calendar created but failed to add team: ' + addTeamData.error, 'error'); return; } showNotification('Calendar created successfully!', 'success'); // Show the sync modal with the new calendar showTeamSyncModal([{ id: calendarId, uuid: calendarUuid, name: leagueName + ' - Team', team_count: 1, league_count: 1 }], teamId, leagueId); } catch (error) { console.error('Error creating calendar for team-league:', error); showNotification('Failed to create calendar', 'error'); } } // Function to auto-create a calendar for a league and sync it async function createCalendarForLeague(leagueId, leagueName) { try { console.log('Auto-creating calendar for league:', leagueName); // Create calendar with league name const createHeaders = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { createHeaders['X-AJAX-TOKEN'] = window.ajaxToken; } const createResponse = await fetch('/api/calendar-management.php?action=create', { method: 'POST', headers: createHeaders, body: JSON.stringify({ name: leagueName }) }); const createData = await createResponse.json(); if (!createResponse.ok) { console.error('Failed to create calendar:', createData.error); showNotification('Failed to create calendar: ' + createData.error, 'error'); return; } const calendarId = createData.calendar.id; const calendarUuid = createData.calendar.uuid; // Add league to the new calendar const addLeagueHeaders = { 'Content-Type': 'application/json' }; if (window.ajaxToken) { addLeagueHeaders['X-AJAX-TOKEN'] = window.ajaxToken; } const addLeagueResponse = await fetch('/api/calendar-management.php?action=add_league', { method: 'POST', headers: addLeagueHeaders, body: JSON.stringify({ calendar_id: calendarId, league_id: leagueId }) }); const addLeagueData = await addLeagueResponse.json(); if (!addLeagueResponse.ok) { console.error('Failed to add league to calendar:', addLeagueData.error); // Check if it's the specific "Calendar ID and League ID required" error if (addLeagueData.error && addLeagueData.error.includes('Calendar ID and League ID required')) { console.log('Suppressed expected add league error:', addLeagueData.error); } else { showNotification('Calendar created but failed to add league: ' + addLeagueData.error, 'error'); } return; } // Show success and open sync modal showNotification(`Calendar "${leagueName}" created and league synced!`, 'success'); // Refresh user calendars and open sync modal await loadUserCalendars(); openSyncModal(calendarUuid, leagueName); } catch (error) { console.error('Error creating calendar for league:', error); showNotification('Failed to create calendar. Please try again.', 'error'); } } // Function to handle sync intents after login function handleSyncIntentAfterLogin() { const syncIntent = sessionStorage.getItem('syncIntent'); if (syncIntent) { try { const intent = JSON.parse(syncIntent); sessionStorage.removeItem('syncIntent'); // Clear the intent console.log('Processing sync intent after login:', intent); if (intent.action === 'sync_team') { // Check if user has calendars loadUserCalendars().then(() => { // Trigger the team sync - this will auto-create if no calendars exist const syncButton = document.querySelector(`[onmouseenter*="showTeamAllSyncDropdown(event, ${intent.team_id}"]`); if (syncButton) { // Trigger sync with click=true to allow calendar creation showTeamAllSyncDropdown(null, intent.team_id, intent.team_name, true); } }); } else if (intent.action === 'sync_league') { // Handle league follow intent loadUserCalendars().then(() => { // Trigger the league follow followLeague(intent.league_id, intent.league_name); }); } } catch (error) { console.error('Error processing sync intent:', error); sessionStorage.removeItem('syncIntent'); } } } async function followLeague(leagueId, leagueName) { try { // Check if user is logged in first if (!serverRendering.isLoggedIn) { // Store the sync intent and redirect to login const currentUrl = window.location.href; const syncIntent = { action: 'sync_league', league_id: leagueId, league_name: leagueName }; sessionStorage.setItem('syncIntent', JSON.stringify(syncIntent)); window.location.href = '/login.php?redirect=' + encodeURIComponent(currentUrl); return; } // First, get user's calendars const headers = {}; if (window.ajaxToken) { headers['X-AJAX-TOKEN'] = window.ajaxToken; } const calendarsResponse = await fetch('/api/calendar-management.php?action=list', { headers }); const calendarsData = await calendarsResponse.json(); if (!calendarsResponse.ok || !calendarsData.calendars || calendarsData.calendars.length === 0) { // User is logged in but has no calendars - auto-create one await createCalendarForLeague(leagueId, leagueName); return; } // If only one calendar, use it directly if (calendarsData.calendars.length === 1) { const calendar = calendarsData.calendars[0]; await syncLeagueAllToCalendar(leagueId, calendar.id, calendar.name, leagueName); return; } // Multiple calendars - show selection modal const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); } catch (error) { console.error('Error following league:', error); showNotification('Failed to follow league. Please try again.', 'error'); } } function closeFollowLeagueModal() { const modal = document.getElementById('follow-league-modal'); if (modal) { modal.remove(); } } // Month navigation for matches let currentMatchesData = []; let currentViewMonth = new Date(); function navigateMonth(direction) { currentViewMonth.setMonth(currentViewMonth.getMonth() + direction); updateMatchesDisplay(); // Sync calendar mockup to same month if (document.getElementById('teamCalendarMockup')) { calendarMockupMonth = new Date(currentViewMonth); updateCalendarMockup(); } } function updateMatchesDisplay() { const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const currentMonthText = monthNames[currentViewMonth.getMonth()] + ' ' + currentViewMonth.getFullYear(); const monthElement = document.getElementById('currentMonth'); if (monthElement) { monthElement.textContent = currentMonthText; } // Filter matches for current month const filteredMatches = currentMatchesData.filter(match => { const matchDate = new Date(match.datetime); return matchDate.getMonth() === currentViewMonth.getMonth() && matchDate.getFullYear() === currentViewMonth.getFullYear(); }); // Update matches list const matchesList = document.getElementById('matchesList'); if (matchesList) { if (filteredMatches.length > 0) { const now = new Date(); const matchesHTML = filteredMatches.map(match => { const matchDate = new Date(match.datetime); const isValidDate = !isNaN(matchDate.getTime()); const isPastMatch = matchDate < now; // Determine match display format and result class let matchTeamsHTML = ''; let resultClass = ''; // Check for scores in multiple possible fields const homeScore = match.score_fulltime_home ?? match.goals_home ?? match.score_halftime_home ?? null; const awayScore = match.score_fulltime_away ?? match.goals_away ?? match.score_halftime_away ?? null; if (isPastMatch && homeScore !== null && awayScore !== null) { // Show inline score format: "Team A 1 - 2 Team B" // Determine result from team's perspective const isCurrentTeamHome = match.home_team === currentTeam?.name; const isCurrentTeamAway = match.away_team === currentTeam?.name; // Debug logging console.log('Match debug:', { homeTeam: match.home_team, awayTeam: match.away_team, currentTeamName: currentTeam?.name, isCurrentTeamHome, isCurrentTeamAway, homeScore, awayScore }); if (homeScore > awayScore) { // Home team won resultClass = isCurrentTeamHome ? ' win' : ' loss'; } else if (awayScore > homeScore) { // Away team won resultClass = isCurrentTeamAway ? ' win' : ' loss'; } else { resultClass = ' draw'; } matchTeamsHTML = `${match.home_team} ${homeScore} - ${awayScore} ${match.away_team}`; } else if (isPastMatch) { // No score available, show "vs" format matchTeamsHTML = `${match.home_team} vs ${match.away_team}`; } else { // Future match matchTeamsHTML = `${match.home_team} vs ${match.away_team}`; } return `
    ${isValidDate ? matchDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : 'TBD'}
    ${isValidDate ? matchDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) : 'TBD'}
    ${isPastMatch && (!homeScore && !awayScore) ? '' : ''}
    ${match.league_name || 'Match'}
    `; }).join(''); matchesList.innerHTML = matchesHTML; } else { matchesList.innerHTML = '
    No matches scheduled for this month
    '; } } // Update navigation buttons const now = new Date(); const prevBtn = document.getElementById('prevMonth'); const nextBtn = document.getElementById('nextMonth'); if (prevBtn) { // Allow going back 6 months from current date const sixMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 6, 1); prevBtn.disabled = currentViewMonth <= sixMonthsAgo; } if (nextBtn) { // Allow going forward 6 months from current date const sixMonthsAhead = new Date(now.getFullYear(), now.getMonth() + 6, 1); nextBtn.disabled = currentViewMonth >= sixMonthsAhead; } } function initializeMatchesNavigation(matchesData) { currentMatchesData = matchesData || []; currentViewMonth = new Date(); // Start with current month updateMatchesDisplay(); // Initialize calendar mockup if present if (document.getElementById('teamCalendarMockup')) { initializeCalendarMockup(); } } // Calendar mockup variables let calendarMockupMonth = new Date(); function initializeCalendarMockup() { // Sync with main calendar month calendarMockupMonth = new Date(currentViewMonth); updateCalendarMockup(); } function changeCalendarMonth(direction) { calendarMockupMonth.setMonth(calendarMockupMonth.getMonth() + direction); updateCalendarMockup(); // Sync main calendar to same month currentViewMonth = new Date(calendarMockupMonth); updateMatchesDisplay(); } function updateCalendarMockup() { const mockup = document.getElementById('teamCalendarMockup'); const header = document.getElementById('calendarHeader'); const prevBtn = document.getElementById('calendarPrevBtn'); const nextBtn = document.getElementById('calendarNextBtn'); if (!mockup || !header) return; // Update header const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; header.textContent = monthNames[calendarMockupMonth.getMonth()] + ' ' + calendarMockupMonth.getFullYear(); // Clear existing days (keep header and day labels) const existingDays = mockup.querySelectorAll('.calendar-mockup-day'); existingDays.forEach(day => day.remove()); // Generate calendar days const year = calendarMockupMonth.getFullYear(); const month = calendarMockupMonth.getMonth(); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const startDate = new Date(firstDay); startDate.setDate(startDate.getDate() - firstDay.getDay()); // Start on Sunday const today = new Date(); const eventDays = getEventDaysForMonth(year, month); // Generate 42 days (6 weeks) for (let i = 0; i < 42; i++) { const date = new Date(startDate); date.setDate(startDate.getDate() + i); const dayElement = document.createElement('div'); dayElement.className = 'calendar-mockup-day'; dayElement.textContent = date.getDate(); // Add classes if (date.getMonth() !== month) { dayElement.classList.add('other-month'); } if (date.toDateString() === today.toDateString()) { dayElement.classList.add('today'); } if (eventDays.includes(date.getDate()) && date.getMonth() === month) { dayElement.classList.add('has-event'); // Add click event for days with events dayElement.addEventListener('click', function(e) { showCalendarTooltip(e.target, date); }); // Add visual feedback for clickable events dayElement.style.cursor = 'pointer'; } mockup.appendChild(dayElement); } // Update navigation buttons const now = new Date(); if (prevBtn) { const sixMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 6, 1); prevBtn.disabled = calendarMockupMonth <= sixMonthsAgo; } if (nextBtn) { const sixMonthsAhead = new Date(now.getFullYear(), now.getMonth() + 6, 1); nextBtn.disabled = calendarMockupMonth >= sixMonthsAhead; } } function getEventDaysForMonth(year, month) { if (!currentMatchesData || !Array.isArray(currentMatchesData)) { return []; } const eventDays = new Set(); currentMatchesData.forEach(match => { if (match.datetime) { const matchDate = new Date(match.datetime); if (matchDate.getFullYear() === year && matchDate.getMonth() === month) { eventDays.add(matchDate.getDate()); } } }); return Array.from(eventDays); } function getEventsForDate(date) { if (!currentMatchesData || !Array.isArray(currentMatchesData)) { return []; } return currentMatchesData.filter(match => { if (match.datetime) { const matchDate = new Date(match.datetime); return matchDate.toDateString() === date.toDateString(); } return false; }); } function showCalendarTooltip(dayElement, date) { // Remove any existing tooltip const existingTooltip = document.querySelector('.calendar-tooltip'); if (existingTooltip) { existingTooltip.remove(); } const events = getEventsForDate(date); if (events.length === 0) return; // Create tooltip const tooltip = document.createElement('div'); tooltip.className = 'calendar-tooltip'; // Format events for tooltip const eventsHtml = events.map(match => { const matchDate = new Date(match.datetime); const timeStr = matchDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); // Format match name let matchName = ''; if (match.home_team && match.away_team) { matchName = `${match.home_team} vs ${match.away_team}`; } else { matchName = match.summary || 'Match'; } return `
    ${matchName}
    ${timeStr} ${match.league_name ? `
    ${match.league_name}` : ''}
    `; }).join(''); tooltip.innerHTML = eventsHtml; // Position tooltip document.body.appendChild(tooltip); const rect = dayElement.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); // Position above the day element let left = rect.left + (rect.width / 2) - (tooltipRect.width / 2); let top = rect.top - tooltipRect.height - 8; // Ensure tooltip stays on screen if (left < 10) left = 10; if (left + tooltipRect.width > window.innerWidth - 10) { left = window.innerWidth - tooltipRect.width - 10; } if (top < 10) { top = rect.bottom + 8; // Show below if no room above } tooltip.style.left = left + 'px'; tooltip.style.top = top + 'px'; // Show tooltip with animation setTimeout(() => tooltip.classList.add('show'), 10); // Auto-hide after 4 seconds setTimeout(() => { if (tooltip.parentNode) { tooltip.classList.remove('show'); setTimeout(() => tooltip.remove(), 200); } }, 4000); // Hide on click outside setTimeout(() => { document.addEventListener('click', function hideTooltip(e) { if (!tooltip.contains(e.target) && e.target !== dayElement) { tooltip.classList.remove('show'); setTimeout(() => tooltip.remove(), 200); document.removeEventListener('click', hideTooltip); } }); }, 100); } // Make functions globally available window.changeCalendarMonth = changeCalendarMonth; // Track search events function trackSearch(query, resultCount) { if (typeof gtag !== 'undefined') { gtag('event', 'search', { search_term: query, result_count: resultCount }); } } // Track calendar sync events function trackCalendarSync(service, calendarName) { if (typeof gtag !== 'undefined') { gtag('event', 'calendar_sync', { sync_service: service, calendar_name: calendarName, event_category: 'engagement' }); } } // Track navigation events function trackNavigation(destination, source) { if (typeof gtag !== 'undefined') { gtag('event', 'navigation', { destination: destination, source: source, event_category: 'navigation' }); } } // Track team/league subscriptions function trackSubscription(type, name) { if (typeof gtag !== 'undefined') { gtag('event', 'subscription', { subscription_type: type, item_name: name, event_category: 'engagement' }); } } // Add click-outside-to-close functionality for guided tour modal document.addEventListener('click', function(event) { const modal = document.getElementById('guided-tour-modal'); const modalContent = modal?.querySelector('.modal-content'); if (modal && modal.style.display === 'block') { // If clicking outside the modal content, close the modal if (!modalContent.contains(event.target)) { closeGuidedTour(); } } }); // Add escape key to close modal document.addEventListener('keydown', function(event) { if (event.key === 'Escape') { const modal = document.getElementById('guided-tour-modal'); if (modal && modal.style.display === 'block') { closeGuidedTour(); } } }); /** * Match Events Rotating Display (Archon-style) * Rotates through goals, cards, and other match events */ class MatchEventsRotator { constructor() { this.rotationInterval = 6000; // 6 seconds per event (doubled from 3) this.activeRotators = new Map(); // Track active rotations per team } // Initialize event rotators for all team events on page initializeEventRotators() { const teamEventsContainers = document.querySelectorAll('.team-events'); teamEventsContainers.forEach(container => { const teamId = container.dataset.teamId; const events = container.querySelectorAll('.event-item'); if (events.length > 1) { this.startRotation(teamId, container, events); } }); } // Start rotation for a specific team's events startRotation(teamId, container, events) { // Clear any existing rotation for this team this.stopRotation(teamId); let currentIndex = 0; const rotateEvents = () => { // Hide current event events[currentIndex].classList.remove('active'); // Move to next event currentIndex = (currentIndex + 1) % events.length; // Show next event with upward slide animation events[currentIndex].classList.add('active'); }; // Start the rotation interval const intervalId = setInterval(rotateEvents, this.rotationInterval); this.activeRotators.set(teamId, intervalId); } // Stop rotation for a specific team stopRotation(teamId) { if (this.activeRotators.has(teamId)) { clearInterval(this.activeRotators.get(teamId)); this.activeRotators.delete(teamId); } } // Stop all rotations stopAllRotations() { this.activeRotators.forEach((intervalId, teamId) => { clearInterval(intervalId); }); this.activeRotators.clear(); } // Restart rotations after content update restartRotations() { this.stopAllRotations(); this.initializeEventRotators(); } } // Global instance window.matchEventsRotator = new MatchEventsRotator(); // Initialize event rotators when fixtures are loaded function initializeMatchEventRotators() { if (window.matchEventsRotator) { window.matchEventsRotator.restartRotations(); } } // Initialize user profile dropdown functionality function initializeUserProfileDropdown(){ const userProfileButton = document.getElementById('user-profile-button'); const userProfileDropdown = document.getElementById('user-profile-dropdown'); if (userProfileButton && userProfileDropdown) { // Toggle dropdown on button click userProfileButton.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); userProfileDropdown.classList.toggle('show'); }); // Close dropdown when clicking outside document.addEventListener('click', function(e) { if (!userProfileButton.contains(e.target) && !userProfileDropdown.contains(e.target)) { userProfileDropdown.classList.remove('show'); } }); // Close dropdown when clicking on dropdown links const dropdownLinks = userProfileDropdown.querySelectorAll('a'); dropdownLinks.forEach(function(link) { link.addEventListener('click', function() { userProfileDropdown.classList.remove('show'); }); }); } } // Make function globally available window.initializeUserProfileDropdown = initializeUserProfileDropdown; // League Tabs System function switchLeagueTab(tabName) { // Remove active class from all tabs and content document.querySelectorAll('.league-tab').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.league-tab-content').forEach(content => { content.classList.remove('active'); }); // Add active class to selected tab and content document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); document.getElementById(`${tabName}-content`).classList.add('active'); // Load matches if switching to matches tab if (tabName === 'matches' && window.currentLeagueData) { loadLeagueMatches(); } } function loadLeagueMatches(startDate = null, direction = 'forward') { const matchesContainer = document.getElementById('league-matches-calendar'); if (!matchesContainer || !window.currentLeagueData) return; // Show loading state matchesContainer.innerHTML = '
    Loading league matches...
    '; // Build URL with pagination parameters let url = `/public/ajax/league_matches.php?league_id=${window.currentLeagueData.id}`; if (startDate) { url += `&start_date=${encodeURIComponent(startDate)}&direction=${direction}`; } // Fetch league matches const fetchOptions = {}; if (window.ajaxToken) { fetchOptions.headers = { 'X-AJAX-TOKEN': window.ajaxToken }; console.log('League matches: Using AJAX token for request'); } else { console.error('League matches: No AJAX token available!'); } fetch(url, fetchOptions) .then(response => response.json()) .then(data => { if (data.success && data.matches) { renderLeagueMatches(data.matches, data.user_timezone); } else { matchesContainer.innerHTML = '
    No matches found for this league.
    '; } }) .catch(error => { console.error('Error loading league matches:', error); matchesContainer.innerHTML = '
    Error loading matches. Please try again.
    '; }); } function renderLeagueMatches(matches, userTimezone) { const matchesContainer = document.getElementById('league-matches-calendar'); if (!matchesContainer) return; if (!matches || matches.length === 0) { matchesContainer.innerHTML = '
    No matches scheduled for this league.
    '; return; } // Group matches by date const matchesByDate = {}; matches.forEach(match => { // Parse the datetime to get just the date part const matchDate = new Date(match.date); const dateKey = matchDate.toISOString().split('T')[0]; // Get YYYY-MM-DD format if (!matchesByDate[dateKey]) { matchesByDate[dateKey] = []; } matchesByDate[dateKey].push(match); }); // Get date range for pagination const sortedDates = Object.keys(matchesByDate).sort(); const earliestDate = sortedDates[0]; const latestDate = sortedDates[sortedDates.length - 1]; // Render matches calendar with pagination let html = '
    '; html += '
    '; html += '

    League Matches

    '; if (userTimezone && userTimezone !== 'UTC') { html += `
    Times shown in ${userTimezone}
    `; } html += '
    '; html += '
    '; html += ``; html += ``; html += ``; html += '
    '; html += '
    '; html += '
    '; Object.keys(matchesByDate).sort().forEach(dateKey => { const dateObj = new Date(dateKey + 'T00:00:00'); // Add time to avoid timezone issues const today = new Date(); const isToday = dateKey === today.toISOString().split('T')[0]; const formattedDate = dateObj.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); html += `
    `; html += `
    ${formattedDate}${isToday ? ' (Today)' : ''}
    `; html += `
    `; matchesByDate[dateKey].forEach(match => { // Parse the full datetime to get the time const matchDateTime = new Date(match.date); const timeString = matchDateTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); const matchUrl = match.url || '#'; html += ``; }); html += `
    `; }); html += '
    '; matchesContainer.innerHTML = html; } function initializeLeaguePage() { // Set up initial tab state if (document.querySelector('.league-tabs')) { // Overview tab is active by default, no need to load matches initially console.log('League page initialized with tabs'); // Initialize Player Stats module for league view (even when hidden in Players tab) if (typeof PlayerStatisticsLibrary !== 'undefined' && PlayerStatisticsLibrary.initializeFromDataAttributes) { // Re-run the initialization to ensure hidden Player Stats widgets are properly initialized PlayerStatisticsLibrary.initializeFromDataAttributes(); console.log('Player Stats module initialized for league page'); } } } // Make functions globally available window.switchLeagueTab = switchLeagueTab; window.loadLeagueMatches = loadLeagueMatches; window.initializeLeaguePage = initializeLeaguePage;