Download script.js from Capermint/telegram-mini-game-dev-odyssey: direct link, hf CLI and curl.
- Browser
- Download file 11.4 kB
-
https://huggingface.co/spaces/Capermint/telegram-mini-game-dev-odyssey/resolve/main/script.js
- Command line
-
hf download hf://spaces/Capermint/telegram-mini-game-dev-odyssey/script.js
-
curl -L -o script.js https://huggingface.co/spaces/Capermint/telegram-mini-game-dev-odyssey/resolve/main/script.js
11.4 kB
| // Telegram Mini-Game Development Research Page Interactivity | |
| class TelegramGameResearch { | |
| constructor() { | |
| this.sections = document.querySelectorAll('.research-section'); | |
| this.init(); | |
| } | |
| init() { | |
| this.initializeIntersectionObserver(); | |
| this.initializeCountUpAnimations(); | |
| this.initializeTooltips(); | |
| this.initializeCopyCodeBlocks(); | |
| this.initializeProgressTracker(); | |
| this.initializeKeywordHighlighter(); | |
| } | |
| initializeIntersectionObserver() { | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.style.opacity = '1'; | |
| entry.target.style.transform = 'translateY(0)'; | |
| // Animate child elements with staggered delay | |
| const childElements = entry.target.querySelectorAll('.animate-child'); | |
| childElements.forEach((el, index) => { | |
| el.style.transitionDelay = `${index * 0.1}s`; | |
| el.classList.add('animated'); | |
| }); | |
| } | |
| }); | |
| }, { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }); | |
| this.sections.forEach(section => { | |
| section.style.opacity = '0'; | |
| section.style.transform = 'translateY(20px)'; | |
| section.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; | |
| observer.observe(section); | |
| }); | |
| } | |
| initializeCountUpAnimations() { | |
| const stats = [ | |
| { element: '.stat-1', value: 15000000, suffix: '+' }, | |
| { element: '.stat-2', value: 800000000, suffix: '+' }, | |
| { element: '.stat-3', value: 94, suffix: '%' }, | |
| { element: '.stat-4', value: 2.3, suffix: 'B' } | |
| ]; | |
| stats.forEach(stat => { | |
| const element = document.querySelector(stat.element); | |
| if (!element) return; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| this.animateCountUp(element, stat.value, stat.suffix); | |
| observer.unobserve(element); | |
| } | |
| }); | |
| }); | |
| observer.observe(element); | |
| }); | |
| } | |
| animateCountUp(element, target, suffix) { | |
| const duration = 2000; | |
| const startTime = Date.now(); | |
| const startValue = 0; | |
| const animate = () => { | |
| const currentTime = Date.now(); | |
| const elapsed = currentTime - startTime; | |
| const progress = Math.min(elapsed / duration, 1); | |
| let currentValue; | |
| if (suffix === '%' || suffix === 'B') { | |
| currentValue = startValue + (target - startValue) * progress; | |
| element.textContent = currentValue.toFixed(suffix === 'B' ? 1 : 0) + suffix; | |
| } else { | |
| currentValue = Math.floor(startValue + (target - startValue) * progress); | |
| element.textContent = this.formatNumber(currentValue) + suffix; | |
| } | |
| if (progress < 1) { | |
| requestAnimationFrame(animate); | |
| } | |
| }; | |
| requestAnimationFrame(animate); | |
| } | |
| formatNumber(num) { | |
| if (num >= 1000000) { | |
| return (num / 1000000).toFixed(1) + 'M'; | |
| } else if (num >= 1000) { | |
| return (num / 1000).toFixed(1) + 'K'; | |
| } | |
| return num.toString(); | |
| } | |
| initializeTooltips() { | |
| // Initialize tooltips for technical terms | |
| const tooltips = document.querySelectorAll('[data-tooltip]'); | |
| tooltips.forEach(element => { | |
| const tooltipText = element.getAttribute('data-tooltip'); | |
| const tooltip = document.createElement('div'); | |
| tooltip.className = 'absolute hidden bg-gray-900 text-white px-3 py-2 rounded-lg text-sm z-50 whitespace-nowrap'; | |
| tooltip.textContent = tooltipText; | |
| // Position calculation | |
| const updatePosition = () => { | |
| const rect = element.getBoundingClientRect(); | |
| tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`; | |
| tooltip.style.top = `${rect.top - tooltip.offsetHeight - 10}px`; | |
| }; | |
| element.addEventListener('mouseenter', () => { | |
| document.body.appendChild(tooltip); | |
| tooltip.classList.remove('hidden'); | |
| updatePosition(); | |
| }); | |
| element.addEventListener('mouseleave', () => { | |
| tooltip.remove(); | |
| }); | |
| }); | |
| } | |
| initializeCopyCodeBlocks() { | |
| const codeBlocks = document.querySelectorAll('.code-snippet pre'); | |
| codeBlocks.forEach(block => { | |
| const copyButton = document.createElement('button'); | |
| copyButton.className = 'absolute top-4 right-4 text-gray-400 hover:text-white transition-colors'; | |
| copyButton.innerHTML = '<i data-feather="copy" class="w-5 h-5"></i>'; | |
| block.parentNode.style.position = 'relative'; | |
| block.parentNode.appendChild(copyButton); | |
| copyButton.addEventListener('click', async () => { | |
| const code = block.textContent; | |
| try { | |
| await navigator.clipboard.writeText(code); | |
| // Visual feedback | |
| const originalIcon = copyButton.innerHTML; | |
| copyButton.innerHTML = '<i data-feather="check" class="w-5 h-5 text-green-500"></i>'; | |
| setTimeout(() => { | |
| copyButton.innerHTML = originalIcon; | |
| feather.replace(); | |
| }, 2000); | |
| } catch (err) { | |
| console.error('Failed to copy code:', err); | |
| } | |
| }); | |
| }); | |
| } | |
| initializeProgressTracker() { | |
| const progressBar = document.createElement('div'); | |
| progressBar.className = 'fixed top-0 left-0 h-1 bg-gradient-to-r from-primary-500 to-secondary-500 z-50 transition-all duration-150'; | |
| progressBar.style.width = '0%'; | |
| document.body.appendChild(progressBar); | |
| window.addEventListener('scroll', () => { | |
| const windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; | |
| const scrolled = (window.scrollY / windowHeight) * 100; | |
| progressBar.style.width = `${scrolled}%`; | |
| }); | |
| } | |
| initializeKeywordHighlighter() { | |
| // SEO-related keyword highlighting | |
| const keywords = [ | |
| 'Telegram Mini-Game', 'Bot API', 'Web App', 'HTML5 Games', | |
| 'JavaScript', 'Phaser', 'PixiJS', 'Monetization', | |
| 'Telegram Features', 'Gaming SEO', 'Real-time', 'Multiplayer' | |
| ]; | |
| const content = document.querySelector('.research-content'); | |
| if (!content) return; | |
| keywords.forEach(keyword => { | |
| const regex = new RegExp(`\\b(${keyword})\\b`, 'gi'); | |
| content.innerHTML = content.innerHTML.replace(regex, | |
| `<span class="keyword-highlight">$1</span>` | |
| ); | |
| }); | |
| } | |
| // Utility function for API data fetching (example) | |
| async fetchTelegramStats() { | |
| try { | |
| // This would be a real API call in production | |
| // const response = await fetch('https://api.telegram.org/stats'); | |
| // return await response.json(); | |
| // Mock data for demonstration | |
| return { | |
| dailyUsers: 15000000, | |
| retentionRate: 94, | |
| revenuePotential: 2.3 | |
| }; | |
| } catch (error) { | |
| console.error('Failed to fetch Telegram stats:', error); | |
| return null; | |
| } | |
| } | |
| // Initialize search functionality | |
| initializeSearch() { | |
| const searchInput = document.createElement('input'); | |
| searchInput.type = 'text'; | |
| searchInput.placeholder = 'Search topics (e.g., API, Monetization, SEO)...'; | |
| searchInput.className = 'fixed top0 right-4 z-50 bg-white shadow-xl border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500'; | |
| document.body.appendChild(searchInput); | |
| searchInput.addEventListener('input', (e) => { | |
| const searchTerm = e.target.value.toLowerCase(); | |
| this.sections.forEach(section => { | |
| const text = section.textContent.toLowerCase(); | |
| const isMatch = text.includes(searchTerm); | |
| section.style.opacity = isMatch ? '1' : '0.3'; | |
| section.style.pointerEvents = isMatch ? 'auto' : 'none'; | |
| }); | |
| }); | |
| } | |
| } | |
| // Initialize when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const researchPage = new TelegramGameResearch(); | |
| // Initialize search if needed | |
| // researchPage.initializeSearch(); | |
| // Update stats from mock API | |
| researchPage.fetchTelegramStats().then(stats => { | |
| if (stats) { | |
| console.log('Telegram Gaming Stats:', stats); | |
| } | |
| }); | |
| // Add keyboard shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| // Ctrl/Cmd + F focuses search | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'f') { | |
| e.preventDefault(); | |
| // Focus search input if exists | |
| const searchInput = document.querySelector('input[placeholder*="Search"]'); | |
| if (searchInput) searchInput.focus(); | |
| } | |
| // Escape to clear search | |
| if (e.key === 'Escape') { | |
| const searchInput = document.querySelector('input[placeholder*="Search"]'); | |
| if (searchInput) searchInput.value = ''; | |
| } | |
| }); | |
| }); | |
| // Enhanced analytics tracking | |
| window.dataLayer = window.dataLayer || []; | |
| function gtag() { dataLayer.push(arguments); } | |
| gtag('js', new Date()); | |
| gtag('config', 'G-XXXXX'); // Replace with actual Analytics ID | |
| // Performance monitoring | |
| if ('performance' in window) { | |
| const timing = performance.timing; | |
| const pageLoadTime = timing.loadEventEnd - timing.navigationStart; | |
| console.log(`Page loaded in ${pageLoadTime}ms`); | |
| // Send to analytics if needed | |
| gtag('event', 'page_load_time', { | |
| value: pageLoadTime, | |
| metric_id: 'page_load' | |
| }); | |
| } | |
| // Lazy loading for images | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const lazyImages = document.querySelectorAll('img[data-src]'); | |
| const imageObserver = new IntersectionObserver((entries, observer) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const img = entry.target; | |
| img.src = img.dataset.src; | |
| img.removeAttribute('data-src'); | |
| observer.unobserve(img); | |
| } | |
| }); | |
| }); | |
| lazyImages.forEach(img => imageObserver.observe(img)); | |
| }); |