Update team visibility and refresh premium UI styling

This commit is contained in:
Krikorios
2026-02-24 00:51:23 +02:00
parent e3a74b2d50
commit 00fda3e045
14 changed files with 299 additions and 83 deletions
+88 -2
View File
@@ -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');
}
}