v3 ?
This commit is contained in:
parent
bb1b8039a3
commit
0edeeacbe2
@ -57,9 +57,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const gridContainer = document.getElementById('interactive-grid');
|
const gridContainer = document.getElementById('interactive-grid');
|
||||||
const CELL = 60;
|
const CELL = 60;
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
'rgba(160,160,155,0.3)',
|
'rgba(232,168,0,0.45)',
|
||||||
'rgba(140,140,135,0.22)',
|
'rgba(232,168,0,0.32)',
|
||||||
'rgba(120,120,115,0.18)',
|
'rgba(232,168,0,0.18)',
|
||||||
];
|
];
|
||||||
|
|
||||||
function buildGrid() {
|
function buildGrid() {
|
||||||
|
|||||||
@ -29,7 +29,7 @@ body {
|
|||||||
/* ---- CURSOR ---- */
|
/* ---- CURSOR ---- */
|
||||||
.cursor-dot {
|
.cursor-dot {
|
||||||
width: 5px; height: 5px;
|
width: 5px; height: 5px;
|
||||||
background: var(--clr-black);
|
background: var(--clr-red);
|
||||||
position: fixed; top: 0; left: 0;
|
position: fixed; top: 0; left: 0;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@ -605,7 +605,7 @@ hr { border: none; border-top: var(--border); margin: 0; }
|
|||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.nl-row button:hover { background: var(--clr-red); }
|
.nl-row button:hover { background: var(--clr-red); color: var(--clr-black); }
|
||||||
|
|
||||||
/* ---- FOOTER ---- */
|
/* ---- FOOTER ---- */
|
||||||
.footer {
|
.footer {
|
||||||
@ -625,13 +625,52 @@ hr { border: none; border-top: var(--border); margin: 0; }
|
|||||||
/* ---- RESPONSIVE ---- */
|
/* ---- RESPONSIVE ---- */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.hero, .newsletter { grid-template-columns: 1fr; }
|
.hero, .newsletter { grid-template-columns: 1fr; }
|
||||||
.hero-left { border-right: none; border-bottom: var(--border); min-height: 55vw; padding: 3rem var(--pad); }
|
.hero-left { border-right: none; border-bottom: var(--border); min-height: 70vw; padding: 3rem var(--pad); }
|
||||||
.hero-right { height: 55vw; }
|
.hero-right { height: 55vw; }
|
||||||
.nl-left { border-right: none; border-bottom: var(--border); }
|
.nl-left { border-right: none; border-bottom: var(--border); }
|
||||||
.product-grid { grid-template-columns: 1fr 1fr; }
|
.product-grid { grid-template-columns: 1fr 1fr; }
|
||||||
.panel-inner { grid-template-columns: 1fr; }
|
.panel-inner { grid-template-columns: 1fr; }
|
||||||
.panel-img-col { height: 50vw; }
|
.panel-img-col { height: 50vw; }
|
||||||
.panel-info-col { overflow-y: auto; }
|
.panel-info-col { overflow-y: auto; }
|
||||||
|
|
||||||
|
/* Floats : repositionnés pour la colonne pleine largeur */
|
||||||
|
.hero-float:nth-child(1) {
|
||||||
|
width: clamp(80px, 22vw, 140px);
|
||||||
|
top: 6%;
|
||||||
|
left: 5%;
|
||||||
|
}
|
||||||
|
.hero-float:nth-child(2) {
|
||||||
|
width: clamp(70px, 18vw, 110px);
|
||||||
|
top: 10%;
|
||||||
|
right: 5%;
|
||||||
|
}
|
||||||
|
.hero-float:nth-child(3) {
|
||||||
|
width: clamp(90px, 24vw, 150px);
|
||||||
|
bottom: 5%;
|
||||||
|
right: 8%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.product-grid { grid-template-columns: 1fr; }
|
||||||
|
.header-nav { gap: 1rem; }
|
||||||
|
.hero-left { min-height: 85vw; }
|
||||||
|
|
||||||
|
/* Floats : plus petits sur mobile, repositionnés pour ne pas couvrir le texte */
|
||||||
|
.hero-float:nth-child(1) {
|
||||||
|
width: clamp(60px, 18vw, 90px);
|
||||||
|
top: 4%;
|
||||||
|
left: 4%;
|
||||||
|
}
|
||||||
|
.hero-float:nth-child(2) {
|
||||||
|
width: clamp(55px, 16vw, 80px);
|
||||||
|
top: 8%;
|
||||||
|
right: 4%;
|
||||||
|
}
|
||||||
|
.hero-float:nth-child(3) {
|
||||||
|
width: clamp(70px, 20vw, 100px);
|
||||||
|
bottom: 4%;
|
||||||
|
right: 6%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.product-grid { grid-template-columns: 1fr; }
|
.product-grid { grid-template-columns: 1fr; }
|
||||||
|
|||||||
120
server.ts
120
server.ts
@ -1,120 +0,0 @@
|
|||||||
import { Elysia, t } from 'elysia'
|
|
||||||
import { cors } from '@elysiajs/cors'
|
|
||||||
import Stripe from 'stripe'
|
|
||||||
|
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '', {
|
|
||||||
apiVersion: '2025-01-27.acacia',
|
|
||||||
})
|
|
||||||
|
|
||||||
const DOMAIN = process.env.DOMAIN ?? 'http://localhost:3000'
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = new Elysia()
|
|
||||||
.use(cors({ origin: '*', methods: ['GET', 'POST'] }))
|
|
||||||
|
|
||||||
// ── SEO : robots + sitemap (nginx ne les génère pas dynamiquement) ──────────
|
|
||||||
.get('/robots.txt', () =>
|
|
||||||
new Response(`User-agent: *\nAllow: /\nSitemap: ${DOMAIN}/sitemap.xml\n`, {
|
|
||||||
headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'public, max-age=86400' },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.get('/sitemap.xml', () => {
|
|
||||||
const today = new Date().toISOString().split('T')[0]
|
|
||||||
return new Response(
|
|
||||||
`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>${DOMAIN}/</loc><lastmod>${today}</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url>\n</urlset>`,
|
|
||||||
{ headers: { 'Content-Type': 'application/xml', 'Cache-Control': 'public, max-age=86400' } }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// ── API Stripe : créer session checkout ───────────────────────────────────
|
|
||||||
.post(
|
|
||||||
'/api/checkout',
|
|
||||||
async ({ body }) => {
|
|
||||||
const product = PRODUCTS[body.product as keyof typeof PRODUCTS]
|
|
||||||
if (!product) return new Response('Produit inconnu', { status: 404 })
|
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
|
||||||
mode: 'payment',
|
|
||||||
payment_method_types: ['card'],
|
|
||||||
line_items: [{
|
|
||||||
price_data: {
|
|
||||||
currency: product.currency,
|
|
||||||
unit_amount: product.amount,
|
|
||||||
product_data: { name: product.name, description: product.description },
|
|
||||||
},
|
|
||||||
quantity: 1,
|
|
||||||
}],
|
|
||||||
success_url: `${DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
||||||
cancel_url: `${DOMAIN}/#collection`,
|
|
||||||
locale: 'fr',
|
|
||||||
customer_email: body.email ?? undefined,
|
|
||||||
custom_text: {
|
|
||||||
submit: { message: 'Pièce unique — fabriquée à Paris. Délai : 6 à 8 semaines.' },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return { url: session.url }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: t.Object({
|
|
||||||
product: t.String(),
|
|
||||||
email: t.Optional(t.String()),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ── API : vérifier session après paiement ─────────────────────────────────
|
|
||||||
.get(
|
|
||||||
'/api/session/:id',
|
|
||||||
async ({ params }) => {
|
|
||||||
const session = await stripe.checkout.sessions.retrieve(params.id)
|
|
||||||
return {
|
|
||||||
status: session.payment_status,
|
|
||||||
amount: session.amount_total,
|
|
||||||
currency: session.currency,
|
|
||||||
customer_email: session.customer_details?.email ?? null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ params: t.Object({ id: t.String() }) }
|
|
||||||
)
|
|
||||||
|
|
||||||
// ── Webhook Stripe ─────────────────────────────────────────────────────────
|
|
||||||
.post('/api/webhook', async ({ request, headers }) => {
|
|
||||||
const sig = headers['stripe-signature']
|
|
||||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET
|
|
||||||
if (!sig || !webhookSecret) return new Response('Missing signature', { status: 400 })
|
|
||||||
|
|
||||||
let event: Stripe.Event
|
|
||||||
try {
|
|
||||||
event = stripe.webhooks.constructEvent(
|
|
||||||
Buffer.from(await request.arrayBuffer()), sig, webhookSecret
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
return new Response('Webhook Error', { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === 'checkout.session.completed') {
|
|
||||||
const session = event.data.object as Stripe.Checkout.Session
|
|
||||||
if (session.payment_status === 'paid') {
|
|
||||||
console.log(`✓ Paiement — ${session.id} — ${session.customer_details?.email}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { received: true }
|
|
||||||
})
|
|
||||||
|
|
||||||
.listen(3000)
|
|
||||||
|
|
||||||
console.log(`
|
|
||||||
┌──────────────────────────────────────┐
|
|
||||||
│ REBOUR API — http://localhost:3000 │
|
|
||||||
│ NODE_ENV: ${process.env.NODE_ENV ?? 'development'}
|
|
||||||
└──────────────────────────────────────┘
|
|
||||||
`)
|
|
||||||
Loading…
x
Reference in New Issue
Block a user