|
|
|
|
|
class SoulSyncApp { |
|
|
constructor() { |
|
|
this.currentUser = null; |
|
|
this.conversationHistory = []; |
|
|
this.isRecording = false; |
|
|
this.isSpeaking = false; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
init() { |
|
|
this.setupEventListeners(); |
|
|
this.loadUserData(); |
|
|
this.setupVoiceRecognition(); |
|
|
console.log('SoulSync initialized - Ready to explore the shadows'); |
|
|
} |
|
|
|
|
|
setupEventListeners() { |
|
|
|
|
|
const themeToggle = document.getElementById('themeToggle'); |
|
|
if (themeToggle) { |
|
|
themeToggle.addEventListener('click', () => this.toggleTheme()); |
|
|
} |
|
|
|
|
|
|
|
|
const voiceBtn = document.getElementById('voiceBtn'); |
|
|
if (voiceBtn) { |
|
|
voiceBtn.addEventListener('click', () => this.toggleVoiceRecording()); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
if (e.target.matches('[data-nav]')) { |
|
|
this.handleNavigation(e.target.getAttribute('data-nav')); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
toggleTheme() { |
|
|
const html = document.documentElement; |
|
|
html.classList.toggle('dark'); |
|
|
const isDark = html.classList.contains('dark'); |
|
|
localStorage.setItem('soulsync-theme', isDark ? 'dark' : 'light'); |
|
|
|
|
|
|
|
|
const themeIcon = document.querySelector('#themeToggle i'); |
|
|
if (themeIcon) { |
|
|
themeIcon.setAttribute('data-feather', isDark ? 'sun' : 'moon'); |
|
|
feather.replace(); |
|
|
} |
|
|
} |
|
|
|
|
|
async toggleVoiceRecording() { |
|
|
if (!this.isRecording) { |
|
|
await this.startVoiceRecording(); |
|
|
} else { |
|
|
this.stopVoiceRecording(); |
|
|
} |
|
|
} |
|
|
|
|
|
async startVoiceRecording() { |
|
|
try { |
|
|
if (!('webkitSpeechRecognition' in window)) { |
|
|
this.showNotification('Voice recognition not supported in your browser', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.isRecording = true; |
|
|
const voiceBtn = document.getElementById('voiceBtn'); |
|
|
if (voiceBtn) { |
|
|
voiceBtn.classList.add('recording'); |
|
|
voiceBtn.innerHTML = '<i data-feather="square"></i>'; |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
|
|
|
const recognition = new webkitSpeechRecognition(); |
|
|
recognition.continuous = true; |
|
|
recognition.interimResults = true; |
|
|
recognition.lang = 'en-US'; |
|
|
|
|
|
recognition.onresult = (event) => { |
|
|
let transcript = ''; |
|
|
for (let i = event.resultIndex; i < event.results.length; i++) { |
|
|
if (event.results[i].isFinal) { |
|
|
transcript += event.results[i][0].transcript; |
|
|
} |
|
|
} |
|
|
|
|
|
if (transcript) { |
|
|
this.processVoiceInput(transcript); |
|
|
} |
|
|
}; |
|
|
|
|
|
recognition.onerror = (event) => { |
|
|
console.error('Speech recognition error:', event.error); |
|
|
this.isRecording = false; |
|
|
this.updateVoiceButton(); |
|
|
}; |
|
|
|
|
|
recognition.onend = () => { |
|
|
this.isRecording = false; |
|
|
this.updateVoiceButton(); |
|
|
}; |
|
|
|
|
|
recognition.start(); |
|
|
} catch (error) { |
|
|
console.error('Voice recording failed:', error); |
|
|
this.isRecording = false; |
|
|
this.updateVoiceButton(); |
|
|
} |
|
|
} |
|
|
|
|
|
stopVoiceRecording() { |
|
|
this.isRecording = false; |
|
|
this.updateVoiceButton(); |
|
|
} |
|
|
|
|
|
updateVoiceButton() { |
|
|
const voiceBtn = document.getElementById('voiceBtn'); |
|
|
if (voiceBtn) { |
|
|
voiceBtn.classList.remove('recording'); |
|
|
voiceBtn.innerHTML = '<i data-feather="mic"></i>'; |
|
|
feather.replace(); |
|
|
} |
|
|
} |
|
|
|
|
|
processVoiceInput(transcript) { |
|
|
console.log('Voice input:', transcript); |
|
|
|
|
|
this.sendMessageToAI(transcript); |
|
|
} |
|
|
|
|
|
async sendMessageToAI(message) { |
|
|
|
|
|
this.showTypingIndicator(); |
|
|
|
|
|
try { |
|
|
|
|
|
const response = await this.simulateAIResponse(message); |
|
|
|
|
|
|
|
|
this.hideTypingIndicator(); |
|
|
|
|
|
|
|
|
this.displayAIResponse(response); |
|
|
|
|
|
|
|
|
this.conversationHistory.push({ |
|
|
user: message, |
|
|
ai: response, |
|
|
timestamp: new Date().toISOString() |
|
|
}); |
|
|
|
|
|
|
|
|
this.saveConversation(); |
|
|
} catch (error) { |
|
|
console.error('AI response error:', error); |
|
|
this.hideTypingIndicator(); |
|
|
this.showNotification('Failed to get response from NewMe', 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
async simulateAIResponse(message) { |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
setTimeout(() => { |
|
|
const responses = [ |
|
|
`I hear the space between your words, habibti. That pause where you almost told the truth... tell me more about that.`, |
|
|
`You're smiling as you say that, but your words don't match your energy. What's really happening behind that smile?`, |
|
|
`Remember that picture you sent me last week? The one with the morning light? You said it made you feel peaceful. What's different about today?`, |
|
|
`I'm noticing a pattern here. You use humor when you're uncomfortable. Let's sit with that discomfort for a moment.`, |
|
|
`Your voice tells me you're tired of pretending. What if we just... stopped?` |
|
|
]; |
|
|
resolve(responses[Math.floor(Math.random() * responses.length)]); |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
|
|
|
showTypingIndicator() { |
|
|
const chatContainer = document.getElementById('chatContainer'); |
|
|
if (chatContainer) { |
|
|
const typingElement = document.createElement('div'); |
|
|
typingElement.id = 'typingIndicator'; |
|
|
typingElement.className = 'flex items-center space-x-1 p-4'; |
|
|
typingElement.innerHTML = ` |
|
|
<div class="w-8 h-8 bg-gradient-to-r from-fuchsia-500 to-violet-500 rounded-full flex items-center justify-center"> |
|
|
<i data-feather="moon" class="w-4 h-4 text-white"></i> |
|
|
</div> |
|
|
<div class="flex space-x-1"> |
|
|
<div class="typing-dot w-2 h-2 bg-fuchsia-400 rounded-full"></div> |
|
|
<div class="typing-dot w-2 h-2 bg-fuchsia-400 rounded-full"></div> |
|
|
<div class="typing-dot w-2 h-2 bg-fuchsia-400 rounded-full"></div> |
|
|
</div> |
|
|
`; |
|
|
chatContainer.appendChild(typingElement); |
|
|
feather.replace(); |
|
|
} |
|
|
} |
|
|
|
|
|
hideTypingIndicator() { |
|
|
const typingIndicator = document.getElementById('typingIndicator'); |
|
|
if (typingIndicator) { |
|
|
typingIndicator.remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
displayAIResponse(response) { |
|
|
const chatContainer = document.getElementById('chatContainer'); |
|
|
if (chatContainer) { |
|
|
const messageElement = document.createElement('div'); |
|
|
messageElement.className = 'chat-bubble bg-dark-100 border border-gray-700 rounded-2xl p-4 mb-4'; |
|
|
messageElement.innerHTML = ` |
|
|
<div class="flex items-start space-x-3"> |
|
|
<div class="w-8 h-8 bg-gradient-to-r from-fuchsia-500 to-violet-500 rounded-full flex items-center justify-center flex-shrink-0"> |
|
|
<i data-feather="moon" class="w-4 h-4 text-white"></i> |
|
|
</div> |
|
|
<div class="flex-1"> |
|
|
<p class="text-white leading-relaxed">${response}</p> |
|
|
<div class="flex items-center space-x-2 mt-2"> |
|
|
<button class="text-xs text-fuchsia-400 hover:text-fuchsia-300">Reply</button> |
|
|
<button class="text-xs text-violet-400 hover:text-violet-300">Voice Response</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
chatContainer.appendChild(messageElement); |
|
|
feather.replace(); |
|
|
|
|
|
|
|
|
chatContainer.scrollTop = chatContainer.scrollHeight; |
|
|
} |
|
|
} |
|
|
|
|
|
showNotification(message, type = 'info') { |
|
|
|
|
|
const notification = document.createElement('div'); |
|
|
notification.className = `fixed top-4 right-4 p-4 rounded-lg z-50 transform transition-all duration-300 ${ |
|
|
type === 'error' ? 'bg-red-500' : |
|
|
type === 'success' ? 'bg-green-500' : |
|
|
'bg-fuchsia-500' |
|
|
} text-white shadow-lg`; |
|
|
notification.textContent = message; |
|
|
|
|
|
document.body.appendChild(notification); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
notification.remove(); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
handleNavigation(page) { |
|
|
|
|
|
window.location.href = `${page}.html`; |
|
|
} |
|
|
|
|
|
loadUserData() { |
|
|
const savedUser = localStorage.getItem('soulsync-user'); |
|
|
const savedConversations = localStorage.getItem('soulsync-conversations'); |
|
|
|
|
|
if (savedUser) { |
|
|
this.currentUser = JSON.parse(savedUser); |
|
|
} |
|
|
|
|
|
if (savedConversations) { |
|
|
this.conversationHistory = JSON.parse(savedConversations); |
|
|
} |
|
|
} |
|
|
|
|
|
saveConversation() { |
|
|
localStorage.setItem('soulsync-conversations', JSON.stringify(this.conversationHistory)); |
|
|
} |
|
|
|
|
|
|
|
|
startShadowWork() { |
|
|
const questions = [ |
|
|
"What roles do you most often play in front of others?", |
|
|
"Which emotions do you feel ashamed to show?", |
|
|
"What beliefs echo in your mind during hard moments?", |
|
|
"What do you tolerate that hurts you?", |
|
|
"Describe your most free, authentic self", |
|
|
"What compliment do you find hardest to accept?", |
|
|
"When was the last time you felt truly seen?", |
|
|
"What part of yourself do you hide because you think it's 'too much'?", |
|
|
"What would you do if you weren't afraid of being judged?", |
|
|
"What childhood dream did you abandon and why?", |
|
|
"What truth about yourself are you ready to acknowledge today?" |
|
|
]; |
|
|
|
|
|
this.showShadowWorkQuestion(0, questions); |
|
|
} |
|
|
|
|
|
showShadowWorkQuestion(index, questions) { |
|
|
if (index >= questions.length) { |
|
|
this.generateShadowWorkInsights(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const question = questions[index]; |
|
|
this.displayAIResponse(question); |
|
|
|
|
|
|
|
|
this.currentQuestionIndex = index; |
|
|
} |
|
|
|
|
|
generateShadowWorkInsights() { |
|
|
const insights = { |
|
|
patterns: ["People-pleasing tendency", "Emotional suppression", "Perfectionism"], |
|
|
strengths: ["Deep empathy", "Intuitive wisdom", "Resilient spirit"], |
|
|
actions: ["Practice saying no once daily", "Journal about suppressed emotions", "Allow yourself to be imperfect"], |
|
|
affirmations: ["I am worthy of taking up space", "My feelings are valid", "I embrace all parts of myself"] |
|
|
}; |
|
|
|
|
|
this.displayShadowWorkResults(insights); |
|
|
} |
|
|
|
|
|
displayShadowWorkResults(insights) { |
|
|
const resultsHTML = ` |
|
|
<div class="bg-dark-300 rounded-2xl p-6 border border-fuchsia-500/30"> |
|
|
<h3 class="text-lg font-semibold text-fuchsia-400 mb-4">Your Shadow Work Insights</h3> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<h4 class="text-violet-400 font-medium mb-2">Core Patterns</h4> |
|
|
<p class="text-gray-300">${insights.patterns.join(', ')}</p> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h4 class="text-violet-400 font-medium mb-2">Hidden Strengths</h4> |
|
|
<p class="text-gray-300">${insights.strengths.join(', ')}</p> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h4 class="text-violet-400 font-medium mb-2">Action Steps</h4> |
|
|
<p class="text-gray-300">${insights.actions.join(', ')}</p> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h4 class="text-violet-400 font-medium mb-2">Daily Affirmations</h4> |
|
|
<p class="text-gray-300 italic">${insights.affirmations.join(' • ')}</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
this.displayAIResponse(resultsHTML); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
window.soulSyncApp = new SoulSyncApp(); |
|
|
}); |
|
|
|
|
|
|
|
|
const SoulSyncUtils = { |
|
|
formatTime: (date) => { |
|
|
return new Date(date).toLocaleTimeString('en-US', { |
|
|
hour: '2-digit', |
|
|
minute: '2-digit' |
|
|
}); |
|
|
}, |
|
|
|
|
|
getEmotionColor: (emotion) => { |
|
|
const colors = { |
|
|
happy: '#10b981', |
|
|
sad: '#ef4444', |
|
|
angry: '#dc2626', |
|
|
excited: '#f59e0b', |
|
|
calm: '#3b82f6', |
|
|
anxious: '#f97316', |
|
|
peaceful: '#06b6d4' |
|
|
}; |
|
|
return colors[emotion] || '#6b7280'; |
|
|
}, |
|
|
|
|
|
debounce: (func, wait) => { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
if (typeof module !== 'undefined' && module.exports) { |
|
|
module.exports = { SoulSyncApp, SoulSyncUtils }; |
|
|
} |