// 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 = ``;
} else if (result.type === 'league' && result.logo) {
iconContent = ``;
} else {
iconContent = getFallbackIcon(result.type);
}
// Generate proper URL for the result
const resultUrl = generateResultUrl(result);
item.innerHTML = `
`).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 += `
`;
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 => `
`).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`}
`;
}
}
// 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 = `
${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 = `
Add to Apple Calendar
Choose your preferred method:
📱 Mac/iPhone/iPad (Automatic)
Click the button below to automatically open Apple Calendar:
💻 Manual Setup
Copy this URL and add it manually in Calendar app:
${icsUrl}
`;
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 = '
';
}
}
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 = '
';
});
}
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 += `
';
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;