JIT
HTML CSS JS Student Registration Form
<!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> transforme ce code html css java en react
Updated files
v1
dans un fichier jsx
Updated files
v2