more web improvements

This commit is contained in:
fynks 2025-08-18 19:55:42 +05:00
parent 08ed1660c9
commit 4836ca5948
3 changed files with 345 additions and 542 deletions

213
dist/css/styles.css vendored
View file

@ -1301,50 +1301,72 @@ select:focus {
flex-shrink: 0; flex-shrink: 0;
margin-top: 2px; margin-top: 2px;
} }
/* Referral Section */
.referral-section { .referral-section {
margin-top: var(--space-16); margin-block: var(--space-16);
padding: var(--space-8); padding: var(--space-8);
background: var(--bg-secondary); background: var(--bg-secondary);
border-radius: var(--radius-xl); border-radius: var(--radius-xl);
border: 1px solid var(--border-primary); border: 1px solid var(--border-primary);
text-align: center;
} }
.referral-section h3 { .referral-header h3 {
margin: 0 0 var(--space-2); margin: 0 0 var(--space-2);
text-align: center; color: var(--text-primary);
color: var(--text-primary); font-size: var(--text-2xl);
font-weight: var(--font-bold);
} }
.referral-section p { .referral-header p {
text-align: center; margin: 0 auto var(--space-6);
color: var(--text-secondary); color: var(--text-secondary);
margin-bottom: var(--space-6); max-width: var(--content-max-width);
} }
.referral-grid { .referral-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: var(--space-4); gap: var(--space-4);
justify-items: center;
} }
.referral-card { .referral-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: var(--space-2); justify-content: center;
padding: var(--space-4); gap: var(--space-2);
background: var(--bg-primary); padding: var(--space-4) var(--space-3);
border: 1px solid var(--border-primary); background: var(--bg-primary);
border-radius: var(--radius-lg); border: 1px solid var(--border-primary);
text-decoration: none; border-radius: var(--radius-lg);
transition: all var(--transition-fast); text-decoration: none;
transition: var(--transition-all);
width: 100%;
max-width: 200px;
box-shadow: var(--shadow-sm);
} }
.referral-card:hover { .referral-card:hover {
border-color: var(--primary); border-color: var(--primary);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
}
.service-name {
font-weight: var(--font-semibold);
color: var(--text-primary);
font-size: var(--text-base);
}
.referral-badge {
font-size: var(--text-xs);
color: var(--primary);
background: rgb(99 102 241 / 0.1);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-md);
font-weight: var(--font-medium);
} }
.service-name { .service-name {
@ -1352,14 +1374,6 @@ select:focus {
color: var(--text-primary); color: var(--text-primary);
} }
.referral-badge {
font-size: var(--text-xs);
color: var(--primary);
background: rgb(99 102 241 / 0.1);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-md);
}
.status-grid { .status-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
@ -1436,103 +1450,6 @@ select:focus {
} }
} }
/* Notifications */
.notification-container {
position: fixed;
top: var(--space-6);
right: var(--space-6);
z-index: var(--z-tooltip);
max-width: 400px;
width: 100%;
}
.notification {
background: var(--bg-primary);
border: 1px solid var(--border-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-xl);
margin-bottom: var(--space-3);
opacity: 0;
transform: translateX(100%);
transition: all var(--transition-normal);
}
.notification--visible {
opacity: 1;
transform: translateX(0);
}
.notification--dismissing {
opacity: 0;
transform: translateX(100%);
}
.notification__content {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-4);
}
.notification__icon {
flex-shrink: 0;
margin-top: 2px;
}
.notification__message {
flex: 1;
font-size: var(--text-sm);
color: var(--text-primary);
}
.notification__close {
background: none;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: var(--space-1);
border-radius: var(--radius-md);
transition: all var(--transition-fast);
flex-shrink: 0;
}
.notification__close:hover {
background: var(--bg-secondary);
color: var(--text-primary);
}
.notification--success {
border-color: var(--success);
}
.notification--success .notification__icon {
color: var(--success);
}
.notification--error {
border-color: var(--error);
}
.notification--error .notification__icon {
color: var(--error);
}
.notification--warning {
border-color: var(--warning);
}
.notification--warning .notification__icon {
color: var(--warning);
}
.notification--info {
border-color: var(--info);
}
.notification--info .notification__icon {
color: var(--info);
}
/* Footer */ /* Footer */
.site-footer { .site-footer {
background: var(--bg-secondary); background: var(--bg-secondary);
@ -1736,9 +1653,6 @@ select:focus {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.referral-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.footer-content { .footer-content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@ -1808,24 +1722,6 @@ select:focus {
font-size: var(--text-xs); font-size: var(--text-xs);
} }
.notification-container {
left: var(--space-4);
right: var(--space-4);
max-width: none;
}
.notification {
transform: translateY(-100%);
}
.notification--visible {
transform: translateY(0);
}
.notification--dismissing {
transform: translateY(-100%);
}
th, th,
td { td {
padding: var(--space-3) var(--space-4); padding: var(--space-3) var(--space-4);
@ -1928,7 +1824,6 @@ select:focus {
.site-footer, .site-footer,
.back-to-top, .back-to-top,
.dark-mode-toggle, .dark-mode-toggle,
.notification-container,
.comparison-filters, .comparison-filters,
.comparison-actions, .comparison-actions,
#close-compare { #close-compare {
@ -1971,10 +1866,6 @@ select:focus {
html { html {
scroll-behavior: auto; scroll-behavior: auto;
} }
.notification {
transition: none;
}
} }
/* High Contrast */ /* High Contrast */

407
dist/index.html vendored

File diff suppressed because one or more lines are too long

247
dist/js/app.js vendored
View file

@ -3,29 +3,31 @@
* Modern, feature-rich JavaScript with performance optimizations and professional UX * Modern, feature-rich JavaScript with performance optimizations and professional UX
*/ */
// Modern utility functions with optimized arrow syntax // Modern utility functions
const Utils = { const Utils = {
// Enhanced debounce with immediate option // Enhanced debounce with immediate option
debounce: (func, delay = 300, immediate = false) => { debounce: (func, delay = 300, immediate = false) => {
let timeoutId; let timeout;
return (...args) => { return function executedFunction(...args) {
const later = () => { const later = () => {
timeoutId = null; timeout = null;
if (!immediate) func(...args); if (!immediate) func.apply(this, args);
}; };
const callNow = immediate && !timeoutId; const callNow = immediate && !timeout;
clearTimeout(timeoutId); clearTimeout(timeout);
timeoutId = setTimeout(later, delay); timeout = setTimeout(later, delay);
if (callNow) func(...args); if (callNow) func.apply(this, args);
}; };
}, },
// Throttle function for scroll events // Throttle function for scroll events
throttle: (func, limit) => { throttle: (func, limit) => {
let inThrottle; let inThrottle;
return function(...args) { return function() {
const args = arguments;
const context = this;
if (!inThrottle) { if (!inThrottle) {
func.apply(this, args); func.apply(context, args);
inThrottle = true; inThrottle = true;
setTimeout(() => inThrottle = false, limit); setTimeout(() => inThrottle = false, limit);
} }
@ -34,10 +36,9 @@ const Utils = {
// Animate elements with Intersection Observer // Animate elements with Intersection Observer
animateOnScroll: (elements, options = {}) => { animateOnScroll: (elements, options = {}) => {
const observerOptions = { const defaultOptions = {
threshold: 0.1, threshold: 0.1,
rootMargin: '0px 0px -50px 0px', rootMargin: '0px 0px -50px 0px'
...options
}; };
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
@ -47,108 +48,23 @@ const Utils = {
observer.unobserve(entry.target); observer.unobserve(entry.target);
} }
}); });
}, observerOptions); }, { ...defaultOptions, ...options });
elements.forEach(el => observer.observe(el)); elements.forEach(el => observer.observe(el));
} }
}; };
// Enhanced notification system with optimized DOM handling // Enhanced loading manager
class NotificationManager {
constructor() {
this.container = this.createContainer();
this.notifications = new Map();
}
createContainer() {
const container = document.createElement('div');
container.className = 'notification-container';
container.setAttribute('aria-live', 'polite');
document.body.appendChild(container);
return container;
}
show(message, type = 'info', duration = 4000) {
const id = Date.now().toString();
const notification = document.createElement('div');
notification.className = `notification notification--${type}`;
notification.setAttribute('role', 'alert');
notification.innerHTML = `
<div class="notification__content">
<svg class="notification__icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
${this.getIcon(type)}
</svg>
<span class="notification__message">${message}</span>
<button class="notification__close" aria-label="Close notification">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
`;
this.container.appendChild(notification);
this.notifications.set(id, notification);
// Auto dismiss
const timeoutId = setTimeout(() => this.dismiss(id), duration);
notification.dataset.timeoutId = timeoutId;
// Manual dismiss
notification.querySelector('.notification__close').addEventListener('click', () => {
this.dismiss(id);
});
// Animate in
requestAnimationFrame(() => {
notification.classList.add('notification--visible');
});
return id;
}
getIcon(type) {
const icons = {
success: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22,4 12,14.01 9,11.01"></polyline>',
error: '<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',
warning: '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>',
info: '<circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path>'
};
return icons[type] || icons.info;
}
dismiss(id) {
const notification = this.notifications.get(id);
if (!notification) return;
notification.classList.add('notification--dismissing');
this.notifications.delete(id);
// Clear auto-dismiss timeout
const timeoutId = notification.dataset.timeoutId;
if (timeoutId) clearTimeout(parseInt(timeoutId, 10));
setTimeout(() => {
if (notification.parentNode) {
this.container.removeChild(notification);
}
}, 300);
}
}
// Enhanced loading manager with optimized DOM handling
class LoadingManager { class LoadingManager {
constructor() { constructor() {
this.activeLoaders = new Map(); this.activeLoaders = new Set();
} }
show(target, text = 'Loading...') { show(target, text = 'Loading...') {
const loaderId = Date.now().toString(); const loaderId = Date.now().toString();
const loader = document.createElement('div'); const loader = document.createElement('div');
loader.className = 'loading-overlay'; loader.className = 'loading-overlay';
loader.dataset.loaderId = loaderId; loader.setAttribute('data-loader-id', loaderId);
loader.innerHTML = ` loader.innerHTML = `
<div class="loading-content"> <div class="loading-content">
<div class="loading-spinner"> <div class="loading-spinner">
@ -160,10 +76,13 @@ class LoadingManager {
</div> </div>
`; `;
const targetElement = typeof target === 'string' ? document.querySelector(target) : target; if (typeof target === 'string') {
targetElement.style.position = 'relative'; target = document.querySelector(target);
targetElement.appendChild(loader); }
this.activeLoaders.set(loaderId, { element: loader, target: targetElement });
target.style.position = 'relative';
target.appendChild(loader);
this.activeLoaders.add(loaderId);
requestAnimationFrame(() => { requestAnimationFrame(() => {
loader.classList.add('loading-overlay--visible'); loader.classList.add('loading-overlay--visible');
@ -173,34 +92,39 @@ class LoadingManager {
} }
hide(target, loaderId) { hide(target, loaderId) {
const targetElement = typeof target === 'string' ? document.querySelector(target) : target; if (typeof target === 'string') {
const loader = targetElement.querySelector(`[data-loader-id="${loaderId}"]`); target = document.querySelector(target);
}
const loader = target.querySelector(`[data-loader-id="${loaderId}"]`);
if (loader) { if (loader) {
loader.classList.add('loading-overlay--hiding'); loader.classList.add('loading-overlay--hiding');
this.activeLoaders.delete(loaderId); this.activeLoaders.delete(loaderId);
setTimeout(() => { setTimeout(() => {
if (loader.parentNode) { if (loader.parentNode) {
targetElement.removeChild(loader); target.removeChild(loader);
} }
}, 300); }, 300);
} }
} }
hideAll() { hideAll() {
this.activeLoaders.forEach(({ element, target }, id) => { this.activeLoaders.forEach(id => {
this.hide(target, id); const loader = document.querySelector(`[data-loader-id="${id}"]`);
if (loader) {
this.hide(loader.parentNode, id);
}
}); });
} }
} }
// Enhanced table manager with optimized rendering // Enhanced table manager with advanced features
class TableManager { class TableManager {
constructor(containerId, searchInputId, clearIconId, options = {}) { constructor(containerId, searchInputId, clearIconId, options = {}) {
this.container = document.getElementById(containerId); this.container = document.querySelector(`#${containerId}`);
this.searchInput = document.getElementById(searchInputId); this.searchInput = document.querySelector(`#${searchInputId}`);
this.clearIcon = document.getElementById(clearIconId); this.clearIcon = document.querySelector(`#${clearIconId}`);
this.options = { this.options = {
sortable: true, sortable: true,
filterable: true, filterable: true,
@ -269,7 +193,7 @@ class TableManager {
<tr data-host="${host.toLowerCase()}" role="row"> <tr data-host="${host.toLowerCase()}" role="row">
<td class="service-cell" role="gridcell"> <td class="service-cell" role="gridcell">
<div class="service-info"> <div class="service-info">
<span class="service-name">${this._escapeHtml(host)}</span> <span class="service-name">${host}</span>
</div> </div>
</td> </td>
${providers.map(provider => ` ${providers.map(provider => `
@ -287,17 +211,6 @@ class TableManager {
`).join(''); `).join('');
} }
_escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '<',
'>': '>',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
_setupSearchFunctionality() { _setupSearchFunctionality() {
const handleSearch = Utils.debounce(() => { const handleSearch = Utils.debounce(() => {
const searchTerm = this.searchInput.value.toLowerCase().trim(); const searchTerm = this.searchInput.value.toLowerCase().trim();
@ -375,7 +288,7 @@ class TableManager {
} }
_handleSort(header) { _handleSort(header) {
const column = header.dataset.column; const column = header.getAttribute('data-column');
const currentDirection = this.currentSort.column === column ? this.currentSort.direction : 'asc'; const currentDirection = this.currentSort.column === column ? this.currentSort.direction : 'asc';
const newDirection = currentDirection === 'asc' ? 'desc' : 'asc'; const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
@ -460,12 +373,12 @@ class TableManager {
} }
} }
// Enhanced comparison manager with optimized rendering // Enhanced comparison manager
class ComparisonManager { class ComparisonManager {
constructor(containerId, select1Id, select2Id, data) { constructor(containerId, select1Id, select2Id, data) {
this.container = document.getElementById(containerId); this.container = document.querySelector(`#${containerId}`);
this.select1 = document.getElementById(select1Id); this.select1 = document.querySelector(`#${select1Id}`);
this.select2 = document.getElementById(select2Id); this.select2 = document.querySelector(`#${select2Id}`);
this.data = data; this.data = data;
this.isComparing = false; this.isComparing = false;
@ -524,18 +437,18 @@ class ComparisonManager {
this.container.innerHTML = ` this.container.innerHTML = `
<div class="comparison-header"> <div class="comparison-header">
<h3>Comparing ${this._escapeHtml(provider1)} vs ${this._escapeHtml(provider2)}</h3> <h3>Comparing ${provider1} vs ${provider2}</h3>
<div class="comparison-stats"> <div class="comparison-stats">
<div class="stat"> <div class="stat">
<span class="stat-label">Shared Support</span> <span class="stat-label">Shared Support</span>
<span class="stat-value">${comparisonData.shared}</span> <span class="stat-value">${comparisonData.shared}</span>
</div> </div>
<div class="stat"> <div class="stat">
<span class="stat-label">${this._escapeHtml(provider1)} Only</span> <span class="stat-label">${provider1} Only</span>
<span class="stat-value">${comparisonData.provider1Only}</span> <span class="stat-value">${comparisonData.provider1Only}</span>
</div> </div>
<div class="stat"> <div class="stat">
<span class="stat-label">${this._escapeHtml(provider2)} Only</span> <span class="stat-label">${provider2} Only</span>
<span class="stat-value">${comparisonData.provider2Only}</span> <span class="stat-value">${comparisonData.provider2Only}</span>
</div> </div>
</div> </div>
@ -569,8 +482,8 @@ class ComparisonManager {
<thead> <thead>
<tr> <tr>
<th>Service Name</th> <th>Service Name</th>
<th class="provider-header ${provider1.toLowerCase()}">${this._escapeHtml(provider1)}</th> <th class="provider-header ${provider1.toLowerCase()}">${provider1}</th>
<th class="provider-header ${provider2.toLowerCase()}">${this._escapeHtml(provider2)}</th> <th class="provider-header ${provider2.toLowerCase()}">${provider2}</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
</thead> </thead>
@ -614,7 +527,7 @@ class ComparisonManager {
return ` return `
<tr class="comparison-row ${statusClass}" data-status="${statusClass}"> <tr class="comparison-row ${statusClass}" data-status="${statusClass}">
<td class="service-name">${this._escapeHtml(host)}</td> <td class="service-name">${host}</td>
<td class="support-status ${support1 ? 'supported' : 'not-supported'}"> <td class="support-status ${support1 ? 'supported' : 'not-supported'}">
<span class="status-indicator" aria-label="${support1 ? 'Supported' : 'Not supported'}"> <span class="status-indicator" aria-label="${support1 ? 'Supported' : 'Not supported'}">
${support1 ? ${support1 ?
@ -632,24 +545,13 @@ class ComparisonManager {
</span> </span>
</td> </td>
<td class="status-text"> <td class="status-text">
<span class="status-badge ${statusClass}">${this._escapeHtml(statusText)}</span> <span class="status-badge ${statusClass}">${statusText}</span>
</td> </td>
</tr> </tr>
`; `;
}).join(''); }).join('');
} }
_escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '<',
'>': '>',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
_calculateComparisonStats(provider1, provider2) { _calculateComparisonStats(provider1, provider2) {
let shared = 0; let shared = 0;
let provider1Only = 0; let provider1Only = 0;
@ -685,7 +587,7 @@ class ComparisonManager {
const rows = this.container.querySelectorAll('.comparison-row'); const rows = this.container.querySelectorAll('.comparison-row');
rows.forEach(row => { rows.forEach(row => {
const status = row.dataset.status; const status = row.getAttribute('data-status');
let show = true; let show = true;
switch (filter) { switch (filter) {
@ -776,10 +678,10 @@ class ComparisonManager {
} }
} }
// Enhanced pricing manager with optimized rendering // Enhanced pricing manager
class PricingManager { class PricingManager {
constructor(containerId) { constructor(containerId) {
this.container = document.getElementById(`${containerId}-table-container`); this.container = document.querySelector(`#${containerId}-table-container`);
this.currentData = null; this.currentData = null;
} }
@ -801,7 +703,7 @@ class PricingManager {
${services.map(service => ` ${services.map(service => `
<th class="service-column"> <th class="service-column">
<div class="service-header"> <div class="service-header">
<span class="service-name">${this._escapeHtml(service)}</span> <span class="service-name">${service}</span>
</div> </div>
</th> </th>
`).join('')} `).join('')}
@ -811,7 +713,7 @@ class PricingManager {
${data.plans.map((plan, index) => ` ${data.plans.map((plan, index) => `
<tr class="pricing-row ${index % 2 === 0 ? 'even' : 'odd'}"> <tr class="pricing-row ${index % 2 === 0 ? 'even' : 'odd'}">
<td class="plan-name"> <td class="plan-name">
<strong>${this._escapeHtml(plan.name)}</strong> <strong>${plan.name}</strong>
</td> </td>
${services.map(service => { ${services.map(service => {
const price = plan[service]; const price = plan[service];
@ -820,7 +722,7 @@ class PricingManager {
return ` return `
<td class="price-cell ${isNumeric ? 'numeric-price' : 'text-price'}"> <td class="price-cell ${isNumeric ? 'numeric-price' : 'text-price'}">
<div class="price-content"> <div class="price-content">
<span class="price-value">${this._escapeHtml(price)}</span> <span class="price-value">${price}</span>
${isNumeric ? '<span class="price-period">/month</span>' : ''} ${isNumeric ? '<span class="price-period">/month</span>' : ''}
</div> </div>
</td> </td>
@ -836,17 +738,6 @@ class PricingManager {
this._setupPricingFeatures(); this._setupPricingFeatures();
} }
_escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '<',
'>': '>',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
_setupPricingFeatures() { _setupPricingFeatures() {
// Add hover effects for better UX // Add hover effects for better UX
const priceCells = this.container.querySelectorAll('.price-cell'); const priceCells = this.container.querySelectorAll('.price-cell');
@ -874,7 +765,7 @@ class PricingManager {
} }
} }
// Performance monitor with optimized metrics collection // Performance monitor
class PerformanceMonitor { class PerformanceMonitor {
constructor() { constructor() {
this.metrics = { this.metrics = {
@ -904,11 +795,10 @@ class PerformanceMonitor {
} }
// Global instances // Global instances
const notificationManager = new NotificationManager();
const loadingManager = new LoadingManager(); const loadingManager = new LoadingManager();
const performanceMonitor = new PerformanceMonitor(); const performanceMonitor = new PerformanceMonitor();
// Enhanced application initialization with optimized async handling // Enhanced application initialization
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
performanceMonitor.markDOMReady(); performanceMonitor.markDOMReady();
@ -963,7 +853,6 @@ document.addEventListener('DOMContentLoaded', async () => {
new ComparisonManager('compare-table-container', 'provider1', 'provider2', fileHostsData.value); new ComparisonManager('compare-table-container', 'provider1', 'provider2', fileHostsData.value);
} else { } else {
loadingManager.hide('#file-hosts-table-container', loaderId1); loadingManager.hide('#file-hosts-table-container', loaderId1);
notificationManager.show('Failed to load file hosts data', 'error');
console.error('File hosts error:', fileHostsData.reason); console.error('File hosts error:', fileHostsData.reason);
} }
@ -972,7 +861,6 @@ document.addEventListener('DOMContentLoaded', async () => {
adultHostsTable.generateTable(adultHostsData.value); adultHostsTable.generateTable(adultHostsData.value);
} else { } else {
loadingManager.hide('#adult-hosts-table-container', loaderId2); loadingManager.hide('#adult-hosts-table-container', loaderId2);
notificationManager.show('Failed to load adult hosts data', 'error');
console.error('Adult hosts error:', adultHostsData.reason); console.error('Adult hosts error:', adultHostsData.reason);
} }
@ -981,7 +869,6 @@ document.addEventListener('DOMContentLoaded', async () => {
pricingManager.generatePricingTable(pricingData.value); pricingManager.generatePricingTable(pricingData.value);
} else { } else {
loadingManager.hide('#pricing-table-container', loaderId3); loadingManager.hide('#pricing-table-container', loaderId3);
notificationManager.show('Failed to load pricing data', 'error');
console.error('Pricing error:', pricingData.reason); console.error('Pricing error:', pricingData.reason);
} }
@ -995,11 +882,9 @@ document.addEventListener('DOMContentLoaded', async () => {
setupOfflineDetection(); setupOfflineDetection();
performanceMonitor.markFullyLoaded(); performanceMonitor.markFullyLoaded();
notificationManager.show('Application loaded successfully', 'success', 2000);
} catch (error) { } catch (error) {
console.error('Critical error initializing application:', error); console.error('Critical error initializing application:', error);
notificationManager.show('Failed to initialize application', 'error');
loadingManager.hideAll(); loadingManager.hideAll();
} }
}); });
@ -1012,8 +897,8 @@ function setupURLComparison() {
if (provider1 && provider2) { if (provider1 && provider2) {
setTimeout(() => { setTimeout(() => {
const select1 = document.getElementById('provider1'); const select1 = document.querySelector('#provider1');
const select2 = document.getElementById('provider2'); const select2 = document.querySelector('#provider2');
if (select1 && select2) { if (select1 && select2) {
select1.value = provider1; select1.value = provider1;
@ -1033,22 +918,20 @@ function setupScrollAnimations() {
// Offline detection // Offline detection
function setupOfflineDetection() { function setupOfflineDetection() {
window.addEventListener('online', () => { window.addEventListener('online', () => {
notificationManager.show('Connection restored', 'success', 3000); console.log('Connection restored');
}); });
window.addEventListener('offline', () => { window.addEventListener('offline', () => {
notificationManager.show('You are offline. Some features may not work.', 'warning', 5000); console.log('You are offline. Some features may not work.');
}); });
} }
// Enhanced error boundary // Enhanced error boundary
window.addEventListener('error', (event) => { window.addEventListener('error', (event) => {
console.error('Global error:', event.error); console.error('Global error:', event.error);
notificationManager.show('An unexpected error occurred', 'error');
}); });
window.addEventListener('unhandledrejection', (event) => { window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason); console.error('Unhandled promise rejection:', event.reason);
notificationManager.show('A network error occurred', 'error');
event.preventDefault(); event.preventDefault();
}); });