Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>How was your class? - Quick Survey</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: '#3B82F6', | |
| 'primary-dark': '#1E40AF', | |
| success: '#10B981', | |
| danger: '#EF4444', | |
| warning: '#F59E0B', | |
| text: '#1F2937', | |
| 'text-secondary': '#6B7280', | |
| border: '#E5E7EB', | |
| background: '#F9FAFB', | |
| surface: '#FFFFFF', | |
| 'surface-alt': '#F3F4F6', | |
| }, | |
| spacing: { | |
| 'grid-unit': '8px', | |
| 'rhythm': '24px', | |
| 'touch-target': '44px', | |
| 'touch-target-mobile': '56px', | |
| }, | |
| borderRadius: { | |
| 'xl': '16px', | |
| }, | |
| boxShadow: { | |
| 'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)', | |
| 'DEFAULT': '0 4px 6px -1px rgba(0, 0, 0, 0.1)', | |
| 'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1)', | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes scaleIn { | |
| from { | |
| transform: scale(0); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: scale(1); | |
| opacity: 1; | |
| } | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 75% { transform: translateX(5px); } | |
| } | |
| .animate-slideUp { | |
| animation: slideUp 0.4s ease-out; | |
| } | |
| .animate-scaleIn { | |
| animation: scaleIn 0.4s ease-out; | |
| } | |
| .loading .submit-btn::after { | |
| content: ""; | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| top: 50%; | |
| left: 50%; | |
| margin-left: -10px; | |
| margin-top: -10px; | |
| border: 2px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: white; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| .error-message.show { | |
| display: block; | |
| animation: shake 0.3s ease-out; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex items-center justify-center p-4 bg-gradient-to-br from-blue-50 to-indigo-100"> | |
| <div class="survey-container w-full max-w-2xl bg-surface rounded-2xl shadow-lg overflow-hidden animate-slideUp"> | |
| <!-- Survey Header --> | |
| <div class="survey-header p-6 bg-surface-alt text-center border-b border-border"> | |
| <div class="flex justify-center mb-4"> | |
| <div class="bg-primary w-12 h-12 rounded-full flex items-center justify-center"> | |
| <i data-feather="star" class="text-white"></i> | |
| </div> | |
| </div> | |
| <h1 class="school-name text-2xl font-bold text-text mb-1">Elite Martial Arts Academy</h1> | |
| <p class="survey-title text-text-secondary">Quick Class Feedback</p> | |
| </div> | |
| <!-- Survey Form --> | |
| <form id="surveyForm" class="survey-body p-8"> | |
| <!-- Hidden fields for tracking --> | |
| <input type="hidden" name="surveyId" value="7SP--yH"> | |
| <input type="hidden" name="memberId" value="{MEMBER_ID}"> | |
| <input type="hidden" name="classNumber" value="{CLASS_NUMBER}"> | |
| <!-- Question Section --> | |
| <div class="question-section mb-8"> | |
| <label class="question-label text-xl font-medium text-text text-center block"> | |
| Hi <span class="member-name text-primary font-semibold">Sarah</span>, how was last night's class? | |
| </label> | |
| <!-- Spacer --> | |
| <div class="h-6"></div> | |
| <!-- Response Options Grid --> | |
| <div class="response-grid grid grid-cols-2 gap-4 mb-6" id="responseGrid"> | |
| <label class="response-option cursor-pointer transition-all duration-300"> | |
| <input type="radio" name="response" value="great" class="hidden" required> | |
| <div class="response-label flex flex-col items-center justify-center min-h-[112px] p-5 bg-surface border-2 border-border rounded-xl text-center transition-all duration-300 hover:bg-surface-alt hover:border-primary hover:transform hover:-translate-y-1 hover:shadow"> | |
| <span class="response-icon text-4xl mb-3">😊</span> | |
| <span class="response-text font-medium text-text">Great Class</span> | |
| </div> | |
| </label> | |
| <label class="response-option cursor-pointer transition-all duration-300"> | |
| <input type="radio" name="response" value="good" class="hidden" required> | |
| <div class="response-label flex flex-col items-center justify-center min-h-[112px] p-5 bg-surface border-2 border-border rounded-xl text-center transition-all duration-300 hover:bg-surface-alt hover:border-primary hover:transform hover:-translate-y-1 hover:shadow"> | |
| <span class="response-icon text-4xl mb-3">👍</span> | |
| <span class="response-text font-medium text-text">Good, enjoyed it</span> | |
| </div> | |
| </label> | |
| <label class="response-option cursor-pointer transition-all duration-300"> | |
| <input type="radio" name="response" value="okay" class="hidden" required> | |
| <div class="response-label flex flex-col items-center justify-center min-h-[112px] p-5 bg-surface border-2 border-border rounded-xl text-center transition-all duration-300 hover:bg-surface-alt hover:border-primary hover:transform hover:-translate-y-1 hover:shadow"> | |
| <span class="response-icon text-4xl mb-3">😐</span> | |
| <span class="response-text font-medium text-text">OK, could be better</span> | |
| </div> | |
| </label> | |
| <label class="response-option cursor-pointer transition-all duration-300"> | |
| <input type="radio" name="response" value="discouraged" class="hidden" required> | |
| <div class="response-label flex flex-col items-center justify-center min-h-[112px] p-5 bg-surface border-2 border-border rounded-xl text-center transition-all duration-300 hover:bg-surface-alt hover:border-primary hover:transform hover:-translate-y-1 hover:shadow"> | |
| <span class="response-icon text-4xl mb-3">😔</span> | |
| <span class="response-text font-medium text-text">I'm discouraged</span> | |
| </div> | |
| </label> | |
| </div> | |
| <!-- Error Message --> | |
| <div class="error-message hidden p-4 bg-red-50 border border-red-100 rounded-lg text-danger text-sm mb-6" id="errorMessage"> | |
| Please select how your class went before submitting. | |
| </div> | |
| </div> | |
| <!-- Optional Feedback Section --> | |
| <div class="feedback-section mb-8"> | |
| <label class="feedback-label block font-medium text-text mb-3" for="feedback"> | |
| Any specific feedback? (Optional) | |
| </label> | |
| <textarea | |
| id="feedback" | |
| name="feedback" | |
| class="feedback-textarea w-full min-h-[110px] p-4 text-text font-sans border-2 border-border rounded-lg resize-y transition-all duration-300 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20" | |
| placeholder="Tell us more about your experience..." | |
| maxlength="500"></textarea> | |
| <p class="feedback-hint text-sm text-text-secondary mt-2">Your feedback helps us improve our classes</p> | |
| </div> | |
| <!-- Submit Button --> | |
| <div class="submit-section text-center"> | |
| <button type="submit" class="submit-btn relative min-w-[200px] min-h-[56px] px-8 py-4 text-lg font-semibold text-white bg-primary border-none rounded-lg cursor-pointer transition-all duration-300 font-sans shadow hover:bg-primary-dark hover:transform hover:-translate-y-1 hover:shadow-lg active:transform active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"> | |
| Submit Feedback | |
| </button> | |
| </div> | |
| </form> | |
| <!-- Success Message (hidden initially) --> | |
| <div class="success-message hidden text-center p-12" id="successMessage"> | |
| <div class="success-icon w-20 h-20 mx-auto mb-6 bg-success rounded-full flex items-center justify-center animate-scaleIn"> | |
| <i data-feather="check" class="text-white w-12 h-12"></i> | |
| </div> | |
| <h2 class="success-title text-2xl font-bold text-text mb-3">Thank you!</h2> | |
| <p class="success-text text-text-secondary">Your feedback has been received and will help us improve.</p> | |
| </div> | |
| <!-- Footer --> | |
| <div class="survey-footer p-5 bg-surface-alt text-center border-t border-border text-sm text-text-secondary"> | |
| <p>© 2025 Elite Martial Arts Academy | <a href="#" target="_blank" class="text-primary hover:underline">Visit Website</a></p> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script> | |
| feather.replace(); | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const form = document.getElementById('surveyForm'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| const successMessage = document.getElementById('successMessage'); | |
| const submitBtn = form.querySelector('.submit-btn'); | |
| // Handle form submission | |
| form.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| // Check if a response is selected | |
| const selectedResponse = form.querySelector('input[name="response"]:checked'); | |
| if (!selectedResponse) { | |
| // Show error message | |
| errorMessage.classList.remove('hidden'); | |
| errorMessage.classList.add('show'); | |
| // Focus on first response option | |
| form.querySelector('input[name="response"]').focus(); | |
| // Remove error after 3 seconds | |
| setTimeout(() => { | |
| errorMessage.classList.remove('show'); | |
| errorMessage.classList.add('hidden'); | |
| }, 3000); | |
| return; | |
| } | |
| // Hide any existing error | |
| errorMessage.classList.remove('show'); | |
| errorMessage.classList.add('hidden'); | |
| // Show loading state | |
| form.classList.add('loading'); | |
| submitBtn.disabled = true; | |
| // Collect form data | |
| const formData = new FormData(form); | |
| // Simulate API call (in production, use actual endpoint) | |
| setTimeout(() => { | |
| // Hide form, show success | |
| form.style.display = 'none'; | |
| successMessage.classList.remove('hidden'); | |
| // Optional: Redirect after success | |
| setTimeout(() => { | |
| // window.location.href = '/thank-you'; | |
| }, 3000); | |
| }, 1500); | |
| }); | |
| // Remove error message when user selects an option | |
| const responseInputs = form.querySelectorAll('input[name="response"]'); | |
| responseInputs.forEach(input => { | |
| input.addEventListener('change', function() { | |
| errorMessage.classList.remove('show'); | |
| errorMessage.classList.add('hidden'); | |
| }); | |
| }); | |
| // Character counter for feedback (optional enhancement) | |
| const feedbackTextarea = document.getElementById('feedback'); | |
| const feedbackHint = document.querySelector('.feedback-hint'); | |
| if (feedbackTextarea) { | |
| feedbackTextarea.addEventListener('input', function() { | |
| const remaining = 500 - this.value.length; | |
| if (remaining < 100) { | |
| feedbackHint.textContent = `${remaining} characters remaining`; | |
| feedbackHint.classList.remove('text-text-secondary'); | |
| feedbackHint.classList.add('text-danger'); | |
| } else { | |
| feedbackHint.textContent = 'Your feedback helps us improve our classes'; | |
| feedbackHint.classList.remove('text-danger'); | |
| feedbackHint.classList.add('text-text-secondary'); | |
| } | |
| }); | |
| } | |
| // Auto-expand textarea | |
| if (feedbackTextarea) { | |
| feedbackTextarea.addEventListener('input', function() { | |
| this.style.height = 'auto'; | |
| this.style.height = Math.min(this.scrollHeight, 300) + 'px'; | |
| }); | |
| } | |
| // Add keyboard navigation for response options | |
| responseInputs.forEach((input, index) => { | |
| input.addEventListener('keydown', function(e) { | |
| if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { | |
| e.preventDefault(); | |
| const nextIndex = (index + 1) % responseInputs.length; | |
| responseInputs[nextIndex].focus(); | |
| responseInputs[nextIndex].checked = true; | |
| updateLabelStyles(); | |
| } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { | |
| e.preventDefault(); | |
| const prevIndex = (index - 1 + responseInputs.length) % responseInputs.length; | |
| responseInputs[prevIndex].focus(); | |
| responseInputs[prevIndex].checked = true; | |
| updateLabelStyles(); | |
| } | |
| }); | |
| // Update label styles when input is checked | |
| input.addEventListener('change', updateLabelStyles); | |
| }); | |
| function updateLabelStyles() { | |
| document.querySelectorAll('.response-option').forEach(option => { | |
| const input = option.querySelector('input'); | |
| const label = option.querySelector('.response-label'); | |
| if (input.checked) { | |
| label.classList.add('bg-blue-50', 'border-primary', 'ring-2', 'ring-primary/20'); | |
| } else { | |
| label.classList.remove('bg-blue-50', 'border-primary', 'ring-2', 'ring-primary/20'); | |
| } | |
| }); | |
| } | |
| // Get URL parameters (for actual member data) | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const memberId = urlParams.get('m'); | |
| const surveyId = urlParams.get('s'); | |
| const classNumber = urlParams.get('c'); | |
| // Update hidden fields if parameters exist | |
| if (memberId) { | |
| form.querySelector('input[name="memberId"]').value = memberId; | |
| } | |
| if (surveyId) { | |
| form.querySelector('input[name="surveyId"]').value = surveyId; | |
| } | |
| if (classNumber) { | |
| form.querySelector('input[name="classNumber"]').value = classNumber; | |
| } | |
| // Check for response format configuration (star vs emoji) | |
| const format = urlParams.get('format') || 'emoji'; | |
| if (format === 'stars') { | |
| updateToStarFormat(); | |
| } | |
| function updateToStarFormat() { | |
| const responseData = [ | |
| { value: 'great', stars: '★★★★', text: 'Great Class' }, | |
| { value: 'good', stars: '★★★', text: 'Good, enjoyed it' }, | |
| { value: 'okay', stars: '★★', text: 'OK, could be better' }, | |
| { value: 'discouraged', stars: '★', text: "I'm discouraged" } | |
| ]; | |
| const responseGrid = document.getElementById('responseGrid'); | |
| responseGrid.innerHTML = ''; | |
| responseData.forEach(response => { | |
| const label = document.createElement('label'); | |
| label.className = 'response-option cursor-pointer transition-all duration-300'; | |
| label.innerHTML = ` | |
| <input type="radio" name="response" value="${response.value}" class="hidden" required> | |
| <div class="response-label flex flex-col items-center justify-center min-h-[112px] p-5 bg-surface border-2 border-border rounded-xl text-center transition-all duration-300 hover:bg-surface-alt hover:border-primary hover:transform hover:-translate-y-1 hover:shadow"> | |
| <span class="response-icon text-warning text-3xl mb-3">${response.stars}</span> | |
| <span class="response-text font-medium text-text">${response.text}</span> | |
| </div> | |
| `; | |
| responseGrid.appendChild(label); | |
| }); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |