Download script.js from JerDaSch/melodic-keys-multiverse: direct link, hf CLI and curl.
- Browser
- Download file 25.4 kB
-
https://huggingface.co/spaces/JerDaSch/melodic-keys-multiverse/resolve/main/script.js
- Command line
-
hf download hf://spaces/JerDaSch/melodic-keys-multiverse/script.js
-
curl -L -o script.js https://huggingface.co/spaces/JerDaSch/melodic-keys-multiverse/resolve/main/script.js
25.4 kB
| // Shared JavaScript across all pages | |
| // Initialize the application | |
| document.addEventListener('DOMContentLoaded', function() { | |
| initializeApp(); | |
| setupEventListeners(); | |
| loadInitialData(); | |
| }); | |
| // Global state | |
| let appState = { | |
| events: [], | |
| resources: [], | |
| currentLanguage: 'en', | |
| darkMode: true, | |
| translations: { | |
| en: { | |
| nav: { | |
| about: "About", | |
| lessons: "Lessons", | |
| events: "Events", | |
| resources: "Resources", | |
| testimonials: "Testimonials", | |
| gallery: "Gallery", | |
| contact: "Contact" | |
| }, | |
| hero: { | |
| title: "Unlock Your Musical Potential", | |
| subtitle: "Professional piano instruction tailored to your unique journey. From classical masterpieces to contemporary hits.", | |
| explore_lessons: "Explore Lessons", | |
| book_trial: "Book Trial Lesson" | |
| }, | |
| about: { | |
| title: "About Your Teacher", | |
| content1: "With over 15 years of teaching experience and a Master's degree in Piano Performance, I'm passionate about helping students of all ages discover the joy of music.", | |
| content2: "My teaching philosophy centers on creating a supportive, engaging environment where students can develop both technical skills and musical expression at their own pace.", | |
| specialties: { | |
| classical: "Classical", | |
| jazz: "Jazz", | |
| contemporary: "Contemporary", | |
| music_theory: "Music Theory" | |
| }, | |
| lessons: { | |
| title: "Lessons & Services", | |
| subtitle: "Flexible lesson formats designed to fit your schedule and learning preferences" | |
| }, | |
| contact: { | |
| title: "Get In Touch", | |
| subtitle: "Ready to start your musical journey? Book a trial lesson today!" | |
| }, | |
| form: { | |
| name: "Full Name", | |
| email: "Email Address", | |
| phone: "Phone Number", | |
| message: "Message", | |
| trial_label: "I'm interested in a trial lesson", | |
| send: "Send Message" | |
| } | |
| }, | |
| he: { | |
| nav: { | |
| about: "אודות", | |
| lessons: "שיעורים", | |
| events: "אירועים", | |
| resources: "משאבים", | |
| testimonials: "המלצות", | |
| gallery: "גלריה", | |
| contact: "צור קשר" | |
| }, | |
| hero: { | |
| title: "שחרר את הפוטנציאל המוזיקלי שלך", | |
| subtitle: "הוראת פסנתר מקצועית המותאמת למסע הייחודי שלך. מיצירות קלאסיות ועד להיטים עכשוויים.", | |
| explore_lessons: "חקור שיעורים", | |
| book_trial: "הזמן שיעור ניסיון" | |
| }, | |
| about: { | |
| title: "אודות המורה שלך", | |
| content1: "עם מעל 15 שנות ניסיון בהוראה ותואר שני בביצוע פסנתר, אני נלהב לעזור לתלמידים בכל הגילאים לגלות את השמחה במוזיקה.", | |
| content2: "פילוסופיית ההוראה שלי מתמקדת ביצירת סביבה תומכת ומעוררת עניין בה תלמידים יכולים לפתח מיומנויות טכניות והבעה מוזיקלית בקצב שלהם.", | |
| specialties: { | |
| classical: "קלאסי", | |
| jazz: "ג'אז", | |
| contemporary: "עכשווי", | |
| music_theory: "תורת המוזיקה" | |
| }, | |
| lessons: { | |
| title: "שיעורים ושירותים", | |
| subtitle: "פורמטים גמישים של שיעורים שנועדו להתאים ללו\"ז שלך ולהעדפות הלמידה" | |
| }, | |
| contact: { | |
| title: "צור קשר", | |
| subtitle: "מוכן להתחיל את המסע המוזיקלי שלך? הזמן שיעור ניסיון היום!" | |
| }, | |
| form: { | |
| name: "שם מלא", | |
| email: "כתובת אימייל", | |
| phone: "מספר טלפון", | |
| message: "הודעה", | |
| trial_label: "אני מעוניין/ת בשיעור ניסיון", | |
| send: "שלח הודעה" | |
| } | |
| }, | |
| ru: { | |
| nav: { | |
| about: "О нас", | |
| lessons: "Уроки", | |
| events: "События", | |
| resources: "Ресурсы", | |
| testimonials: "Отзывы", | |
| gallery: "Галерея", | |
| contact: "Контакты" | |
| }, | |
| hero: { | |
| title: "Раскройте свой музыкальный потенциал", | |
| subtitle: "Профессиональное обучение игре на фортепиано, адаптированное к вашему уникальному пути. От классических шедевров до современных хитов.", | |
| explore_lessons: "Изучить уроки", | |
| book_trial: "Записаться на пробный урок" | |
| }, | |
| about: { | |
| title: "О вашем преподавателе", | |
| content1: "С более чем 15-летним опытом преподавания и степенью магистра в области фортепианного исполнения, я увлечен тем, что помогаю ученикам всех возрастов открыть для себя радость музыки.", | |
| content2: "Моя философия преподавания сосредоточена на создании поддерживающей, увлекательной среды, где ученики могут развивать как технические навыки, так и музыкальную выразительность в своем собственном темпе.", | |
| specialties: { | |
| classical: "Классика", | |
| jazz: "Джаз", | |
| contemporary: "Современный", | |
| music_theory: "Теория музыки" | |
| }, | |
| lessons: { | |
| title: "Уроки и услуги", | |
| subtitle: "Гибкие форматы уроков, разработанные с учетом вашего расписания и предпочтений в обучении" | |
| }, | |
| contact: { | |
| title: "Свяжитесь с нами", | |
| subtitle: "Готовы начать свой музыкальный путь? Запишитесь на пробный урок сегодня!" | |
| }, | |
| form: { | |
| name: "Полное имя", | |
| email: "Адрес электронной почты", | |
| phone: "Номер телефона", | |
| message: "Сообщение", | |
| trial_label: "Я заинтересован(а) в пробном уроке", | |
| send: "Отправить сообщение" | |
| } | |
| } | |
| } | |
| }; | |
| // Initialize application | |
| function initializeApp() { | |
| console.log('Melodic Keys Academy initialized'); | |
| // Initialize language | |
| initializeLanguage(); | |
| // Set initial dark mode | |
| document.documentElement.classList.add('dark'); | |
| // Load initial data | |
| loadEvents(); | |
| loadResources(); | |
| // Setup analytics (placeholder) | |
| setupAnalytics(); | |
| // Setup privacy consent | |
| setupPrivacyConsent(); | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Contact form submission | |
| const contactForm = document.getElementById('contact-form'); | |
| if (contactForm) { | |
| contactForm.addEventListener('submit', handleContactFormSubmit); | |
| } | |
| // Language switcher | |
| const langSwitchers = document.querySelectorAll('.lang-switch'); | |
| langSwitchers.forEach(switcher => { | |
| switcher.addEventListener('click', handleLanguageSwitch); | |
| }); | |
| } | |
| // Load initial data | |
| function loadInitialData() { | |
| // These would typically come from an API | |
| loadEvents(); | |
| loadResources(); | |
| } | |
| // Events management | |
| async function loadEvents() { | |
| try { | |
| // Simulate API call | |
| const mockEvents = [ | |
| { | |
| id: 1, | |
| title: 'Spring Recital', | |
| date: '2024-03-15', | |
| time: '2:00 PM', | |
| type: 'recital', | |
| location: 'Community Center', | |
| description: 'Annual spring student recital showcasing progress' | |
| }, | |
| { | |
| id: 2, | |
| title: 'Music Theory Workshop', | |
| date: '2024-03-22', | |
| time: '4:00 PM', | |
| type: 'workshop', | |
| location: 'Studio', | |
| description: 'Interactive workshop on music fundamentals' | |
| }, | |
| { | |
| id: 3, | |
| title: 'Summer Registration Opens', | |
| date: '2024-04-01', | |
| time: 'All Day', | |
| type: 'registration', | |
| location: 'Online', | |
| description: 'Registration for summer lessons and camps' | |
| } | |
| ]; | |
| appState.events = mockEvents; | |
| renderEvents(); | |
| } catch (error) { | |
| console.error('Error loading events:', error); | |
| } | |
| } | |
| function renderEvents() { | |
| const container = document.getElementById('events-container'); | |
| if (!container) return; | |
| container.innerHTML = appState.events.map(event => ` | |
| <div class="bg-gray-700 rounded-2xl p-6 card-hover"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <span class="bg-primary-500 text-white px-3 py-1 rounded-full text-sm"> | |
| ${event.type} | |
| </span> | |
| <h3 class="text-xl font-bold text-white mb-2">${event.title}</h3> | |
| <div class="flex items-center gap-2 text-gray-400 mb-2"> | |
| <i data-feather="calendar" class="w-4 h-4"></i> | |
| <span>${formatDate(event.date)} at ${event.time}</span> | |
| </div> | |
| <div class="flex items-center gap-2 text-gray-400 mb-3"> | |
| <i data-feather="map-pin" class="w-4 h-4"></i> | |
| <span>${event.location}</span> | |
| </div> | |
| <p class="text-gray-300">${event.description}</p> | |
| </div> | |
| `).join(''); | |
| feather.replace(); | |
| } | |
| function loadMoreEvents() { | |
| // In a real app, this would load more events from an API | |
| console.log('Loading more events...'); | |
| // Show loading state | |
| const container = document.getElementById('events-container'); | |
| if (container) { | |
| container.innerHTML += ` | |
| <div class="col-span-full text-center py-8"> | |
| <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-500 mx-auto"></div> | |
| </div> | |
| `; | |
| // Simulate API delay | |
| setTimeout(() => { | |
| const additionalEvents = [ | |
| { | |
| id: 4, | |
| title: 'Jazz Improvisation Class', | |
| date: '2024-04-05', | |
| time: '5:00 PM', | |
| type: 'class', | |
| location: 'Studio', | |
| description: 'Learn the art of jazz improvisation' | |
| } | |
| ]; | |
| appState.events = [...appState.events, ...additionalEvents]; | |
| renderEvents(); | |
| }, 1000); | |
| } | |
| // Resources management | |
| async function loadResources() { | |
| try { | |
| // Simulate API call | |
| const mockResources = [ | |
| { | |
| id: 1, | |
| title: 'Beginner Practice Routine', | |
| type: 'video', | |
| category: 'practice', | |
| description: 'Daily exercises for new piano students' | |
| }, | |
| { | |
| id: 2, | |
| title: 'Classical Repertoire Collection', | |
| type: 'sheet_music', | |
| category: 'repertoire', | |
| description: 'Essential pieces for developing technique' | |
| } | |
| ]; | |
| appState.resources = mockResources; | |
| renderResources(); | |
| } catch (error) { | |
| console.error('Error loading resources:', error); | |
| } | |
| } | |
| function renderResources() { | |
| const container = document.getElementById('resources-content'); | |
| if (!container) return; | |
| container.innerHTML = ` | |
| <h3 class="text-2xl font-bold mb-6 text-primary-400">Featured Resources</h3> | |
| <div class="grid md:grid-cols-2 gap-6"> | |
| ${appState.resources.map(resource => ` | |
| <div class="bg-gray-700 rounded-2xl p-6"> | |
| <div class="flex items-center gap-3 mb-3"> | |
| <div class="bg-primary-500/20 p-2 rounded-lg"> | |
| <i data-feather="${getResourceIcon(resource.type)}" class="text-primary-400"></i> | |
| </div> | |
| <h4 class="font-bold text-white mb-2">${resource.title}</h4> | |
| <p class="text-gray-400 text-sm">${resource.description}</p> | |
| <button class="mt-4 bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg transition-all duration-300"> | |
| Access Resource | |
| </button> | |
| </div> | |
| `).join('')} | |
| </div> | |
| `; | |
| feather.replace(); | |
| } | |
| function getResourceIcon(type) { | |
| const icons = { | |
| video: 'play-circle', | |
| sheet_music: 'music', | |
| article: 'file-text', | |
| audio: 'headphones' | |
| }; | |
| return icons[type] || 'file'; | |
| } | |
| // Contact form handling | |
| async function handleContactFormSubmit(event) { | |
| event.preventDefault(); | |
| const formData = new FormData(event.target); | |
| const data = Object.fromEntries(formData); | |
| try { | |
| // Show loading state | |
| const submitBtn = event.target.querySelector('button[type="submit"]'); | |
| const originalText = submitBtn.textContent; | |
| submitBtn.textContent = 'Sending...'; | |
| submitBtn.disabled = true; | |
| // Simulate API call | |
| await new Promise(resolve => setTimeout(resolve, 1500)); | |
| // Show success message | |
| showNotification('Message sent successfully! We\'ll get back to you soon.', 'success'); | |
| // Reset form | |
| event.target.reset(); | |
| submitBtn.textContent = originalText; | |
| submitBtn.disabled = false; | |
| } catch (error) { | |
| console.error('Error sending message:', error); | |
| showNotification('Error sending message. Please try again.', 'error'); | |
| } | |
| } | |
| // Language initialization and switching | |
| function initializeLanguage() { | |
| const savedLang = localStorage.getItem('preferredLanguage'); | |
| const browserLang = navigator.language.split('-')[0]; | |
| let defaultLang = 'en'; | |
| if (savedLang) { | |
| defaultLang = savedLang; | |
| } else if (['he', 'ru'].includes(browserLang)) { | |
| defaultLang = browserLang; | |
| } | |
| switchLanguage(defaultLang); | |
| } | |
| function switchLanguage(lang) { | |
| appState.currentLanguage = lang; | |
| localStorage.setItem('preferredLanguage', lang); | |
| // Update HTML attributes | |
| document.documentElement.setAttribute('lang', lang); | |
| if (lang === 'he') { | |
| document.documentElement.setAttribute('dir', 'rtl'); | |
| } else { | |
| document.documentElement.setAttribute('dir', 'ltr'); | |
| } | |
| updatePageContent(); | |
| } | |
| function updatePageContent() { | |
| const currentLang = appState.currentLanguage; | |
| const t = appState.translations?.[currentLang]; | |
| if (!t) return; | |
| // Update meta tags | |
| document.title = 'Melodic Keys Academy'; | |
| // Update navigation | |
| updateNavigationText(); | |
| // Update hero section | |
| updateHeroSection(); | |
| // Update about section | |
| updateAboutSection(); | |
| // Update contact section | |
| updateContactSection(); | |
| } | |
| function updateNavigationText() { | |
| const currentLang = appState.currentLanguage; | |
| const t = appState.translations?.[currentLang]?.nav; | |
| if (!t) return; | |
| // Update navbar links | |
| const navLinks = document.querySelectorAll('a[href="#about"], a[href="#lessons"], a[href="#events"], a[href="#resources"], a[href="#testimonials"], a[href="#gallery"], a[href="#contact"]'); | |
| navLinks.forEach(link => { | |
| const href = link.getAttribute('href'); | |
| if (href === '#about') link.textContent = t.about; | |
| else if (href === '#lessons') link.textContent = t.lessons; | |
| else if (href === '#events') link.textContent = t.events; | |
| else if (href === '#resources') link.textContent = t.resources; | |
| else if (href === '#testimonials') link.textContent = t.testimonials; | |
| else if (href === '#gallery') link.textContent = t.gallery; | |
| else if (href === '#contact') link.textContent = t.contact; | |
| }); | |
| } | |
| function updateHeroSection() { | |
| const currentLang = appState.currentLanguage; | |
| const t = appState.translations?.[currentLang]?.hero; | |
| if (!t) return; | |
| const heroTitle = document.querySelector('section h1'); | |
| const heroSubtitle = document.querySelector('section p.text-xl'); | |
| const heroButtons = document.querySelectorAll('section a'); | |
| if (heroTitle && t.title) heroTitle.textContent = t.title; | |
| if (heroSubtitle && t.subtitle) heroSubtitle.textContent = t.subtitle; | |
| if (heroButtons[0] && t.explore_lessons) heroButtons[0].textContent = t.explore_lessons; | |
| if (heroButtons[1] && t.book_trial) heroButtons[1].textContent = t.book_trial; | |
| } | |
| function updateAboutSection() { | |
| const currentLang = appState.currentLanguage; | |
| const t = appState.translations?.[currentLang]?.about; | |
| if (!t) return; | |
| const aboutTitle = document.querySelector('#about h2'); | |
| const aboutContent = document.querySelectorAll('#about p.text-lg'); | |
| if (aboutTitle && t.title) aboutTitle.textContent = t.title; | |
| if (aboutContent[0] && t.content1) aboutContent[0].textContent = t.content1; | |
| if (aboutContent[1] && t.content2) aboutContent[1].textContent = t.content2; | |
| // Update specialties | |
| const specialties = document.querySelectorAll('#about span'); | |
| if (specialties[0] && t.specialties?.classical) specialties[0].textContent = t.specialties.classical; | |
| if (specialties[1] && t.specialties?.jazz) specialties[1].textContent = t.specialties.jazz; | |
| if (specialties[2] && t.specialties?.contemporary) specialties[2].textContent = t.specialties.contemporary; | |
| if (specialties[3] && t.specialties?.music_theory) specialties[3].textContent = t.specialties.music_theory; | |
| } | |
| function updateContactSection() { | |
| const currentLang = appState.currentLanguage; | |
| const t = appState.translations?.[currentLang]?.contact; | |
| if (!t) return; | |
| const contactTitle = document.querySelector('#contact h2'); | |
| const contactSubtitle = document.querySelector('#contact p.text-xl'); | |
| const contactForm = document.querySelector('#contact-form'); | |
| if (contactTitle && t.title) contactTitle.textContent = t.title; | |
| if (contactSubtitle && t.subtitle) contactSubtitle.textContent = t.subtitle; | |
| // Update form labels | |
| const formLabels = contactForm?.querySelectorAll('label'); | |
| const formButton = contactForm?.querySelector('button'); | |
| if (contactTitle && t.title) contactTitle.textContent = t.title; | |
| if (contactSubtitle && t.subtitle) contactSubtitle.textContent = t.subtitle; | |
| if (formLabels[0] && t.form?.name) formLabels[0].textContent = t.form.name; | |
| if (formLabels[1] && t.form?.email) formLabels[1].textContent = t.form.email; | |
| if (formLabels[2] && t.form?.phone) formLabels[2].textContent = t.form.phone; | |
| if (formLabels[3] && t.form?.message) formLabels[3].textContent = t.form.message; | |
| if (formLabels[4] && t.form?.trial_label) formLabels[4].textContent = t.form.trial_label; | |
| if (formButton && t.form?.send) formButton.textContent = t.form.send; | |
| } | |
| function updateURL(lang) { | |
| const currentPath = window.location.pathname; | |
| const newPath = `/${lang}${currentPath.replace(/^\/(en|he|ru)/, '')}`; | |
| // Only update URL if it's different from current | |
| if (!currentPath.startsWith(`/${lang}`)) { | |
| window.history.replaceState({}, '', newPath); | |
| } | |
| } | |
| function updateStructuredData() { | |
| const script = document.querySelector('script[type="application/ld+json"]'); | |
| if (script) { | |
| const data = JSON.parse(script.textContent); | |
| data.availableLanguage = ['English', 'Hebrew', 'Russian']; | |
| script.textContent = JSON.stringify(data); | |
| } | |
| } | |
| // Handle language change events | |
| document.addEventListener('languageChange', (e) => { | |
| switchLanguage(e.detail.lang); | |
| }); | |
| // Notification system | |
| function showNotification(message, type = 'info') { | |
| // Create notification element | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transform transition-all duration-300 ${ | |
| type === 'success' ? 'bg-green-500 text-white' : | |
| type === 'error' ? 'bg-red-500 text-white' : | |
| 'bg-primary-500 text-white' | |
| }`; | |
| notification.textContent = message; | |
| document.body.appendChild(notification); | |
| // Animate in | |
| setTimeout(() => { | |
| notification.classList.add('translate-x-0', 'opacity-100'); | |
| notification.classList.remove('translate-x-full', 'opacity-0'); | |
| }, 100); | |
| // Auto remove after 5 seconds | |
| setTimeout(() => { | |
| notification.classList.remove('translate-x-0', 'opacity-100'); | |
| notification.classList.add('translate-x-full', 'opacity-0'); | |
| setTimeout(() => { | |
| if (notification.parentNode) { | |
| notification.parentNode.removeChild(notification); | |
| } | |
| }, 300); | |
| }, 5000); | |
| } | |
| // Analytics setup (placeholder) | |
| function setupAnalytics() { | |
| // In a real app, this would initialize Google Analytics, etc. | |
| console.log('Analytics initialized'); | |
| } | |
| // Privacy consent banner | |
| function setupPrivacyConsent() { | |
| // Check if consent already given | |
| const consentGiven = localStorage.getItem('privacyConsent'); | |
| if (!consentGiven) { | |
| // Create consent banner | |
| const banner = document.createElement('div'); | |
| banner.className = 'fixed bottom-0 left-0 right-0 bg-gray-800 border-t border-gray-700 p-4 z-40"> | |
| <div class="container mx-auto flex flex-col md:flex-row items-center justify-between gap-4"> | |
| <p class="text-gray-300 text-sm">We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.'; | |
| banner.innerHTML = ` | |
| <div class="flex-1"> | |
| <p class="text-gray-300">We use cookies to improve your experience. <a href="/privacy.html" class="text-primary-400 underline">Learn more</a></p> | |
| <div class="flex gap-4"> | |
| <button onclick="acceptPrivacyConsent()" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors duration-300"> | |
| Accept | |
| </button> | |
| <button onclick="declinePrivacyConsent()" class="bg-gray-700 hover:bg-gray-600 text-white px-6 py-2 rounded-lg transition-colors duration-300"> | |
| Decline | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(banner); | |
| } | |
| } | |
| function acceptPrivacyConsent() { | |
| localStorage.setItem('privacyConsent', 'true'); | |
| document.querySelector('.privacy-banner').remove(); | |
| } | |
| function declinePrivacyConsent() { | |
| localStorage.setItem('privacyConsent', 'false'); | |
| document.querySelector('.privacy-banner').remove(); | |
| } | |
| // Utility functions | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('en-US', { | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }); | |
| } | |
| // Performance monitoring | |
| if ('performance' in window) { | |
| window.addEventListener('load', () => { | |
| const perfData = performance.timing; | |
| const loadTime = perfData.loadEventEnd - perfData.navigationStart; | |
| console.log(`Page loaded in ${loadTime}ms`); | |
| }); | |
| // Service Worker registration (for PWA capabilities) | |
| if ('serviceWorker' in navigator) { | |
| window.addEventListener('load', () => { | |
| navigator.serviceWorker.register('/sw.js') | |
| .then(registration => console.log('SW registered: ', registration)) | |
| .catch(registrationError => console.log('SW registration failed: ', registrationError)); | |
| }); | |
| } | |
| // Export for module usage if needed | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { | |
| initializeApp, | |
| loadEvents, | |
| loadResources | |
| }; | |
| } |