rebours/public/main.js

282 lines
10 KiB
JavaScript

/**
* REBOUR — Main Script
*/
document.addEventListener('DOMContentLoaded', () => {
// ---- HEADER HEIGHT → CSS VAR ----
const setHeaderHeight = () => {
const h = document.querySelector('.header')?.offsetHeight || 44;
document.documentElement.style.setProperty('--header-h', h + 'px');
};
setHeaderHeight();
window.addEventListener('resize', setHeaderHeight);
// ---- CUSTOM CURSOR ----
const cursorDot = document.querySelector('.cursor-dot');
const cursorOutline = document.querySelector('.cursor-outline');
let mouseX = 0, mouseY = 0;
let outlineX = 0, outlineY = 0;
let rafId = null;
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
// Première fois : initialise l'outline à la position courante et rend visible
if (outlineX === 0 && outlineY === 0) {
outlineX = mouseX;
outlineY = mouseY;
cursorDot.style.opacity = '1';
cursorOutline.style.opacity = '1';
}
cursorDot.style.transform = `translate(calc(-50% + ${mouseX}px), calc(-50% + ${mouseY}px))`;
if (!rafId) rafId = requestAnimationFrame(animateOutline);
}, { once: false });
function animateOutline() {
rafId = null;
outlineX += (mouseX - outlineX) * 0.18;
outlineY += (mouseY - outlineY) * 0.18;
cursorOutline.style.transform = `translate(calc(-50% + ${outlineX}px), calc(-50% + ${outlineY}px))`;
if (Math.abs(mouseX - outlineX) > 0.1 || Math.abs(mouseY - outlineY) > 0.1) {
rafId = requestAnimationFrame(animateOutline);
}
}
let hoverCount = 0;
function attachCursorHover(elements) {
elements.forEach(el => {
el.addEventListener('mouseenter', () => {
cursorOutline.style.width = '38px';
cursorOutline.style.height = '38px';
});
el.addEventListener('mouseleave', () => {
cursorOutline.style.width = '26px';
cursorOutline.style.height = '26px';
});
});
}
attachCursorHover(document.querySelectorAll('a, button, input, .product-card, summary, .panel-close'));
// ---- INTERACTIVE GRID ----
const gridContainer = document.getElementById('interactive-grid');
const CELL = window.innerWidth <= 600 ? 38 : 60;
const COLORS = [
'rgba(232,168,0,0.45)',
'rgba(232,168,0,0.32)',
'rgba(232,168,0,0.18)',
];
function buildGrid() {
if (!gridContainer) return;
gridContainer.innerHTML = '';
const CELL = window.innerWidth <= 600 ? 38 : 60;
const cols = Math.ceil(window.innerWidth / CELL);
const rows = Math.ceil(window.innerHeight / CELL);
gridContainer.style.display = 'grid';
gridContainer.style.gridTemplateColumns = `repeat(${cols}, ${CELL}px)`;
gridContainer.style.gridTemplateRows = `repeat(${rows}, ${CELL}px)`;
for (let i = 0; i < cols * rows; i++) {
const cell = document.createElement('div');
cell.className = 'grid-cell';
cell.addEventListener('mouseenter', () => {
cell.style.transition = 'none';
cell.style.backgroundColor = COLORS[Math.floor(Math.random() * COLORS.length)];
});
cell.addEventListener('mouseleave', () => {
cell.style.transition = 'background-color 1.4s ease-out';
cell.style.backgroundColor = 'transparent';
});
gridContainer.appendChild(cell);
}
}
buildGrid();
let rt;
window.addEventListener('resize', () => { clearTimeout(rt); rt = setTimeout(buildGrid, 150); });
// ---- PRODUCT PANEL ----
const panel = document.getElementById('product-panel');
const panelClose = document.getElementById('panel-close');
const cards = document.querySelectorAll('.product-card');
// Champs du panel
const fields = {
img: document.getElementById('panel-img'),
index: document.getElementById('panel-index'),
name: document.getElementById('panel-name'),
type: document.getElementById('panel-type'),
mat: document.getElementById('panel-mat'),
year: document.getElementById('panel-year'),
status: document.getElementById('panel-status'),
desc: document.getElementById('panel-desc'),
specs: document.getElementById('panel-specs'),
notes: document.getElementById('panel-notes'),
};
// ---- CHECKOUT LOGIC ----
const checkoutSection = document.getElementById('checkout-section');
const checkoutToggleBtn = document.getElementById('checkout-toggle-btn');
const checkoutFormWrap = document.getElementById('checkout-form-wrap');
const checkoutForm = document.getElementById('checkout-form');
const checkoutSubmitBtn = document.getElementById('checkout-submit-btn');
const checkoutPriceEl = document.querySelector('.checkout-price');
// Current product's stripe key (set dynamically when opening panel)
let currentStripeKey = null;
// Format price in cents to display string
function formatPrice(cents) {
return (cents / 100).toLocaleString('fr-FR') + ' €';
}
// Toggle affichage du form
if (checkoutToggleBtn) {
checkoutToggleBtn.addEventListener('click', () => {
const isOpen = checkoutFormWrap.style.display !== 'none';
checkoutFormWrap.style.display = isOpen ? 'none' : 'block';
checkoutToggleBtn.textContent = isOpen
? '[ COMMANDER CETTE PIÈCE ]'
: '[ ANNULER ]';
});
}
// Submit → appel API → redirect Stripe
if (checkoutForm) {
checkoutForm.addEventListener('submit', async (e) => {
e.preventDefault();
if (!currentStripeKey) return;
const email = document.getElementById('checkout-email').value;
checkoutSubmitBtn.disabled = true;
checkoutSubmitBtn.textContent = 'CONNEXION STRIPE...';
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ product: currentStripeKey, email }),
});
const data = await res.json();
if (data.url) {
window.location.href = data.url;
} else {
throw new Error('No URL returned');
}
} catch (err) {
checkoutSubmitBtn.disabled = false;
checkoutSubmitBtn.textContent = 'ERREUR — RÉESSAYER';
console.error(err);
}
});
}
// Slug à partir du nom du produit : "Solar_Altar" → "solar-altar"
function toSlug(name) {
return name
.toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
.replace(/_/g, '-')
.replace(/[^a-z0-9-]/g, '');
}
function openPanel(card, pushState = true) {
fields.img.src = card.dataset.img;
fields.img.alt = card.dataset.name;
fields.index.textContent = card.dataset.index;
fields.name.textContent = card.dataset.name;
fields.type.textContent = card.dataset.type;
fields.mat.textContent = card.dataset.mat;
fields.year.textContent = card.dataset.year;
fields.status.textContent = card.dataset.status;
fields.desc.textContent = card.dataset.desc;
fields.specs.textContent = card.dataset.specs;
fields.notes.textContent = card.dataset.notes;
// Checkout : afficher uniquement si le produit a un prix et un stripe key
const price = card.dataset.price;
const stripeKey = card.dataset.stripeKey;
const isOrderable = price && stripeKey;
if (isOrderable) {
currentStripeKey = stripeKey;
checkoutPriceEl.textContent = formatPrice(parseInt(price, 10));
checkoutSection.style.display = 'block';
} else {
currentStripeKey = null;
checkoutSection.style.display = 'none';
}
// Reset form state
checkoutFormWrap.style.display = 'none';
checkoutToggleBtn.textContent = '[ COMMANDER CETTE PIÈCE ]';
checkoutSubmitBtn.disabled = false;
checkoutSubmitBtn.textContent = 'PROCÉDER AU PAIEMENT →';
checkoutForm.reset();
// Ferme les accordéons
panel.querySelectorAll('details').forEach(d => d.removeAttribute('open'));
panel.classList.add('is-open');
panel.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
// Refresh cursor sur les nouveaux éléments
attachCursorHover(panel.querySelectorAll('summary, .panel-close, .checkout-btn, .checkout-submit'));
// Mise à jour de l'URL
if (pushState) {
const slug = toSlug(card.dataset.name);
history.pushState({ slug }, '', `/collection/${slug}`);
}
}
function closePanel(pushState = true) {
panel.classList.remove('is-open');
panel.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
if (pushState) {
history.pushState({}, '', '/');
}
}
cards.forEach(card => {
card.addEventListener('click', () => openPanel(card));
});
// Ouverture automatique si on arrive directement sur /collection/[slug]
if (window.__OPEN_PANEL__) {
const name = window.__OPEN_PANEL__;
const card = [...cards].find(c => c.dataset.name === name);
if (card) openPanel(card, false);
}
if (panelClose) panelClose.addEventListener('click', () => closePanel());
// Echap pour fermer
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closePanel();
});
// Bouton retour navigateur
window.addEventListener('popstate', () => {
if (panel.classList.contains('is-open')) {
closePanel(false);
} else {
// Tente de rouvrir si on navigue vers un slug connu
const match = location.pathname.match(/^\/collection\/(.+)$/);
if (match) {
const slug = match[1];
const card = [...cards].find(c => toSlug(c.dataset.name) === slug);
if (card) openPanel(card, false);
}
}
});
});