Files
AI_Creazione/app.js
2025-09-08 10:02:03 +02:00

583 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}