Update team visibility and refresh premium UI styling
This commit is contained in:
+88
-2
@@ -6,6 +6,22 @@
|
||||
// API Base URL - adjust based on your hosting setup
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// Central icon map: change values here to swap icons site-wide where data-icon-key is used.
|
||||
const ICON_CLASS_MAP = {
|
||||
service_it_support: 'fas fa-headset',
|
||||
service_cybersecurity: 'fas fa-shield-alt',
|
||||
service_cloud: 'fas fa-cloud',
|
||||
service_consulting: 'fas fa-chess-king',
|
||||
framework_iso27001: 'fas fa-certificate',
|
||||
framework_nist_csf: 'fas fa-sitemap',
|
||||
framework_soc2: 'fas fa-user-shield',
|
||||
framework_pci_dss: 'fas fa-credit-card',
|
||||
framework_hipaa: 'fas fa-heartbeat',
|
||||
framework_gdpr_ccpa: 'fas fa-scale-balanced',
|
||||
framework_nist_800: 'fas fa-building-shield',
|
||||
framework_cobit_telco: 'fas fa-network-wired'
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape HTML special characters to prevent XSS when inserting into innerHTML.
|
||||
* Use for ALL user/API data interpolated into template literals.
|
||||
@@ -40,6 +56,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all modules
|
||||
initPremiumExperience();
|
||||
initSiteLinkEnhancements();
|
||||
initReplaceableIcons();
|
||||
optimizeImagesForCLS();
|
||||
loadPublicSiteSettings();
|
||||
initPreloader();
|
||||
@@ -62,6 +79,50 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
loadTeam();
|
||||
});
|
||||
|
||||
function sanitizeIconClasses(iconClasses, fallback = 'fas fa-circle') {
|
||||
const tokens = String(iconClasses || '')
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.filter(token => /^fa[a-z0-9-]+$/i.test(token));
|
||||
|
||||
if (!tokens.length) {
|
||||
return fallback.split(/\s+/);
|
||||
}
|
||||
|
||||
const hasStyleToken = tokens.some(token => /^(fas|far|fab|fal|fat|fa-solid|fa-regular|fa-brands)$/i.test(token));
|
||||
if (!hasStyleToken) {
|
||||
tokens.unshift('fas');
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function initReplaceableIcons(scope = document) {
|
||||
const iconNodes = scope.querySelectorAll('[data-icon-key], [data-icon]');
|
||||
if (!iconNodes.length) return;
|
||||
|
||||
iconNodes.forEach(node => {
|
||||
const targetIcon = node.matches('i') ? node : node.querySelector('i');
|
||||
if (!targetIcon) return;
|
||||
|
||||
const explicitIcon = node.getAttribute('data-icon') || targetIcon.getAttribute('data-icon');
|
||||
const iconKey = node.getAttribute('data-icon-key') || targetIcon.getAttribute('data-icon-key');
|
||||
const mappedIcon = iconKey ? ICON_CLASS_MAP[iconKey] : '';
|
||||
const selected = explicitIcon || mappedIcon;
|
||||
if (!selected) return;
|
||||
|
||||
Array.from(targetIcon.classList).forEach(cls => {
|
||||
if (/^fa/i.test(cls)) {
|
||||
targetIcon.classList.remove(cls);
|
||||
}
|
||||
});
|
||||
|
||||
sanitizeIconClasses(selected).forEach(cls => targetIcon.classList.add(cls));
|
||||
targetIcon.setAttribute('aria-hidden', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
function initSiteLinkEnhancements() {
|
||||
// Fallback social URLs: only used for placeholder links (href="#" or "javascript:void(0)").
|
||||
// Preferred approach: set correct URLs directly in HTML. These are a safety net.
|
||||
@@ -536,6 +597,7 @@ function animateCounter(element, target) {
|
||||
}
|
||||
|
||||
/* ==================== Testimonials Slider ==================== */
|
||||
let _testimonialInterval = null;
|
||||
function initTestimonials() {
|
||||
const testimonials = document.querySelectorAll('.testimonial-card');
|
||||
const prevBtn = document.querySelector('.testimonial-prev');
|
||||
@@ -544,8 +606,17 @@ function initTestimonials() {
|
||||
|
||||
if (!testimonials.length) return;
|
||||
|
||||
// Clear any previous interval to prevent stacking
|
||||
if (_testimonialInterval) {
|
||||
clearInterval(_testimonialInterval);
|
||||
_testimonialInterval = null;
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
|
||||
// Clear previous dots before recreating
|
||||
if (dotsContainer) dotsContainer.innerHTML = '';
|
||||
|
||||
// Create dots
|
||||
testimonials.forEach((_, index) => {
|
||||
const dot = document.createElement('span');
|
||||
@@ -587,7 +658,7 @@ function initTestimonials() {
|
||||
testimonialContainer.addEventListener('focusin', () => { testimonialPaused = true; });
|
||||
testimonialContainer.addEventListener('focusout', () => { testimonialPaused = false; });
|
||||
}
|
||||
setInterval(() => {
|
||||
_testimonialInterval = setInterval(() => {
|
||||
if (!testimonialPaused) goToTestimonial(currentIndex + 1);
|
||||
}, 8000);
|
||||
}
|
||||
@@ -951,7 +1022,7 @@ async function loadServices() {
|
||||
|
||||
return `
|
||||
<div class="service-card" id="${escapeAttr(slug)}" data-aos="fade-up" data-aos-delay="${index * 100}">
|
||||
<div class="service-icon">
|
||||
<div class="service-icon" data-icon="${escapeAttr(icon)}">
|
||||
<i class="fas ${escapeAttr(icon)}"></i>
|
||||
</div>
|
||||
<h3 class="service-title">${escapeHTML(service.name || 'Service')}</h3>
|
||||
@@ -969,6 +1040,8 @@ async function loadServices() {
|
||||
servicesGrids.forEach(grid => {
|
||||
grid.innerHTML = servicesMarkup;
|
||||
});
|
||||
|
||||
initReplaceableIcons();
|
||||
} else {
|
||||
// No API data returned — static fallback cards already rendered in HTML
|
||||
}
|
||||
@@ -981,9 +1054,15 @@ async function loadServices() {
|
||||
/* ==================== Load Team from API ==================== */
|
||||
async function loadTeam() {
|
||||
const teamGrid = document.querySelector('[data-team-grid]');
|
||||
const teamSection = teamGrid ? teamGrid.closest('.team-section') : null;
|
||||
|
||||
if (!teamGrid) return;
|
||||
|
||||
// Keep hidden until admin-managed content exists
|
||||
if (teamSection) {
|
||||
teamSection.hidden = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/team.php`);
|
||||
const result = await response.json();
|
||||
@@ -1018,8 +1097,15 @@ async function loadTeam() {
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (teamSection) {
|
||||
teamSection.hidden = false;
|
||||
}
|
||||
} else {
|
||||
teamGrid.innerHTML = '';
|
||||
}
|
||||
} catch (error) {
|
||||
teamGrid.innerHTML = '';
|
||||
console.log('Using static team content or API unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user