From 82226b2313c88653668a900232485b01ed74a5bf Mon Sep 17 00:00:00 2001 From: ordinarthur Date: Fri, 27 Feb 2026 16:25:54 +0100 Subject: [PATCH] correct js --- public/main.js | 305 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 198 insertions(+), 107 deletions(-) diff --git a/public/main.js b/public/main.js index 8a23c76..f8d2a35 100644 --- a/public/main.js +++ b/public/main.js @@ -1,115 +1,206 @@ -import Fastify from 'fastify' -import cors from '@fastify/cors' -import Stripe from 'stripe' -import { readFileSync } from 'node:fs' -import dotenv from 'dotenv'; -dotenv.config() +/** + * REBOUR — Main Script + */ -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '') -const DOMAIN = process.env.DOMAIN ?? 'http://localhost:3000' +document.addEventListener('DOMContentLoaded', () => { -const PRODUCTS = { - lumiere_orbitale: { - name: 'LUMIÈRE_ORBITALE — REBOUR', - description: 'Lampe de table unique. Béton texturé coulé à la main + dôme céramique laqué. Collection 001.', - amount: 180000, - currency: 'eur', - }, -} + // ---- CUSTOM CURSOR ---- + const cursorDot = document.querySelector('.cursor-dot'); + const cursorOutline = document.querySelector('.cursor-outline'); -const app = Fastify({ logger: true }) + let mouseX = 0, mouseY = 0; + let outlineX = 0, outlineY = 0; + let rafId = null; -await app.register(cors, { origin: '*', methods: ['GET', 'POST'] }) + 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 }); -// ── SEO ─────────────────────────────────────────────────────────────────────── -app.get('/robots.txt', (_, reply) => { - reply - .type('text/plain') - .header('Cache-Control', 'public, max-age=86400') - .send(`User-agent: *\nAllow: /\nSitemap: ${DOMAIN}/sitemap.xml\n`) -}) - -app.get('/sitemap.xml', (_, reply) => { - const today = new Date().toISOString().split('T')[0] - reply - .type('application/xml') - .header('Cache-Control', 'public, max-age=86400') - .send( - `\n\n ${DOMAIN}/${today}weekly1.0\n` - ) -}) - -// ── Checkout Stripe ─────────────────────────────────────────────────────────── -app.post('/api/checkout', async (request, reply) => { - const { product, email } = request.body ?? {} - const p = PRODUCTS[product] - if (!p) return reply.code(404).send({ error: 'Produit inconnu' }) - - const session = await stripe.checkout.sessions.create({ - mode: 'payment', - payment_method_types: ['card'], - line_items: [{ - price_data: { - currency: p.currency, - unit_amount: p.amount, - product_data: { name: p.name, description: p.description }, - }, - quantity: 1, - }], - success_url: `${DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${DOMAIN}/#collection`, - locale: 'fr', - customer_email: email ?? undefined, - custom_text: { - submit: { message: 'Pièce unique — fabriquée à Paris. Délai : 6 à 8 semaines.' }, - }, - }) - - return { url: session.url } -}) - -// ── Vérification session ────────────────────────────────────────────────────── -app.get('/api/session/:id', async (request) => { - const session = await stripe.checkout.sessions.retrieve(request.params.id) - return { - status: session.payment_status, - amount: session.amount_total, - currency: session.currency, - customer_email: session.customer_details?.email ?? null, - } -}) - -// ── Webhook Stripe ──────────────────────────────────────────────────────────── -app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => { - done(null, body) -}) - -app.post('/api/webhook', async (request, reply) => { - const sig = request.headers['stripe-signature'] - const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET - if (!sig || !webhookSecret) return reply.code(400).send('Missing signature') - - let event - try { - event = stripe.webhooks.constructEvent(request.body, sig, webhookSecret) - } catch { - return reply.code(400).send('Webhook Error') - } - - if (event.type === 'checkout.session.completed') { - const session = event.data.object - if (session.payment_status === 'paid') { - app.log.info(`✓ Paiement — ${session.id} — ${session.customer_details?.email}`) + 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); + } } - } - return { received: true } -}) + function attachCursorHover(elements) { + elements.forEach(el => { + el.addEventListener('mouseenter', () => { + cursorOutline.style.width = '38px'; + cursorOutline.style.height = '38px'; + cursorDot.style.opacity = '0'; + }); + el.addEventListener('mouseleave', () => { + cursorOutline.style.width = '26px'; + cursorOutline.style.height = '26px'; + cursorDot.style.opacity = '1'; + }); + }); + } -// ── Start ───────────────────────────────────────────────────────────────────── -try { - await app.listen({ port: 3000, host: '127.0.0.1' }) -} catch (err) { - app.log.error(err) - process.exit(1) -} \ No newline at end of file + attachCursorHover(document.querySelectorAll('a, button, input, .product-card, summary, .panel-close')); + + // ---- INTERACTIVE GRID ---- + const gridContainer = document.getElementById('interactive-grid'); + const CELL = 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 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'); + + // Toggle affichage du form + 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 Elysia → redirect Stripe + checkoutForm.addEventListener('submit', async (e) => { + e.preventDefault(); + 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: 'lumiere_orbitale', 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); + } + }); + + function openPanel(card) { + 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; + + // Affiche le bouton de commande uniquement pour PROJET_001 + const isOrderable = card.dataset.index === 'PROJET_001'; + checkoutSection.style.display = isOrderable ? 'block' : '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')); + } + + function closePanel() { + panel.classList.remove('is-open'); + panel.setAttribute('aria-hidden', 'true'); + document.body.style.overflow = ''; + } + + cards.forEach(card => { + card.addEventListener('click', () => openPanel(card)); + }); + + panelClose.addEventListener('click', closePanel); + + // Echap pour fermer + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') closePanel(); + }); + +});