<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Prendre rendez-vous — Wafa Mazouz — Glow Up Signature</title>
<meta name="robots" content="noindex">
<!-- PWA -->
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#E8935E">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="GUS RDV">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,500;0,600;1,500&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--cream: #FDFBF7; --cream-deep: #F7EEE3;
--peach: #F4B896; --coral: #E8935E;
--burgundy: #5C2A2A; --burgundy-soft: #8a5a4a;
}
* { box-sizing: border-box; }
html, body { margin:0; padding:0; background: var(--cream); font-family:'Manrope',sans-serif; color: var(--burgundy); }
.wrap { max-width: 560px; margin: 0 auto; padding: 24px 18px 60px; }
.top-header { text-align: center; margin-bottom: 24px; }
.top-header__eyebrow { font-size: .7rem; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color: var(--coral); display:block; margin-bottom: 8px; }
.top-header__title { font-family:'Playfair Display',serif; font-weight:600; font-size:1.7rem; margin:0; }
.steps { display:flex; justify-content:center; gap:8px; margin-bottom: 28px; }
.step-dot { width:8px; height:8px; border-radius:50%; background:#e5d8c9; transition:background .2s; }
.step-dot.active { background: var(--coral); width:22px; border-radius:5px; }
.panel { display:none; }
.panel.active { display:block; animation: fadein .25s ease; }
@keyframes fadein { from{opacity:0;transform:translateY(6px);} to{opacity:1;transform:translateY(0);} }
.cat-title { font-family:'Playfair Display',serif; font-weight:600; font-size:1.05rem; margin: 20px 0 10px; }
.cat-title:first-child { margin-top:0; }
.service-card {
display:flex; justify-content:space-between; align-items:center;
width:100%; text-align:left; background:#fff; border:1.5px solid #ece1d4; border-radius:14px;
padding:14px 16px; margin-bottom:10px; cursor:pointer; transition:border-color .15s, background .15s;
}
.service-card:hover, .service-card.selected { border-color: var(--coral); background:#fff9f4; }
.service-card__name { font-weight:700; font-size:.92rem; }
.service-card__duration { font-size:.72rem; color: var(--burgundy-soft); margin-top:2px; }
.service-card__price { font-weight:800; color: var(--coral); font-size:.95rem; white-space:nowrap; padding-left:10px; }
.btn-back { background:none; border:none; color: var(--burgundy-soft); font-size:.82rem; font-weight:600; cursor:pointer; margin-bottom:16px; padding:0; display:inline-flex; align-items:center; gap:5px; }
input[type=date] {
width:100%; padding:12px 14px; border-radius:12px; border:1.5px solid #ece1d4;
font-family:'Manrope',sans-serif; font-size:.9rem; color: var(--burgundy); margin-bottom:18px;
}
.slots-grid { display:grid; grid-template-columns: repeat(3,1fr); gap:8px; margin-bottom: 10px; }
.slot-btn {
padding:11px 4px; border-radius:10px; border:1.5px solid #ece1d4; background:#fff;
font-size:.82rem; font-weight:700; color: var(--burgundy); cursor:pointer; transition:.15s;
}
.slot-btn:hover { border-color: var(--coral); }
.slot-btn.selected { background: var(--coral); border-color: var(--coral); color:#fff; }
.slots-empty, .slots-loading { text-align:center; color: var(--burgundy-soft); font-size:.85rem; padding:20px 0; }
label { display:block; font-size:.78rem; font-weight:700; margin-bottom:5px; color: var(--burgundy-soft); }
.field { margin-bottom:14px; }
input[type=text], input[type=email], input[type=tel], textarea {
width:100%; padding:11px 14px; border-radius:12px; border:1.5px solid #ece1d4;
font-family:'Manrope',sans-serif; font-size:.9rem; color: var(--burgundy);
}
textarea { resize: vertical; min-height:60px; }
.btn-primary {
width:100%; padding:15px; border:none; border-radius:50px;
background: linear-gradient(135deg, var(--coral), #d97a45); color:#fff; font-weight:700; font-size:.95rem;
cursor:pointer; box-shadow: 0 10px 24px rgba(232,147,94,.35); margin-top:6px;
}
.btn-primary:disabled { opacity:.5; cursor:not-allowed; box-shadow:none; }
.recap { background: var(--cream-deep); border-radius:14px; padding:16px 18px; margin-bottom:18px; font-size:.85rem; line-height:1.7; }
.recap strong { color: var(--burgundy); }
.error-msg { background:#fdeeea; color:#b3402a; border-radius:10px; padding:10px 14px; font-size:.82rem; margin-bottom:14px; display:none; }
.error-msg.show { display:block; }
.confirm-box { text-align:center; padding: 20px 0; }
.confirm-box__icon { width:56px; height:56px; border-radius:50%; background:linear-gradient(135deg,var(--peach),var(--coral)); display:flex; align-items:center; justify-content:center; margin:0 auto 16px; }
.confirm-box__title { font-family:'Playfair Display',serif; font-weight:600; font-size:1.4rem; margin:0 0 10px; }
.confirm-box__text { font-size:.88rem; color: var(--burgundy-soft); line-height:1.6; }
.install-prompt {
display: none;
text-align: left;
background: var(--cream-deep);
border-radius: 16px;
padding: 18px 20px;
margin-top: 28px;
}
.install-prompt.show { display: block; }
.install-prompt__title {
font-family: 'Playfair Display', serif;
font-weight: 600;
font-size: 1rem;
color: var(--burgundy);
margin: 0 0 6px;
display: flex;
align-items: center;
gap: 8px;
}
.install-prompt__text {
font-size: .82rem;
color: var(--burgundy-soft);
line-height: 1.55;
margin: 0 0 14px;
}
.install-prompt__text strong { color: var(--burgundy); }
.install-prompt__btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 11px 20px;
border-radius: 50px;
background: linear-gradient(135deg, var(--coral), #d97a45);
color: #fff;
border: none;
font-weight: 700;
font-size: .85rem;
cursor: pointer;
}
.install-prompt__ios-steps {
font-size: .82rem;
color: var(--burgundy);
background: #fff;
border-radius: 10px;
padding: 12px 14px;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="wrap">
<div class="top-header">
<span class="top-header__eyebrow">Wafa Mazouz — Glow Up Signature</span>
<h1 class="top-header__title">Prendre rendez-vous</h1>
</div>
<div class="steps">
<div class="step-dot active" data-step-dot="1"></div>
<div class="step-dot" data-step-dot="2"></div>
<div class="step-dot" data-step-dot="3"></div>
<div class="step-dot" data-step-dot="4"></div>
</div>
<div class="error-msg" id="globalError"></div>
<!-- ÉTAPE 1 : choix de la prestation -->
<div class="panel active" data-panel="1">
<div class="cat-title">Diagnostic</div>
<div class="service-card"
data-id="1"
data-nom="Diagnostic Peau"
data-duree="60">
<div>
<div class="service-card__name">Diagnostic Peau</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">30€</div>
</div>
<div class="cat-title">Soins Visage</div>
<div class="service-card"
data-id="2"
data-nom="Hydrafacial"
data-duree="60">
<div>
<div class="service-card__name">Hydrafacial</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">65€</div>
</div>
<div class="service-card"
data-id="3"
data-nom="Soin Régénérant ACS"
data-duree="60">
<div>
<div class="service-card__name">Soin Régénérant ACS</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">75€</div>
</div>
<div class="service-card"
data-id="4"
data-nom="Soin Anti-Âge & Fermeté"
data-duree="60">
<div>
<div class="service-card__name">Soin Anti-Âge & Fermeté</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">55€</div>
</div>
<div class="service-card"
data-id="5"
data-nom="Soin Peau Grasse"
data-duree="60">
<div>
<div class="service-card__name">Soin Peau Grasse</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">55€</div>
</div>
<div class="service-card"
data-id="6"
data-nom="Soin Peau Sèche"
data-duree="60">
<div>
<div class="service-card__name">Soin Peau Sèche</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">55€</div>
</div>
<div class="service-card"
data-id="7"
data-nom="Soin Hydratant"
data-duree="60">
<div>
<div class="service-card__name">Soin Hydratant</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">55€</div>
</div>
<div class="service-card"
data-id="8"
data-nom="Luminothérapie"
data-duree="40">
<div>
<div class="service-card__name">Luminothérapie</div>
<div class="service-card__duration">40 min</div>
</div>
<div class="service-card__price">25€</div>
</div>
<div class="service-card"
data-id="9"
data-nom="Gua Sha Visage"
data-duree="45">
<div>
<div class="service-card__name">Gua Sha Visage</div>
<div class="service-card__duration">45 min</div>
</div>
<div class="service-card__price">Sur devis</div>
</div>
<div class="service-card"
data-id="10"
data-nom="Ventouses Visage"
data-duree="30">
<div>
<div class="service-card__name">Ventouses Visage</div>
<div class="service-card__duration">30 min</div>
</div>
<div class="service-card__price">50€</div>
</div>
<div class="cat-title">Drainage</div>
<div class="service-card"
data-id="11"
data-nom="Drainage Lymphatique Visage"
data-duree="45">
<div>
<div class="service-card__name">Drainage Lymphatique Visage</div>
<div class="service-card__duration">45 min</div>
</div>
<div class="service-card__price">50€</div>
</div>
<div class="service-card"
data-id="12"
data-nom="Drainage Post-Opératoire Visage"
data-duree="60">
<div>
<div class="service-card__name">Drainage Post-Opératoire Visage</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">70€</div>
</div>
<div class="service-card"
data-id="13"
data-nom="Drainage Lymphatique Brésilien"
data-duree="60">
<div>
<div class="service-card__name">Drainage Lymphatique Brésilien</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">70€</div>
</div>
<div class="service-card"
data-id="14"
data-nom="Madérothérapie"
data-duree="60">
<div>
<div class="service-card__name">Madérothérapie</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">70€</div>
</div>
<div class="cat-title">Massages</div>
<div class="service-card"
data-id="15"
data-nom="Kobido"
data-duree="60">
<div>
<div class="service-card__name">Kobido</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">80€</div>
</div>
<div class="service-card"
data-id="16"
data-nom="Massage Suédois Californien"
data-duree="60">
<div>
<div class="service-card__name">Massage Suédois Californien</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">70€</div>
</div>
<div class="service-card"
data-id="17"
data-nom="Massage AromaTouch"
data-duree="60">
<div>
<div class="service-card__name">Massage AromaTouch</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">60€</div>
</div>
<div class="service-card"
data-id="18"
data-nom="Massage Signature"
data-duree="60">
<div>
<div class="service-card__name">Massage Signature</div>
<div class="service-card__duration">60 min</div>
</div>
<div class="service-card__price">65€</div>
</div>
<div class="service-card"
data-id="19"
data-nom="Massage Bébé"
data-duree="30">
<div>
<div class="service-card__name">Massage Bébé</div>
<div class="service-card__duration">30 min</div>
</div>
<div class="service-card__price">50€</div>
</div>
</div>
<!-- ÉTAPE 2 : date + créneau -->
<div class="panel" data-panel="2">
<button class="btn-back" data-back="1">← Changer de prestation</button>
<label for="dateInput">Choisissez une date</label>
<input type="date" id="dateInput">
<div id="slotsContainer"></div>
</div>
<!-- ÉTAPE 3 : coordonnées -->
<div class="panel" data-panel="3">
<button class="btn-back" data-back="2">← Changer de créneau</button>
<div class="recap" id="recapBox"></div>
<div class="field"><label>Nom complet</label><input type="text" id="champNom"></div>
<div class="field"><label>Email</label><input type="email" id="champEmail"></div>
<div class="field"><label>Téléphone</label><input type="tel" id="champTelephone"></div>
<div class="field">
<label>🎂 Date de naissance <span style="font-weight:500;text-transform:none;letter-spacing:0;">(facultatif)</span></label>
<input type="date" id="champDateNaissance">
<p style="font-size:.72rem;color:var(--burgundy-soft);margin:5px 0 0;line-height:1.4;">
Uniquement pour vous préparer une petite surprise le jour de votre anniversaire — jamais utilisé à d'autres fins.
</p>
</div>
<div class="field"><label>Message (optionnel)</label><textarea id="champNotes"></textarea></div>
<button class="btn-primary" id="btnConfirmer">Confirmer le rendez-vous</button>
</div>
<!-- ÉTAPE 4 : confirmation -->
<div class="panel" data-panel="4">
<div class="confirm-box">
<div class="confirm-box__icon">
<svg width="26" height="26" viewbox="0 0 24 24" fill="none" stroke="#5C2A2A" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<h2 class="confirm-box__title">C'est confirmé !</h2>
<p class="confirm-box__text" id="confirmText"></p>
<div class="install-prompt" id="installPrompt">
<p class="install-prompt__title">🌿 Envie de réserver encore plus vite ?</p>
<p class="install-prompt__text">
Ajoutez cette page à votre écran d'accueil, comme une vraie appli — en un geste, sans rien installer depuis un store.
<strong>Aucun téléchargement, aucune donnée en plus collectée</strong>, juste un raccourci pratique pour la prochaine fois.
</p>
<button class="install-prompt__btn" id="btnInstallAndroid" style="display:none;">
📲 Ajouter à mon écran d'accueil
</button>
<div class="install-prompt__ios-steps" id="stepsIos" style="display:none;">
Sur iPhone : appuyez sur <strong>Partager</strong> <span style="font-size:1.1em;">⬆️</span> en bas de l'écran, puis sur
<strong>« Sur l'écran d'accueil »</strong>.
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
const state = { prestationId: null, prestationNom: '', dureeMinutes: 0, date: null, heure: null };
let currentStep = 1;
let deferredInstallPrompt = null;
// Capturé tôt : Chrome/Android proposent cet évènement pour déclencher
// l'installation par programmation plutôt que d'attendre leur bannière native.
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredInstallPrompt = e;
});
function afficherPropositionInstallation() {
const bloc = document.getElementById('installPrompt');
const btnAndroid = document.getElementById('btnInstallAndroid');
const stepsIos = document.getElementById('stepsIos');
// Déjà installée en PWA ? On ne montre rien.
if (window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone) {
return;
}
const estIos = /iphone|ipad|ipod/i.test(navigator.userAgent);
if (deferredInstallPrompt) {
btnAndroid.style.display = 'inline-flex';
} else if (estIos) {
stepsIos.style.display = 'block';
} else {
return; // Ni prompt Android disponible, ni iOS -> pas de bloc à montrer
}
bloc.classList.add('show');
}
document.getElementById('btnInstallAndroid').addEventListener('click', async () => {
if (!deferredInstallPrompt) return;
deferredInstallPrompt.prompt();
await deferredInstallPrompt.userChoice;
deferredInstallPrompt = null;
document.getElementById('btnInstallAndroid').style.display = 'none';
});
function goToStep(n) {
document.querySelectorAll('[data-panel]').forEach(p => p.classList.toggle('active', p.dataset.panel == n));
document.querySelectorAll('[data-step-dot]').forEach(d => d.classList.toggle('active', d.dataset.stepDot <= n));
currentStep = n;
hideError();
if (n === 4) {
afficherPropositionInstallation();
}
}
function showError(msg) {
const el = document.getElementById('globalError');
el.textContent = msg;
el.classList.add('show');
}
function hideError() {
const el = document.getElementById('globalError');
if (el) el.classList.remove('show');
}
// Étape 1 -> 2
document.querySelectorAll('.service-card').forEach(card => {
card.addEventListener('click', () => {
document.querySelectorAll('.service-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
state.prestationId = card.dataset.id;
state.prestationNom = card.dataset.nom;
state.dureeMinutes = card.dataset.duree;
goToStep(2);
const dateInput = document.getElementById('dateInput');
const today = new Date();
const minDate = new Date(today.getTime() + 24*3600*1000);
const maxDate = new Date(today.getTime() + 60*24*3600*1000);
dateInput.min = minDate.toISOString().split('T')[0];
dateInput.max = maxDate.toISOString().split('T')[0];
dateInput.value = '';
document.getElementById('slotsContainer').innerHTML = '';
});
});
document.querySelectorAll('[data-back]').forEach(btn => {
btn.addEventListener('click', () => goToStep(parseInt(btn.dataset.back)));
});
// Étape 2 : chargement des créneaux
document.getElementById('dateInput').addEventListener('change', async (e) => {
const date = e.target.value;
if (!date) return;
state.date = date;
state.heure = null;
const container = document.getElementById('slotsContainer');
container.innerHTML = '<div class="slots-loading">Recherche des créneaux disponibles…</div>';
try {
const res = await fetch(`api/creneaux.php?prestation_id=${state.prestationId}&date=${date}`);
const data = await res.json();
if (!data.success) { showError(data.error || 'Erreur de chargement.'); container.innerHTML=''; return; }
if (data.creneaux.length === 0) {
container.innerHTML = '<div class="slots-empty">Aucun créneau disponible ce jour-là. Essayez une autre date.</div>';
return;
}
const grid = document.createElement('div');
grid.className = 'slots-grid';
data.creneaux.forEach(c => {
const btn = document.createElement('button');
btn.className = 'slot-btn';
btn.textContent = c.debut;
btn.addEventListener('click', () => {
document.querySelectorAll('.slot-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
state.heure = c.debut;
state.heureFin = c.fin;
setTimeout(() => goToStep(3), 200);
document.getElementById('recapBox').innerHTML =
`<strong>${state.prestationNom}</strong><br>${formatDateFr(state.date)} à ${state.heure}`;
});
grid.appendChild(btn);
});
container.innerHTML = '';
container.appendChild(grid);
} catch (err) {
showError('Impossible de charger les créneaux. Vérifiez votre connexion.');
container.innerHTML = '';
}
});
function formatDateFr(iso) {
const d = new Date(iso + 'T00:00:00');
return d.toLocaleDateString('fr-FR', { weekday:'long', day:'numeric', month:'long' });
}
// Étape 3 : confirmation de réservation
document.getElementById('btnConfirmer').addEventListener('click', async () => {
const nom = document.getElementById('champNom').value.trim();
const email = document.getElementById('champEmail').value.trim();
const telephone = document.getElementById('champTelephone').value.trim();
const dateNaissance = document.getElementById('champDateNaissance').value;
const notes = document.getElementById('champNotes').value.trim();
if (!nom || !email || !telephone) {
showError('Merci de remplir votre nom, email et téléphone.');
return;
}
const btn = document.getElementById('btnConfirmer');
btn.disabled = true;
btn.textContent = 'Envoi en cours…';
try {
const formData = new URLSearchParams();
formData.append('prestation_id', state.prestationId);
formData.append('date', state.date);
formData.append('heure', state.heure);
formData.append('nom', nom);
formData.append('email', email);
formData.append('telephone', telephone);
formData.append('date_naissance', dateNaissance);
formData.append('notes', notes);
const res = await fetch('api/reserver.php', { method: 'POST', body: formData });
const data = await res.json();
if (!data.success) {
showError(data.error || 'Une erreur est survenue.');
btn.disabled = false;
btn.textContent = 'Confirmer le rendez-vous';
return;
}
document.getElementById('confirmText').innerHTML =
`Votre rendez-vous pour <strong>${state.prestationNom}</strong> est enregistré le
<strong>${formatDateFr(state.date)}</strong> à <strong>${data.rdv.heure_debut}</strong>.<br><br>
Un email de confirmation vous a été envoyé à ${email}.`;
goToStep(4);
} catch (err) {
showError('Impossible de finaliser la réservation. Réessayez ou contactez-nous directement.');
btn.disabled = false;
btn.textContent = 'Confirmer le rendez-vous';
}
});
})();
</script>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js').catch(() => {});
}
</script>
</body>
</html>