172 lines
6.7 KiB
JavaScript
172 lines
6.7 KiB
JavaScript
/**
|
|
* OmégaKube — Services Tabs Renderer
|
|
* Charge assets/data/services.json et construit dynamiquement
|
|
* les onglets de navigation et leurs contenus.
|
|
*
|
|
* Dépend de main.js et services-process.js (déjà chargés avant ce script).
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
/* ── Helpers ─────────────────────────────────────────────── */
|
|
|
|
function stampHtml(stamp, extraClass) {
|
|
if (!stamp) return '';
|
|
const animated = stamp.animated ? ' service-stamp-animated' : '';
|
|
const extra = extraClass ? ' ' + extraClass : '';
|
|
return `<span class="service-stamp service-stamp-${stamp.type}${animated}${extra}">${stamp.text}</span>`;
|
|
}
|
|
|
|
function particlesHtml() {
|
|
return `<div class="service-visual-particles">${'<div class="particle"></div>'.repeat(6)}</div>`;
|
|
}
|
|
|
|
/* ── Rendu ───────────────────────────────────────────────── */
|
|
|
|
function renderNavBtn(service, isFirst) {
|
|
return `
|
|
<button class="service-tab-btn${isFirst ? ' active' : ''}" data-tab="${service.id}">
|
|
<i data-feather="${service.nav.icon}"></i>
|
|
<span>${service.nav.label}</span>
|
|
${stampHtml(service.nav.stamp)}
|
|
</button>`;
|
|
}
|
|
|
|
function renderVisual(service) {
|
|
const { accentFrom, accentTo, stamp } = service.visual;
|
|
return `
|
|
<div class="service-visual" style="--accent-primary:${accentFrom};--accent-secondary:${accentTo};">
|
|
${stampHtml(stamp, 'service-visual-stamp')}
|
|
${particlesHtml()}
|
|
<div class="service-visual-icon">
|
|
<div class="icon-main" style="background:linear-gradient(135deg,${accentFrom},${accentTo});">
|
|
<i data-feather="${service.nav.icon}"></i>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function renderHighlights(highlights) {
|
|
return highlights.map(h => `
|
|
<div class="service-highlight">
|
|
<div class="service-highlight-value">${h.value}</div>
|
|
<div class="service-highlight-label">${h.label}</div>
|
|
</div>`).join('');
|
|
}
|
|
|
|
function renderFeatures(features, accentFrom, accentTo) {
|
|
return features.map(f => `
|
|
<div class="service-feature-item">
|
|
<div class="service-feature-icon" style="background:linear-gradient(135deg,${accentFrom},${accentTo});">
|
|
<i data-feather="${f.icon}"></i>
|
|
</div>
|
|
<div class="service-feature-text">
|
|
<h4>${f.title}</h4>
|
|
<p>${f.desc}</p>
|
|
</div>
|
|
</div>`).join('');
|
|
}
|
|
|
|
function renderNote(note) {
|
|
if (!note || !note.length) return '';
|
|
return `<p class="service-note">${note.map(n => `<span class="service-note-mark">${n.value}</span> ${n.label}<br>`).join(' ')}</p>`;
|
|
}
|
|
|
|
function renderCtas(ctas) {
|
|
return ctas.map(c => `
|
|
<a href="${c.href}" class="btn btn-${c.style}">
|
|
<i data-feather="${c.icon}"></i>
|
|
${c.label}
|
|
</a>`).join('');
|
|
}
|
|
|
|
function renderTabContent(service, isFirst) {
|
|
const { accentFrom, accentTo } = service.visual;
|
|
const titleStamp = service.titleStamp
|
|
? ' ' + stampHtml({ ...service.titleStamp }, 'service-stamp-inline')
|
|
: '';
|
|
|
|
return `
|
|
<div class="service-tab-content${isFirst ? ' active' : ''}" id="tab-${service.id}">
|
|
<div class="service-tab-grid">
|
|
${renderVisual(service)}
|
|
<div class="service-tab-details">
|
|
<div class="service-tab-header">
|
|
<h2>${service.title}${titleStamp}</h2>
|
|
<p>${service.description}</p>
|
|
</div>
|
|
<div class="service-highlights">
|
|
${renderHighlights(service.highlights)}
|
|
</div>
|
|
<div class="service-tab-features">
|
|
${renderFeatures(service.features, accentFrom, accentTo)}
|
|
</div>
|
|
<div class="service-tab-note">
|
|
${renderNote(service.note)}
|
|
</div>
|
|
<div class="service-tab-cta">
|
|
${renderCtas(service.cta)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
/* ── Logique des onglets ─────────────────────────────────── */
|
|
|
|
function initTabs() {
|
|
const tabBtns = document.querySelectorAll('.service-tab-btn');
|
|
const tabContents = document.querySelectorAll('.service-tab-content');
|
|
if (!tabBtns.length || !tabContents.length) return;
|
|
|
|
tabBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const tabId = btn.dataset.tab;
|
|
|
|
tabBtns.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
|
|
tabContents.forEach(content => {
|
|
content.classList.remove('active');
|
|
if (content.id === 'tab-' + tabId) content.classList.add('active');
|
|
});
|
|
|
|
if (window.feather) window.feather.replace();
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ── Init ────────────────────────────────────────────────── */
|
|
|
|
async function init() {
|
|
const navContainer = document.getElementById('service-tabs-nav');
|
|
const contentContainer = document.getElementById('service-tabs-contents');
|
|
if (!navContainer || !contentContainer) return;
|
|
|
|
let services;
|
|
try {
|
|
const res = await fetch('../assets/data/services.json');
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
services = await res.json();
|
|
} catch (err) {
|
|
console.error('[services-tabs.js] Impossible de charger services.json :', err);
|
|
return;
|
|
}
|
|
|
|
if (!Array.isArray(services) || services.length === 0) {
|
|
console.warn('[services-tabs.js] services.json est vide ou mal formé.');
|
|
return;
|
|
}
|
|
|
|
navContainer.innerHTML = services.map((s, i) => renderNavBtn(s, i === 0)).join('');
|
|
contentContainer.innerHTML = services.map((s, i) => renderTabContent(s, i === 0)).join('');
|
|
|
|
if (window.feather) window.feather.replace();
|
|
|
|
initTabs();
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
|
})();
|