console.log('Quiz.js loading...'); // New default appearance-based quiz data const appearanceQuizData = { eyeColors: [ { id: "braun", title: "Braun", description: "Warme, erdige Augenfarbe" }, { id: "blau", title: "Blau", description: "Klare, helle Augenfarbe" }, { id: "gruen", title: "Grün", description: "Lebendige, natürliche Augenfarbe" }, { id: "grau", title: "Grau", description: "Mystische, wechselnde Augenfarbe" }, { id: "bunt", title: "Bunt", description: "Mehrfarbige oder besondere Augenfarbe" }, { id: "andere", title: "Andere", description: "Falls nichts anderes zutrifft" } ], hairColors: [ { id: "blond", title: "Blond", description: "Helle, goldene Haarfarbe" }, { id: "braun", title: "Braun", description: "Natürliche, mittlere Haarfarbe" }, { id: "schwarz", title: "Schwarz", description: "Dunkle, intensive Haarfarbe" }, { id: "rot", title: "Rot/Rothaarig", description: "Feurige, auffällige Haarfarbe" }, { id: "grau", title: "Grau", description: "Silberne, reife Haarfarbe" }, { id: "bunt", title: "Bunt", description: "Gefärbte, kreative Haarfarbe" }, { id: "andere", title: "Andere", description: "Falls nichts anderes zutrifft" } ], hairLengths: [ { id: "kurz", title: "Kurz", description: "Bis zum Kinn oder kürzer" }, { id: "mittel", title: "Mittel", description: "Schulterlang bis zur Brust" }, { id: "lang", title: "Lang", description: "Länger als Brust/Rücken" } ] }; // Original story-based quiz data (for ?quiz=story parameter) const storyQuizData = { settings: [ { id: "home", title: "Zuhause / Schlafzimmer", description: "Vertraut und intim" }, { id: "office", title: "Büro / Arbeitsplatz", description: "Verbotene Spannung" }, { id: "nature", title: "Draußen / in der Natur", description: "Wild und frei" }, { id: "hotel", title: "Hotel / Urlaubsumgebung", description: "Luxuriös und entspannt" }, { id: "car", title: "Auto / unterwegs", description: "Spontan und aufregend" }, { id: "fantasy", title: "Fantasy-Welt", description: "Magisch und grenzenlos" } ], dynamics: [ { id: "gentle_dom", title: "Sanfter Dom", description: "Beschützend, liebevolle Dominanz" }, { id: "equal", title: "Gleichwertig / verspielt", description: "Beidseitig, flirtend" }, { id: "submissive", title: "Submissiver Partner", description: "Hörer übernimmt die Führung" } ], relationships: [ { id: "partners", title: "Feste Partner", description: "Ehe / langjährige Beziehung" }, { id: "friends_to_lovers", title: "Freunde, die sich ineinander verlieben", description: "Langsame Entwicklung der Gefühle" }, { id: "strangers", title: "Fremde / erstes Aufeinandertreffen", description: "Aufregende Unbekannte" }, { id: "authority", title: "Autoritätsperson", description: "Chef, Professor usw." } ], intensity: [ { id: "sensual", title: "Sinnlich", description: "Zärtlich und gefühlvoll" }, { id: "passionate", title: "Leidenschaftlich", description: "Intensiv und emotional" }, { id: "bold", title: "Mutig / direkt", description: "Selbstbewusst und direkt" } ] }; // Dynamic quiz data assignment based on URL parameter let quizData; console.log('Quiz data loaded:', quizData); class QuizManager { constructor() { console.log('QuizManager constructor called'); // Determine quiz type and edit mode from URL parameters this.quizType = this.getQuizType(); this.editMode = this.getEditMode(); console.log('Quiz type detected:', this.quizType); console.log('Edit mode:', this.editMode); // Set quiz data and configuration based on type if (this.quizType === 'story') { quizData = storyQuizData; this.currentStep = 1; this.totalSteps = 5; this.selections = { setting: null, dynamic: null, relationship: null, intensity: null, firstName: "" }; } else { // Default appearance quiz quizData = appearanceQuizData; this.currentStep = 1; this.totalSteps = 4; this.selections = { eyeColor: null, hairColor: null, hairLength: null, firstName: "" }; } this.isAuthenticated = false; this.currentUser = null; // Audio visualization properties this.audioContext = null; this.analyser = null; this.dataArray = null; this.audioSource = null; this.isVisualizationPlaying = false; // Don't check auth at start - only at the end this.init(); } getQuizType() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('quiz') || 'appearance'; // Default to appearance quiz } getEditMode() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('edit') === 'true'; } async checkAuthForGeneration() { // Wait for Supabase to be ready if (!window.supabase) { setTimeout(() => this.checkAuthForGeneration(), 100); return false; } try { const { data: { session } } = await supabase.auth.getSession(); if (!session || !session.user) { return false; } this.isAuthenticated = true; this.currentUser = session.user; return true; } catch (error) { console.error('Auth check error:', error); return false; } } checkForReturn() { console.log('🔍 Checking for return from Stripe...'); const urlParams = new URLSearchParams(window.location.search); const isReturn = urlParams.get('return') === 'true'; if (isReturn) { console.log('✅ User returned from Stripe checkout'); // Try to restore quiz state let quizState = null; try { const sessionState = sessionStorage.getItem('quizState'); const localState = localStorage.getItem('quizStateBackup'); if (sessionState) { quizState = JSON.parse(sessionState); console.log('📦 Restored quiz state from sessionStorage:', quizState); } else if (localState) { quizState = JSON.parse(localState); console.log('📦 Restored quiz state from localStorage:', quizState); } if (quizState && quizState.audioFileId) { // Restore the audio file ID to sessionStorage sessionStorage.setItem('audioFileId', quizState.audioFileId); // Navigate to story preview if (quizState.returnToStoryPreview) { console.log('🎯 Navigating to story preview...'); this.navigateToStoryPreview(); } } else { console.warn('⚠️ No quiz state found to restore'); } // Clean up URL parameter window.history.replaceState({}, document.title, window.location.pathname); } catch (error) { console.error('❌ Error restoring quiz state:', error); } } return isReturn; } init() { console.log('QuizManager initializing...'); try { // Check if returning from Stripe first const returnedFromStripe = this.checkForReturn(); // Only setup normal quiz flow if NOT returning from Stripe if (!returnedFromStripe) { // Setup quiz steps based on type this.setupQuizSteps(); this.populateQuizOptions(); this.bindEvents(); this.updateProgress(); } else { // For Stripe returns, only bind essential events this.bindEvents(); } // Add resize listener for mobile header handling and quiz layout window.addEventListener('resize', () => { this.handleResize(); this.handleQuizLayoutResize(); }); // Initialize mobile header state this.handleMobileHeader(this.currentStep); console.log('QuizManager initialized successfully'); } catch (error) { console.error('Error in QuizManager init:', error); } } setupQuizSteps() { const appearanceSteps = document.querySelectorAll('.quiz-step:not(.story-quiz-step)'); const storySteps = document.querySelectorAll('.story-quiz-step'); if (this.quizType === 'story') { console.log('Setting up story quiz steps...'); // Hide ALL appearance steps except shared ones appearanceSteps.forEach(step => { if (step.id !== 'summaryStep' && step.id !== 'generationStep' && step.id !== 'storyPreview') { step.style.display = 'none'; step.classList.remove('active'); } }); // Setup story steps - hide ALL first, then show only first storySteps.forEach((step, index) => { step.style.display = 'block'; step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; // Keep original story step IDs but make them navigable const stepNumber = index + 1; step.setAttribute('data-step', stepNumber); }); // Show only the first story step as active and visible const firstStoryStep = document.querySelector('.story-quiz-step[data-step="1"]'); if (firstStoryStep) { firstStoryStep.classList.add('active'); firstStoryStep.style.visibility = 'visible'; firstStoryStep.style.position = 'static'; firstStoryStep.style.top = 'auto'; } // Update progress bar for 5 steps const totalStepsElement = document.getElementById('totalSteps'); if (totalStepsElement) { totalStepsElement.textContent = '5'; } } else { console.log('Setting up appearance quiz steps...'); // Hide ALL story steps storySteps.forEach(step => { step.style.display = 'none'; step.classList.remove('active'); }); // Setup appearance steps - hide all first, then show only first appearanceSteps.forEach((step, index) => { if (step.id !== 'summaryStep' && step.id !== 'generationStep' && step.id !== 'storyPreview') { step.style.display = 'block'; step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; step.setAttribute('data-step', index + 1); } }); // Show only the first appearance step as active and visible const firstAppearanceStep = document.querySelector('#step1'); if (firstAppearanceStep) { firstAppearanceStep.classList.add('active'); firstAppearanceStep.style.visibility = 'visible'; firstAppearanceStep.style.position = 'static'; firstAppearanceStep.style.top = 'auto'; } // Update progress bar for 4 steps const totalStepsElement = document.getElementById('totalSteps'); if (totalStepsElement) { totalStepsElement.textContent = '4'; } } } saveQuizStateForCheckout() { // Save quiz state before going to Stripe checkout const state = { selections: { ...this.selections }, quizType: this.quizType, currentStep: this.currentStep, audioFileId: this.audioFileId, returnToStoryPreview: true }; console.log('💾 Saving quiz state for checkout:', state); // Save to both sessionStorage (primary) and localStorage (backup) sessionStorage.setItem('quizState', JSON.stringify(state)); localStorage.setItem('quizStateBackup', JSON.stringify(state)); console.log('✅ Quiz state saved successfully'); } checkForReturn() { // Check URL params for return flag const urlParams = new URLSearchParams(window.location.search); const isReturn = urlParams.get('return') === 'true'; console.log('🔄 Checking for Stripe return:', { isReturn, url: window.location.href }); if (isReturn) { // Remove the return parameter from URL window.history.replaceState({}, '', window.location.pathname); // Try to restore quiz state from sessionStorage first let savedState = sessionStorage.getItem('quizState'); // Fallback to localStorage if sessionStorage is empty if (!savedState) { console.log('💾 No sessionStorage, trying localStorage backup...'); savedState = localStorage.getItem('quizStateBackup'); } if (savedState) { try { const state = JSON.parse(savedState); console.log('💾 Restoring quiz state:', state); if (state.returnToStoryPreview && state.selections) { // Restore selections this.selections = { ...state.selections }; this.audioFileId = state.audioFileId; console.log('✅ Selections restored:', this.selections); // Go directly to story preview setTimeout(() => { console.log('🚀 Navigating to story preview...'); this.showStoryPreview(); }, 100); // Clean up both storages sessionStorage.removeItem('quizState'); localStorage.removeItem('quizStateBackup'); return true; } else { console.warn('⚠️ Invalid state structure:', state); } } catch (error) { console.error('❌ Error restoring quiz state:', error); } } else { console.warn('⚠️ No saved quiz state found'); } // If we get here, restoration failed - clean up any remaining state sessionStorage.removeItem('quizState'); localStorage.removeItem('quizStateBackup'); } return false; } populateQuizOptions() { console.log('Starting to populate quiz options...'); if (this.quizType === 'story') { // Original story quiz this.populateSettings(); this.populateDynamics(); this.populateRelationships(); this.populateIntensity(); } else { // Default appearance quiz this.populateEyeColors(); this.populateHairColors(); this.populateHairLengths(); } } populateDynamics() { const container = document.getElementById('dynamicGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(250px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.dynamics.forEach(dynamic => { const div = document.createElement('div'); div.className = 'dynamic-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); // Mobile-responsive padding and height const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.dynamic-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; // Reset text color to dark const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.dynamic = dynamic.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; // Force white text const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(2, true); }); container.appendChild(div); }); } populateRelationships() { const container = document.getElementById('relationshipGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(280px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.relationships.forEach(relationship => { const div = document.createElement('div'); div.className = 'relationship-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); // Mobile-responsive padding and height const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.relationship-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; // Reset text color to dark const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.relationship = relationship.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; // Force white text const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(3, true); }); container.appendChild(div); }); } populateSettings() { const container = document.getElementById('settingGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(250px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.settings.forEach(setting => { const div = document.createElement('div'); div.className = 'setting-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); // Mobile-responsive padding and height const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.setting-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; // Reset text color to dark const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.setting = setting.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; // Force white text const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(1, true); }); container.appendChild(div); }); } populateIntensity() { const container = document.getElementById('intensityGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(250px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.intensity.forEach(intensity => { const div = document.createElement('div'); div.className = 'intensity-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); // Mobile-responsive padding and height const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.intensity-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; // Reset text color to dark const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.intensity = intensity.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; // Force white text const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(4, true); }); container.appendChild(div); }); } // New appearance quiz population methods populateEyeColors() { const container = document.getElementById('eyeColorGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(250px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.eyeColors.forEach(eyeColor => { const div = document.createElement('div'); div.className = 'eye-color-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.eye-color-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.eyeColor = eyeColor.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(1, true); }); container.appendChild(div); }); } populateHairColors() { const container = document.getElementById('hairColorGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(250px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.hairColors.forEach(hairColor => { const div = document.createElement('div'); div.className = 'hair-color-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.hair-color-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.hairColor = hairColor.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(2, true); }); container.appendChild(div); }); } populateHairLengths() { const container = document.getElementById('hairLengthGrid'); if (!container) return; const isMobile = window.innerWidth <= 768; container.style.display = 'grid'; container.style.gridTemplateColumns = isMobile ? '1fr' : 'repeat(auto-fill, minmax(250px, 1fr))'; container.style.gap = isMobile ? '0.75rem' : '1rem'; container.style.margin = isMobile ? '1rem 0' : '2rem 0'; quizData.hairLengths.forEach(hairLength => { const div = document.createElement('div'); div.className = 'hair-length-option option-card'; div.innerHTML = ` `; const label = div.querySelector('.option-label'); const isMobile = window.innerWidth <= 768; label.style.cssText = ` display: block; padding: ${isMobile ? '1rem' : '1.5rem'}; background: white; border: 2px solid #E8E8E8; border-radius: 12px; cursor: pointer; transition: all 0.15s ease-in-out; min-height: ${isMobile ? '80px' : '120px'}; box-sizing: border-box; `; const title = div.querySelector('.option-title'); title.style.cssText = `display: block; font-weight: 600; margin-bottom: ${isMobile ? '0.25rem' : '0.5rem'}; font-size: ${isMobile ? '0.9rem' : '1rem'};`; const desc = div.querySelector('.option-description'); desc.style.cssText = `color: #666; font-size: ${isMobile ? '0.8rem' : '0.875rem'}; line-height: ${isMobile ? '1.3' : '1.4'};`; div.addEventListener('click', () => { document.querySelectorAll('.hair-length-option label').forEach(l => { l.style.background = 'white'; l.style.borderColor = '#E8E8E8'; l.style.transform = 'translateY(0)'; l.style.boxShadow = 'none'; const textElements = l.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', '#2C1810', 'important'); }); l.style.setProperty('color', '#2C1810', 'important'); }); this.selections.hairLength = hairLength.id; label.style.background = '#A67BA0'; label.style.borderColor = '#8B5A83'; label.style.transform = 'translateY(-2px)'; label.style.boxShadow = '0 4px 15px rgba(139, 90, 131, 0.2)'; const textElements = label.querySelectorAll('*'); textElements.forEach(el => { el.style.setProperty('color', 'white', 'important'); }); label.style.setProperty('color', 'white', 'important'); this.updateStepButton(3, true); }); container.appendChild(div); }); } bindEvents() { console.log('Binding events...'); if (this.quizType === 'story') { // Bind story quiz buttons for (let i = 1; i <= this.totalSteps; i++) { const nextBtn = document.getElementById('nextStoryStep' + i); const prevBtn = document.getElementById('prevStoryStep' + i); if (nextBtn) { nextBtn.addEventListener('click', () => this.nextStep()); } if (prevBtn) { prevBtn.addEventListener('click', () => this.prevStep()); } } } else { // Bind appearance quiz buttons for (let i = 1; i <= this.totalSteps; i++) { const nextBtn = document.getElementById('nextStep' + i); const prevBtn = document.getElementById('prevStep' + i); if (nextBtn) { nextBtn.addEventListener('click', () => this.nextStep()); } if (prevBtn) { prevBtn.addEventListener('click', () => this.prevStep()); } } } const generateBtn = document.getElementById('generateStory'); if (generateBtn) { generateBtn.addEventListener('click', () => this.handleNameInput()); } const generateStoryBtn = document.getElementById('generateStoryQuiz'); if (generateStoryBtn) { generateStoryBtn.addEventListener('click', () => this.handleNameInput()); } // Name input validation const nameInput = document.getElementById('firstName'); if (nameInput) { // Set maxlength attribute nameInput.setAttribute('maxlength', '15'); // Create error message element let errorMsg = document.getElementById('firstName-error'); if (!errorMsg) { errorMsg = document.createElement('div'); errorMsg.id = 'firstName-error'; errorMsg.style.cssText = ` color: #e74c3c; font-size: 0.875rem; margin-top: 0.5rem; display: none; `; nameInput.parentNode.appendChild(errorMsg); } nameInput.addEventListener('input', () => { let value = nameInput.value; const originalValue = value; let hasInvalidChars = false; // Check for invalid characters if (/[^a-zA-ZäöüÄÖÜß\s-]/.test(value)) { hasInvalidChars = true; } // Remove non-letter characters (keep spaces and hyphens for names like "Anna-Maria") value = value.replace(/[^a-zA-ZäöüÄÖÜß\s-]/g, ''); // Check if length exceeds 15 let exceedsLength = value.length > 15; // Limit to 15 characters if (value.length > 15) { value = value.substring(0, 15); } // Show/hide error message if (hasInvalidChars || exceedsLength) { let errorText = ''; if (hasInvalidChars && exceedsLength) { errorText = 'Nur Buchstaben erlaubt (max. 15 Zeichen)'; } else if (hasInvalidChars) { errorText = 'Nur Buchstaben erlaubt'; } else if (exceedsLength) { errorText = 'Maximal 15 Zeichen erlaubt'; } errorMsg.textContent = errorText; errorMsg.style.display = 'block'; // Hide error after 3 seconds setTimeout(() => { errorMsg.style.display = 'none'; }, 3000); } else { errorMsg.style.display = 'none'; } // Update input if value was modified if (nameInput.value !== value) { nameInput.value = value; } this.selections.firstName = value.trim(); if (this.quizType === 'story') { this.updateStepButton(5, value.trim().length >= 2); } else { // Appearance quiz - step 4 this.updateStepButton(4, value.trim().length >= 2); } }); } // Story quiz name input validation const storyNameInput = document.getElementById('storyFirstName'); if (storyNameInput) { // Set maxlength attribute storyNameInput.setAttribute('maxlength', '15'); // Create error message element let storyErrorMsg = document.getElementById('storyFirstName-error'); if (!storyErrorMsg) { storyErrorMsg = document.createElement('div'); storyErrorMsg.id = 'storyFirstName-error'; storyErrorMsg.style.cssText = ` color: #e74c3c; font-size: 0.875rem; margin-top: 0.5rem; display: none; `; storyNameInput.parentNode.appendChild(storyErrorMsg); } storyNameInput.addEventListener('input', () => { let value = storyNameInput.value; const originalValue = value; let hasInvalidChars = false; // Check for invalid characters if (/[^a-zA-ZäöüÄÖÜß\s-]/.test(value)) { hasInvalidChars = true; } // Remove non-letter characters (keep spaces and hyphens for names like "Anna-Maria") value = value.replace(/[^a-zA-ZäöüÄÖÜß\s-]/g, ''); // Check if length exceeds 15 let exceedsLength = value.length > 15; // Limit to 15 characters if (value.length > 15) { value = value.substring(0, 15); } // Show/hide error message if (hasInvalidChars || exceedsLength) { let errorText = ''; if (hasInvalidChars && exceedsLength) { errorText = 'Nur Buchstaben erlaubt (max. 15 Zeichen)'; } else if (hasInvalidChars) { errorText = 'Nur Buchstaben erlaubt'; } else if (exceedsLength) { errorText = 'Maximal 15 Zeichen erlaubt'; } storyErrorMsg.textContent = errorText; storyErrorMsg.style.display = 'block'; // Hide error after 3 seconds setTimeout(() => { storyErrorMsg.style.display = 'none'; }, 3000); } else { storyErrorMsg.style.display = 'none'; } // Update input if value was modified if (storyNameInput.value !== value) { storyNameInput.value = value; } this.selections.firstName = value.trim(); this.updateStepButton(5, value.trim().length >= 2); }); } // Summary step buttons const prevSummaryBtn = document.getElementById('prevStepSummary'); if (prevSummaryBtn) { // Remove any existing listeners first prevSummaryBtn.replaceWith(prevSummaryBtn.cloneNode(true)); const newPrevBtn = document.getElementById('prevStepSummary'); if (newPrevBtn) { newPrevBtn.addEventListener('click', () => { console.log('Previous summary clicked for quiz type:', this.quizType); // Go back to last step (step 4 for appearance, step 5 for story) this.currentStep = this.totalSteps; this.showStep(this.currentStep); this.updateProgress(); }); } } const confirmGenerateBtn = document.getElementById('confirmAndGenerate'); if (confirmGenerateBtn) { // Change button text for edit mode if (this.editMode) { confirmGenerateBtn.textContent = 'Bestätigen'; console.log('Button text changed to "Bestätigen" for edit mode'); } // Remove any existing listeners first confirmGenerateBtn.replaceWith(confirmGenerateBtn.cloneNode(true)); const newConfirmBtn = document.getElementById('confirmAndGenerate'); if (newConfirmBtn) { // Set text again after replacing element if (this.editMode) { newConfirmBtn.textContent = 'Bestätigen'; } newConfirmBtn.addEventListener('click', () => { console.log('Confirm button clicked for quiz type:', this.quizType, 'Edit mode:', this.editMode); if (this.editMode) { // Edit mode: Save preferences and return to dashboard this.saveAndReturnToDashboard(); } else { // Normal mode: Start generation process this.startGeneration(); } }); } } } nextStep() { if (this.currentStep < this.totalSteps) { this.currentStep++; this.showStep(this.currentStep); this.updateProgress(); } else if (this.currentStep === this.totalSteps) { this.generateStory(); } } prevStep() { if (this.currentStep > 1) { this.currentStep--; this.showStep(this.currentStep); this.updateProgress(); } } showStep(stepNumber) { // Small delay to prevent button disappearing setTimeout(() => { if (this.quizType === 'story') { // Hide all story steps document.querySelectorAll('.story-quiz-step').forEach(step => { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; }); // Show only the requested story step let currentStepElement; if (typeof stepNumber === 'string') { currentStepElement = document.getElementById(stepNumber); // For shared steps like summary, generation, storyPreview - make them visible if (stepNumber === 'summaryStep' || stepNumber === 'generationStep' || stepNumber === 'storyPreview') { if (currentStepElement) { currentStepElement.style.visibility = 'visible'; currentStepElement.style.position = 'static'; currentStepElement.style.top = 'auto'; currentStepElement.style.display = 'block'; } } } else { currentStepElement = document.querySelector(`.story-quiz-step[data-step="${stepNumber}"]`); } if (currentStepElement) { currentStepElement.classList.add('active'); currentStepElement.style.visibility = 'visible'; currentStepElement.style.position = 'static'; currentStepElement.style.top = 'auto'; console.log('Showing story step:', currentStepElement.id, 'step number:', stepNumber); } else { console.error('Story step element not found for step:', stepNumber); } } else { // Appearance quiz logic - hide all, show only requested step const appearanceSteps = document.querySelectorAll('.quiz-step:not(.story-quiz-step)'); appearanceSteps.forEach(step => { if (step.id !== 'summaryStep' && step.id !== 'generationStep' && step.id !== 'storyPreview') { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; } }); let currentStepElement; if (typeof stepNumber === 'string') { currentStepElement = document.getElementById(stepNumber); // For shared steps like summary, generation, storyPreview - make them visible if (stepNumber === 'summaryStep' || stepNumber === 'generationStep' || stepNumber === 'storyPreview') { if (currentStepElement) { currentStepElement.style.visibility = 'visible'; currentStepElement.style.position = 'static'; currentStepElement.style.top = 'auto'; currentStepElement.style.display = 'block'; } } } else { currentStepElement = document.getElementById('step' + stepNumber); } if (currentStepElement) { currentStepElement.classList.add('active'); currentStepElement.style.visibility = 'visible'; currentStepElement.style.position = 'static'; currentStepElement.style.top = 'auto'; console.log('Showing appearance step:', currentStepElement.id); } else { console.error('Appearance step element not found for step:', stepNumber); } } // Remove story-preview class if not showing story preview if (stepNumber !== 'storyPreview') { document.body.classList.remove('story-preview-active'); } // Handle mobile header visibility (only h1/p, not navbar) this.handleMobileHeader(stepNumber); window.scrollTo({ top: 0, behavior: 'smooth' }); }, 50); } updateProgress() { const progressFill = document.getElementById('progressFill'); const currentStepSpan = document.getElementById('currentStep'); if (progressFill) { const progress = (this.currentStep / this.totalSteps) * 100; progressFill.style.width = progress + '%'; } if (currentStepSpan) { currentStepSpan.textContent = this.currentStep; } } updateStepButton(stepNumber, enabled) { let buttonId; if (this.quizType === 'story') { if (stepNumber === 5) { buttonId = 'generateStoryQuiz'; } else { buttonId = 'nextStoryStep' + stepNumber; } } else { if (stepNumber === 4) { buttonId = 'generateStory'; } else { buttonId = 'nextStep' + stepNumber; } } const button = document.getElementById(buttonId); if (button) { button.disabled = !enabled; } } generateStory() { if (!this.validateSelections()) return; // Show summary step instead of going directly to story preview this.showSummary(); } handleNameInput() { if (!this.validateNameInput()) return; // Show summary step this.generateStory(); } validateNameInput() { const name = this.selections.firstName.trim(); if (name.length < 2) { alert('Bitte geben Sie einen gültigen Namen ein (mindestens 2 Zeichen).'); return false; } return true; } validateSelections() { const errors = []; if (this.quizType === 'story') { if (!this.selections.setting) errors.push('Bitte wählen Sie einen Schauplatz aus.'); if (!this.selections.dynamic) errors.push('Bitte wählen Sie eine Dynamik aus.'); if (!this.selections.relationship) errors.push('Bitte wählen Sie eine Beziehung aus.'); if (!this.selections.intensity) errors.push('Bitte wählen Sie eine Intensität aus.'); } else { // Appearance quiz validation if (!this.selections.eyeColor) errors.push('Bitte wählen Sie eine Augenfarbe aus.'); if (!this.selections.hairColor) errors.push('Bitte wählen Sie eine Haarfarbe aus.'); if (!this.selections.hairLength) errors.push('Bitte wählen Sie eine Haarlänge aus.'); } if (!this.selections.firstName || this.selections.firstName.trim().length < 2) { errors.push('Bitte geben Sie einen gültigen Namen ein.'); } if (errors.length > 0) { alert(errors.join('\n')); return false; } return true; } showSummary() { console.log('showSummary called for quiz type:', this.quizType); if (this.quizType === 'story') { // Hide all story steps, show summary document.querySelectorAll('.story-quiz-step').forEach(step => { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; }); } else { // Hide all appearance steps for summary const appearanceSteps = document.querySelectorAll('.quiz-step:not(.story-quiz-step)'); appearanceSteps.forEach(step => { if (step.id !== 'summaryStep' && step.id !== 'generationStep' && step.id !== 'storyPreview') { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; } }); } // Show summary step (this is shared between both quiz types) const summaryStep = document.getElementById('summaryStep'); if (summaryStep) { summaryStep.classList.add('active'); summaryStep.style.visibility = 'visible'; summaryStep.style.position = 'static'; summaryStep.style.top = 'auto'; summaryStep.style.display = 'block'; } // Populate summary with user selections this.populateSummary(); } populateSummary() { if (this.quizType === 'story') { // Show story summary sections, hide appearance document.querySelectorAll('.appearance-summary').forEach(el => el.style.display = 'none'); document.querySelectorAll('.story-summary').forEach(el => el.style.display = 'block'); // Populate story settings const settingEl = document.getElementById('summarySetting'); if (settingEl) { const setting = quizData.settings.find(s => s.id === this.selections.setting); settingEl.textContent = setting ? setting.title : '--'; } const dynamicEl = document.getElementById('summaryDynamic'); if (dynamicEl) { const dynamic = quizData.dynamics.find(d => d.id === this.selections.dynamic); dynamicEl.textContent = dynamic ? dynamic.title : '--'; } const relationshipEl = document.getElementById('summaryRelationship'); if (relationshipEl) { const relationship = quizData.relationships.find(r => r.id === this.selections.relationship); relationshipEl.textContent = relationship ? relationship.title : '--'; } const intensityEl = document.getElementById('summaryIntensity'); if (intensityEl) { const intensity = quizData.intensity.find(i => i.id === this.selections.intensity); intensityEl.textContent = intensity ? intensity.title : '--'; } const storyNameEl = document.getElementById('summaryStoryName'); if (storyNameEl) storyNameEl.textContent = this.selections.firstName || '--'; } else { // Show appearance summary sections, hide story document.querySelectorAll('.story-summary').forEach(el => el.style.display = 'none'); document.querySelectorAll('.appearance-summary').forEach(el => el.style.display = 'block'); // Populate appearance settings const eyeColorEl = document.getElementById('summaryEyeColor'); if (eyeColorEl) { const eyeColor = quizData.eyeColors.find(e => e.id === this.selections.eyeColor); eyeColorEl.textContent = eyeColor ? eyeColor.title : '--'; } const hairColorEl = document.getElementById('summaryHairColor'); if (hairColorEl) { const hairColor = quizData.hairColors.find(h => h.id === this.selections.hairColor); hairColorEl.textContent = hairColor ? hairColor.title : '--'; } const hairLengthEl = document.getElementById('summaryHairLength'); if (hairLengthEl) { const hairLength = quizData.hairLengths.find(h => h.id === this.selections.hairLength); hairLengthEl.textContent = hairLength ? hairLength.title : '--'; } const nameEl = document.getElementById('summaryName'); if (nameEl) nameEl.textContent = this.selections.firstName || '--'; } } getAudioFilename(selections, audioFileId = null) { // Generate UUID-based preview filename with all parameters const fileId = audioFileId || crypto.randomUUID(); const storyName = "WEIHNACHTEN"; // Can be made dynamic later const params = []; // Add appearance parameters if they exist if (selections.eyeColor) params.push(selections.eyeColor); if (selections.hairColor) params.push(selections.hairColor); if (selections.hairLength) params.push(selections.hairLength); // Add story parameters if they exist if (selections.setting) params.push(selections.setting); if (selections.dynamic) params.push(selections.dynamic); if (selections.relationship) params.push(selections.relationship); if (selections.intensity) params.push(selections.intensity); // Add first name at the end if (selections.firstName) params.push(selections.firstName); // Return preview filename (30 seconds) return `${fileId}_${storyName}_preview_${params.join('_')}.mp3`; } getFullAudioFilename(selections, audioFileId) { // Generate UUID-based full filename with all parameters const storyName = "WEIHNACHTEN"; const params = []; // Add appearance parameters if they exist if (selections.eyeColor) params.push(selections.eyeColor); if (selections.hairColor) params.push(selections.hairColor); if (selections.hairLength) params.push(selections.hairLength); // Add story parameters if they exist if (selections.setting) params.push(selections.setting); if (selections.dynamic) params.push(selections.dynamic); if (selections.relationship) params.push(selections.relationship); if (selections.intensity) params.push(selections.intensity); // Add first name at the end if (selections.firstName) params.push(selections.firstName); // Return full filename (12-15 minutes) return `${audioFileId}_${storyName}_full_${params.join('_')}.mp3`; } generatePersonalizedTitle(firstName) { // Generate personalized title for Stripe const storyType = "sinnliche Weihnachtsgeschichte"; return `${firstName}s SoftSins ${storyType}`; } async startGeneration() { // Check if user is authenticated before generation const isAuth = await this.checkAuthForGeneration(); if (!isAuth) { // User not authenticated, show login modal if (window.openLoginModal) { window.openLoginModal(); } else if (window.softSinsAuth) { window.softSinsAuth.openLoginModal(); } else if (window.authModal) { window.authModal.open('login'); } return; } // Generate unique audio file ID and filename this.audioFileId = crypto.randomUUID(); const audioFilename = this.getAudioFilename(this.selections, this.audioFileId); sessionStorage.setItem('userAudioFile', audioFilename); sessionStorage.setItem('audioFileId', this.audioFileId); console.log('Audio file for user:', audioFilename); console.log('Audio file ID:', this.audioFileId); // Save preferences to database first (including audio file ID) await this.savePreferences(); // Trigger preview generation in parallel to animation this.triggerPreviewGeneration(); // Hide summary and show generation step this.showGenerationStep(); // Start the generation animation sequence this.animateGeneration(); } showGenerationStep() { console.log('Showing generation step for quiz type:', this.quizType); // First, hide the summary step (shared between both quiz types) const summaryStep = document.getElementById('summaryStep'); if (summaryStep) { summaryStep.classList.remove('active'); summaryStep.style.visibility = 'hidden'; summaryStep.style.position = 'absolute'; summaryStep.style.top = '-9999px'; console.log('Summary step hidden'); } if (this.quizType === 'story') { // Hide all story steps document.querySelectorAll('.story-quiz-step').forEach(step => { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; }); } else { // Hide all appearance steps const appearanceSteps = document.querySelectorAll('.quiz-step:not(.story-quiz-step)'); appearanceSteps.forEach(step => { if (step.id !== 'generationStep' && step.id !== 'storyPreview') { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; } }); } // Show generation step const generationStep = document.getElementById('generationStep'); if (generationStep) { // Mobile scroll BEFORE showing animation if (window.innerWidth < 768) { window.scrollTo(0, 0); console.log('📱 Mobile: Scrolled to top before animation'); } generationStep.classList.add('active'); generationStep.style.visibility = 'visible'; generationStep.style.position = 'static'; generationStep.style.top = 'auto'; generationStep.style.display = 'block'; console.log('Generation step is now visible'); } else { console.error('Generation step not found!'); } } generateStoryTitle() { // Generate a nice title based on selections const settingTitles = { home: 'Zuhause bei dir', office: 'Im Büro', nature: 'In der Natur', hotel: 'Im Hotel', car: 'Unterwegs im Auto', fantasy: 'In einer Fantasy-Welt' }; const dynamicDescriptions = { gentle_dom: 'liebevoll dominante', equal: 'gleichberechtigte', submissive: 'hingebungsvolle' }; // Fallback values if selections are undefined const setting = settingTitles[this.selections?.setting] || this.selections?.setting || 'Ein romantisches Abenteuer'; const dynamic = dynamicDescriptions[this.selections?.dynamic] || this.selections?.dynamic || 'sinnliche'; return `${setting} - Eine ${dynamic} Geschichte`; } async savePreferences() { if (!this.isAuthenticated || !this.currentUser) { console.error('User not authenticated'); return; } try { if (this.quizType === 'story') { // Save story preferences to user_preferences table const preferenceData = { user_id: this.currentUser.id, setting: this.selections.setting, dynamic: this.selections.dynamic, relationship: this.selections.relationship, intensity: this.selections.intensity, first_name: this.selections.firstName, quiz_type: 'story', created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; // 🔒 SECURE: Use secure API instead of direct database access // Note: Preferences are handled within createUserStory in the secure API console.log('Story preferences will be saved via secure API'); } else { // Save appearance preferences to appearance_preferences table const appearanceData = { user_id: this.currentUser.id, eye_color: this.selections.eyeColor, hair_color: this.selections.hairColor, hair_length: this.selections.hairLength, first_name: this.selections.firstName }; // 🔒 SECURE: Use secure API instead of direct database access // Note: Appearance preferences are handled within createUserStory in the secure API } // Also create story entry for dashboard const previewFilename = this.getAudioFilename(this.selections, this.audioFileId); const fullFilename = this.getFullAudioFilename(this.selections, this.audioFileId); // Prepare quiz preferences for persistent storage const quizPreferences = { ...this.selections, quiz_type: this.quizType, generated_at: new Date().toISOString() }; const storyData = { user_id: this.currentUser.id, title: this.quizType === 'story' ? this.generateStoryTitle() : this.generatePersonalizedTitle(this.selections.firstName), description: `Personalisierte Geschichte basierend auf deinen Wünschen`, setting: this.selections.setting, dynamic: this.selections.dynamic, relationship: this.selections.relationship, intensity: this.selections.intensity, first_name: this.selections.firstName, audio_filename: previewFilename, // Keep for backward compatibility preview_filename: previewFilename, full_filename: fullFilename, audio_url: this.generatedAudioUrl, // Store the actual audio URL from Lambda preview_generated: true, // Set to true when preview generation starts full_generated: false, // Will be set to true when full audio is generated after purchase audio_file_id: this.audioFileId, story_name: 'WEIHNACHTEN', quiz_preferences: quizPreferences, is_purchased: false, preview_created_at: new Date().toISOString() // Remove created_at and updated_at - let Supabase handle them }; console.log('Attempting to save story data:', storyData); try { const { data: storyResult, error: storyError } = await supabase .from('user_stories') .insert([storyData]) .select() .single(); console.log('Insert result:', { data: storyResult, error: storyError }); if (storyError) { console.error('Story save error:', storyError); throw storyError; } console.log('Story saved to dashboard:', storyResult); } catch (insertError) { console.error('Story insert failed:', insertError); throw insertError; } } catch (error) { console.error('Error saving data:', error); // Continue anyway, don't block the user experience } } async saveAndReturnToDashboard() { console.log('🔄 Edit mode: Saving preferences and returning to dashboard...'); // Check if user is authenticated const isAuth = await this.checkAuthForGeneration(); if (!isAuth) { console.error('User not authenticated for saving'); if (window.openLoginModal) { window.openLoginModal(); } return; } try { // Save the preferences (without creating story entry) await this.savePreferencesOnly(); // Show success message briefly const confirmBtn = document.getElementById('confirmAndGenerate'); if (confirmBtn) { const originalText = confirmBtn.textContent; confirmBtn.textContent = '✅ Gespeichert'; confirmBtn.disabled = true; // Return to dashboard after short delay setTimeout(() => { console.log('🏠 Redirecting to dashboard...'); window.location.href = 'dashboard.html#preferences'; }, 1000); } } catch (error) { console.error('Error saving preferences:', error); // Show error message const confirmBtn = document.getElementById('confirmAndGenerate'); if (confirmBtn) { confirmBtn.textContent = '❌ Fehler'; setTimeout(() => { confirmBtn.textContent = 'Bestätigen'; confirmBtn.disabled = false; }, 2000); } } } async savePreferencesOnly() { // Save preferences without creating story entry if (!this.isAuthenticated || !this.currentUser) { console.error('User not authenticated'); return; } try { // Generate a UUID for edit mode (not used for audio generation) const editModeId = this.generateEditModeId(); // Use the secure API to save preferences if (window.secureAPI) { console.log('🔒 Saving preferences via secure API in edit mode...'); // Prepare selections with all required fields const selectionsWithDefaults = { ...this.selections, // For story quiz, appearance fields get defaults eyeColor: this.selections.eyeColor || 'braun', hairColor: this.selections.hairColor || 'braun', hairLength: this.selections.hairLength || 'mittel', // For appearance quiz, story fields would be undefined (that's OK) }; await window.secureAPI.createUserStory( selectionsWithDefaults, editModeId, this.quizType, true // isEditMode = true ); console.log('✅ Preferences saved successfully in edit mode'); } else { throw new Error('Secure API not available'); } } catch (error) { console.error('Error saving preferences only:', error); throw error; } } generateEditModeId() { // Generate a simple UUID for edit mode // Format: edit-XXXXXXXX-XXXX-4XXX-YXXX-XXXXXXXXXXXX return 'edit-' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } animateGeneration() { const stepIndicators = document.querySelectorAll('.step-indicator'); let currentStep = 0; let isPreviewReady = false; let pollingInterval = null; const TOTAL_DURATION = 30000; // Fixed 30 seconds total const STEPS_COUNT = stepIndicators.length; // Should be 4 steps // Start polling for preview_generated status const startPreviewPolling = () => { pollingInterval = setInterval(async () => { try { const { data, error } = await supabase .from('user_stories') .select('preview_generated') .eq('audio_file_id', this.audioFileId) .single(); if (!error && data && data.preview_generated) { isPreviewReady = true; clearInterval(pollingInterval); console.log('✅ Preview is ready! Audio file generated.'); } } catch (pollError) { console.error('Error polling preview status:', pollError); } }, 2000); // Check every 2 seconds }; const progressSteps = () => { // Remove active class from all indicators stepIndicators.forEach(indicator => indicator.classList.remove('active')); // Add active class to current step if (stepIndicators[currentStep]) { stepIndicators[currentStep].classList.add('active'); console.log(`🎬 Animation Step ${currentStep + 1}: ${stepIndicators[currentStep].textContent}`); } currentStep++; // Continue until all steps are done if (currentStep < stepIndicators.length) { let stepDelay; // Fixed timing: distribute 25 seconds across all steps evenly // Last 5 seconds reserved for showing the final step before redirect if (currentStep === stepIndicators.length - 1) { // Last step gets shown at 25 seconds stepDelay = 25000 / (STEPS_COUNT - 1); } else { // Distribute first 25 seconds evenly among steps stepDelay = 25000 / (STEPS_COUNT - 1); } console.log(`⏱️ Step ${currentStep}: Fixed delay ${stepDelay}ms`); setTimeout(progressSteps, stepDelay); } else { // All animation steps complete - wait the final 5 seconds console.log('🏁 All steps shown, waiting final 5 seconds...'); setTimeout(() => { this.completeGeneration(); }, 5000); } }; // Complete generation after exactly 30 seconds const completeGeneration = () => { // Clear polling if still running if (pollingInterval) { clearInterval(pollingInterval); } if (isPreviewReady) { // Preview is ready, show immediately console.log('🎉 30 seconds complete and preview ready! Showing story...'); this.showStoryPreview(); } else { // Still waiting for preview, show waiting message and continue polling console.log('⏳ 30 seconds complete but preview still generating...'); this.showWaitingForPreview(); } }; // Start the step progression and polling startPreviewPolling(); setTimeout(progressSteps, 500); } async triggerPreviewGeneration() { try { console.log('🚀 Triggering preview generation...'); // Try to initialize AudioGenerator if not available if (!window.audioGenerator) { if (window.initializeAudioGenerator) { console.log('🔄 Attempting to initialize AudioGenerator...'); const initialized = window.initializeAudioGenerator(); if (!initialized) { console.warn('⚠️ AudioGenerator initialization failed, skipping preview generation'); return; } } else { console.warn('⚠️ AudioGenerator not available, skipping preview generation'); return; } } // Generate preview in background const result = await window.audioGenerator.generatePreview(this.selections, this.audioFileId); console.log('✅ Preview generation triggered:', result); // Store the generated audio URL for later use if (result && (result.audio_url || result.audioUrl)) { this.generatedAudioUrl = result.audio_url || result.audioUrl; console.log('🔗 Stored generated audio URL:', this.generatedAudioUrl); // Extract actual filename from Lambda response URL const actualFilename = this.generatedAudioUrl.split('/').pop(); console.log('🔗 Extracted actual filename from Lambda:', actualFilename); // Update the Supabase record with both the actual audio URL and correct filename try { const { error: updateError } = await supabase .from('user_stories') .update({ audio_url: this.generatedAudioUrl, preview_filename: actualFilename // Fix: Use actual Lambda-generated filename }) .eq('audio_file_id', this.audioFileId); if (updateError) { console.error('❌ Failed to update audio_url and preview_filename in Supabase:', updateError); } else { console.log('✅ Audio URL and preview filename updated in Supabase:', { audio_url: this.generatedAudioUrl, preview_filename: actualFilename }); } } catch (updateErr) { console.error('❌ Error updating audio_url and preview_filename:', updateErr); } // Re-initialize audio preview now that we have the correct URL // First reset the button state with loading spinner const playBtn = document.getElementById('previewPlayBtn'); if (playBtn) { playBtn.innerHTML = `
Lade Audio... `; playBtn.disabled = true; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; } this.initializeAudioPreview(); } } catch (error) { console.error('❌ Preview generation trigger failed:', error); // Don't block the animation - user will see waiting state if needed } } completeGeneration() { console.log('🎉 Generation animation complete!'); if (this.previewReady) { // Preview is ready, navigate to dashboard console.log('✅ Preview ready, redirecting to dashboard...'); window.location.href = 'dashboard.html'; } else { // Show waiting state this.showWaitingForPreview(); } } showWaitingForPreview() { // Show a waiting state if animation completes before preview is ready const stepIndicators = document.querySelectorAll('.step-indicator'); stepIndicators.forEach(indicator => indicator.classList.remove('active')); // Add a special waiting state const lastStep = stepIndicators[stepIndicators.length - 1]; if (lastStep) { lastStep.classList.add('active'); lastStep.textContent = 'Audio wird generiert...'; } // Continue polling until preview is ready const waitingPolling = setInterval(async () => { try { const { data, error } = await supabase .from('user_stories') .select('preview_generated') .eq('audio_file_id', this.audioFileId) .single(); if (!error && data && data.preview_generated) { clearInterval(waitingPolling); console.log('✅ Preview finally ready! Showing story...'); this.showStoryPreview(); } } catch (pollError) { console.error('Error in waiting polling:', pollError); } }, 1000); // Check every second while waiting } showStoryPreview() { console.log('Showing story preview for quiz type:', this.quizType); // First, hide the generation step (shared between both quiz types) const generationStep = document.getElementById('generationStep'); if (generationStep) { generationStep.classList.remove('active'); generationStep.style.visibility = 'hidden'; generationStep.style.position = 'absolute'; generationStep.style.top = '-9999px'; console.log('Generation step hidden'); } // Also hide the summary step (in case it's still visible) const summaryStep = document.getElementById('summaryStep'); if (summaryStep) { summaryStep.classList.remove('active'); summaryStep.style.visibility = 'hidden'; summaryStep.style.position = 'absolute'; summaryStep.style.top = '-9999px'; } if (this.quizType === 'story') { // Hide all story steps document.querySelectorAll('.story-quiz-step').forEach(step => { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; }); } else { // Hide all appearance steps const appearanceSteps = document.querySelectorAll('.quiz-step:not(.story-quiz-step)'); appearanceSteps.forEach(step => { if (step.id !== 'storyPreview') { step.classList.remove('active'); step.style.visibility = 'hidden'; step.style.position = 'absolute'; step.style.top = '-9999px'; } }); } // Show story preview step const storyPreviewStep = document.getElementById('storyPreview'); if (storyPreviewStep) { storyPreviewStep.classList.add('active'); storyPreviewStep.style.visibility = 'visible'; storyPreviewStep.style.position = 'static'; storyPreviewStep.style.top = 'auto'; storyPreviewStep.style.display = 'block'; console.log('Story preview step is now visible'); } else { console.error('Story preview step not found!'); } // Add class to body to hide quiz header document.body.classList.add('story-preview-active'); // Set personalized headline this.setPersonalizedHeadline(); const titleEl = document.getElementById('storyTitle'); if (titleEl) titleEl.textContent = this.generateStoryTitle(); const priceEl = document.getElementById('storyPrice'); if (priceEl) priceEl.textContent = '€3.99'; // Set narrator const narratorEl = document.getElementById('storyNarrator'); if (narratorEl) narratorEl.textContent = 'Erzähler: Henry'; // Update duration display this.updateStoryDuration(); // Initialize story waveform animation this.initializeStoryWaveform(); // Initialize audio preview this.initializeAudioPreview(); } initializeStoryWaveform() { // Initialize desktop waveform this.initializeWaveformCanvas('storyWaveCanvas', 8, 100, 60); // Initialize mobile waveform this.initializeWaveformCanvas('storyWaveCanvasMobile', 6, 80, 20); } initializeWaveformCanvas(canvasId, numBars, width, height) { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; // Set canvas dimensions canvas.width = width; canvas.height = height; // Store canvas reference for this specific canvas canvas.quizCtx = ctx; canvas.numBars = numBars; canvas.barWidth = canvas.width / numBars; canvas.maxHeight = canvas.height * 0.7; canvas.frame = 0; // Start with idle animation this.drawIdleWaveform(canvas); } drawIdleWaveform(canvas) { if (!canvas || !canvas.quizCtx) return; const ctx = canvas.quizCtx; const numBars = canvas.numBars; const barWidth = canvas.barWidth; const maxHeight = canvas.maxHeight; const animate = () => { // Stop idle animation when real visualization is playing if (this.isVisualizationPlaying) return; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; for (let i = 0; i < numBars; i++) { // Create animated sine wave pattern (idle) const phase = canvas.frame * 0.08 + i * 0.4; const baseHeight = 0.2 + 0.6 * (Math.sin(phase) * 0.5 + 0.5); const randomFactor = 0.8 + 0.4 * Math.sin(canvas.frame * 0.03 + i); const height = baseHeight * randomFactor * maxHeight; const x = i * barWidth + barWidth * 0.2; const y = (canvas.height - height) / 2; const width = barWidth * 0.6; // Rounded rectangle bars ctx.beginPath(); ctx.roundRect(x, y, width, height, width / 2); ctx.fill(); } canvas.frame++; if (!this.isVisualizationPlaying) { requestAnimationFrame(animate); } }; // Start idle animation animate(); } drawRealWaveform(canvas) { if (!canvas || !canvas.quizCtx || !this.isVisualizationPlaying || !this.analyser || !this.dataArray) { return; } const ctx = canvas.quizCtx; const numBars = canvas.numBars; const barWidth = canvas.barWidth; const maxHeight = canvas.maxHeight; // Continue animation loop requestAnimationFrame(() => this.drawRealWaveform(canvas)); // Get frequency data from the analyser this.analyser.getByteFrequencyData(this.dataArray); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; // Calculate step for frequency data const step = Math.floor(this.dataArray.length / numBars); // Check if this is the mobile canvas for amplification const isMobileCanvas = canvas.id === 'storyWaveCanvasMobile'; const amplification = isMobileCanvas ? 2.5 : 1.5; // Extra boost for mobile // Draw bars with real audio data for (let i = 0; i < numBars; i++) { const dataIndex = i * step; const rawValue = this.dataArray[dataIndex] || 0; let barHeight = (rawValue / 255) * maxHeight * amplification; // Cap maximum height to canvas bounds barHeight = Math.min(barHeight, maxHeight * 0.9); // Ensure minimum height for visual consistency barHeight = Math.max(barHeight, maxHeight * 0.15); const x = i * barWidth + barWidth * 0.2; const y = (canvas.height - barHeight) / 2; const width = barWidth * 0.6; // Rounded rectangle bars ctx.beginPath(); ctx.roundRect(x, y, width, barHeight, width / 2); ctx.fill(); } } initializeAudioPreview() { console.log('🎵 Initializing audio preview...', { generatedAudioUrl: this.generatedAudioUrl, hasAudioSource: !!document.getElementById('previewAudioSource'), hasAudio: !!document.getElementById('previewAudio'), hasPlayBtn: !!document.getElementById('previewPlayBtn') }); const audioSource = document.getElementById('previewAudioSource'); const audio = document.getElementById('previewAudio'); const playBtn = document.getElementById('previewPlayBtn'); if (!audioSource || !audio) { console.error('❌ Audio elements not found:', { audioSource: !!audioSource, audio: !!audio }); return; } try { // Use the audio URL from Lambda response if available if (this.generatedAudioUrl) { console.log('✅ Using Lambda-generated audio URL for re-initialization:', this.generatedAudioUrl); this.loadAudioAsBlob(this.generatedAudioUrl, [], audioSource, audio, playBtn); return; } else { console.warn('⚠️ No generatedAudioUrl available, falling back to constructed URL'); } // Fallback: Try to get audio URL from config or use S3 direct const audioFilename = this.getAudioFilename(this.selections); const s3BaseUrl = window.config?.audioCdnUrl || 'https://audiobook-outputs-024239493753.s3.eu-north-1.amazonaws.com'; // Check if using audio proxy (Supabase Edge Function) or direct S3 const primaryUrl = s3BaseUrl.includes('supabase.co') ? `${s3BaseUrl}/${audioFilename}` // Audio proxy handles path internally : `${s3BaseUrl}/audiobooks/${audioFilename}`; // Direct S3 access const fallbackUrls = [ // Add some fallback URLs if needed ]; console.log('Using generated audio URL:', primaryUrl); this.loadAudioAsBlob(primaryUrl, fallbackUrls, audioSource, audio, playBtn); } catch (error) { console.error('Error initializing audio preview:', error); if (playBtn) { // Keep SVG structure but show disabled state - NO TEXT! playBtn.innerHTML = ` `; playBtn.disabled = true; playBtn.style.fontSize = '0.8rem'; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; } } } async loadAudioAsBlob(primaryUrl, fallbackUrls, audioSource, audio, playBtn) { const urls = [primaryUrl, ...fallbackUrls]; for (let i = 0; i < urls.length; i++) { try { console.log(`Trying to load audio from: ${urls[i]}`); // Fetch audio as blob const response = await fetch(urls[i]); if (!response.ok) throw new Error(`HTTP ${response.status}`); const audioBlob = await response.blob(); // Clean up previous blob URL if exists if (this.currentBlobUrl) { URL.revokeObjectURL(this.currentBlobUrl); } // Create blob URL for security this.currentBlobUrl = URL.createObjectURL(audioBlob); audioSource.src = this.currentBlobUrl; audio.load(); console.log('✅ Audio loaded successfully as Blob'); // Setup audio context for visualization this.setupAudioContext(audio); // Initialize player controls this.setupAudioPreviewControls(audio); // CRITICAL: Set button to playable state with proper SVG icons like index.html - NO TEXT! if (playBtn) { playBtn.innerHTML = ` `; playBtn.disabled = false; playBtn.style.fontSize = ''; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; console.log('✅ Button set to playable state with SVG icons'); } return; // Success, exit function } catch (error) { console.log(`❌ Failed to load from ${urls[i]}:`, error); // If this was the last URL, show error if (i === urls.length - 1) { console.error('All audio URLs failed'); if (playBtn) { // Keep SVG structure but show disabled state - NO TEXT! playBtn.innerHTML = ` `; playBtn.disabled = true; playBtn.style.fontSize = '0.8rem'; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; } } } } } setupAudioContext(audio) { try { console.log('🎵 Setting up audio context for visualization...'); // Create audio context if it doesn't exist if (!this.audioContext) { this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); // Resume audio context if suspended if (this.audioContext.state === 'suspended') { this.audioContext.resume(); } this.analyser = this.audioContext.createAnalyser(); this.analyser.fftSize = 256; // Smaller for quiz preview this.analyser.smoothingTimeConstant = 0.8; this.dataArray = new Uint8Array(this.analyser.frequencyBinCount); console.log('✅ Audio context created successfully'); } // Connect audio element to analyser if (!this.audioSource) { try { this.audioSource = this.audioContext.createMediaElementSource(audio); this.audioSource.connect(this.analyser); this.analyser.connect(this.audioContext.destination); console.log('✅ Audio source connected to analyser'); } catch (corsError) { console.warn('⚠️ CORS error connecting audio source:', corsError.message); // Continue without real visualization - will use simulated } } } catch (error) { console.error('❌ Error setting up audio context:', error); } } setupAudioPreviewControls(audio) { const playBtn = document.getElementById('previewPlayBtn'); const progressFill = document.getElementById('previewProgressFill'); const currentTime = document.getElementById('previewCurrentTime'); const progressBar = document.querySelector('.progress-bar-preview'); let isPlaying = false; let startTime = null; // Track when playback started // Set default volume to 100% for preview audio.volume = 1.0; // Play/Pause functionality if (playBtn) { playBtn.addEventListener('click', () => { if (isPlaying) { console.log('⏸️ Manual pause triggered'); audio.pause(); isPlaying = false; startTime = null; // Reset start time playBtn.querySelector('#playIcon').style.display = 'inline'; playBtn.querySelector('#pauseIcon').style.display = 'none'; // Stop visualization this.stopWaveformVisualization(); } else { // Resume audio context if needed if (this.audioContext && this.audioContext.state === 'suspended') { this.audioContext.resume(); } // Try to play audio const playPromise = audio.play(); if (playPromise !== undefined) { playPromise.then(() => { isPlaying = true; startTime = Date.now(); // CRITICAL: Track start time console.log('🎵 Preview started at:', new Date(startTime).toLocaleTimeString()); playBtn.querySelector('#playIcon').style.display = 'none'; playBtn.querySelector('#pauseIcon').style.display = 'inline'; // Start visualization this.startWaveformVisualization(); }).catch(error => { console.log('Audio play failed:', error); // Show fallback message console.warn('⚠️ Audio error but keeping button enabled for retry'); }); } } }); } // Progress tracking audio.addEventListener('timeupdate', () => { if (audio.duration && !isNaN(audio.duration)) { const progress = (audio.currentTime / audio.duration) * 100; if (progressFill) progressFill.style.width = progress + '%'; if (currentTime) { const minutes = Math.floor(audio.currentTime / 60); const seconds = Math.floor(audio.currentTime % 60); currentTime.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } } }); // Update total time when metadata loads audio.addEventListener('loadedmetadata', () => { const totalTime = document.getElementById('previewTotalTime'); if (totalTime && audio.duration && !isNaN(audio.duration)) { const minutes = Math.floor(audio.duration / 60); const seconds = Math.floor(audio.duration % 60); totalTime.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } }); // Progress bar click - Allow seeking through full audio if (progressBar) { progressBar.addEventListener('click', (e) => { if (audio.duration && !isNaN(audio.duration)) { const rect = progressBar.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; const targetTime = percent * audio.duration; audio.currentTime = Math.min(targetTime, audio.duration); console.log(`⏯️ Seeking to: ${targetTime.toFixed(1)}s`); } }); } // Handle audio loading errors audio.addEventListener('error', (e) => { console.log('Audio loading error:', e); if (playBtn) { // Keep SVG structure but show disabled state - NO TEXT! playBtn.innerHTML = ` `; playBtn.disabled = true; playBtn.style.fontSize = '0.8rem'; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; } }); // Handle when audio can't be loaded audio.addEventListener('stalled', () => { console.log('Audio loading stalled'); if (playBtn && !audio.duration) { // Show loading state with SVG - NO TEXT! playBtn.innerHTML = ` `; playBtn.disabled = true; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; setTimeout(() => { if (!audio.duration) { // Show error state with SVG - NO TEXT! playBtn.innerHTML = ` `; playBtn.style.fontSize = '0.8rem'; } }, 5000); } }); } setPersonalizedHeadline() { const headlineEl = document.getElementById('personalizedHeadline'); if (!headlineEl) { console.warn('⚠️ personalizedHeadline element not found'); return; } // Try multiple sources for firstName let firstName = 'Du'; if (this.selections?.firstName) { firstName = this.selections.firstName; } else if (window.quizManager?.selections?.firstName) { firstName = window.quizManager.selections.firstName; } else if (localStorage.getItem('quizSelections')) { try { const stored = JSON.parse(localStorage.getItem('quizSelections')); firstName = stored.firstName || 'Du'; } catch (e) { console.warn('Failed to parse stored selections:', e); } } console.log('🏷️ Setting personalized headline:', { firstName, thisSelections: this.selections, windowSelections: window.quizManager?.selections }); // Create the personalized message - always fallback to "Du" if (!firstName || firstName === 'undefined' || firstName.trim() === '') { firstName = 'Du'; } if (firstName === 'Du') { headlineEl.innerHTML = `Deine SoftSins wartet`; } else { headlineEl.innerHTML = `${firstName}, deine SoftSins wartet`; } // Add subtle animation delay setTimeout(() => { headlineEl.style.animation = 'personalizedGlow 2s ease-in-out infinite alternate'; }, 500); } updateStoryDuration() { const durationEl = document.getElementById('storyDuration'); if (!durationEl) return; // Fixed text instead of complex calculation durationEl.textContent = 'Dauer: ~10 Minuten'; console.log('📝 Duration set to fixed text: "bis zu 10 Minuten"'); } getEstimatedDuration() { // Use audio mapper for consistent duration calculation if (window.audioMapper) { try { const templateName = this.getAudioFilename(this.selections).replace('.mp3', ''); console.log('🕐 Getting duration for template:', templateName); console.log('🕐 Selections:', this.selections); const duration = window.audioMapper.estimateDuration(templateName); console.log('🕐 Calculated duration:', duration); return duration; } catch (error) { console.error('Error getting duration from audio mapper:', error); } } console.log('🕐 Using fallback duration calculation'); // Enhanced fallback calculation if audio mapper is not available let baseDuration = 12; // Base duration in minutes // Adjust based on intensity if (this.selections.intensity === 'sensual') baseDuration += 3; else if (this.selections.intensity === 'passionate') baseDuration += 1; else if (this.selections.intensity === 'bold') baseDuration -= 1; // Adjust based on relationship complexity if (this.selections.relationship === 'friends_to_lovers') baseDuration += 2; else if (this.selections.relationship === 'strangers') baseDuration += 1; else if (this.selections.relationship === 'authority') baseDuration += 0.5; // Adjust based on setting complexity if (this.selections.setting === 'fantasy') baseDuration += 2; else if (this.selections.setting === 'office' || this.selections.setting === 'nature') baseDuration += 1; else if (this.selections.setting === 'hotel') baseDuration += 0.5; else if (this.selections.setting === 'car') baseDuration -= 0.5; // Ensure minimum duration baseDuration = Math.max(baseDuration, 8); console.log('🕐 Fallback calculated duration (minutes):', baseDuration); const minutes = Math.floor(baseDuration); const seconds = Math.floor((baseDuration % 1) * 60); const formattedDuration = `${minutes}:${seconds.toString().padStart(2, '0')}`; console.log('🕐 Formatted duration:', formattedDuration); return formattedDuration; } handleMobileHeader(stepNumber) { // Only affect mobile devices (< 768px) if (window.innerWidth >= 768) return; const headerTitle = document.querySelector('.quiz-header h1'); const headerSubtitle = document.querySelector('.quiz-header p'); if (!headerTitle || !headerSubtitle) return; // Hide headline and subheadline after first question (step 2 and beyond) if (typeof stepNumber === 'number' && stepNumber > 1) { headerTitle.classList.add('mobile-hidden'); headerSubtitle.classList.add('mobile-hidden'); } else if (typeof stepNumber === 'string' && stepNumber !== 'step1') { headerTitle.classList.add('mobile-hidden'); headerSubtitle.classList.add('mobile-hidden'); } else { headerTitle.classList.remove('mobile-hidden'); headerSubtitle.classList.remove('mobile-hidden'); } } // Handle window resize to reset header state handleResize() { if (window.innerWidth >= 768) { const headerTitle = document.querySelector('.quiz-header h1'); const headerSubtitle = document.querySelector('.quiz-header p'); if (headerTitle) headerTitle.classList.remove('mobile-hidden'); if (headerSubtitle) headerSubtitle.classList.remove('mobile-hidden'); } else { // Re-apply mobile logic based on current step this.handleMobileHeader(this.currentStep); } } // CRITICAL: Force stop preview function for 30-second limit forceStopPreview(audio, isPlaying, playBtn, progressFill, currentTime, previewTimer) { console.log('🛑 FORCE STOPPING preview - safety mechanism activated'); // Force pause the audio audio.pause(); audio.currentTime = 0; // Reset UI if (playBtn) { playBtn.querySelector('.play-icon').style.display = 'inline'; playBtn.querySelector('.pause-icon').style.display = 'none'; } if (progressFill) progressFill.style.width = '0%'; if (currentTime) currentTime.textContent = '0:00'; // Clear timer if (previewTimer) { clearTimeout(previewTimer); } // Stop visualization this.stopWaveformVisualization(); console.log('✅ Preview force-stopped successfully'); } // Audio visualization control functions startWaveformVisualization() { console.log('🎵 Starting waveform visualization...'); this.isVisualizationPlaying = true; // Start real visualization for both desktop and mobile canvas const desktopCanvas = document.getElementById('storyWaveCanvas'); const mobileCanvas = document.getElementById('storyWaveCanvasMobile'); if (desktopCanvas) this.drawRealWaveform(desktopCanvas); if (mobileCanvas) this.drawRealWaveform(mobileCanvas); } stopWaveformVisualization() { console.log('🎵 Stopping waveform visualization...'); this.isVisualizationPlaying = false; // Return to idle animation for both canvases const desktopCanvas = document.getElementById('storyWaveCanvas'); const mobileCanvas = document.getElementById('storyWaveCanvasMobile'); if (desktopCanvas) this.drawIdleWaveform(desktopCanvas); if (mobileCanvas) this.drawIdleWaveform(mobileCanvas); } // Cleanup blob URLs when component is destroyed cleanup() { if (this.currentBlobUrl) { URL.revokeObjectURL(this.currentBlobUrl); this.currentBlobUrl = null; console.log('🧹 Audio Blob URL cleaned up'); } // Also cleanup audio context if (this.audioContext && this.audioContext.state !== 'closed') { this.audioContext.close().then(() => { console.log('🧹 Quiz audio context closed'); }).catch(error => { console.warn('Quiz audio context cleanup error:', error); }); } } setupAudioPreviewControls(audio) { const playBtn = document.getElementById('previewPlayBtn'); const progressFill = document.getElementById('previewProgressFill'); const currentTime = document.getElementById('previewCurrentTime'); const progressBar = document.querySelector('.progress-bar-preview'); let isPlaying = false; let startTime = null; // Track when playback started // Set default volume to 100% for preview audio.volume = 1.0; // Play/Pause functionality if (playBtn) { playBtn.addEventListener('click', () => { if (isPlaying) { console.log('⏸️ Manual pause triggered'); audio.pause(); isPlaying = false; startTime = null; // Reset start time playBtn.querySelector('#playIcon').style.display = 'inline'; playBtn.querySelector('#pauseIcon').style.display = 'none'; // Stop visualization this.stopWaveformVisualization(); } else { // Resume audio context if needed if (this.audioContext && this.audioContext.state === 'suspended') { this.audioContext.resume(); } // Try to play audio const playPromise = audio.play(); if (playPromise !== undefined) { playPromise.then(() => { isPlaying = true; startTime = Date.now(); // CRITICAL: Track start time console.log('🎵 Preview started at:', new Date(startTime).toLocaleTimeString()); playBtn.querySelector('#playIcon').style.display = 'none'; playBtn.querySelector('#pauseIcon').style.display = 'inline'; // Start visualization this.startWaveformVisualization(); }).catch(error => { console.log('Audio play failed:', error); // Show fallback message console.warn('⚠️ Audio error but keeping button enabled for retry'); }); } } }); } // Progress tracking audio.addEventListener('timeupdate', () => { if (audio.duration && !isNaN(audio.duration)) { const progress = (audio.currentTime / audio.duration) * 100; if (progressFill) progressFill.style.width = progress + '%'; if (currentTime) { const minutes = Math.floor(audio.currentTime / 60); const seconds = Math.floor(audio.currentTime % 60); currentTime.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } } }); // Update total time when metadata loads audio.addEventListener('loadedmetadata', () => { const totalTime = document.getElementById('previewTotalTime'); if (totalTime && audio.duration && !isNaN(audio.duration)) { const minutes = Math.floor(audio.duration / 60); const seconds = Math.floor(audio.duration % 60); totalTime.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } }); // Progress bar click - Allow seeking through full audio if (progressBar) { progressBar.addEventListener('click', (e) => { if (audio.duration && !isNaN(audio.duration)) { const rect = progressBar.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; const targetTime = percent * audio.duration; audio.currentTime = Math.min(targetTime, audio.duration); console.log(`⏯️ Seeking to: ${targetTime.toFixed(1)}s`); } }); } // Handle audio loading errors audio.addEventListener('error', (e) => { console.log('Audio loading error:', e); if (playBtn) { // Keep SVG structure but show disabled state - NO TEXT! playBtn.innerHTML = ` `; playBtn.disabled = true; playBtn.style.fontSize = '0.8rem'; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; } }); // Handle when audio can't be loaded audio.addEventListener('stalled', () => { console.log('Audio loading stalled'); if (playBtn && !audio.duration) { // Show loading state with SVG - NO TEXT! playBtn.innerHTML = ` `; playBtn.disabled = true; playBtn.style.display = 'flex'; playBtn.style.alignItems = 'center'; playBtn.style.justifyContent = 'center'; setTimeout(() => { if (!audio.duration) { // Show error state with SVG - NO TEXT! playBtn.innerHTML = ` `; playBtn.style.fontSize = '0.8rem'; } }, 5000); } }); } handleMobileHeader(stepNumber) { // Only affect mobile devices (< 768px) if (window.innerWidth >= 768) return; const headerTitle = document.querySelector('.quiz-header h1'); const headerSubtitle = document.querySelector('.quiz-header p'); if (!headerTitle || !headerSubtitle) return; // Hide headline and subheadline after first question (step 2 and beyond) if (typeof stepNumber === 'number' && stepNumber > 1) { headerTitle.classList.add('mobile-hidden'); headerSubtitle.classList.add('mobile-hidden'); } else if (typeof stepNumber === 'string' && stepNumber !== 'step1') { headerTitle.classList.add('mobile-hidden'); headerSubtitle.classList.add('mobile-hidden'); } else { headerTitle.classList.remove('mobile-hidden'); headerSubtitle.classList.remove('mobile-hidden'); } } // Handle window resize to reset header state handleResize() { if (window.innerWidth >= 768) { const headerTitle = document.querySelector('.quiz-header h1'); const headerSubtitle = document.querySelector('.quiz-header p'); if (headerTitle) headerTitle.classList.remove('mobile-hidden'); if (headerSubtitle) headerSubtitle.classList.remove('mobile-hidden'); } else { // Re-apply mobile logic based on current step this.handleMobileHeader(this.currentStep); } } // Handle quiz layout resize for mobile/desktop transitions handleQuizLayoutResize() { // Repopulate quiz options if they exist to apply new responsive styles const containers = ['settingGrid', 'dynamicGrid', 'relationshipGrid', 'intensityGrid']; containers.forEach(containerId => { const container = document.getElementById(containerId); if (container && container.children.length > 0) { // Store current selections before clearing const selections = { ...this.selections }; // Clear and repopulate this container switch(containerId) { case 'settingGrid': container.innerHTML = ''; this.populateSettings(); break; case 'dynamicGrid': container.innerHTML = ''; this.populateDynamics(); break; case 'relationshipGrid': container.innerHTML = ''; this.populateRelationships(); break; case 'intensityGrid': container.innerHTML = ''; this.populateIntensity(); break; } // Restore selections this.selections = selections; this.restoreSelections(containerId); } }); } // Restore user selections after layout resize restoreSelections(containerId) { const selectionMap = { 'settingGrid': { type: 'setting', class: 'setting-option' }, 'dynamicGrid': { type: 'dynamic', class: 'dynamic-option' }, 'relationshipGrid': { type: 'relationship', class: 'relationship-option' }, 'intensityGrid': { type: 'intensity', class: 'intensity-option' } }; const config = selectionMap[containerId]; if (!config || !this.selections[config.type]) return; // Find and re-select the correct option const options = document.querySelectorAll(`.${config.class}`); options.forEach(option => { option.addEventListener('click', () => { // This will be handled by the individual populate functions }); }); } } document.addEventListener('DOMContentLoaded', function() { console.log('DOM loaded, checking for customizer page...'); if (document.querySelector('.customizer-main')) { console.log('Customizer page detected, initializing QuizManager...'); try { window.quizManager = new QuizManager(); // Add cleanup on page unload window.addEventListener('beforeunload', () => { if (window.quizManager && window.quizManager.cleanup) { window.quizManager.cleanup(); } }); } catch (error) { console.error('Error initializing QuizManager:', error); } } else { console.log('Not a customizer page, quiz not initialized'); } }); console.log('Quiz.js fully loaded');