/** * @fileoverview Enhanced Debrid Services Comparison Application * Modern, feature-rich JavaScript with performance optimizations and professional UX */ // Modern utility functions const Utils = { // Enhanced debounce with immediate option debounce: (func, delay = 300, immediate = false) => { let timeout; return function executedFunction(...args) { const later = () => { timeout = null; if (!immediate) func.apply(this, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, delay); if (callNow) func.apply(this, args); }; }, // Throttle function for scroll events throttle: (func, limit) => { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }, // Animate elements with Intersection Observer animateOnScroll: (elements, options = {}) => { const defaultOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-in'); observer.unobserve(entry.target); } }); }, { ...defaultOptions, ...options }); elements.forEach(el => observer.observe(el)); } }; // Enhanced loading manager class LoadingManager { constructor() { this.activeLoaders = new Set(); } show(target, text = 'Loading...') { const loaderId = Date.now().toString(); const loader = document.createElement('div'); loader.className = 'loading-overlay'; loader.setAttribute('data-loader-id', loaderId); loader.innerHTML = `

${text}

`; if (typeof target === 'string') { target = document.querySelector(target); } target.style.position = 'relative'; target.appendChild(loader); this.activeLoaders.add(loaderId); requestAnimationFrame(() => { loader.classList.add('loading-overlay--visible'); }); return loaderId; } hide(target, loaderId) { if (typeof target === 'string') { target = document.querySelector(target); } const loader = target.querySelector(`[data-loader-id="${loaderId}"]`); if (loader) { loader.classList.add('loading-overlay--hiding'); this.activeLoaders.delete(loaderId); setTimeout(() => { if (loader.parentNode) { target.removeChild(loader); } }, 300); } } hideAll() { this.activeLoaders.forEach(id => { const loader = document.querySelector(`[data-loader-id="${id}"]`); if (loader) { this.hide(loader.parentNode, id); } }); } } // Enhanced table manager with advanced features class TableManager { constructor(containerId, searchInputId, clearIconId, options = {}) { this.container = document.querySelector(`#${containerId}`); this.searchInput = document.querySelector(`#${searchInputId}`); this.clearIcon = document.querySelector(`#${clearIconId}`); this.options = { sortable: true, filterable: true, pagination: false, itemsPerPage: 50, ...options }; this.currentData = {}; this.filteredData = {}; this.currentSort = { column: null, direction: 'asc' }; this.currentPage = 1; this._setupSearchFunctionality(); this._setupKeyboardNavigation(); } generateTable(data = {}) { if (!Object.keys(data).length) { this.container.innerHTML = '

No data available.

'; return; } this.currentData = data; this.filteredData = { ...data }; const providers = Object.keys(data[Object.keys(data)[0]]); const tableId = this.container.id.replace('-container', ''); // Create table with enhanced features this.container.innerHTML = `
${providers.map(provider => ` `).join('')} ${this._generateTableRows(this.filteredData, providers)}
Service Name ${provider}
${this.options.pagination ? this._createPagination() : ''} `; this._setupTableFeatures(); this._setupAccessibility(); } _generateTableRows(data, providers) { return Object.entries(data).map(([host, providerData]) => `
${host}
${providers.map(provider => ` ${providerData[provider] === 'yes' ? '' : '' } `).join('')} `).join(''); } _setupSearchFunctionality() { const handleSearch = Utils.debounce(() => { const searchTerm = this.searchInput.value.toLowerCase().trim(); if (!searchTerm) { this.filteredData = { ...this.currentData }; this.clearIcon.style.display = 'none'; } else { this.filteredData = {}; Object.entries(this.currentData).forEach(([host, data]) => { if (host.toLowerCase().includes(searchTerm)) { this.filteredData[host] = data; } }); this.clearIcon.style.display = 'block'; } this._updateTableContent(); this._updateSearchResults(searchTerm); }, 300); this.searchInput?.addEventListener('input', handleSearch); this.searchInput?.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this._clearSearch(); } }); this.clearIcon?.addEventListener('click', () => { this._clearSearch(); }); } _clearSearch() { this.searchInput.value = ''; this.filteredData = { ...this.currentData }; this.clearIcon.style.display = 'none'; this._updateTableContent(); this._updateSearchResults(''); } _updateSearchResults(searchTerm) { const resultCount = Object.keys(this.filteredData).length; const totalCount = Object.keys(this.currentData).length; // Update or create search results indicator let indicator = this.container.querySelector('.search-results'); if (!indicator) { indicator = document.createElement('div'); indicator.className = 'search-results'; this.container.insertBefore(indicator, this.container.querySelector('.table-wrapper')); } if (searchTerm) { indicator.textContent = `Showing ${resultCount} of ${totalCount} services`; indicator.style.display = 'block'; } else { indicator.style.display = 'none'; } } _setupTableFeatures() { // Setup sorting if (this.options.sortable) { this.container.querySelectorAll('th.sortable').forEach(header => { header.addEventListener('click', () => this._handleSort(header)); header.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this._handleSort(header); } }); }); } } _handleSort(header) { const column = header.getAttribute('data-column'); const currentDirection = this.currentSort.column === column ? this.currentSort.direction : 'asc'; const newDirection = currentDirection === 'asc' ? 'desc' : 'asc'; this.currentSort = { column, direction: newDirection }; // Update header states this.container.querySelectorAll('th.sortable').forEach(th => { th.setAttribute('aria-sort', 'none'); th.classList.remove('sorted-asc', 'sorted-desc'); }); header.setAttribute('aria-sort', newDirection === 'asc' ? 'ascending' : 'descending'); header.classList.add(`sorted-${newDirection}`); this._sortData(); this._updateTableContent(); } _sortData() { const { column, direction } = this.currentSort; const entries = Object.entries(this.filteredData); entries.sort(([hostA, dataA], [hostB, dataB]) => { let valueA, valueB; if (column === 'service') { valueA = hostA.toLowerCase(); valueB = hostB.toLowerCase(); } else { // Sort by provider support (yes > no) valueA = dataA[column] === 'yes' ? 1 : 0; valueB = dataB[column] === 'yes' ? 1 : 0; } if (direction === 'asc') { return valueA > valueB ? 1 : valueA < valueB ? -1 : 0; } else { return valueA < valueB ? 1 : valueA > valueB ? -1 : 0; } }); this.filteredData = Object.fromEntries(entries); } _updateTableContent() { const tbody = this.container.querySelector('tbody'); const providers = Object.keys(this.currentData[Object.keys(this.currentData)[0]]); if (tbody) { tbody.innerHTML = this._generateTableRows(this.filteredData, providers); this._setupTableFeatures(); } } _setupKeyboardNavigation() { this.container.addEventListener('keydown', (e) => { if (e.target.matches('th.sortable') && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); this._handleSort(e.target); } }); } _setupAccessibility() { // Add ARIA labels and descriptions const table = this.container.querySelector('table'); if (table) { table.setAttribute('role', 'grid'); table.setAttribute('aria-label', 'Debrid services comparison table'); } } _createPagination() { // Pagination implementation for large datasets return ` `; } } // Enhanced comparison manager class ComparisonManager { constructor(containerId, select1Id, select2Id, data) { this.container = document.querySelector(`#${containerId}`); this.select1 = document.querySelector(`#${select1Id}`); this.select2 = document.querySelector(`#${select2Id}`); this.data = data; this.isComparing = false; this._populateDropdowns(); this._setupEventListeners(); this._setupKeyboardShortcuts(); } _populateDropdowns() { const providers = Object.keys(this.data[Object.keys(this.data)[0]]); [this.select1, this.select2].forEach((select, index) => { // Add loading state select.innerHTML = ''; // Simulate async loading for better UX setTimeout(() => { select.innerHTML = ''; providers.forEach(provider => { const option = document.createElement('option'); option.value = provider; option.textContent = provider; select.appendChild(option); }); }, 100 * (index + 1)); }); } _generateCompareTable() { const provider1 = this.select1.value; const provider2 = this.select2.value; if (!provider1 || !provider2) { this._showEmptyState(); return; } if (provider1 === provider2) { this._showSameProviderWarning(); return; } this.isComparing = true; const loaderId = loadingManager.show(this.container, 'Generating comparison...'); // Simulate processing time for better UX setTimeout(() => { this._renderComparisonTable(provider1, provider2); loadingManager.hide(this.container, loaderId); this._trackComparison(provider1, provider2); }, 500); } _renderComparisonTable(provider1, provider2) { const comparisonData = this._calculateComparisonStats(provider1, provider2); this.container.innerHTML = `

Comparing ${provider1} vs ${provider2}

Shared Support ${comparisonData.shared}
${provider1} Only ${comparisonData.provider1Only}
${provider2} Only ${comparisonData.provider2Only}
${this._generateComparisonRows(provider1, provider2)}
Service Name ${provider1} ${provider2} Status
`; this.container.style.display = 'block'; this._setupComparisonFeatures(); // Animate table in requestAnimationFrame(() => { this.container.classList.add('comparison-visible'); }); } _generateComparisonRows(provider1, provider2) { return Object.entries(this.data).map(([host, providerData]) => { const support1 = providerData[provider1] === 'yes'; const support2 = providerData[provider2] === 'yes'; let statusClass = ''; let statusText = ''; if (support1 && support2) { statusClass = 'both-supported'; statusText = 'Both'; } else if (support1) { statusClass = 'provider1-only'; statusText = `${provider1} only`; } else if (support2) { statusClass = 'provider2-only'; statusText = `${provider2} only`; } else { statusClass = 'neither-supported'; statusText = 'Neither'; } return ` ${host} ${support1 ? '' : '' } ${support2 ? '' : '' } ${statusText} `; }).join(''); } _calculateComparisonStats(provider1, provider2) { let shared = 0; let provider1Only = 0; let provider2Only = 0; Object.values(this.data).forEach(providerData => { const support1 = providerData[provider1] === 'yes'; const support2 = providerData[provider2] === 'yes'; if (support1 && support2) shared++; else if (support1) provider1Only++; else if (support2) provider2Only++; }); return { shared, provider1Only, provider2Only }; } _setupComparisonFeatures() { // Close button this.container.querySelector('#close-compare')?.addEventListener('click', () => { this._closeComparison(); }); // Filter functionality this.container.querySelectorAll('input[name="comparison-filter"]').forEach(radio => { radio.addEventListener('change', (e) => { this._filterComparison(e.target.value); }); }); } _filterComparison(filter) { const rows = this.container.querySelectorAll('.comparison-row'); rows.forEach(row => { const status = row.getAttribute('data-status'); let show = true; switch (filter) { case 'both': show = status === 'both-supported'; break; case 'different': show = status === 'provider1-only' || status === 'provider2-only'; break; case 'all': default: show = true; } row.style.display = show ? '' : 'none'; }); // Update visible count const visibleRows = this.container.querySelectorAll('.comparison-row:not([style*="display: none"])').length; let indicator = this.container.querySelector('.filter-results'); if (!indicator) { indicator = document.createElement('div'); indicator.className = 'filter-results'; this.container.querySelector('.table-wrapper').insertAdjacentElement('beforebegin', indicator); } indicator.textContent = `Showing ${visibleRows} services`; } _closeComparison() { this.container.classList.add('comparison-hiding'); setTimeout(() => { this.container.style.display = 'none'; this.container.classList.remove('comparison-visible', 'comparison-hiding'); this.isComparing = false; this._showEmptyState(); }, 300); } _showEmptyState() { this.container.innerHTML = `
⚖️

Ready to Compare

Select two services above to see a detailed comparison

`; this.container.style.display = 'block'; } _showSameProviderWarning() { this.container.innerHTML = `
⚠️

Same Service Selected

Please select two different services to compare

`; this.container.style.display = 'block'; } _setupEventListeners() { [this.select1, this.select2].forEach(select => { select?.addEventListener('change', () => { this._generateCompareTable(); }); }); } _setupKeyboardShortcuts() { document.addEventListener('keydown', (e) => { if (this.isComparing && e.key === 'Escape') { this._closeComparison(); } }); } _trackComparison(provider1, provider2) { // Analytics tracking for popular comparisons if (typeof gtag !== 'undefined') { gtag('event', 'comparison_generated', { provider_1: provider1, provider_2: provider2 }); } } } // Enhanced pricing manager class PricingManager { constructor(containerId) { this.container = document.querySelector(`#${containerId}-table-container`); this.currentData = null; } generatePricingTable(data = {}) { if (!data.plans?.length) { this.container.innerHTML = '

Error: Invalid pricing data structure.

'; return; } this.currentData = data; const services = Object.keys(data.plans[0]).filter(key => key !== 'name'); this.container.innerHTML = `
${services.map(service => ` `).join('')} ${data.plans.map((plan, index) => ` ${services.map(service => { const price = plan[service]; const isNumeric = !isNaN(parseFloat(price)); return ` `; }).join('')} `).join('')}
Plans
${service}
${plan.name}
${price} ${isNumeric ? '/month' : ''}
`; this._setupPricingFeatures(); } _setupPricingFeatures() { // Add hover effects for better UX const priceCells = this.container.querySelectorAll('.price-cell'); priceCells.forEach(cell => { cell.addEventListener('mouseenter', () => { const column = Array.from(cell.parentNode.children).indexOf(cell); this._highlightColumn(column); }); cell.addEventListener('mouseleave', () => { this._removeColumnHighlight(); }); }); } _highlightColumn(columnIndex) { const table = this.container.querySelector('table'); const cells = table.querySelectorAll(`td:nth-child(${columnIndex + 1}), th:nth-child(${columnIndex + 1})`); cells.forEach(cell => cell.classList.add('highlighted')); } _removeColumnHighlight() { const highlightedCells = this.container.querySelectorAll('.highlighted'); highlightedCells.forEach(cell => cell.classList.remove('highlighted')); } } // Performance monitor class PerformanceMonitor { constructor() { this.metrics = { loadStart: performance.now(), domReady: null, fullyLoaded: null }; } markDOMReady() { this.metrics.domReady = performance.now(); console.log(`DOM Ready: ${(this.metrics.domReady - this.metrics.loadStart).toFixed(2)}ms`); } markFullyLoaded() { this.metrics.fullyLoaded = performance.now(); console.log(`Fully Loaded: ${(this.metrics.fullyLoaded - this.metrics.loadStart).toFixed(2)}ms`); } getMetrics() { return { ...this.metrics, totalLoadTime: this.metrics.fullyLoaded - this.metrics.loadStart, domLoadTime: this.metrics.domReady - this.metrics.loadStart }; } } // Global instances const loadingManager = new LoadingManager(); const performanceMonitor = new PerformanceMonitor(); // Enhanced application initialization document.addEventListener('DOMContentLoaded', async () => { performanceMonitor.markDOMReady(); try { // Initialize managers with enhanced options const fileHostsTable = new TableManager( 'file-hosts-table-container', 'host-search-input', 'clear-host-search', { sortable: true } ); const adultHostsTable = new TableManager( 'adult-hosts-table-container', 'adult-host-search-input', 'clear-adult-host-search', { sortable: true } ); const pricingManager = new PricingManager('pricing'); // Show loading states const loaderId1 = loadingManager.show('#file-hosts-table-container', 'Loading file hosts...'); const loaderId2 = loadingManager.show('#adult-hosts-table-container', 'Loading adult hosts...'); const loaderId3 = loadingManager.show('#pricing-table-container', 'Loading pricing data...'); // Enhanced data loading with retry logic const loadDataWithRetry = async (url, retries = 3) => { for (let i = 0; i < retries; i++) { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }; // Load data with enhanced error handling const [fileHostsData, adultHostsData, pricingData] = await Promise.allSettled([ loadDataWithRetry('./json/file-hosts.json'), loadDataWithRetry('./json/adult-hosts.json'), loadDataWithRetry('./json/pricing.json') ]); // Process results if (fileHostsData.status === 'fulfilled') { loadingManager.hide('#file-hosts-table-container', loaderId1); fileHostsTable.generateTable(fileHostsData.value); new ComparisonManager('compare-table-container', 'provider1', 'provider2', fileHostsData.value); } else { loadingManager.hide('#file-hosts-table-container', loaderId1); console.error('File hosts error:', fileHostsData.reason); } if (adultHostsData.status === 'fulfilled') { loadingManager.hide('#adult-hosts-table-container', loaderId2); adultHostsTable.generateTable(adultHostsData.value); } else { loadingManager.hide('#adult-hosts-table-container', loaderId2); console.error('Adult hosts error:', adultHostsData.reason); } if (pricingData.status === 'fulfilled') { loadingManager.hide('#pricing-table-container', loaderId3); pricingManager.generatePricingTable(pricingData.value); } else { loadingManager.hide('#pricing-table-container', loaderId3); console.error('Pricing error:', pricingData.reason); } // Setup URL-based comparison setupURLComparison(); // Setup scroll animations setupScrollAnimations(); // Setup offline detection setupOfflineDetection(); performanceMonitor.markFullyLoaded(); } catch (error) { console.error('Critical error initializing application:', error); loadingManager.hideAll(); } }); // URL-based comparison feature function setupURLComparison() { const urlParams = new URLSearchParams(window.location.search); const provider1 = urlParams.get('compare'); const provider2 = urlParams.get('with'); if (provider1 && provider2) { setTimeout(() => { const select1 = document.querySelector('#provider1'); const select2 = document.querySelector('#provider2'); if (select1 && select2) { select1.value = provider1; select2.value = provider2; select1.dispatchEvent(new Event('change')); } }, 1000); } } // Scroll animations setup function setupScrollAnimations() { const animatedElements = document.querySelectorAll('.section, .card, .quick-nav-card'); Utils.animateOnScroll(animatedElements); } // Offline detection function setupOfflineDetection() { window.addEventListener('online', () => { console.log('Connection restored'); }); window.addEventListener('offline', () => { console.log('You are offline. Some features may not work.'); }); } // Enhanced error boundary window.addEventListener('error', (event) => { console.error('Global error:', event.error); }); window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); event.preventDefault(); }); // ===== MODERN MOBILE-FIRST ENHANCEMENTS ===== // Enhanced Touch Gesture Support class TouchGestureManager { constructor() { this.touchStart = null; this.touchEnd = null; this.minSwipeDistance = 100; this.maxSwipeTime = 300; this.setupTouchListeners(); } setupTouchListeners() { document.addEventListener('touchstart', (e) => { this.touchStart = { x: e.changedTouches[0].screenX, y: e.changedTouches[0].screenY, time: Date.now() }; }, { passive: true }); document.addEventListener('touchend', (e) => { if (!this.touchStart) return; this.touchEnd = { x: e.changedTouches[0].screenX, y: e.changedTouches[0].screenY, time: Date.now() }; this.handleSwipe(); }, { passive: true }); } handleSwipe() { if (!this.touchStart || !this.touchEnd) return; const deltaX = this.touchEnd.x - this.touchStart.x; const deltaY = this.touchEnd.y - this.touchStart.y; const deltaTime = this.touchEnd.time - this.touchStart.time; if (deltaTime > this.maxSwipeTime) return; const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance < this.minSwipeDistance) return; // Determine swipe direction if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe if (deltaX > 0) { this.dispatchSwipeEvent('swiperight'); } else { this.dispatchSwipeEvent('swipeleft'); } } else { // Vertical swipe if (deltaY > 0) { this.dispatchSwipeEvent('swipedown'); } else { this.dispatchSwipeEvent('swipeup'); } } } dispatchSwipeEvent(type) { document.dispatchEvent(new CustomEvent(type, { detail: { startPos: this.touchStart, endPos: this.touchEnd } })); } } // Virtual Scrolling for Large Datasets class VirtualScrollManager { constructor(container, itemHeight = 50) { this.container = container; this.itemHeight = itemHeight; this.viewportHeight = container.clientHeight; this.totalItems = 0; this.visibleStart = 0; this.visibleEnd = 0; this.scrollContent = null; this.renderCallback = null; } init(items, renderCallback) { this.totalItems = items.length; this.renderCallback = renderCallback; this.setupVirtualScroll(); this.updateVisibleRange(); } setupVirtualScroll() { this.container.innerHTML = `
`; this.scrollContent = this.container.querySelector('.virtual-scroll-viewport'); this.container.addEventListener('scroll', Utils.throttle(() => { this.updateVisibleRange(); }, 16)); // 60fps } updateVisibleRange() { const scrollTop = this.container.scrollTop; const buffer = Math.ceil(this.viewportHeight / this.itemHeight); this.visibleStart = Math.max(0, Math.floor(scrollTop / this.itemHeight) - buffer); this.visibleEnd = Math.min(this.totalItems, this.visibleStart + buffer * 3); this.renderVisibleItems(); } renderVisibleItems() { if (!this.renderCallback) return; const fragment = document.createDocumentFragment(); for (let i = this.visibleStart; i < this.visibleEnd; i++) { const item = this.renderCallback(i); item.style.position = 'absolute'; item.style.top = `${i * this.itemHeight}px`; item.style.height = `${this.itemHeight}px`; item.style.width = '100%'; fragment.appendChild(item); } this.scrollContent.innerHTML = ''; this.scrollContent.appendChild(fragment); } } // Enhanced Search with Debouncing and Suggestions class SearchManager { constructor(input, suggestionsContainer) { this.input = input; this.suggestionsContainer = suggestionsContainer; this.currentIndex = -1; this.suggestions = []; this.setupEventListeners(); } setupEventListeners() { this.input.addEventListener('input', Utils.debounce((e) => { this.handleInput(e.target.value); }, 300)); this.input.addEventListener('keydown', (e) => { this.handleKeyDown(e); }); this.input.addEventListener('blur', () => { setTimeout(() => this.hideSuggestions(), 200); }); document.addEventListener('click', (e) => { if (!this.input.contains(e.target) && !this.suggestionsContainer.contains(e.target)) { this.hideSuggestions(); } }); } async handleInput(query) { if (query.length < 2) { this.hideSuggestions(); return; } try { this.suggestions = await this.fetchSuggestions(query); this.renderSuggestions(); } catch (error) { console.error('Error fetching suggestions:', error); } } async fetchSuggestions(query) { // In a real app, this would fetch from an API // For now, we'll filter from existing data const allHosts = Object.keys(window.fileHostsData || {}); return allHosts .filter(host => host.toLowerCase().includes(query.toLowerCase())) .slice(0, 5); } renderSuggestions() { if (this.suggestions.length === 0) { this.hideSuggestions(); return; } this.suggestionsContainer.innerHTML = this.suggestions .map((suggestion, index) => `
${this.highlightMatch(suggestion, this.input.value)}
`) .join(''); this.suggestionsContainer.classList.add('visible'); this.currentIndex = -1; // Add click listeners this.suggestionsContainer.querySelectorAll('.search-suggestion').forEach((el, index) => { el.addEventListener('click', () => { this.selectSuggestion(index); }); }); } highlightMatch(text, query) { const regex = new RegExp(`(${query})`, 'gi'); return text.replace(regex, '$1'); } handleKeyDown(e) { if (!this.suggestionsContainer.classList.contains('visible')) return; switch (e.key) { case 'ArrowDown': e.preventDefault(); this.navigateSuggestions(1); break; case 'ArrowUp': e.preventDefault(); this.navigateSuggestions(-1); break; case 'Enter': e.preventDefault(); if (this.currentIndex >= 0) { this.selectSuggestion(this.currentIndex); } break; case 'Escape': this.hideSuggestions(); break; } } navigateSuggestions(direction) { const suggestions = this.suggestionsContainer.querySelectorAll('.search-suggestion'); // Remove current highlight if (this.currentIndex >= 0) { suggestions[this.currentIndex].classList.remove('highlighted'); } // Update index this.currentIndex += direction; if (this.currentIndex < 0) this.currentIndex = suggestions.length - 1; if (this.currentIndex >= suggestions.length) this.currentIndex = 0; // Add new highlight suggestions[this.currentIndex].classList.add('highlighted'); suggestions[this.currentIndex].scrollIntoView({ block: 'nearest' }); } selectSuggestion(index) { this.input.value = this.suggestions[index]; this.hideSuggestions(); this.input.dispatchEvent(new Event('input')); } hideSuggestions() { this.suggestionsContainer.classList.remove('visible'); this.currentIndex = -1; } } // Pull-to-Refresh for Mobile class PullToRefreshManager { constructor(container, callback) { this.container = container; this.callback = callback; this.isRefreshing = false; this.startY = 0; this.currentY = 0; this.threshold = 80; this.setupPullToRefresh(); } setupPullToRefresh() { this.container.classList.add('pull-to-refresh'); const indicator = document.createElement('div'); indicator.className = 'pull-to-refresh-indicator'; indicator.innerHTML = '
'; this.container.appendChild(indicator); this.container.addEventListener('touchstart', (e) => { if (this.container.scrollTop === 0 && !this.isRefreshing) { this.startY = e.touches[0].clientY; } }, { passive: true }); this.container.addEventListener('touchmove', (e) => { if (this.startY && !this.isRefreshing) { this.currentY = e.touches[0].clientY; const pullDistance = this.currentY - this.startY; if (pullDistance > 0 && this.container.scrollTop === 0) { e.preventDefault(); const pullRatio = Math.min(pullDistance / this.threshold, 1); if (pullDistance > this.threshold) { this.container.classList.add('pulling'); } } } }, { passive: false }); this.container.addEventListener('touchend', () => { if (this.startY && this.currentY && !this.isRefreshing) { const pullDistance = this.currentY - this.startY; if (pullDistance > this.threshold) { this.triggerRefresh(); } } this.container.classList.remove('pulling'); this.startY = 0; this.currentY = 0; }, { passive: true }); } async triggerRefresh() { if (this.isRefreshing) return; this.isRefreshing = true; try { await this.callback(); } catch (error) { console.error('Refresh failed:', error); } finally { this.isRefreshing = false; } } } // Toast Notification System class ToastManager { constructor() { this.container = this.createContainer(); this.toasts = new Map(); } createContainer() { let container = document.querySelector('.toast-container'); if (!container) { container = document.createElement('div'); container.className = 'toast-container'; document.body.appendChild(container); } return container; } show(message, type = 'info', duration = 3000) { const id = Date.now().toString(); const toast = this.createToast(message, type, id); this.container.appendChild(toast); this.toasts.set(id, toast); // Auto-remove after duration if (duration > 0) { setTimeout(() => { this.remove(id); }, duration); } return id; } createToast(message, type, id) { const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.setAttribute('data-toast-id', id); toast.innerHTML = `
${message}
`; const closeBtn = toast.querySelector('.toast-close'); closeBtn.addEventListener('click', () => { this.remove(id); }); return toast; } remove(id) { const toast = this.toasts.get(id); if (toast) { toast.style.animation = 'slideOutRight 0.3s ease forwards'; setTimeout(() => { if (toast.parentNode) { this.container.removeChild(toast); } this.toasts.delete(id); }, 300); } } success(message, duration = 3000) { return this.show(message, 'success', duration); } error(message, duration = 5000) { return this.show(message, 'error', duration); } warning(message, duration = 4000) { return this.show(message, 'warning', duration); } info(message, duration = 3000) { return this.show(message, 'info', duration); } } // Mobile Navigation Manager class MobileNavManager { constructor() { this.setupMobileNav(); } setupMobileNav() { if (window.innerWidth <= 768) { this.createMobileNav(); } window.addEventListener('resize', Utils.debounce(() => { if (window.innerWidth <= 768) { this.createMobileNav(); } else { this.removeMobileNav(); } }, 300)); } createMobileNav() { if (document.querySelector('.mobile-nav-toggle')) return; const toggle = document.createElement('button'); toggle.className = 'mobile-nav-toggle'; toggle.innerHTML = '☰'; toggle.setAttribute('aria-label', 'Toggle mobile navigation'); const menu = document.createElement('div'); menu.className = 'mobile-nav-menu'; menu.innerHTML = ` Pricing File Hosts Compare Adult Hosts Status `; document.body.appendChild(toggle); document.body.appendChild(menu); toggle.addEventListener('click', () => { menu.classList.toggle('visible'); }); // Close menu when clicking outside document.addEventListener('click', (e) => { if (!toggle.contains(e.target) && !menu.contains(e.target)) { menu.classList.remove('visible'); } }); // Close menu when clicking on a link menu.querySelectorAll('.mobile-nav-item').forEach(link => { link.addEventListener('click', () => { menu.classList.remove('visible'); }); }); } removeMobileNav() { const toggle = document.querySelector('.mobile-nav-toggle'); const menu = document.querySelector('.mobile-nav-menu'); if (toggle) toggle.remove(); if (menu) menu.remove(); } } // Progressive Enhancement Manager class ProgressiveEnhancementManager { constructor() { this.features = new Map(); this.detectFeatures(); } detectFeatures() { // Detect various browser capabilities this.features.set('intersectionObserver', 'IntersectionObserver' in window); this.features.set('webp', this.supportsWebP()); this.features.set('serviceWorker', 'serviceWorker' in navigator); this.features.set('touchEvents', 'ontouchstart' in window); this.features.set('pointerEvents', 'PointerEvent' in window); this.features.set('containerQueries', CSS.supports('container-type: inline-size')); } async supportsWebP() { return new Promise((resolve) => { const webP = new Image(); webP.onload = webP.onerror = () => { resolve(webP.height === 2); }; webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA'; }); } hasFeature(feature) { return this.features.get(feature) || false; } enhanceIfSupported(feature, enhancementCallback) { if (this.hasFeature(feature)) { enhancementCallback(); } } } // Initialize modern mobile-first features const touchGestureManager = new TouchGestureManager(); const toastManager = new ToastManager(); const mobileNavManager = new MobileNavManager(); const progressiveEnhancementManager = new ProgressiveEnhancementManager(); // Initialize search enhancements document.addEventListener('DOMContentLoaded', () => { // Enhanced search for file hosts const fileHostSearch = document.querySelector('#file-host-search'); const fileHostSuggestions = document.createElement('div'); fileHostSuggestions.className = 'search-suggestions'; if (fileHostSearch) { fileHostSearch.parentNode.classList.add('search-container'); fileHostSearch.parentNode.appendChild(fileHostSuggestions); new SearchManager(fileHostSearch, fileHostSuggestions); } // Enhanced search for adult hosts const adultHostSearch = document.querySelector('#adult-host-search'); const adultHostSuggestions = document.createElement('div'); adultHostSuggestions.className = 'search-suggestions'; if (adultHostSearch) { adultHostSearch.parentNode.classList.add('search-container'); adultHostSearch.parentNode.appendChild(adultHostSuggestions); new SearchManager(adultHostSearch, adultHostSuggestions); } // Setup pull-to-refresh for mobile if (progressiveEnhancementManager.hasFeature('touchEvents')) { const mainContent = document.querySelector('main'); if (mainContent) { new PullToRefreshManager(mainContent, async () => { toastManager.info('Refreshing data...'); // Simulate refresh await new Promise(resolve => setTimeout(resolve, 1000)); window.location.reload(); }); } } // Add scroll progress indicator const scrollIndicator = document.createElement('div'); scrollIndicator.className = 'scroll-indicator'; scrollIndicator.innerHTML = '
'; document.body.appendChild(scrollIndicator); const updateScrollProgress = () => { const winScroll = document.body.scrollTop || document.documentElement.scrollTop; const height = document.documentElement.scrollHeight - document.documentElement.clientHeight; const scrolled = (winScroll / height) * 100; const bar = scrollIndicator.querySelector('.scroll-indicator-bar'); bar.style.width = scrolled + '%'; }; window.addEventListener('scroll', Utils.throttle(updateScrollProgress, 16)); }); // Enhanced swipe gesture handling document.addEventListener('swipeleft', (e) => { // Handle left swipe - could navigate to next section console.log('Swiped left'); }); document.addEventListener('swiperight', (e) => { // Handle right swipe - could navigate to previous section console.log('Swiped right'); }); // Service Worker registration enhancement if (progressiveEnhancementManager.hasFeature('serviceWorker')) { navigator.serviceWorker.register('/sw.js') .then((registration) => { console.log('ServiceWorker registration successful'); // Check for updates registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { toastManager.info('New version available! Refresh to update.', 0); } }); }); }) .catch((error) => { console.log('ServiceWorker registration failed:', error); }); } // Performance monitoring if ('PerformanceObserver' in window) { const perfObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'largest-contentful-paint') { console.log(`LCP: ${entry.startTime}ms`); } if (entry.entryType === 'first-input') { console.log(`FID: ${entry.processingStart - entry.startTime}ms`); } } }); try { perfObserver.observe({ entryTypes: ['largest-contentful-paint', 'first-input'] }); } catch (e) { console.log('Performance observation not supported'); } }