<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Inscription Moonlie - Étudiante</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
/>
<style>
:root {
--primary: #6366f1; /* Indigo / Violet */
--secondary: #f472b6;
--background: #f8fafc;
--surface: #ffffff;
--text: #1e293b;
--radius: 16px;
--transition: all 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Inter', system-ui;
-webkit-tap-highlight-color: transparent;
}
body {
background: #1a1a1a;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 1rem;
}
.phone-frame {
width: 375px;
height: 667px;
background: var(--surface);
border-radius: 40px;
box-shadow: 0 0 0 12px #000, 0 20px 40px rgba(0,0,0,0.3);
overflow: hidden;
position: relative;
}
.notch {
width: 30%;
height: 24px;
background: #000;
border-radius: 0 0 20px 20px;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 2;
}
.container {
padding: 2rem 1.5rem;
height: calc(100% - 80px);
position: relative;
overflow: hidden; /* Pour éviter d’éventuels débordements */
}
.step {
position: absolute;
width: calc(100% - 3rem);
opacity: 0;
transform: translateX(30px);
transition: var(--transition);
pointer-events: none;
}
.step.active {
opacity: 1;
transform: translateX(0);
pointer-events: all;
}
/* Bouton Retour avec l’icône chevron-left */
.back-btn {
display: none;
position: absolute;
left: 1.5rem;
top: 3rem;
background: none;
border: none;
color: var(--primary);
font-size: 1.2rem;
z-index: 1000;
cursor: pointer;
}
.back-btn.visible {
display: block;
}
/* Barre de progression */
.progress-bar {
width: calc(100% - 3rem);
height: 6px;
background: #e0e7ff;
border-radius: 12px;
margin: 0 auto 2rem;
position: relative;
overflow: hidden;
}
.progress-fill {
height: 100%;
width: 0%;
background: linear-gradient(
90deg,
#8b5cf6 0%,
#6366f1 50%,
#ec4899 100%
);
border-radius: 12px;
transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 40%;
height: 200%;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.4) 50%,
rgba(255, 255, 255, 0) 100%
);
transform: rotate(15deg);
animation: progress-shine 2.5s infinite;
}
@keyframes progress-shine {
0% {
right: -20%;
}
100% {
right: 120%;
}
}
h2 {
font-size: 1.5rem;
margin-bottom: 2rem;
text-align: center;
color: #111827; /* Couleur plus sombre sur fond blanc */
}
.input-group {
margin-bottom: 1.5rem;
}
input,
select {
width: 100%;
padding: 1rem;
border: 2px solid #e2e8f0;
border-radius: var(--radius);
font-size: 1rem;
transition: var(--transition);
}
input:focus,
select:focus {
border-color: var(--primary);
outline: none;
}
/* Bouton Continuer plus réactif */
.continue-btn {
position: absolute;
bottom: 20px;
left: 1.5rem;
right: 1.5rem;
width: calc(100% - 3rem);
padding: 1rem;
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius);
font-size: 1rem;
cursor: pointer;
transform: translateY(0);
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.continue-btn:hover {
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.continue-btn:active {
transform: scale(0.95);
background: #4f46e5; /* Légèrement plus foncé */
}
.error-message {
color: #dc2626;
background: #fee2e2;
padding: 12px;
border-radius: 8px;
margin: 10px 0;
border: 1px solid #fca5a5;
animation: shake 0.4s;
display: none;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Roues plus grandes - 240px */
.wheel-container {
position: relative;
height: 240px; /* hauteur élargie */
margin: 1rem 0;
overflow: hidden;
border-radius: var(--radius);
background: #f8fafc;
touch-action: none; /* pour gérer le drag plus facilement */
}
.wheel {
position: absolute;
width: 100%;
transform-style: preserve-3d;
transition: transform 0.2s ease-out;
}
.wheel-item {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
color: #64748b;
position: relative;
will-change: transform, opacity;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
transition: all 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
/* Traits violets dynamiques quand élément sélectionné */
.wheel-item.highlighted {
color: var(--primary);
transform: scale(1.2);
font-weight: 600;
z-index: 2;
position: relative;
}
.wheel-item.highlighted::before,
.wheel-item.highlighted::after {
content: '';
position: absolute;
left: 0;
right: 0;
height: 3px;
background: var(--primary);
}
.wheel-item.highlighted::before {
top: -8px;
}
.wheel-item.highlighted::after {
bottom: -8px;
}
/* Dual wheels */
.dual-wheels {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 24px 0;
}
/* Centres d’intérêt */
.interest-category {
margin-bottom: 1.5rem;
}
.category-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8fafc;
border-radius: var(--radius);
cursor: pointer;
transition: var(--transition);
position: relative;
border: 2px solid #e2e8f0;
}
.category-header.validated {
border-color: var(--primary);
}
.category-header.unvalidated {
border-color: #ef4444;
animation: shake 0.4s;
}
.validation-status {
position: absolute;
right: 40px;
color: var(--primary);
opacity: 0;
transition: var(--transition);
}
.validated .validation-status {
opacity: 1;
}
.subcategories {
display: none;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 1rem;
padding-left: 0.5rem;
}
.subcategory-tag {
background: var(--surface);
border: 2px solid #e2e8f0;
border-radius: 2rem;
padding: 0.5rem 1rem;
cursor: pointer;
transition: var(--transition);
font-size: 0.9rem;
}
.subcategory-tag.selected {
background: var(--primary);
color: white;
border-color: var(--primary);
}
/* Localisation */
.location-step {
text-align: center;
}
.location-icon {
font-size: 3rem;
color: var(--primary);
margin-bottom: 2rem;
}
.enable-location-btn {
background: var(--primary);
color: white;
border: none;
border-radius: 12px;
padding: 1rem 2rem;
margin-top: 2rem;
cursor: pointer;
position: relative;
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.enable-location-btn:hover {
box-shadow: 0 4px 8px rgba(99, 102, 241, 0.3);
}
.enable-location-btn:active {
transform: scale(0.95);
background: #4f46e5; /* Légèrement plus foncé */
}
.enable-location-btn.clicked {
animation: pressAnimation 0.2s forwards;
}
@keyframes pressAnimation {
0% {
transform: scale(1);
}
50% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
.warning-text {
color: #ef4444;
font-size: 0.9rem;
text-align: center;
margin-top: 1rem;
}
/* Vérification */
.verification-options {
display: flex;
flex-direction: column;
gap: 1rem;
}
.upload-box {
border: 2px dashed #e2e8f0;
padding: 2rem;
text-align: center;
border-radius: var(--radius);
cursor: pointer;
transition: var(--transition);
}
.upload-box:hover {
border-color: var(--primary);
}
.verification-toggle {
display: flex;
align-items: center;
gap: 1rem;
justify-content: center;
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: '';
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--primary);
}
input:checked + .slider:before {
transform: translateX(26px);
}
</style>
</head>
<body>
<div class="phone-frame">
<div class="notch"></div>
<div class="container">
<!-- Barre de progression -->
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
<!-- Bouton retour (fa-chevron-left) -->
<button class="back-btn" id="backBtn">
<i class="fas fa-chevron-left"></i>
</button>
<!-- Étape 1 : Création de compte -->
<div class="step active" data-step="1">
<h2>Crée ton compte</h2>
<div class="input-group">
<input type="email" placeholder="Adresse email" required />
</div>
<div class="input-group">
<input type="text" placeholder="Nom complet" required />
</div>
<div class="input-group">
<input type="password" id="password" placeholder="Mot de passe" required />
</div>
<div class="input-group">
<input
type="password"
id="confirmPassword"
placeholder="Confirmer le mot de passe"
required
/>
</div>
<div class="error-message" id="passwordError"></div>
</div>
<!-- Étape 2 : Établissement -->
<div class="step" data-step="2">
<h2>Ton établissement</h2>
<div class="wheel-container">
<div class="wheel" id="schoolWheel">
<div class="wheel-item">Université</div>
<div class="wheel-item">École de commerce</div>
<div class="wheel-item">École d'ingénieur</div>
<div class="wheel-item">DUT/BTS</div>
<div class="wheel-item">Autre</div>
</div>
</div>
<div class="input-group" style="margin-top: 1.5rem;">
<input type="text" placeholder="Nom de l'établissement" required />
</div>
</div>
<!-- Étape 3 : Formation -->
<div class="step" data-step="3">
<h2>Ta formation</h2>
<div class="dual-wheels">
<div class="wheel-container">
<div class="wheel" id="domainWheel">
<div class="wheel-item">Mathématiques</div>
<div class="wheel-item">Économie</div>
<div class="wheel-item">Ingénierie</div>
<div class="wheel-item">Informatique</div>
<div class="wheel-item">Biologie</div>
</div>
</div>
<div class="wheel-container">
<div class="wheel" id="levelWheel">
<div class="wheel-item">L1</div>
<div class="wheel-item">L2</div>
<div class="wheel-item">L3</div>
<div class="wheel-item">M1</div>
<div class="wheel-item">M2</div>
<div class="wheel-item">Doctorat</div>
</div>
</div>
</div>
</div>
<!-- Étape 4 : Centres d'intérêt -->
<div class="step" data-step="4">
<h2>Tes centres d'intérêt</h2>
<div class="error-message" id="interestError">
Veuillez sélectionner au moins un intérêt dans chaque catégorie !
</div>
<!-- Musique -->
<div class="interest-category">
<div class="category-header">
<span>🎵 Musique</span>
<i class="fas fa-chevron-down"></i>
<i class="fas fa-check-circle validation-status"></i>
</div>
<div class="subcategories">
<div class="subcategory-tag">Rock</div>
<div class="subcategory-tag">Electro</div>
<div class="subcategory-tag">Hip-Hop</div>
<div class="subcategory-tag">Classique</div>
<div class="subcategory-tag">Jazz</div>
<div class="subcategory-tag">Rap</div>
<div class="subcategory-tag">Techno</div>
<div class="subcategory-tag">Reggae</div>
</div>
</div>
<!-- Sport -->
<div class="interest-category">
<div class="category-header">
<span>⚽ Sport</span>
<i class="fas fa-chevron-down"></i>
<i class="fas fa-check-circle validation-status"></i>
</div>
<div class="subcategories">
<div class="subcategory-tag">Football</div>
<div class="subcategory-tag">Basketball</div>
<div class="subcategory-tag">Tennis</div>
<div class="subcategory-tag">Rugby</div>
<div class="subcategory-tag">Natation</div>
<div class="subcategory-tag">Escalade</div>
<div class="subcategory-tag">Cyclisme</div>
<div class="subcategory-tag">Musculation</div>
</div>
</div>
<!-- Technologie -->
<div class="interest-category">
<div class="category-header">
<span>💻 Technologie</span>
<i class="fas fa-chevron-down"></i>
<i class="fas fa-check-circle validation-status"></i>
</div>
<div class="subcategories">
<div class="subcategory-tag">Intelligence Artificielle</div>
<div class="subcategory-tag">Robotique</div>
<div class="subcategory-tag">Gaming</div>
<div class="subcategory-tag">Cybersécurité</div>
<div class="subcategory-tag">Développement Web</div>
<div class="subcategory-tag">Blockchain</div>
<div class="subcategory-tag">Réalité Virtuelle</div>
<div class="subcategory-tag">IoT (Internet des Objets)</div>
</div>
</div>
</div>
<!-- Étape 5 : Localisation -->
<div class="step" data-step="5">
<div class="location-step">
<i class="fas fa-map-marker-alt location-icon"></i>
<h2>Activer la localisation</h2>
<button class="enable-location-btn" id="locationBtn">
Autoriser l'accès
</button>
<p class="warning-text">Modifiable dans les paramètres</p>
</div>
</div>
<!-- Étape 6 : Vérification -->
<div class="step" data-step="6">
<h2>Vérification</h2>
<div class="verification-options">
<div class="upload-box" id="uploadBox">
<i class="fas fa-file-upload fa-2x"></i>
<p>Déposez votre carte étudiante ou certificat</p>
<input
type="file"
id="verificationFile"
hidden
accept=".pdf,.jpg,.png"
/>
</div>
<div class="verification-toggle">
<label class="switch">
<input type="checkbox" id="postponeVerification" />
<span class="slider"></span>
</label>
<p>Vérifier plus tard</p>
</div>
<p class="warning-text">
⚠️ Accès limité aux événements privés sans vérification
</p>
</div>
</div>
<!-- Bouton Continuer -->
<button class="continue-btn" id="continueBtn">Continuer</button>
</div>
</div>
<script>
let currentStep = 1;
const totalSteps = 6;
const steps = document.querySelectorAll('.step');
const continueBtn = document.getElementById('continueBtn');
const backBtn = document.getElementById('backBtn');
const progressFill = document.getElementById('progressFill');
// -----------------------------------
// FONCTIONS DE VALIDATION
// -----------------------------------
function validatePassword() {
const password = document.getElementById('password').value;
const confirm = document.getElementById('confirmPassword').value;
const errorElement = document.getElementById('passwordError');
if (!password || !confirm) {
errorElement.textContent = "Veuillez remplir tous les champs";
errorElement.style.display = 'block';
return false;
}
if (password !== confirm) {
errorElement.textContent = "Les mots de passe ne correspondent pas";
errorElement.style.display = 'block';
errorElement.style.animation = 'shake 0.4s';
return false;
}
errorElement.style.display = 'none';
return true;
}
function validateSchool() {
const schoolName = document.querySelector('[data-step="2"] input').value.trim();
const wheelHighlighted = document.querySelector('#schoolWheel .highlighted');
return schoolName.length > 0 && !!wheelHighlighted;
}
function validateFormation() {
const domainHighlighted = document.querySelector('#domainWheel .highlighted');
const levelHighlighted = document.querySelector('#levelWheel .highlighted');
return !!domainHighlighted && !!levelHighlighted;
}
function checkAllValidated() {
const allCategories = document.querySelectorAll('.interest-category');
let allValid = true;
allCategories.forEach(category => {
if (!category.querySelector('.subcategory-tag.selected')) {
category.querySelector('.category-header').classList.add('unvalidated');
allValid = false;
} else {
category.querySelector('.category-header').classList.remove('unvalidated');
category.querySelector('.category-header').classList.add('validated');
}
});
return allValid;
}
function showInterestError() {
const interestError = document.getElementById('interestError');
interestError.style.display = 'block';
interestError.style.animation = 'shake 0.4s';
}
async function validateCurrentStep() {
switch (currentStep) {
case 1:
return validatePassword();
case 2:
return validateSchool();
case 3:
return validateFormation();
case 4:
if (!checkAllValidated()) {
showInterestError();
return false;
}
return true;
default:
return true;
}
}
// -----------------------------------
// FONCTIONS DE NAVIGATION
// -----------------------------------
function updateUI() {
// Bouton retour visible sauf à l'étape 2
backBtn.classList.toggle('visible', currentStep > 1 && currentStep !== 2);
const progressPercent = ((currentStep - 1) / (totalSteps - 1)) * 100;
progressFill.style.width = `${progressPercent}%`;
steps.forEach(step => {
const stepNumber = parseInt(step.dataset.step);
step.classList.toggle('active', stepNumber === currentStep);
});
}
function finishRegistration() {
alert("Inscription terminée !");
}
continueBtn.addEventListener('click', async () => {
if (!(await validateCurrentStep())) return;
if (currentStep < totalSteps) {
currentStep++;
updateUI();
} else {
finishRegistration();
}
});
backBtn.addEventListener('click', () => {
if (currentStep > 1 && currentStep !== 2) {
currentStep--;
updateUI();
}
});
// swipe horizontal (optionnel)
let touchStartX = 0;
document.addEventListener('touchstart', e => {
touchStartX = e.touches[0].clientX;
});
document.addEventListener('touchend', e => {
const touchEndX = e.changedTouches[0].clientX;
const deltaX = touchStartX - touchEndX;
if (Math.abs(deltaX) > 50) {
if (deltaX > 0 && currentStep < totalSteps) {
currentStep++;
} else if (deltaX < 0 && currentStep > 1 && currentStep !== 2) {
currentStep--;
}
updateUI();
}
});
// Pour masquer l’erreur mot de passe dès qu’on tape
document.querySelectorAll('#password, #confirmPassword').forEach(input => {
input.addEventListener('input', () => {
const errorElement = document.getElementById('passwordError');
errorElement.style.display = 'none';
errorElement.style.animation = '';
});
});
// Gestion des catégories d’intérêts
document.querySelectorAll('.category-header').forEach(header => {
header.addEventListener('click', () => {
const subcats = header.nextElementSibling;
subcats.style.display = subcats.style.display === 'flex' ? 'none' : 'flex';
header
.querySelector('i.fa-chevron-down')
?.classList.toggle('fa-chevron-up');
});
});
document.querySelectorAll('.subcategory-tag').forEach(tag => {
tag.addEventListener('click', () => {
tag.classList.toggle('selected');
const category = tag.closest('.interest-category');
const header = category.querySelector('.category-header');
if (category.querySelector('.subcategory-tag.selected')) {
header.classList.add('validated');
header.classList.remove('unvalidated');
} else {
header.classList.remove('validated');
}
const interestError = document.getElementById('interestError');
if (checkAllValidated()) {
interestError.style.display = 'none';
interestError.style.animation = '';
}
});
});
// Vérification retardée
document.getElementById('postponeVerification').addEventListener('change', e => {
const uploadBox = document.getElementById('uploadBox');
uploadBox.style.opacity = e.target.checked ? 0.5 : 1;
uploadBox.style.pointerEvents = e.target.checked ? 'none' : 'all';
});
// -----------------------------------
// CLASSE WHEELPICKER
// -----------------------------------
class WheelPicker {
constructor(container) {
this.container = container;
this.items = Array.from(container.children);
this.itemHeight = 60; // correspond à .wheel-item { height: 60px }
this.isDragging = false;
this.startY = 0;
this.currentPos = this.calculateInitialPosition();
this.init();
this.update(); // Force la mise à jour initiale
}
calculateInitialPosition() {
const containerHeight = this.container.parentElement.clientHeight;
return (containerHeight / 2) - (this.itemHeight / 2);
}
init() {
this.addEventListeners();
this.container.parentElement.addEventListener('wheel', e => e.preventDefault());
}
addEventListeners() {
this.container.addEventListener('touchstart', e => this.handleStart(e));
this.container.addEventListener('touchmove', e => this.handleMove(e));
this.container.addEventListener('touchend', () => this.handleEnd());
this.container.addEventListener('mousedown', e => this.handleStart(e));
document.addEventListener('mousemove', e => this.handleMove(e));
document.addEventListener('mouseup', () => this.handleEnd());
}
handleStart(e) {
this.isDragging = true;
this.startY = this.getClientY(e);
this.container.style.transition = 'none';
e.preventDefault();
}
handleMove(e) {
if (!this.isDragging) return;
const y = this.getClientY(e);
const delta = y - this.startY;
this.currentPos += delta;
this.startY = y;
this.applyBoundaries();
this.update();
}
handleEnd() {
this.isDragging = false;
this.snapToClosest();
}
getClientY(e) {
return e.clientY || (e.touches && e.touches[0].clientY) || 0;
}
applyBoundaries() {
const maxPos = this.calculateInitialPosition();
const minPos = -this.itemHeight * (this.items.length - 1) + maxPos;
this.currentPos = Math.min(maxPos, Math.max(minPos, this.currentPos));
}
snapToClosest() {
const containerHeight = this.container.parentElement.clientHeight;
const targetIndex = Math.round(
(containerHeight / 2 - this.currentPos - this.itemHeight / 2) / this.itemHeight
);
this.currentPos =
containerHeight / 2 -
targetIndex * this.itemHeight -
this.itemHeight / 2;
this.container.style.transition =
'transform 0.6s cubic-bezier(0.23, 1, 0.32, 1)';
this.update();
}
update() {
this.container.style.transform = `translateY(${this.currentPos}px)`;
this.highlightCenterItem();
}
highlightCenterItem() {
const containerCenter = this.container.parentElement.clientHeight / 2;
this.items.forEach(item => {
const itemTop = item.offsetTop + this.currentPos;
const itemCenter = itemTop + this.itemHeight / 2;
const isHighlighted =
Math.abs(itemCenter - containerCenter) < this.itemHeight / 3;
item.classList.toggle('highlighted', isHighlighted);
});
}
}
// -----------------------------------
// INITIALISATION + BOUTON LOCALISATION
// -----------------------------------
document.addEventListener('DOMContentLoaded', () => {
updateUI();
// Roulettes
new WheelPicker(document.getElementById('schoolWheel'));
new WheelPicker(document.getElementById('domainWheel'));
new WheelPicker(document.getElementById('levelWheel'));
// Bouton localisation dynamique
const locationBtn = document.getElementById('locationBtn');
locationBtn.addEventListener('click', () => {
// Petit effet visuel
locationBtn.classList.add('clicked');
setTimeout(() => {
locationBtn.classList.remove('clicked');
}, 200);
// Confirmation
const userResponse = confirm("Voulez-vous autoriser l'accès à votre localisation ?");
if (userResponse) {
alert("Localisation activée !");
// Ici vous pouvez inscrire une logique supplémentaire si nécessaire
} else {
alert("Accès refusé.");
}
});
});
</script>
</body>
</html>
convertir ce code html css java en react