Spaces:
Running
Running
| // Main JavaScript for NotteClone Pro | |
| // Theme Toggle functionality | |
| class ThemeManager { | |
| constructor() { | |
| this.theme = localStorage.getItem('theme') || 'dark'; | |
| this.init(); | |
| } | |
| init() { | |
| // Set initial theme | |
| this.setTheme(this.theme); | |
| // Listen for system theme changes | |
| if (window.matchMedia) { | |
| window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { | |
| if (!localStorage.getItem('theme')) { | |
| this.setTheme(e.matches ? 'dark' : 'light'); | |
| } | |
| }); | |
| } | |
| } | |
| setTheme(theme) { | |
| this.theme = theme; | |
| document.documentElement.className = theme; | |
| localStorage.setItem('theme', theme); | |
| } | |
| toggleTheme() { | |
| this.setTheme(this.theme === 'dark' ? 'light' : 'dark'); | |
| } | |
| } | |
| // Initialize theme manager | |
| const themeManager = new ThemeManager(); | |
| // API Integration for testimonials (using a placeholder API) | |
| async function fetchTestimonials() { | |
| try { | |
| // Using JSONPlaceholder for demo data | |
| const response = await fetch('https://jsonplaceholder.typicode.com/comments?_limit=3'); | |
| const comments = await response.json(); | |
| // Transform API data to testimonial format | |
| const testimonials = comments.map(comment => ({ | |
| name: comment.name.split(' ')[0] + ' ' + comment.name.split(' ')[1], | |
| role: 'NotteClone User', | |
| content: comment.body.length > 120 ? comment.body.substring(0, 120) + '...' : comment.body, | |
| rating: Math.floor(Math.random() * 3) + 3 // 3-5 stars | |
| })); | |
| return testimonials; | |
| } catch (error) { | |
| console.error('Error fetching testimonials:', error); | |
| return getFallbackTestimonials(); | |
| } | |
| } | |
| function getFallbackTestimonials() { | |
| return [ | |
| { | |
| name: "Alex Johnson", | |
| role: "Product Designer", | |
| content: "NotteClone Pro has completely transformed how I organize my design notes. The dark mode is perfect for late-night brainstorming sessions.", | |
| rating: 5 | |
| }, | |
| { | |
| name: "Sam Rivera", | |
| role: "Software Engineer", | |
| content: "As a developer who spends hours in dark-themed IDEs, having a note-taking app that matches my workflow is a game-changer.", | |
| rating: 5 | |
| }, | |
| { | |
| name: "Taylor Morgan", | |
| role: "Content Creator", | |
| content: "The organization features are incredible. I can finally keep all my ideas sorted without switching between multiple apps.", | |
| rating: 4 | |
| } | |
| ]; | |
| } | |
| // Initialize testimonials when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', async function() { | |
| // Load testimonials if section exists | |
| const testimonialsSection = document.getElementById('testimonials'); | |
| if (testimonialsSection) { | |
| const testimonials = await fetchTestimonials(); | |
| renderTestimonials(testimonials); | |
| } | |
| // Add scroll animations | |
| setupScrollAnimations(); | |
| // Initialize tooltips if needed | |
| initTooltips(); | |
| }); | |
| function renderTestimonials(testimonials) { | |
| const container = document.querySelector('#testimonials .testimonials-container'); | |
| if (!container) return; | |
| container.innerHTML = testimonials.map((testimonial, index) => ` | |
| <div class="testimonial-card bg-gray-800/50 border border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:border-primary-500 hover:shadow-lg" style="animation-delay: ${index * 100}ms"> | |
| <div class="flex items-center mb-4"> | |
| ${'<i data-feather="star" class="w-4 h-4 text-amber-500 fill-amber-500"></i>'.repeat(testimonial.rating)} | |
| ${'<i data-feather="star" class="w-4 h-4 text-gray-600"></i>'.repeat(5 - testimonial.rating)} | |
| </div> | |
| <p class="text-gray-300 mb-6 italic">"${testimonial.content}"</p> | |
| <div class="flex items-center"> | |
| <div class="w-12 h-12 rounded-full bg-gradient-to-r from-primary-500 to-secondary-500 flex items-center justify-center mr-4"> | |
| <span class="font-bold text-white">${testimonial.name.charAt(0)}</span> | |
| </div> | |
| <div> | |
| <h4 class="font-bold">${testimonial.name}</h4> | |
| <p class="text-gray-400 text-sm">${testimonial.role}</p> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| feather.replace(); | |
| } | |
| function setupScrollAnimations() { | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('animate-slide-in'); | |
| observer.unobserve(entry.target); | |
| } | |
| }); | |
| }, observerOptions); | |
| // Observe all elements with the data-animate attribute | |
| document.querySelectorAll('[data-animate]').forEach(el => { | |
| observer.observe(el); | |
| }); | |
| } | |
| function initTooltips() { | |
| // Initialize tooltips for feature icons | |
| const tooltipElements = document.querySelectorAll('[data-tooltip]'); | |
| tooltipElements.forEach(el => { | |
| el.addEventListener('mouseenter', function(e) { | |
| const tooltipText = this.getAttribute('data-tooltip'); | |
| const tooltip = document.createElement('div'); | |
| tooltip.className = 'tooltip absolute z-50 px-3 py-2 bg-gray-800 text-white text-sm rounded-lg shadow-xl border border-gray-700'; | |
| tooltip.textContent = tooltipText; | |
| tooltip.style.top = (e.clientY - 40) + 'px'; | |
| tooltip.style.left = (e.clientX - 60) + 'px'; | |
| document.body.appendChild(tooltip); | |
| this._tooltip = tooltip; | |
| }); | |
| el.addEventListener('mouseleave', function() { | |
| if (this._tooltip) { | |
| this._tooltip.remove(); | |
| } | |
| }); | |
| }); | |
| } | |
| // Export for module usage if needed | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { ThemeManager, fetchTestimonials }; | |
| } |