improved site

This commit is contained in:
fynks 2025-08-18 19:40:43 +05:00
parent a113f98e64
commit 08ed1660c9
3 changed files with 1473 additions and 1394 deletions

2533
dist/css/styles.css vendored

File diff suppressed because it is too large Load diff

160
dist/index.html vendored

File diff suppressed because one or more lines are too long

174
dist/js/app.js vendored
View file

@ -3,31 +3,29 @@
* Modern, feature-rich JavaScript with performance optimizations and professional UX * Modern, feature-rich JavaScript with performance optimizations and professional UX
*/ */
// Modern utility functions // Modern utility functions with optimized arrow syntax
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 timeout; let timeoutId;
return function executedFunction(...args) { return (...args) => {
const later = () => { const later = () => {
timeout = null; timeoutId = null;
if (!immediate) func.apply(this, args); if (!immediate) func(...args);
}; };
const callNow = immediate && !timeout; const callNow = immediate && !timeoutId;
clearTimeout(timeout); clearTimeout(timeoutId);
timeout = setTimeout(later, delay); timeoutId = setTimeout(later, delay);
if (callNow) func.apply(this, args); if (callNow) func(...args);
}; };
}, },
// Throttle function for scroll events // Throttle function for scroll events
throttle: (func, limit) => { throttle: (func, limit) => {
let inThrottle; let inThrottle;
return function() { return function(...args) {
const args = arguments;
const context = this;
if (!inThrottle) { if (!inThrottle) {
func.apply(context, args); func.apply(this, args);
inThrottle = true; inThrottle = true;
setTimeout(() => inThrottle = false, limit); setTimeout(() => inThrottle = false, limit);
} }
@ -36,9 +34,10 @@ const Utils = {
// Animate elements with Intersection Observer // Animate elements with Intersection Observer
animateOnScroll: (elements, options = {}) => { animateOnScroll: (elements, options = {}) => {
const defaultOptions = { const observerOptions = {
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) => {
@ -48,17 +47,17 @@ const Utils = {
observer.unobserve(entry.target); observer.unobserve(entry.target);
} }
}); });
}, { ...defaultOptions, ...options }); }, observerOptions);
elements.forEach(el => observer.observe(el)); elements.forEach(el => observer.observe(el));
} }
}; };
// Enhanced notification system // Enhanced notification system with optimized DOM handling
class NotificationManager { class NotificationManager {
constructor() { constructor() {
this.container = this.createContainer(); this.container = this.createContainer();
this.notifications = new Set(); this.notifications = new Map();
} }
createContainer() { createContainer() {
@ -70,8 +69,8 @@ class NotificationManager {
} }
show(message, type = 'info', duration = 4000) { show(message, type = 'info', duration = 4000) {
const notification = document.createElement('div');
const id = Date.now().toString(); const id = Date.now().toString();
const notification = document.createElement('div');
notification.className = `notification notification--${type}`; notification.className = `notification notification--${type}`;
notification.setAttribute('role', 'alert'); notification.setAttribute('role', 'alert');
@ -91,14 +90,15 @@ class NotificationManager {
`; `;
this.container.appendChild(notification); this.container.appendChild(notification);
this.notifications.add(id); this.notifications.set(id, notification);
// Auto dismiss // Auto dismiss
setTimeout(() => this.dismiss(notification, id), duration); const timeoutId = setTimeout(() => this.dismiss(id), duration);
notification.dataset.timeoutId = timeoutId;
// Manual dismiss // Manual dismiss
notification.querySelector('.notification__close').addEventListener('click', () => { notification.querySelector('.notification__close').addEventListener('click', () => {
this.dismiss(notification, id); this.dismiss(id);
}); });
// Animate in // Animate in
@ -119,10 +119,17 @@ class NotificationManager {
return icons[type] || icons.info; return icons[type] || icons.info;
} }
dismiss(notification, id) { dismiss(id) {
const notification = this.notifications.get(id);
if (!notification) return;
notification.classList.add('notification--dismissing'); notification.classList.add('notification--dismissing');
this.notifications.delete(id); this.notifications.delete(id);
// Clear auto-dismiss timeout
const timeoutId = notification.dataset.timeoutId;
if (timeoutId) clearTimeout(parseInt(timeoutId, 10));
setTimeout(() => { setTimeout(() => {
if (notification.parentNode) { if (notification.parentNode) {
this.container.removeChild(notification); this.container.removeChild(notification);
@ -131,17 +138,17 @@ class NotificationManager {
} }
} }
// Enhanced loading manager // Enhanced loading manager with optimized DOM handling
class LoadingManager { class LoadingManager {
constructor() { constructor() {
this.activeLoaders = new Set(); this.activeLoaders = new Map();
} }
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.setAttribute('data-loader-id', loaderId); loader.dataset.loaderId = loaderId;
loader.innerHTML = ` loader.innerHTML = `
<div class="loading-content"> <div class="loading-content">
<div class="loading-spinner"> <div class="loading-spinner">
@ -153,13 +160,10 @@ class LoadingManager {
</div> </div>
`; `;
if (typeof target === 'string') { const targetElement = typeof target === 'string' ? document.querySelector(target) : target;
target = document.querySelector(target); targetElement.style.position = 'relative';
} 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');
@ -169,39 +173,34 @@ class LoadingManager {
} }
hide(target, loaderId) { hide(target, loaderId) {
if (typeof target === 'string') { const targetElement = typeof target === 'string' ? document.querySelector(target) : target;
target = document.querySelector(target); const loader = targetElement.querySelector(`[data-loader-id="${loaderId}"]`);
}
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) {
target.removeChild(loader); targetElement.removeChild(loader);
} }
}, 300); }, 300);
} }
} }
hideAll() { hideAll() {
this.activeLoaders.forEach(id => { this.activeLoaders.forEach(({ element, target }, id) => {
const loader = document.querySelector(`[data-loader-id="${id}"]`); this.hide(target, id);
if (loader) {
this.hide(loader.parentNode, id);
}
}); });
} }
} }
// Enhanced table manager with advanced features // Enhanced table manager with optimized rendering
class TableManager { class TableManager {
constructor(containerId, searchInputId, clearIconId, options = {}) { constructor(containerId, searchInputId, clearIconId, options = {}) {
this.container = document.querySelector(`#${containerId}`); this.container = document.getElementById(containerId);
this.searchInput = document.querySelector(`#${searchInputId}`); this.searchInput = document.getElementById(searchInputId);
this.clearIcon = document.querySelector(`#${clearIconId}`); this.clearIcon = document.getElementById(clearIconId);
this.options = { this.options = {
sortable: true, sortable: true,
filterable: true, filterable: true,
@ -270,7 +269,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">${host}</span> <span class="service-name">${this._escapeHtml(host)}</span>
</div> </div>
</td> </td>
${providers.map(provider => ` ${providers.map(provider => `
@ -288,6 +287,17 @@ 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();
@ -365,7 +375,7 @@ class TableManager {
} }
_handleSort(header) { _handleSort(header) {
const column = header.getAttribute('data-column'); const column = header.dataset.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';
@ -450,12 +460,12 @@ class TableManager {
} }
} }
// Enhanced comparison manager // Enhanced comparison manager with optimized rendering
class ComparisonManager { class ComparisonManager {
constructor(containerId, select1Id, select2Id, data) { constructor(containerId, select1Id, select2Id, data) {
this.container = document.querySelector(`#${containerId}`); this.container = document.getElementById(containerId);
this.select1 = document.querySelector(`#${select1Id}`); this.select1 = document.getElementById(select1Id);
this.select2 = document.querySelector(`#${select2Id}`); this.select2 = document.getElementById(select2Id);
this.data = data; this.data = data;
this.isComparing = false; this.isComparing = false;
@ -514,18 +524,18 @@ class ComparisonManager {
this.container.innerHTML = ` this.container.innerHTML = `
<div class="comparison-header"> <div class="comparison-header">
<h3>Comparing ${provider1} vs ${provider2}</h3> <h3>Comparing ${this._escapeHtml(provider1)} vs ${this._escapeHtml(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">${provider1} Only</span> <span class="stat-label">${this._escapeHtml(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">${provider2} Only</span> <span class="stat-label">${this._escapeHtml(provider2)} Only</span>
<span class="stat-value">${comparisonData.provider2Only}</span> <span class="stat-value">${comparisonData.provider2Only}</span>
</div> </div>
</div> </div>
@ -559,8 +569,8 @@ class ComparisonManager {
<thead> <thead>
<tr> <tr>
<th>Service Name</th> <th>Service Name</th>
<th class="provider-header ${provider1.toLowerCase()}">${provider1}</th> <th class="provider-header ${provider1.toLowerCase()}">${this._escapeHtml(provider1)}</th>
<th class="provider-header ${provider2.toLowerCase()}">${provider2}</th> <th class="provider-header ${provider2.toLowerCase()}">${this._escapeHtml(provider2)}</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
</thead> </thead>
@ -604,7 +614,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">${host}</td> <td class="service-name">${this._escapeHtml(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 ?
@ -622,13 +632,24 @@ class ComparisonManager {
</span> </span>
</td> </td>
<td class="status-text"> <td class="status-text">
<span class="status-badge ${statusClass}">${statusText}</span> <span class="status-badge ${statusClass}">${this._escapeHtml(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;
@ -664,7 +685,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.getAttribute('data-status'); const status = row.dataset.status;
let show = true; let show = true;
switch (filter) { switch (filter) {
@ -755,10 +776,10 @@ class ComparisonManager {
} }
} }
// Enhanced pricing manager // Enhanced pricing manager with optimized rendering
class PricingManager { class PricingManager {
constructor(containerId) { constructor(containerId) {
this.container = document.querySelector(`#${containerId}-table-container`); this.container = document.getElementById(`${containerId}-table-container`);
this.currentData = null; this.currentData = null;
} }
@ -780,7 +801,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">${service}</span> <span class="service-name">${this._escapeHtml(service)}</span>
</div> </div>
</th> </th>
`).join('')} `).join('')}
@ -790,7 +811,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>${plan.name}</strong> <strong>${this._escapeHtml(plan.name)}</strong>
</td> </td>
${services.map(service => { ${services.map(service => {
const price = plan[service]; const price = plan[service];
@ -799,7 +820,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">${price}</span> <span class="price-value">${this._escapeHtml(price)}</span>
${isNumeric ? '<span class="price-period">/month</span>' : ''} ${isNumeric ? '<span class="price-period">/month</span>' : ''}
</div> </div>
</td> </td>
@ -815,6 +836,17 @@ 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');
@ -842,7 +874,7 @@ class PricingManager {
} }
} }
// Performance monitor // Performance monitor with optimized metrics collection
class PerformanceMonitor { class PerformanceMonitor {
constructor() { constructor() {
this.metrics = { this.metrics = {
@ -876,7 +908,7 @@ const notificationManager = new NotificationManager();
const loadingManager = new LoadingManager(); const loadingManager = new LoadingManager();
const performanceMonitor = new PerformanceMonitor(); const performanceMonitor = new PerformanceMonitor();
// Enhanced application initialization // Enhanced application initialization with optimized async handling
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
performanceMonitor.markDOMReady(); performanceMonitor.markDOMReady();
@ -980,8 +1012,8 @@ function setupURLComparison() {
if (provider1 && provider2) { if (provider1 && provider2) {
setTimeout(() => { setTimeout(() => {
const select1 = document.querySelector('#provider1'); const select1 = document.getElementById('provider1');
const select2 = document.querySelector('#provider2'); const select2 = document.getElementById('provider2');
if (select1 && select2) { if (select1 && select2) {
select1.value = provider1; select1.value = provider1;