Spaces:
Running
Running
| /** | |
| * The AI-Powered Developer - Presentation Engine | |
| * Pure JavaScript - No Dependencies | |
| * REVISED with interactive voting | |
| */ | |
| (function () { | |
| 'use strict'; | |
| // State | |
| let currentSlide = 1; | |
| const totalSlides = 13; | |
| let isAnimating = false; | |
| // Voting state | |
| const llmVotes = { | |
| anthropic: 0, | |
| openai: 0, | |
| gemini: 0, | |
| grok: 0, | |
| mistral: 0, | |
| kimi: 0, | |
| deepseek: 0, | |
| other: 0 | |
| }; | |
| const editorVotes = { | |
| vscode: 0, | |
| cursor: 0, | |
| antigravity: 0, | |
| windsurf: 0, | |
| kiro: 0, | |
| qoder: 0 | |
| }; | |
| // DOM Elements | |
| const slidesWrapper = document.getElementById('slidesWrapper'); | |
| const progressFill = document.getElementById('progressFill'); | |
| const currentSlideEl = document.getElementById('currentSlide'); | |
| const totalSlidesEl = document.getElementById('totalSlides'); | |
| const prevBtn = document.getElementById('prevBtn'); | |
| const nextBtn = document.getElementById('nextBtn'); | |
| const slides = document.querySelectorAll('.slide'); | |
| // Initialize | |
| function init() { | |
| totalSlidesEl.textContent = totalSlides; | |
| updateSlide(1); | |
| bindEvents(); | |
| initVoting(); | |
| addInteractiveFeatures(); | |
| } | |
| // Bind Events | |
| function bindEvents() { | |
| // Navigation buttons | |
| prevBtn.addEventListener('click', goToPrevSlide); | |
| nextBtn.addEventListener('click', goToNextSlide); | |
| // Keyboard navigation | |
| document.addEventListener('keydown', handleKeyDown); | |
| // Touch/Swipe support | |
| let touchStartX = 0; | |
| let touchEndX = 0; | |
| document.addEventListener('touchstart', (e) => { | |
| touchStartX = e.changedTouches[0].screenX; | |
| }, { passive: true }); | |
| document.addEventListener('touchend', (e) => { | |
| touchEndX = e.changedTouches[0].screenX; | |
| handleSwipe(); | |
| }, { passive: true }); | |
| function handleSwipe() { | |
| const swipeThreshold = 50; | |
| const diff = touchStartX - touchEndX; | |
| if (Math.abs(diff) > swipeThreshold) { | |
| if (diff > 0) { | |
| goToNextSlide(); | |
| } else { | |
| goToPrevSlide(); | |
| } | |
| } | |
| } | |
| // Mouse wheel (optional) | |
| let wheelTimeout; | |
| document.addEventListener('wheel', (e) => { | |
| if (wheelTimeout) return; | |
| wheelTimeout = setTimeout(() => { | |
| wheelTimeout = null; | |
| }, 800); | |
| if (e.deltaY > 0) { | |
| goToNextSlide(); | |
| } else if (e.deltaY < 0) { | |
| goToPrevSlide(); | |
| } | |
| }, { passive: true }); | |
| } | |
| // Keyboard handler | |
| function handleKeyDown(e) { | |
| switch (e.key) { | |
| case 'ArrowRight': | |
| case ' ': | |
| case 'Enter': | |
| e.preventDefault(); | |
| goToNextSlide(); | |
| break; | |
| case 'ArrowLeft': | |
| case 'Backspace': | |
| e.preventDefault(); | |
| goToPrevSlide(); | |
| break; | |
| case 'Home': | |
| e.preventDefault(); | |
| goToSlide(1); | |
| break; | |
| case 'End': | |
| e.preventDefault(); | |
| goToSlide(totalSlides); | |
| break; | |
| } | |
| // Number keys for quick navigation | |
| const num = parseInt(e.key); | |
| if (!isNaN(num) && num >= 1 && num <= 9 && num <= totalSlides) { | |
| goToSlide(num); | |
| } | |
| } | |
| // Navigation functions | |
| function goToNextSlide() { | |
| if (currentSlide < totalSlides && !isAnimating) { | |
| goToSlide(currentSlide + 1); | |
| } | |
| } | |
| function goToPrevSlide() { | |
| if (currentSlide > 1 && !isAnimating) { | |
| goToSlide(currentSlide - 1); | |
| } | |
| } | |
| function goToSlide(slideNumber) { | |
| if (slideNumber === currentSlide || slideNumber < 1 || slideNumber > totalSlides || isAnimating) { | |
| return; | |
| } | |
| isAnimating = true; | |
| currentSlide = slideNumber; | |
| // Update slides | |
| slides.forEach((slide, index) => { | |
| const slideNum = index + 1; | |
| slide.classList.remove('active', 'prev'); | |
| if (slideNum === currentSlide) { | |
| slide.classList.add('active'); | |
| triggerSlideAnimations(slide); | |
| } else if (slideNum < currentSlide) { | |
| slide.classList.add('prev'); | |
| } | |
| }); | |
| updateSlide(currentSlide); | |
| setTimeout(() => { | |
| isAnimating = false; | |
| }, 500); | |
| } | |
| // Update UI elements | |
| function updateSlide(slideNumber) { | |
| currentSlideEl.textContent = slideNumber; | |
| const progress = (slideNumber / totalSlides) * 100; | |
| progressFill.style.width = `${progress}%`; | |
| prevBtn.style.opacity = slideNumber === 1 ? '0.3' : '1'; | |
| prevBtn.style.pointerEvents = slideNumber === 1 ? 'none' : 'auto'; | |
| nextBtn.style.opacity = slideNumber === totalSlides ? '0.3' : '1'; | |
| nextBtn.style.pointerEvents = slideNumber === totalSlides ? 'none' : 'auto'; | |
| history.replaceState(null, null, `#slide-${slideNumber}`); | |
| } | |
| // Trigger animations when slide becomes active | |
| function triggerSlideAnimations(slide) { | |
| const animatedElements = slide.querySelectorAll('.agenda-item, .step-card, [style*="--delay"]'); | |
| animatedElements.forEach(el => { | |
| el.style.animation = 'none'; | |
| el.offsetHeight; | |
| el.style.animation = null; | |
| }); | |
| } | |
| // ========== VOTING SYSTEM ========== | |
| function initVoting() { | |
| // LLM voting cards | |
| const llmCards = document.querySelectorAll('.voting-card'); | |
| llmCards.forEach(card => { | |
| card.addEventListener('click', () => { | |
| const llm = card.getAttribute('data-llm'); | |
| if (llm && llmVotes.hasOwnProperty(llm)) { | |
| llmVotes[llm]++; | |
| updateVoteDisplay(card, llmVotes[llm], getTotalVotes(llmVotes)); | |
| card.classList.add('voted'); | |
| // Animate | |
| card.style.transform = 'scale(0.98)'; | |
| setTimeout(() => { | |
| card.style.transform = ''; | |
| }, 150); | |
| } | |
| }); | |
| }); | |
| // Editor voting cards | |
| const editorCards = document.querySelectorAll('.voting-editor'); | |
| editorCards.forEach(card => { | |
| card.addEventListener('click', () => { | |
| const editor = card.getAttribute('data-editor'); | |
| if (editor && editorVotes.hasOwnProperty(editor)) { | |
| editorVotes[editor]++; | |
| updateVoteDisplay(card, editorVotes[editor], getTotalVotes(editorVotes)); | |
| card.classList.add('voted'); | |
| // Animate | |
| card.style.transform = 'scale(0.98)'; | |
| setTimeout(() => { | |
| card.style.transform = ''; | |
| }, 150); | |
| } | |
| }); | |
| }); | |
| } | |
| function updateVoteDisplay(card, votes, total) { | |
| const voteFill = card.querySelector('.vote-fill'); | |
| const voteCount = card.querySelector('.vote-count'); | |
| if (voteFill) { | |
| const percentage = total > 0 ? (votes / total) * 100 : 0; | |
| voteFill.style.width = `${Math.max(percentage, 5)}%`; | |
| voteFill.setAttribute('data-votes', votes); | |
| } | |
| if (voteCount) { | |
| voteCount.textContent = `${votes} vote${votes !== 1 ? 's' : ''}`; | |
| } | |
| // Update all cards in the same group to recalculate percentages | |
| setTimeout(() => { | |
| updateAllVoteBars(card.closest('.llm-platforms-grid') || card.closest('.editors-poll-grid')); | |
| }, 50); | |
| } | |
| function updateAllVoteBars(container) { | |
| if (!container) return; | |
| const isLLM = container.classList.contains('llm-platforms-grid'); | |
| const votes = isLLM ? llmVotes : editorVotes; | |
| const total = getTotalVotes(votes); | |
| container.querySelectorAll('.vote-fill').forEach(fill => { | |
| const card = fill.closest('[data-llm], [data-editor]'); | |
| const key = card.getAttribute('data-llm') || card.getAttribute('data-editor'); | |
| if (key && votes[key] !== undefined) { | |
| const percentage = total > 0 ? (votes[key] / total) * 100 : 0; | |
| fill.style.width = `${Math.max(percentage, votes[key] > 0 ? 5 : 0)}%`; | |
| } | |
| }); | |
| } | |
| function getTotalVotes(votesObj) { | |
| return Object.values(votesObj).reduce((sum, v) => sum + v, 0); | |
| } | |
| // Add interactive features | |
| function addInteractiveFeatures() { | |
| // Approach cards (Slide 3) | |
| const approachCards = document.querySelectorAll('.approach-card'); | |
| approachCards.forEach(card => { | |
| card.addEventListener('click', () => { | |
| approachCards.forEach(c => c.classList.remove('selected')); | |
| card.classList.add('selected'); | |
| }); | |
| }); | |
| // External links - open in new tab | |
| document.querySelectorAll('a[target="_blank"]').forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| // Allow default behavior for external links | |
| }); | |
| }); | |
| } | |
| // Handle URL hash for direct slide access | |
| function handleHash() { | |
| const hash = window.location.hash; | |
| if (hash && hash.startsWith('#slide-')) { | |
| const slideNum = parseInt(hash.replace('#slide-', '')); | |
| if (slideNum >= 1 && slideNum <= totalSlides) { | |
| goToSlide(slideNum); | |
| } | |
| } | |
| } | |
| window.addEventListener('hashchange', handleHash); | |
| // Initialize when DOM is ready | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| init(); | |
| handleHash(); | |
| }); | |
| } else { | |
| init(); | |
| handleHash(); | |
| } | |
| // Expose API | |
| window.PresentationAPI = { | |
| next: goToNextSlide, | |
| prev: goToPrevSlide, | |
| goTo: goToSlide, | |
| getCurrentSlide: () => currentSlide, | |
| getTotalSlides: () => totalSlides, | |
| getLLMVotes: () => ({ ...llmVotes }), | |
| getEditorVotes: () => ({ ...editorVotes }), | |
| resetVotes: () => { | |
| Object.keys(llmVotes).forEach(k => llmVotes[k] = 0); | |
| Object.keys(editorVotes).forEach(k => editorVotes[k] = 0); | |
| document.querySelectorAll('.vote-fill').forEach(f => f.style.width = '0%'); | |
| document.querySelectorAll('.vote-count').forEach(c => c.textContent = '0 votes'); | |
| document.querySelectorAll('.voted').forEach(c => c.classList.remove('voted')); | |
| } | |
| }; | |
| })(); | |