Spaces:
Running
Running
Build a modern, animated client review website named Nomarddesk that allows users to select a service they received (Telegram Promotion, Telegram Ads, Telegram Bot Development, Vibe Coding / App Development, App Promotion, Music Marketing, Crypto Marketing), rate each service using star ratings across multiple categories (Community Support, Customer Service, Amount Spent Value, Service Quality, Recommendation Level), and optionally leave written feedback, with a fullscreen hero section featuring an animated video background related to Telegram services, app development, marketing, music promotion, and crypto, a headline reading “Rate Your Experience with Nomarddesk”, a call-to-action button saying “Leave a Review”, dynamic review display cards with filtering by service and rating, smooth futuristic UI animations, fast loading performance, mobile responsiveness, spam prevention, and an optional admin panel for viewing and moderating reviews.
18a3a34 verified | // Global variables | |
| let selectedService = null; | |
| let ratings = { | |
| community: 0, | |
| customer: 0, | |
| value: 0, | |
| quality: 0, | |
| recommendation: 0 | |
| }; | |
| let reviews = JSON.parse(localStorage.getItem('nomarddeskReviews')) || []; | |
| // Initialize the app | |
| document.addEventListener('DOMContentLoaded', function() { | |
| initializeParticles(); | |
| loadReviews(); | |
| feather.replace(); | |
| }); | |
| // Create animated particles | |
| function initializeParticles() { | |
| const bg = document.querySelector('.animated-bg'); | |
| if (!bg) return; | |
| for (let i = 0; i < 3; i++) { | |
| const particle = document.createElement('div'); | |
| particle.className = 'particle'; | |
| bg.appendChild(particle); | |
| } | |
| } | |
| // Scroll to review section | |
| function scrollToReview() { | |
| document.getElementById('review-section').scrollIntoView({ | |
| behavior: 'smooth' | |
| }); | |
| } | |
| // Select service | |
| function selectService(service) { | |
| selectedService = service; | |
| // Update UI | |
| document.querySelectorAll('.service-card').forEach(card => { | |
| card.classList.remove('selected'); | |
| if (card.textContent.includes(service)) { | |
| card.classList.add('selected'); | |
| } | |
| }); | |
| // Show rating section with animation | |
| const ratingSection = document.getElementById('rating-section'); | |
| ratingSection.classList.remove('hidden'); | |
| ratingSection.style.animation = 'slideUp 0.5s ease-out'; | |
| // Reset ratings | |
| Object.keys(ratings).forEach(key => { | |
| ratings[key] = 0; | |
| updateStarDisplay(key); | |
| }); | |
| } | |
| // Set rating for category | |
| function setRating(category, rating) { | |
| ratings[category] = rating; | |
| updateStarDisplay(category); | |
| // Add animation effect | |
| const stars = document.querySelectorAll(`[data-category="${category}"] .star`); | |
| stars.forEach((star, index) => { | |
| setTimeout(() => { | |
| star.style.transform = 'scale(1.2)'; | |
| setTimeout(() => { | |
| star.style.transform = 'scale(1)'; | |
| }, 100); | |
| }, index * 50); | |
| }); | |
| } | |
| // Update star display | |
| function updateStarDisplay(category) { | |
| const stars = document.querySelectorAll(`[data-category="${category}"] .star`); | |
| stars.forEach((star, index) => { | |
| if (index < ratings[category]) { | |
| star.classList.add('filled'); | |
| star.classList.remove('text-gray-600'); | |
| } else { | |
| star.classList.remove('filled'); | |
| star.classList.add('text-gray-600'); | |
| } | |
| }); | |
| } | |
| // Submit review | |
| function submitReview() { | |
| // Validate | |
| if (!selectedService) { | |
| showNotification('Please select a service', 'error'); | |
| return; | |
| } | |
| const hasRatings = Object.values(ratings).some(r => r > 0); | |
| if (!hasRatings) { | |
| showNotification('Please provide at least one rating', 'error'); | |
| return; | |
| } | |
| // Create review object | |
| const review = { | |
| id: Date.now(), | |
| service: selectedService, | |
| ratings: { ...ratings }, | |
| feedback: document.getElementById('feedback').value, | |
| date: new Date().toISOString(), | |
| averageRating: Object.values(ratings).reduce((a, b) => a + b, 0) / Object.values(ratings).filter(r => r > 0).length | |
| }; | |
| // Save review | |
| reviews.unshift(review); | |
| localStorage.setItem('nomarddeskReviews', JSON.stringify(reviews)); | |
| // Show success modal | |
| showSuccessModal(); | |
| // Reset form | |
| resetForm(); | |
| // Update display | |
| loadReviews(); | |
| } | |
| // Reset form | |
| function resetForm() { | |
| selectedService = null; | |
| Object.keys(ratings).forEach(key => { | |
| ratings[key] = 0; | |
| }); | |
| document.getElementById('feedback').value = ''; | |
| document.querySelectorAll('.service-card').forEach(card => { | |
| card.classList.remove('selected'); | |
| }); | |
| document.getElementById('rating-section').classList.add('hidden'); | |
| } | |
| // Show success modal | |
| function showSuccessModal() { | |
| const modal = document.getElementById('success-modal'); | |
| modal.style.display = 'flex'; | |
| modal.classList.add('animate-fadeIn'); | |
| } | |
| // Close modal | |
| function closeModal() { | |
| const modal = document.getElementById('success-modal'); | |
| modal.style.display = 'none'; | |
| } | |
| // Load reviews | |
| function loadReviews() { | |
| const container = document.getElementById('reviews-container'); | |
| container.innerHTML = ''; | |
| // Sample reviews if no reviews exist | |
| if (reviews.length === 0) { | |
| const sampleReviews = [ | |
| { | |
| id: 1, | |
| service: 'Telegram Promotion', | |
| ratings: { community: 5, customer: 5, value: 4, quality: 5, recommendation: 5 }, | |
| feedback: 'Excellent service! Our Telegram community grew by 10k members in just 2 weeks.', | |
| date: new Date(Date.now() - 86400000).toISOString(), | |
| averageRating: 4.8 | |
| }, | |
| { | |
| id: 2, | |
| service: 'Vibe Coding / App Development', | |
| ratings: { community: 5, customer: 5, value: 5, quality: 5, recommendation: 5 }, | |
| feedback: 'The app they built for us exceeded all expectations. Professional team and great communication!', | |
| date: new Date(Date.now() - 172800000).toISOString(), | |
| averageRating: 5 | |
| }, | |
| { | |
| id: 3, | |
| service: 'Music Marketing', | |
| ratings: { community: 4, customer: 4, value: 4, quality: 4, recommendation: 4 }, | |
| feedback: 'Helped promote my latest track effectively. Got great results within budget.', | |
| date: new Date(Date.now() - 259200000).toISOString(), | |
| averageRating: 4 | |
| } | |
| ]; | |
| reviews = sampleReviews; | |
| } | |
| // Display reviews | |
| reviews.slice(0, 6).forEach(review => { | |
| const card = createReviewCard(review); | |
| container.appendChild(card); | |
| }); | |
| } | |
| // Create review card | |
| function createReviewCard(review) { | |
| const card = document.createElement('div'); | |
| card.className = 'review-card'; | |
| const starsHtml = '★'.repeat(Math.round(review.averageRating)) + '☆'.repeat(5 - Math.round(review.averageRating)); | |
| card.innerHTML = ` | |
| <div class="flex items-center justify-between mb-3"> | |
| <span class="review-stars">${starsHtml}</span> | |
| <span class="text-sm text-gray-400">${formatDate(review.date)}</span> | |
| </div> | |
| <span class="review-service">${review.service}</span> | |
| ${review.feedback ? `<p class="mt-3 text-gray-300">${review.feedback}</p>` : ''} | |
| <div class="mt-4 grid grid-cols-5 gap-2 text-xs"> | |
| <div class="text-center"> | |
| <div class="text-yellow-400">★</div> | |
| <div class="text-gray-500">Comm</div> | |
| <div>${review.ratings.community || '-'}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-yellow-400">★</div> | |
| <div class="text-gray-500">Service</div> | |
| <div>${review.ratings.customer || '-'}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-yellow-400">★</div> | |
| <div class="text-gray-500">Value</div> | |
| <div>${review.ratings.value || '-'}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-yellow-400">★</div> | |
| <div class="text-gray-500">Quality</div> | |
| <div>${review.ratings.quality || '-'}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-yellow-400">★</div> | |
| <div class="text-gray-500">Rec</div> | |
| <div>${review.ratings.recommendation || '-'}</div> | |
| </div> | |
| </div> | |
| `; | |
| return card; | |
| } | |
| // Format date | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| const now = new Date(); | |
| const diffTime = Math.abs(now - date); | |
| const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); | |
| if (diffDays === 0) return 'Today'; | |
| if (diffDays === 1) return 'Yesterday'; | |
| if (diffDays < 7) return `${diffDays} days ago`; | |
| if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`; | |
| if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`; | |
| return `${Math.floor(diffDays / 365)} years ago`; | |
| } | |
| // Filter reviews | |
| function filterReviews(filter) { | |
| const buttons = document.querySelectorAll('.filter-btn'); | |
| buttons.forEach(btn => btn.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| let filteredReviews = [...reviews]; | |
| switch(filter) { | |
| case 'highest': | |
| filteredReviews.sort((a, b) => b.averageRating - a.averageRating); | |
| break; | |
| case 'recent': | |
| filteredReviews.sort((a, b) => new Date(b.date) - new Date(a.date)); | |
| break; | |
| } | |
| const container = document.getElementById('reviews-container'); | |
| container.innerHTML = ''; | |
| filteredReviews.slice(0, 6).forEach(review => { | |
| const card = createReviewCard(review); | |
| container.appendChild(card); | |
| }); | |
| } | |
| // Show notification | |
| function showNotification(message, type = 'info') { | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed top-20 right-4 px-6 py-3 rounded-lg z-50 animate-slideUp ${ | |
| type === 'error' ? 'bg-red-500' : 'bg-green-500' | |
| }`; | |
| notification.textContent = message; | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.remove(); | |
| }, 3000); | |
| } | |
| // Add keyboard navigation | |
| document.addEventListener('keydown', function(e) { | |
| if (e.key === 'Escape') { | |
| closeModal(); | |
| } | |
| }); | |
| // Add smooth scroll behavior | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| target.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| }); | |
| }); | |
| // Add parallax effect to hero section | |
| window.addEventListener('scroll', function() { | |
| const scrolled = window.pageYOffset; | |
| const parallax = document.querySelector('.animated-bg'); | |
| if (parallax) { | |
| parallax.style.transform = `translateY(${scrolled * 0.5}px)`; | |
| } | |
| }); | |
| // Add intersection observer for animations | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.style.animation = 'slideUp 0.5s ease-out forwards'; | |
| } | |
| }); | |
| }, observerOptions); | |
| // Observe all review cards | |
| document.addEventListener('DOMContentLoaded', function() { | |
| document.querySelectorAll('.review-card').forEach(card => { | |
| observer.observe(card); | |
| }); | |
| }); |