class-feedback-wizard / index.html
usezivvy's picture
<!DOCTYPE html>
a254f44 verified
Raw
History Blame Contribute Delete
19.3 kB
<!DOCTYPE html>
<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>