dodey917's picture
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);
});
});