Download app.js from alexdatamed/Lecture5_IntrotoGenAI: direct link, hf CLI and curl.
- Browser
- Download file 8.16 kB
-
https://huggingface.co/spaces/alexdatamed/Lecture5_IntrotoGenAI/resolve/main/app.js
- Command line
-
hf download hf://spaces/alexdatamed/Lecture5_IntrotoGenAI/app.js
-
curl -L -o app.js https://huggingface.co/spaces/alexdatamed/Lecture5_IntrotoGenAI/resolve/main/app.js
8.16 kB
| class PresentationController { | |
| constructor() { | |
| this.currentSlide = 1; | |
| this.totalSlides = 12; | |
| this.slides = document.querySelectorAll('.slide'); | |
| this.prevBtn = document.getElementById('prevBtn'); | |
| this.nextBtn = document.getElementById('nextBtn'); | |
| this.currentSlideSpan = document.getElementById('currentSlide'); | |
| this.totalSlidesSpan = document.getElementById('totalSlides'); | |
| this.isTransitioning = false; | |
| this.init(); | |
| } | |
| init() { | |
| // Set total slides | |
| this.totalSlidesSpan.textContent = this.totalSlides; | |
| // Add event listeners | |
| this.prevBtn.addEventListener('click', () => this.previousSlide()); | |
| this.nextBtn.addEventListener('click', () => this.nextSlide()); | |
| // Keyboard navigation | |
| document.addEventListener('keydown', (e) => this.handleKeyboard(e)); | |
| // Initialize first slide | |
| this.showSlide(1); | |
| // Initialize progress bar | |
| this.initProgressBar(); | |
| console.log('Presentation initialized with', this.totalSlides, 'slides'); | |
| } | |
| initProgressBar() { | |
| // Create and add progress bar | |
| const progressBar = document.createElement('div'); | |
| progressBar.className = 'progress-bar'; | |
| progressBar.innerHTML = '<div class="progress-fill"></div>'; | |
| document.body.insertBefore(progressBar, document.body.firstChild); | |
| this.progressBar = progressBar; | |
| this.updateProgress(); | |
| } | |
| showSlide(slideNumber) { | |
| if (slideNumber < 1 || slideNumber > this.totalSlides || this.isTransitioning) { | |
| return; | |
| } | |
| this.isTransitioning = true; | |
| // Hide all slides | |
| this.slides.forEach(slide => { | |
| slide.classList.remove('active', 'exiting'); | |
| slide.style.opacity = '0'; | |
| slide.style.visibility = 'hidden'; | |
| slide.style.transform = 'translateX(50px)'; | |
| }); | |
| // Update current slide | |
| this.currentSlide = slideNumber; | |
| // Show target slide with a small delay to ensure clean transition | |
| setTimeout(() => { | |
| const targetSlide = document.querySelector(`[data-slide="${slideNumber}"]`); | |
| if (targetSlide) { | |
| targetSlide.style.visibility = 'visible'; | |
| targetSlide.classList.add('active'); | |
| // Force a repaint | |
| targetSlide.offsetHeight; | |
| targetSlide.style.opacity = '1'; | |
| targetSlide.style.transform = 'translateX(0)'; | |
| } | |
| // Update UI elements | |
| this.updateButtonStates(); | |
| this.updateSlideCounter(); | |
| this.updateProgress(); | |
| // Allow next transition after animation completes | |
| setTimeout(() => { | |
| this.isTransitioning = false; | |
| }, 450); | |
| }, 50); | |
| } | |
| nextSlide() { | |
| if (this.currentSlide < this.totalSlides && !this.isTransitioning) { | |
| this.showSlide(this.currentSlide + 1); | |
| } | |
| } | |
| previousSlide() { | |
| if (this.currentSlide > 1 && !this.isTransitioning) { | |
| this.showSlide(this.currentSlide - 1); | |
| } | |
| } | |
| updateButtonStates() { | |
| // Update previous button | |
| this.prevBtn.disabled = (this.currentSlide === 1); | |
| // Update next button | |
| this.nextBtn.disabled = (this.currentSlide === this.totalSlides); | |
| } | |
| updateSlideCounter() { | |
| this.currentSlideSpan.textContent = this.currentSlide; | |
| } | |
| updateProgress() { | |
| if (this.progressBar) { | |
| const progressFill = this.progressBar.querySelector('.progress-fill'); | |
| const percentage = (this.currentSlide / this.totalSlides) * 100; | |
| progressFill.style.width = `${percentage}%`; | |
| } | |
| } | |
| handleKeyboard(e) { | |
| // Prevent default behavior for navigation keys | |
| if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) { | |
| e.preventDefault(); | |
| } | |
| switch(e.key) { | |
| case 'ArrowLeft': | |
| this.previousSlide(); | |
| break; | |
| case 'ArrowRight': | |
| this.nextSlide(); | |
| break; | |
| case 'Home': | |
| this.showSlide(1); | |
| break; | |
| case 'End': | |
| this.showSlide(this.totalSlides); | |
| break; | |
| case 'f': | |
| case 'F': | |
| this.toggleFullscreen(); | |
| break; | |
| case 'Escape': | |
| this.exitFullscreen(); | |
| break; | |
| } | |
| } | |
| toggleFullscreen() { | |
| if (!document.fullscreenElement) { | |
| document.documentElement.requestFullscreen().catch(err => { | |
| console.log(`Error attempting to enable fullscreen: ${err.message}`); | |
| }); | |
| } else { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| exitFullscreen() { | |
| if (document.fullscreenElement) { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| } | |
| // Touch/swipe support for mobile | |
| class TouchHandler { | |
| constructor(controller) { | |
| this.controller = controller; | |
| this.touchStartX = 0; | |
| this.touchEndX = 0; | |
| this.minSwipeDistance = 50; | |
| this.init(); | |
| } | |
| init() { | |
| const slidesContainer = document.getElementById('slidesContainer'); | |
| slidesContainer.addEventListener('touchstart', (e) => { | |
| this.touchStartX = e.changedTouches[0].screenX; | |
| }, { passive: true }); | |
| slidesContainer.addEventListener('touchend', (e) => { | |
| this.touchEndX = e.changedTouches[0].screenX; | |
| this.handleSwipe(); | |
| }, { passive: true }); | |
| } | |
| handleSwipe() { | |
| const diffX = this.touchStartX - this.touchEndX; | |
| if (Math.abs(diffX) > this.minSwipeDistance) { | |
| if (diffX > 0) { | |
| // Swipe left - next slide | |
| this.controller.nextSlide(); | |
| } else { | |
| // Swipe right - previous slide | |
| this.controller.previousSlide(); | |
| } | |
| } | |
| } | |
| } | |
| // Simple hover effects without problematic animations | |
| function addHoverEffects() { | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| .tech-card:hover, | |
| .company-card:hover, | |
| .use-case-card:hover, | |
| .challenge-card:hover, | |
| .trend-item:hover, | |
| .takeaway-card:hover { | |
| background: rgba(255, 255, 255, 0.15) !important; | |
| cursor: pointer; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| // Initialize presentation when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Prevent scrolling and ensure proper viewport | |
| document.body.style.overflow = 'hidden'; | |
| document.body.style.margin = '0'; | |
| document.body.style.padding = '0'; | |
| document.documentElement.style.overflow = 'hidden'; | |
| // Initialize the presentation controller | |
| const presentation = new PresentationController(); | |
| // Add touch support | |
| new TouchHandler(presentation); | |
| // Add simple hover effects | |
| addHoverEffects(); | |
| // Prevent default scrolling behavior | |
| document.addEventListener('wheel', (e) => { | |
| e.preventDefault(); | |
| }, { passive: false }); | |
| // Prevent arrow key scrolling | |
| document.addEventListener('keydown', (e) => { | |
| if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.key)) { | |
| e.preventDefault(); | |
| } | |
| }); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| // Force a repaint to handle responsive changes | |
| const activeSlide = document.querySelector('.slide.active'); | |
| if (activeSlide) { | |
| activeSlide.style.height = '100%'; | |
| } | |
| }); | |
| console.log('Presentation ready'); | |
| }); | |
| // Export controller for debugging | |
| window.PresentationController = PresentationController; |