correction de bug formulaire de contact
This commit is contained in:
parent
c14f0531ff
commit
d705c47fbc
8 changed files with 379 additions and 665 deletions
|
|
@ -1,57 +1,164 @@
|
|||
// Services page scripts (moved from inline in pages/services.html)
|
||||
// Depends on ../main.js which initializes feather and other globals
|
||||
/**
|
||||
* 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() {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Timeline Animation
|
||||
const timeline = document.getElementById('process-timeline');
|
||||
const steps = document.querySelectorAll('.process-step');
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function animateTimeline() {
|
||||
if (!timeline) return;
|
||||
const timelineTop = timeline.getBoundingClientRect().top;
|
||||
/* ── Helpers ─────────────────────────────────────────────── */
|
||||
|
||||
if (timelineTop < window.innerHeight - 200) {
|
||||
timeline.classList.add('animated');
|
||||
|
||||
// Animate steps sequentially
|
||||
steps.forEach((step, index) => {
|
||||
setTimeout(() => {
|
||||
step.classList.add('active');
|
||||
}, index * 400);
|
||||
});
|
||||
}
|
||||
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>`;
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', animateTimeline);
|
||||
window.addEventListener('load', animateTimeline);
|
||||
|
||||
// Service Tabs
|
||||
const tabBtns = document.querySelectorAll('.service-tab-btn');
|
||||
const tabContents = document.querySelectorAll('.service-tab-content');
|
||||
|
||||
if (tabBtns.length && tabContents.length) {
|
||||
tabBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tabId = btn.dataset.tab;
|
||||
|
||||
// Update buttons
|
||||
tabBtns.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
// Update content
|
||||
tabContents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
if (content.id === 'tab-' + tabId) {
|
||||
content.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Re-init feather icons for new content (safe if feather is loaded)
|
||||
if (window.feather) window.feather.replace();
|
||||
});
|
||||
});
|
||||
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 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-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);
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue