583 lines
20 KiB
JavaScript
583 lines
20 KiB
JavaScript
class PresentationApp {
|
||
constructor() {
|
||
this.currentSlide = 1;
|
||
this.totalSlides = 26;
|
||
this.isFullscreen = false;
|
||
this.autoAdvance = false;
|
||
this.autoAdvanceInterval = null;
|
||
this.touchStartX = 0;
|
||
this.touchStartY = 0;
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.setupElements();
|
||
this.setupEventListeners();
|
||
this.updateDisplay();
|
||
this.preloadSlides();
|
||
}
|
||
|
||
setupElements() {
|
||
this.slidesContainer = document.getElementById('slidesContainer');
|
||
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.progressFill = document.querySelector('.progress-fill');
|
||
this.fullscreenBtn = document.getElementById('fullscreenBtn');
|
||
this.overviewBtn = document.getElementById('overviewBtn');
|
||
|
||
// Update total slides display
|
||
this.totalSlidesSpan.textContent = this.totalSlides;
|
||
}
|
||
|
||
setupEventListeners() {
|
||
// Navigation buttons
|
||
this.prevBtn.addEventListener('click', () => this.previousSlide());
|
||
this.nextBtn.addEventListener('click', () => this.nextSlide());
|
||
|
||
// Keyboard navigation
|
||
document.addEventListener('keydown', (e) => {
|
||
switch(e.key) {
|
||
case 'ArrowRight':
|
||
case ' ':
|
||
case 'PageDown':
|
||
e.preventDefault();
|
||
this.nextSlide();
|
||
break;
|
||
case 'ArrowLeft':
|
||
case 'PageUp':
|
||
e.preventDefault();
|
||
this.previousSlide();
|
||
break;
|
||
case 'Home':
|
||
e.preventDefault();
|
||
this.goToSlide(1);
|
||
break;
|
||
case 'End':
|
||
e.preventDefault();
|
||
this.goToSlide(this.totalSlides);
|
||
break;
|
||
case 'Escape':
|
||
if (this.isFullscreen) {
|
||
this.exitFullscreen();
|
||
}
|
||
break;
|
||
case 'f':
|
||
case 'F':
|
||
this.toggleFullscreen();
|
||
break;
|
||
}
|
||
});
|
||
|
||
// Click to advance
|
||
this.slidesContainer.addEventListener('click', (e) => {
|
||
// Don't advance if clicking on interactive elements
|
||
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
|
||
return;
|
||
}
|
||
this.nextSlide();
|
||
});
|
||
|
||
// Touch/swipe support
|
||
this.slidesContainer.addEventListener('touchstart', (e) => {
|
||
this.touchStartX = e.touches[0].clientX;
|
||
this.touchStartY = e.touches[0].clientY;
|
||
}, { passive: true });
|
||
|
||
this.slidesContainer.addEventListener('touchend', (e) => {
|
||
if (!this.touchStartX || !this.touchStartY) return;
|
||
|
||
const touchEndX = e.changedTouches[0].clientX;
|
||
const touchEndY = e.changedTouches[0].clientY;
|
||
const deltaX = this.touchStartX - touchEndX;
|
||
const deltaY = this.touchStartY - touchEndY;
|
||
|
||
// Check if horizontal swipe is more significant than vertical
|
||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||
if (Math.abs(deltaX) > 50) { // Minimum swipe distance
|
||
if (deltaX > 0) {
|
||
this.nextSlide();
|
||
} else {
|
||
this.previousSlide();
|
||
}
|
||
}
|
||
}
|
||
|
||
this.touchStartX = 0;
|
||
this.touchStartY = 0;
|
||
}, { passive: true });
|
||
|
||
// Control buttons
|
||
this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
|
||
this.overviewBtn.addEventListener('click', () => this.showOverview());
|
||
|
||
// Fullscreen change events
|
||
document.addEventListener('fullscreenchange', () => {
|
||
this.isFullscreen = !this.isFullscreen;
|
||
this.updateFullscreenButton();
|
||
});
|
||
|
||
// Prevent context menu on right click
|
||
document.addEventListener('contextmenu', (e) => {
|
||
e.preventDefault();
|
||
});
|
||
|
||
// Handle window resize
|
||
window.addEventListener('resize', () => {
|
||
this.handleResize();
|
||
});
|
||
}
|
||
|
||
preloadSlides() {
|
||
// Add entrance animations to bullet points
|
||
this.slides.forEach((slide, index) => {
|
||
const bullets = slide.querySelectorAll('.bullet-list li');
|
||
bullets.forEach((bullet, bulletIndex) => {
|
||
bullet.style.animationDelay = `${(bulletIndex + 1) * 0.1}s`;
|
||
});
|
||
});
|
||
}
|
||
|
||
nextSlide() {
|
||
if (this.currentSlide < this.totalSlides) {
|
||
this.goToSlide(this.currentSlide + 1);
|
||
}
|
||
}
|
||
|
||
previousSlide() {
|
||
if (this.currentSlide > 1) {
|
||
this.goToSlide(this.currentSlide - 1);
|
||
}
|
||
}
|
||
|
||
goToSlide(slideNumber) {
|
||
if (slideNumber < 1 || slideNumber > this.totalSlides) return;
|
||
|
||
// Remove active class from current slide
|
||
const currentSlideElement = document.querySelector('.slide.active');
|
||
if (currentSlideElement) {
|
||
currentSlideElement.classList.remove('active');
|
||
currentSlideElement.classList.add('prev');
|
||
}
|
||
|
||
// Add active class to new slide
|
||
const newSlideElement = document.querySelector(`[data-slide="${slideNumber}"]`);
|
||
if (newSlideElement) {
|
||
// Remove prev class from all slides
|
||
this.slides.forEach(slide => slide.classList.remove('prev'));
|
||
|
||
newSlideElement.classList.add('active');
|
||
|
||
// Trigger entrance animations
|
||
this.triggerSlideAnimations(newSlideElement);
|
||
}
|
||
|
||
this.currentSlide = slideNumber;
|
||
this.updateDisplay();
|
||
}
|
||
|
||
triggerSlideAnimations(slideElement) {
|
||
// Reset and trigger bullet point animations
|
||
const bullets = slideElement.querySelectorAll('.bullet-list li');
|
||
bullets.forEach((bullet, index) => {
|
||
bullet.style.animation = 'none';
|
||
bullet.offsetHeight; // Trigger reflow
|
||
bullet.style.animation = `fadeInUp 0.6s ease-out ${(index + 1) * 0.1}s both`;
|
||
});
|
||
|
||
// Trigger other element animations
|
||
const animatedElements = slideElement.querySelectorAll('.aspect-card, .transform-item, .mode-card');
|
||
animatedElements.forEach((element, index) => {
|
||
element.style.transform = 'translateY(20px)';
|
||
element.style.opacity = '0';
|
||
|
||
setTimeout(() => {
|
||
element.style.transition = 'all 0.6s ease-out';
|
||
element.style.transform = 'translateY(0)';
|
||
element.style.opacity = '1';
|
||
}, (index + 1) * 100);
|
||
});
|
||
}
|
||
|
||
updateDisplay() {
|
||
// Update slide counter
|
||
this.currentSlideSpan.textContent = this.currentSlide;
|
||
|
||
// Update progress bar
|
||
const progress = (this.currentSlide / this.totalSlides) * 100;
|
||
this.progressFill.style.width = `${progress}%`;
|
||
|
||
// Update navigation buttons
|
||
this.prevBtn.disabled = this.currentSlide === 1;
|
||
this.nextBtn.disabled = this.currentSlide === this.totalSlides;
|
||
|
||
// Update button opacity based on state
|
||
this.prevBtn.style.opacity = this.currentSlide === 1 ? '0.5' : '1';
|
||
this.nextBtn.style.opacity = this.currentSlide === this.totalSlides ? '0.5' : '1';
|
||
}
|
||
|
||
toggleFullscreen() {
|
||
if (!this.isFullscreen) {
|
||
this.enterFullscreen();
|
||
} else {
|
||
this.exitFullscreen();
|
||
}
|
||
}
|
||
|
||
enterFullscreen() {
|
||
const element = document.documentElement;
|
||
if (element.requestFullscreen) {
|
||
element.requestFullscreen();
|
||
} else if (element.mozRequestFullScreen) {
|
||
element.mozRequestFullScreen();
|
||
} else if (element.webkitRequestFullscreen) {
|
||
element.webkitRequestFullscreen();
|
||
} else if (element.msRequestFullscreen) {
|
||
element.msRequestFullscreen();
|
||
}
|
||
}
|
||
|
||
exitFullscreen() {
|
||
if (document.exitFullscreen) {
|
||
document.exitFullscreen();
|
||
} else if (document.mozCancelFullScreen) {
|
||
document.mozCancelFullScreen();
|
||
} else if (document.webkitExitFullscreen) {
|
||
document.webkitExitFullscreen();
|
||
} else if (document.msExitFullscreen) {
|
||
document.msExitFullscreen();
|
||
}
|
||
}
|
||
|
||
updateFullscreenButton() {
|
||
this.fullscreenBtn.textContent = this.isFullscreen ? '⛶' : '⛶';
|
||
this.fullscreenBtn.title = this.isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen';
|
||
}
|
||
|
||
showOverview() {
|
||
// Create overview modal
|
||
const modal = document.createElement('div');
|
||
modal.className = 'overview-modal';
|
||
modal.innerHTML = `
|
||
<div class="overview-content">
|
||
<div class="overview-header">
|
||
<h2>Slide Overview</h2>
|
||
<button class="close-overview">×</button>
|
||
</div>
|
||
<div class="overview-grid">
|
||
${this.generateOverviewThumbnails()}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Add modal styles
|
||
const style = document.createElement('style');
|
||
style.textContent = `
|
||
.overview-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.9);
|
||
z-index: 2000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
.overview-content {
|
||
background: white;
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
max-width: 1200px;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
width: 100%;
|
||
}
|
||
.overview-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30px;
|
||
border-bottom: 2px solid #3498db;
|
||
padding-bottom: 15px;
|
||
}
|
||
.overview-header h2 {
|
||
color: #2c3e50;
|
||
margin: 0;
|
||
}
|
||
.close-overview {
|
||
background: #e74c3c;
|
||
color: white;
|
||
border: none;
|
||
width: 35px;
|
||
height: 35px;
|
||
border-radius: 50%;
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.close-overview:hover {
|
||
background: #c0392b;
|
||
}
|
||
.overview-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
.overview-slide {
|
||
background: #f8f9fa;
|
||
border: 2px solid #e9ecef;
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-align: center;
|
||
}
|
||
.overview-slide:hover {
|
||
border-color: #3498db;
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 10px 30px rgba(52, 152, 219, 0.3);
|
||
}
|
||
.overview-slide.current {
|
||
border-color: #27ae60;
|
||
background: rgba(39, 174, 96, 0.1);
|
||
}
|
||
.overview-slide-number {
|
||
background: #3498db;
|
||
color: white;
|
||
border-radius: 50%;
|
||
width: 30px;
|
||
height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: bold;
|
||
margin: 0 auto 10px;
|
||
}
|
||
.overview-slide.current .overview-slide-number {
|
||
background: #27ae60;
|
||
}
|
||
.overview-slide-title {
|
||
font-size: 0.9rem;
|
||
color: #2c3e50;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
}
|
||
`;
|
||
|
||
document.head.appendChild(style);
|
||
document.body.appendChild(modal);
|
||
|
||
// Add event listeners
|
||
modal.querySelector('.close-overview').addEventListener('click', () => {
|
||
document.body.removeChild(modal);
|
||
document.head.removeChild(style);
|
||
});
|
||
|
||
modal.addEventListener('click', (e) => {
|
||
if (e.target === modal) {
|
||
document.body.removeChild(modal);
|
||
document.head.removeChild(style);
|
||
}
|
||
});
|
||
|
||
// Add slide navigation
|
||
modal.querySelectorAll('.overview-slide').forEach((slide, index) => {
|
||
slide.addEventListener('click', () => {
|
||
this.goToSlide(index + 1);
|
||
document.body.removeChild(modal);
|
||
document.head.removeChild(style);
|
||
});
|
||
});
|
||
}
|
||
|
||
generateOverviewThumbnails() {
|
||
const slideTitles = [
|
||
"Title Slide",
|
||
"Abstract",
|
||
"Introduction: AI in Creation",
|
||
"Theological Foundations",
|
||
"The Language of Imago Dei",
|
||
"Teilhard's Digital Noosphere",
|
||
"The Two Books and Digital Code",
|
||
"AI as a Semantic System",
|
||
"Semantic Grounding",
|
||
"Artificial Consciousness",
|
||
"Language Bridge",
|
||
"Behavioral Transformations",
|
||
"Research Changes",
|
||
"Philosophical Reasoning",
|
||
"AI-Assisted Theology",
|
||
"Ethical Dimensions",
|
||
"Church's Magisterium",
|
||
"Ethics of Algorithms",
|
||
"Theological Governance",
|
||
"Conclusions",
|
||
"Theology for Digital Age",
|
||
"Gift and Responsibility",
|
||
"Toward Digital Hope",
|
||
"Questions and Answers",
|
||
"Sample Q&A",
|
||
"Continue the Dialogue"
|
||
];
|
||
|
||
return slideTitles.map((title, index) => {
|
||
const slideNumber = index + 1;
|
||
const currentClass = slideNumber === this.currentSlide ? 'current' : '';
|
||
return `
|
||
<div class="overview-slide ${currentClass}" data-slide="${slideNumber}">
|
||
<div class="overview-slide-number">${slideNumber}</div>
|
||
<div class="overview-slide-title">${title}</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
handleResize() {
|
||
// Adjust layout for different screen sizes
|
||
const slideContents = document.querySelectorAll('.slide-content');
|
||
slideContents.forEach(content => {
|
||
if (window.innerWidth < 768) {
|
||
content.style.fontSize = '0.9rem';
|
||
} else {
|
||
content.style.fontSize = '';
|
||
}
|
||
});
|
||
}
|
||
|
||
// Auto-advance functionality
|
||
startAutoAdvance(intervalSeconds = 30) {
|
||
this.stopAutoAdvance();
|
||
this.autoAdvance = true;
|
||
this.autoAdvanceInterval = setInterval(() => {
|
||
if (this.currentSlide < this.totalSlides) {
|
||
this.nextSlide();
|
||
} else {
|
||
this.stopAutoAdvance();
|
||
}
|
||
}, intervalSeconds * 1000);
|
||
}
|
||
|
||
stopAutoAdvance() {
|
||
if (this.autoAdvanceInterval) {
|
||
clearInterval(this.autoAdvanceInterval);
|
||
this.autoAdvanceInterval = null;
|
||
}
|
||
this.autoAdvance = false;
|
||
}
|
||
|
||
// Utility methods
|
||
addCustomEventListeners() {
|
||
// Add number key navigation
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key >= '1' && e.key <= '9') {
|
||
const slideNumber = parseInt(e.key);
|
||
if (slideNumber <= this.totalSlides) {
|
||
this.goToSlide(slideNumber);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Add mouse wheel navigation
|
||
document.addEventListener('wheel', (e) => {
|
||
if (Math.abs(e.deltaY) > 50) {
|
||
if (e.deltaY > 0) {
|
||
this.nextSlide();
|
||
} else {
|
||
this.previousSlide();
|
||
}
|
||
}
|
||
}, { passive: true });
|
||
}
|
||
|
||
// Initialize additional features
|
||
initializeAdvancedFeatures() {
|
||
this.addCustomEventListeners();
|
||
|
||
// Add presentation timer
|
||
this.startTime = Date.now();
|
||
|
||
// Add slide visit tracking
|
||
this.slideVisits = new Array(this.totalSlides).fill(0);
|
||
this.slideVisits[0] = 1; // First slide visited
|
||
|
||
// Update visit count when slide changes
|
||
const originalGoToSlide = this.goToSlide.bind(this);
|
||
this.goToSlide = function(slideNumber) {
|
||
originalGoToSlide(slideNumber);
|
||
if (slideNumber >= 1 && slideNumber <= this.totalSlides) {
|
||
this.slideVisits[slideNumber - 1]++;
|
||
}
|
||
};
|
||
}
|
||
|
||
// Get presentation statistics
|
||
getPresentationStats() {
|
||
const currentTime = Date.now();
|
||
const duration = Math.floor((currentTime - this.startTime) / 1000);
|
||
const minutes = Math.floor(duration / 60);
|
||
const seconds = duration % 60;
|
||
|
||
return {
|
||
duration: `${minutes}:${seconds.toString().padStart(2, '0')}`,
|
||
currentSlide: this.currentSlide,
|
||
totalSlides: this.totalSlides,
|
||
progress: `${Math.round((this.currentSlide / this.totalSlides) * 100)}%`,
|
||
slideVisits: this.slideVisits
|
||
};
|
||
}
|
||
}
|
||
|
||
// Initialize the presentation when DOM is loaded
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const presentation = new PresentationApp();
|
||
presentation.initializeAdvancedFeatures();
|
||
|
||
// Expose to global scope for debugging
|
||
window.presentation = presentation;
|
||
|
||
// Add some helpful console commands
|
||
console.log('Presentation loaded! Available commands:');
|
||
console.log('- presentation.goToSlide(n) - Go to slide n');
|
||
console.log('- presentation.nextSlide() - Next slide');
|
||
console.log('- presentation.previousSlide() - Previous slide');
|
||
console.log('- presentation.toggleFullscreen() - Toggle fullscreen');
|
||
console.log('- presentation.showOverview() - Show slide overview');
|
||
console.log('- presentation.startAutoAdvance(seconds) - Start auto-advance');
|
||
console.log('- presentation.stopAutoAdvance() - Stop auto-advance');
|
||
console.log('- presentation.getPresentationStats() - Get presentation statistics');
|
||
});
|
||
|
||
// Add some additional utility functions
|
||
window.addEventListener('load', () => {
|
||
// Smooth scroll behavior for any internal links
|
||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||
anchor.addEventListener('click', function (e) {
|
||
e.preventDefault();
|
||
const target = document.querySelector(this.getAttribute('href'));
|
||
if (target) {
|
||
target.scrollIntoView({
|
||
behavior: 'smooth'
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// Add loading animation completion
|
||
setTimeout(() => {
|
||
document.body.classList.add('loaded');
|
||
}, 100);
|
||
});
|
||
|
||
// Export for potential module use
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = PresentationApp;
|
||
} |