${date.toLocaleDateString('en-US', {
day: 'numeric',
month: 'short'
})}
`;
dateItem.addEventListener('click', () => {
document.querySelectorAll('.date-item').forEach(item =>
item.classList.remove('active'));
dateItem.classList.add('active');
selectedDate = date;
fetchGuide();
});
dateScroll.appendChild(dateItem);
}
}
function isSameDate(date1, date2) {
return date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getFullYear() === date2.getFullYear();
}
function initializeScrollButtons() {
const dateScroll = document.getElementById('dateScroll');
const scrollLeft = document.querySelector('.scroll-left');
const scrollRight = document.querySelector('.scroll-right');
scrollLeft.addEventListener('click', () => {
dateScroll.scrollBy({ left: -200, behavior: 'smooth' });
});
scrollRight.addEventListener('click', () => {
dateScroll.scrollBy({ left: 200, behavior: 'smooth' });
});
}
// Category-related functions
function extractCategories(manifest) {
const uniqueCategories = new Set(['All']);
Object.values(manifest).forEach(channel => {
if (channel.category) {
uniqueCategories.add(channel.category);
}
});
return Array.from(uniqueCategories);
}
function createCategoryFilters(categories) {
const filterContainer = document.getElementById('category-filters');
filterContainer.innerHTML = '';
categories.forEach(category => {
const filterButton = document.createElement('button');
filterButton.className = `category-filter ${category === activeCategory ? 'active' : ''}`;
filterButton.textContent = category;
filterButton.onclick = () => {
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
filterButton.classList.add('active');
activeCategory = category;
fetchGuide();
};
filterContainer.appendChild(filterButton);
});
}
// Time and program-related functions
function getCurrentTimeBase() {
// If we're near the end of the day (after 8 PM), adjust the base time
// to ensure we show programs from the next day
const now = new Date(selectedDate);
const currentHour = now.getHours();
// If we're approaching the end of the day, adjust the time window
if (currentHour >= 20) { // After 8 PM
// Move the time window forward to show more of tomorrow's content
now.setHours(now.getHours() - 2); // Set base time 2 hours earlier to show more future content
}
const minutes = now.getMinutes();
const roundedMinutes = Math.floor(minutes / 30) * 30;
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), roundedMinutes, 0, 0);
}
function formatDateTime(timestamp) {
if (!timestamp) return '';
const year = timestamp.substring(0, 4);
const month = timestamp.substring(4, 6);
const day = timestamp.substring(6, 8);
const hour = timestamp.substring(8, 10);
const minute = timestamp.substring(10, 12);
return `${year}-${month}-${day}T${hour}:${minute}:00`;
}
function calculateProgramPosition(startTime, endTime) {
const baseTime = getCurrentTimeBase();
const adjustedStartTime = startTime < baseTime ? baseTime : startTime;
const timeDiff = adjustedStartTime - baseTime;
const duration = (endTime - adjustedStartTime) / (1000 * 60 * 30);
const position = (timeDiff / (30 * 60 * 1000)) * 120;
return {
left: Math.max(0, position),
width: Math.max(duration * 120, 10)
};
}
function checkTimelineShift() {
const now = new Date();
const currentMinutes = now.getMinutes();
const currentSeconds = now.getSeconds();
if ((currentMinutes === 29 || currentMinutes === 59) && currentSeconds === 59) {
shiftTimeline();
}
}
function shiftTimeline() {
const timeHeader = document.querySelector('.time-header');
const programContainers = document.querySelectorAll('.programs-container');
timeHeader.classList.add('shifting');
programContainers.forEach(container => {
container.classList.add('shifting');
});
setTimeout(() => {
timeHeader.classList.remove('shifting');
programContainers.forEach(container => {
container.classList.remove('shifting');
});
updateTimeCells();
updateProgramPositions();
fetchGuide();
}, 1000);
}
function updateTimeCells() {
const timeHeader = document.querySelector('.time-header');
const timeCells = Array.from(timeHeader.querySelectorAll('.time-cell:not(:first-child)'));
if (timeCells.length > 0) {
timeCells[0].remove();
}
const lastCell = timeCells[timeCells.length - 1];
if (lastCell) {
const baseTime = getCurrentTimeBase();
const lastTime = new Date(baseTime.getTime() + (parseInt(lastCell.dataset.minuteOffset) + 30) * 60 * 1000);
const newCell = document.createElement('div');
newCell.className = 'time-cell';
newCell.dataset.minuteOffset = (parseInt(lastCell.dataset.minuteOffset) + 30).toString();
newCell.textContent = lastTime.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
if (lastTime.getMinutes() === 0) {
newCell.style.fontWeight = 'bold';
newCell.style.backgroundColor = '#2d2d2d';
}
// Add visual indicator for next day's time slots
const nextDay = new Date(baseTime);
nextDay.setDate(nextDay.getDate() + 1);
nextDay.setHours(0, 0, 0, 0);
if (lastTime >= nextDay) {
newCell.style.borderTop = '2px solid #e53e3e';
}
timeHeader.appendChild(newCell);
}
}
function updateProgramPositions() {
const programs = document.querySelectorAll('.program');
const baseTime = getCurrentTimeBase();
programs.forEach(program => {
const startTime = new Date(program.dataset.start);
const endTime = new Date(program.dataset.end);
if (endTime < baseTime) {
program.remove();
} else {
const position = calculateProgramPosition(startTime, endTime);
program.style.left = `${position.left}px`;
program.style.width = `${position.width}px`;
}
});
}
function createTimeHeader() {
const timeHeader = document.createElement('div');
timeHeader.className = 'time-header';
const channelHeaderCell = document.createElement('div');
channelHeaderCell.className = 'time-cell';
channelHeaderCell.textContent = 'Channels';
timeHeader.appendChild(channelHeaderCell);
const baseTime = getCurrentTimeBase();
const intervalMinutes = 30;
// Increase the number of intervals to show more future time slots
const totalIntervals = 48; // 24 hours of 30-minute intervals
for (let i = 0; i < totalIntervals; i++) {
const timeSlot = new Date(baseTime.getTime() + (i * intervalMinutes * 60 * 1000));
const timeCell = document.createElement('div');
timeCell.className = 'time-cell';
timeCell.dataset.minuteOffset = i * intervalMinutes;
// Add day indicator for times past midnight
const nextDay = new Date(baseTime);
nextDay.setDate(nextDay.getDate() + 1);
nextDay.setHours(0, 0, 0, 0);
const formattedTime = timeSlot.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Add visual indicator for next day's time slots
if (timeSlot >= nextDay) {
timeCell.style.borderTop = '2px solid #e53e3e';
timeCell.textContent = formattedTime;
} else {
timeCell.textContent = formattedTime;
}
if (timeSlot.getMinutes() === 0) {
timeCell.style.fontWeight = 'bold';
timeCell.style.backgroundColor = '#2d2d2d';
}
timeHeader.appendChild(timeCell);
}
return timeHeader;
}
function updateCurrentPrograms() {
const now = new Date();
const programs = document.querySelectorAll('.program');
programs.forEach(program => {
const startTime = new Date(program.dataset.start);
const endTime = new Date(program.dataset.end);
if (now >= startTime && now < endTime) {
program.classList.add('current');
const progress = ((now - startTime) / (endTime - startTime)) * 100;
let progressBar = program.querySelector('.progress-bar');
if (!progressBar) {
progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
program.appendChild(progressBar);
}
progressBar.style.width = `${progress}%`;
} else {
program.classList.remove('current');
const progressBar = program.querySelector('.progress-bar');
if (progressBar) {
progressBar.remove();
}
}
});
}
function updateSeekerLine() {
const now = new Date();
const guideContainer = document.querySelector('.guide-container');
let seeker = document.querySelector('.seeker-line');
if (!seeker && guideContainer) {
seeker = document.createElement('div');
seeker.className = 'seeker-line';
const seekerTime = document.createElement('div');
seekerTime.className = 'seeker-time';
seeker.appendChild(seekerTime);
guideContainer.appendChild(seeker);
}
if (seeker) {
const baseTime = getCurrentTimeBase();
const timeDiff = now - baseTime;
const position = (timeDiff / (30 * 60 * 1000)) * 120;
seeker.style.left = `${200 + position}px`;
const timeDisplay = now.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
seeker.querySelector('.seeker-time').textContent = timeDisplay;
const timeCells = document.querySelectorAll('.time-cell');
const currentMinutes = now.getHours() * 60 + now.getMinutes();
timeCells.forEach(cell => {
cell.classList.remove('current-time');
const cellMinutes = parseInt(cell.dataset.minuteOffset || 0);
const baseMinutes = baseTime.getHours() * 60 + baseTime.getMinutes();
if (currentMinutes >= baseMinutes + cellMinutes &&
currentMinutes < baseMinutes + cellMinutes + 30) {
cell.classList.add('current-time');
}
});
}
updateCurrentPrograms();
}
function scrollToCurrentTime() {
const guideContainer = document.querySelector('.guide-container');
const now = new Date();
const baseTime = getCurrentTimeBase();
const timeDiff = now - baseTime;
const position = (timeDiff / (30 * 60 * 1000)) * 120;
guideContainer.scrollLeft = Math.max(0, position - 240);
}
// Main fetch function
async function fetchGuide() {
const loading = document.getElementById('loading');
const container = document.getElementById('guide-container');
try {
const [xmlResponse, manifestResponse] = await Promise.all([
fetch('https://azimabid00.github.io/epg/astro_epg.xml'),
fetch('manifest.json')
]);
if (!xmlResponse.ok || !manifestResponse.ok) {
throw new Error('Failed to fetch data');
}
const xmlText = await xmlResponse.text();
manifestData = await manifestResponse.json();
if (categories.size === 1) {
categories = new Set(extractCategories(manifestData));
createCategoryFilters(Array.from(categories));
}
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
const guideContainer = document.createElement('div');
guideContainer.className = 'guide-container';
guideContainer.appendChild(createTimeHeader());
// Get the current time-based window instead of just the selected day
const startOfDay = new Date(selectedDate);
startOfDay.setHours(0, 0, 0, 0);
// Create a window that extends 24 hours from the current time
// This ensures programs from the next day appear as current day ends
const currentTime = new Date();
const endTime = new Date(currentTime);
endTime.setHours(currentTime.getHours() + 24); // 24-hour window
const channelIds = Object.keys(manifestData).sort();
for (let channelId of channelIds) {
const channelInfo = manifestData[channelId];
if (activeCategory !== 'All' && channelInfo.category !== activeCategory) {
continue;
}
const channelRow = document.createElement('div');
channelRow.className = 'channel-row';
const channelCell = document.createElement('div');
channelCell.className = 'channel-cell';
channelCell.innerHTML = `
${channelInfo.logo ? `
${title}
${startTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })} -
${endTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })}
`;
programsContainer.appendChild(programDiv);
}
}
if (hasCurrentOrUpcoming) {
channelRow.appendChild(programsContainer);
guideContainer.appendChild(channelRow);
}
}
container.innerHTML = '';
container.appendChild(guideContainer);
loading.style.display = 'none';
updateSeekerLine();
} catch (error) {
loading.textContent = `Error loading TV Guide: ${error.message}`;
loading.style.backgroundColor = '#dc2626';
loading.style.padding = '20px';
loading.style.borderRadius = '8px';
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeGuide);