website/assets/js/pages/services.js

173 lines
6.7 KiB
JavaScript
Raw Normal View History

/**
* 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>`;
2026-02-06 18:36:03 +01:00
}
function particlesHtml() {
return `<div class="service-visual-particles">${'<div class="particle"></div>'.repeat(6)}</div>`;
}
2026-02-06 18:36:03 +01:00
/* ── Rendu ───────────────────────────────────────────────── */
2026-02-06 18:36:03 +01:00
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>`;
}
2026-02-06 18:36:03 +01:00
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>`;
}
2026-02-06 18:36:03 +01:00
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('');
}
2026-04-05 20:49:52 +02:00
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>
2026-04-05 20:49:52 +02:00
<div class="service-tab-note">
${renderNote(service.note)}
</div>
<div class="service-tab-cta">
${renderCtas(service.cta)}
</div>
</div>
</div>
</div>`;
}
2026-02-06 18:36:03 +01:00
/* ── 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();
});
2026-02-06 18:36:03 +01:00
});
}
/* ── 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);
2026-02-06 18:36:03 +01:00
})();