Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Mindful Voice - Mental Wellbeing Platform</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| transition: background-color 0.3s, color 0.3s; | |
| } | |
| .waveform { | |
| background: linear-gradient(90deg, rgba(99,102,241,0.2) 0%, rgba(168,85,247,0.2) 100%); | |
| height: 100px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .waveform::before { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 50 Q 25 25 50 50 T 100 50 T 150 50 T 200 50' stroke='%236363f5' fill='none' stroke-width='2'/%3E%3C/svg%3E") repeat-x; | |
| background-size: 200px 100px; | |
| animation: wave 5s linear infinite; | |
| opacity: 0.7; | |
| } | |
| @keyframes wave { | |
| 0% { background-position-x: 0; } | |
| 100% { background-position-x: 200px; } | |
| } | |
| .brainwave-indicator { | |
| height: 4px; | |
| border-radius: 2px; | |
| background: linear-gradient(90deg, #6366f1, #8b5cf6); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .brainwave-indicator::after { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, rgba(255,255,255,0.1), rgba(255,255,255,0.4), rgba(255,255,255,0.1)); | |
| animation: shimmer 2s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .toast { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| transform: translateY(100px); | |
| opacity: 0; | |
| transition: all 0.3s ease; | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| .frequency-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #8b5cf6; | |
| cursor: pointer; | |
| } | |
| .frequency-slider::-moz-range-thumb { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #8b5cf6; | |
| cursor: pointer; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .affirmation-card { | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| } | |
| .affirmation-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| } | |
| .recording-indicator { | |
| animation: pulse 1.5s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| 100% { opacity: 1; } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen"> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="toast bg-gray-800 text-white px-6 py-3 rounded-lg shadow-lg"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-check-circle mr-2 text-green-400"></i> | |
| <span id="toast-message">Action completed successfully</span> | |
| </div> | |
| </div> | |
| <!-- Main Container --> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="flex justify-between items-center mb-8"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-brain text-3xl mr-3 text-indigo-500"></i> | |
| <h1 class="text-2xl font-bold">Mindful Voice</h1> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700"> | |
| <i class="fas fa-moon dark:hidden"></i> | |
| <i class="fas fa-sun hidden dark:block"></i> | |
| </button> | |
| <button class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg"> | |
| <i class="fas fa-user mr-2"></i>Sign In | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main> | |
| <!-- Tabs Navigation --> | |
| <div class="flex border-b border-gray-200 dark:border-gray-700 mb-6"> | |
| <button class="tab-btn px-4 py-2 font-medium border-b-2 border-transparent hover:text-indigo-600 dark:hover:text-indigo-400" data-tab="affirmations"> | |
| <i class="fas fa-comment-dots mr-2"></i>Affirmations | |
| </button> | |
| <button class="tab-btn px-4 py-2 font-medium border-b-2 border-transparent hover:text-indigo-600 dark:hover:text-indigo-400" data-tab="mixes"> | |
| <i class="fas fa-music mr-2"></i>Mixes | |
| </button> | |
| <button class="tab-btn px-4 py-2 font-medium border-b-2 border-transparent hover:text-indigo-600 dark:hover:text-indigo-400" data-tab="binaural"> | |
| <i class="fas fa-wave-square mr-2"></i>Binaural Beats | |
| </button> | |
| </div> | |
| <!-- Tab Contents --> | |
| <div class="tab-contents"> | |
| <!-- Affirmations Tab --> | |
| <div id="affirmations" class="tab-content active"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-semibold">Your Affirmations</h2> | |
| <button id="add-affirmation-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg"> | |
| <i class="fas fa-plus mr-2"></i>Add Affirmation | |
| </button> | |
| </div> | |
| <!-- Categories Filter --> | |
| <div class="mb-6"> | |
| <div class="flex flex-wrap gap-2"> | |
| <button class="category-filter px-3 py-1 rounded-full text-sm bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200" data-category="all">All</button> | |
| <button class="category-filter px-3 py-1 rounded-full text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" data-category="confidence">Confidence</button> | |
| <button class="category-filter px-3 py-1 rounded-full text-sm bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200" data-category="health">Health</button> | |
| <button class="category-filter px-3 py-1 rounded-full text-sm bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200" data-category="wealth">Wealth</button> | |
| <button class="category-filter px-3 py-1 rounded-full text-sm bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200" data-category="love">Love</button> | |
| </div> | |
| </div> | |
| <!-- Affirmations Grid --> | |
| <div id="affirmations-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Affirmation cards will be added here by JavaScript --> | |
| </div> | |
| <!-- Add Affirmation Modal --> | |
| <div id="add-affirmation-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold">Add New Affirmation</h3> | |
| <button id="close-affirmation-modal" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <form id="affirmation-form"> | |
| <div class="mb-4"> | |
| <label for="affirmation-text" class="block text-sm font-medium mb-1">Affirmation Text</label> | |
| <textarea id="affirmation-text" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700" rows="3" placeholder="I am confident and capable..."></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="affirmation-category" class="block text-sm font-medium mb-1">Category</label> | |
| <select id="affirmation-category" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"> | |
| <option value="confidence">Confidence</option> | |
| <option value="health">Health</option> | |
| <option value="wealth">Wealth</option> | |
| <option value="love">Love</option> | |
| <option value="other">Other</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Record Audio</label> | |
| <div class="flex items-center space-x-4"> | |
| <button id="record-btn" type="button" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg"> | |
| <i class="fas fa-microphone mr-2"></i>Record | |
| </button> | |
| <div id="recording-indicator" class="recording-indicator hidden items-center text-red-500"> | |
| <div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div> | |
| <span>Recording...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button type="button" id="cancel-affirmation" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">Cancel</button> | |
| <button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg">Save</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mixes Tab --> | |
| <div id="mixes" class="tab-content"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-semibold">Your Mixes</h2> | |
| <button id="add-mix-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg"> | |
| <i class="fas fa-plus mr-2"></i>Create Mix | |
| </button> | |
| </div> | |
| <div id="mixes-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Mix cards will be added here by JavaScript --> | |
| </div> | |
| <!-- Add Mix Modal --> | |
| <div id="add-mix-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-2xl"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold">Create New Mix</h3> | |
| <button id="close-mix-modal" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <form id="mix-form"> | |
| <div class="mb-4"> | |
| <label for="mix-name" class="block text-sm font-medium mb-1">Mix Name</label> | |
| <input type="text" id="mix-name" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700" placeholder="Morning Motivation"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Select Affirmations</label> | |
| <div class="max-h-48 overflow-y-auto border border-gray-300 dark:border-gray-600 rounded-lg p-2 bg-white dark:bg-gray-700"> | |
| <!-- Affirmation checkboxes will be added here by JavaScript --> | |
| <div id="mix-affirmations-list" class="space-y-2"></div> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="mix-interval" class="block text-sm font-medium mb-1">Interval Between Affirmations (seconds)</label> | |
| <input type="number" id="mix-interval" min="1" max="60" value="5" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="mix-background" class="block text-sm font-medium mb-1">Background Sound</label> | |
| <select id="mix-background" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"> | |
| <option value="none">None</option> | |
| <option value="rain">Rain</option> | |
| <option value="ocean">Ocean Waves</option> | |
| <option value="forest">Forest</option> | |
| <option value="white-noise">White Noise</option> | |
| <option value="brown-noise">Brown Noise</option> | |
| </select> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button type="button" id="cancel-mix" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">Cancel</button> | |
| <button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg">Create Mix</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Binaural Beats Tab --> | |
| <div id="binaural" class="tab-content"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-semibold">Binaural Beats Generator</h2> | |
| <button id="save-beat-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg"> | |
| <i class="fas fa-save mr-2"></i>Save Beat | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <div class="lg:col-span-2"> | |
| <!-- Waveform Visualization --> | |
| <div class="waveform rounded-lg mb-6"> | |
| <div class="absolute inset-0 flex items-center justify-center"> | |
| <span id="current-frequency" class="text-2xl font-bold text-white">0 Hz</span> | |
| </div> | |
| </div> | |
| <!-- Brainwave State Info --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-medium">Current Brainwave State</h3> | |
| <span id="brainwave-state" class="px-2 py-1 rounded-full text-xs font-medium bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200">None</span> | |
| </div> | |
| <div class="brainwave-indicator mb-2"></div> | |
| <p id="brainwave-description" class="text-sm text-gray-600 dark:text-gray-400">No frequency selected</p> | |
| </div> | |
| <!-- Presets --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6"> | |
| <h3 class="font-medium mb-3">Preset Frequencies</h3> | |
| <div class="grid grid-cols-2 md:grid-cols-5 gap-2"> | |
| <button class="preset-btn px-3 py-2 rounded-lg text-sm bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600" data-frequency="3" data-state="delta"> | |
| <div class="font-medium">Delta</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400">0.5-4 Hz</div> | |
| </button> | |
| <button class="preset-btn px-3 py-2 rounded-lg text-sm bg-blue-100 dark:bg-blue-900 hover:bg-blue-200 dark:hover:bg-blue-800" data-frequency="6" data-state="theta"> | |
| <div class="font-medium">Theta</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400">4-8 Hz</div> | |
| </button> | |
| <button class="preset-btn px-3 py-2 rounded-lg text-sm bg-green-100 dark:bg-green-900 hover:bg-green-200 dark:hover:bg-green-800" data-frequency="10" data-state="alpha"> | |
| <div class="font-medium">Alpha</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400">8-12 Hz</div> | |
| </button> | |
| <button class="preset-btn px-3 py-2 rounded-lg text-sm bg-yellow-100 dark:bg-yellow-900 hover:bg-yellow-200 dark:hover:bg-yellow-800" data-frequency="18" data-state="beta"> | |
| <div class="font-medium">Beta</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400">12-30 Hz</div> | |
| </button> | |
| <button class="preset-btn px-3 py-2 rounded-lg text-sm bg-purple-100 dark:bg-purple-900 hover:bg-purple-200 dark:hover:bg-purple-800" data-frequency="40" data-state="gamma"> | |
| <div class="font-medium">Gamma</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400">30-100 Hz</div> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <!-- Controls --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-medium">Controls</h3> | |
| <div class="flex space-x-2"> | |
| <button id="play-beat-btn" class="bg-green-500 hover:bg-green-600 text-white p-2 rounded-full"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| <button id="stop-beat-btn" class="bg-red-500 hover:bg-red-600 text-white p-2 rounded-full"> | |
| <i class="fas fa-stop"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="base-frequency" class="block text-sm font-medium mb-1">Base Frequency (Hz)</label> | |
| <input type="range" id="base-frequency" min="20" max="500" value="200" class="w-full frequency-slider"> | |
| <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400"> | |
| <span>20 Hz</span> | |
| <span id="base-frequency-value">200 Hz</span> | |
| <span>500 Hz</span> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="beat-frequency" class="block text-sm font-medium mb-1">Beat Frequency (Hz)</label> | |
| <input type="range" id="beat-frequency" min="0.5" max="30" value="10" step="0.5" class="w-full frequency-slider"> | |
| <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400"> | |
| <span>0.5 Hz</span> | |
| <span id="beat-frequency-value">10 Hz</span> | |
| <span>30 Hz</span> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="waveform" class="block text-sm font-medium mb-1">Waveform</label> | |
| <select id="waveform" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"> | |
| <option value="sine">Sine</option> | |
| <option value="square">Square</option> | |
| <option value="sawtooth">Sawtooth</option> | |
| <option value="triangle">Triangle</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="volume" class="block text-sm font-medium mb-1">Volume</label> | |
| <input type="range" id="volume" min="0" max="1" value="0.5" step="0.01" class="w-full"> | |
| <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400"> | |
| <span>0%</span> | |
| <span id="volume-value">50%</span> | |
| <span>100%</span> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="balance" class="block text-sm font-medium mb-1">Balance (L/R)</label> | |
| <input type="range" id="balance" min="-1" max="1" value="0" step="0.1" class="w-full"> | |
| <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400"> | |
| <span>Left</span> | |
| <span>Center</span> | |
| <span>Right</span> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="duration" class="block text-sm font-medium mb-1">Duration (minutes)</label> | |
| <input type="number" id="duration" min="1" max="120" value="15" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"> | |
| </div> | |
| <div class="flex items-center"> | |
| <input type="checkbox" id="ambient-noise" class="mr-2"> | |
| <label for="ambient-noise" class="text-sm">Add Ambient Noise</label> | |
| </div> | |
| </div> | |
| <!-- Saved Beats --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
| <h3 class="font-medium mb-3">Your Saved Beats</h3> | |
| <div id="saved-beats-list" class="space-y-2"> | |
| <!-- Saved beats will be added here by JavaScript --> | |
| <div class="text-center py-4 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-music text-2xl mb-2"></i> | |
| <p>No saved beats yet</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <script> | |
| // Theme Toggle | |
| const themeToggle = document.getElementById('theme-toggle'); | |
| const html = document.documentElement; | |
| if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) { | |
| html.classList.add('dark'); | |
| } else { | |
| html.classList.remove('dark'); | |
| } | |
| themeToggle.addEventListener('click', () => { | |
| html.classList.toggle('dark'); | |
| localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light'); | |
| }); | |
| // Tab Navigation | |
| const tabButtons = document.querySelectorAll('.tab-btn'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| const tabId = button.getAttribute('data-tab'); | |
| // Update active tab button | |
| tabButtons.forEach(btn => { | |
| btn.classList.remove('border-indigo-600', 'text-indigo-600', 'dark:border-indigo-400', 'dark:text-indigo-400'); | |
| }); | |
| button.classList.add('border-indigo-600', 'text-indigo-600', 'dark:border-indigo-400', 'dark:text-indigo-400'); | |
| // Show selected tab content | |
| tabContents.forEach(content => { | |
| content.classList.remove('active'); | |
| }); | |
| document.getElementById(tabId).classList.add('active'); | |
| }); | |
| }); | |
| // Set first tab as active by default | |
| if (tabButtons.length > 0) { | |
| tabButtons[0].classList.add('border-indigo-600', 'text-indigo-600', 'dark:border-indigo-400', 'dark:text-indigo-400'); | |
| } | |
| // Toast Notification | |
| function showToast(message, type = 'success') { | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toast-message'); | |
| toastMessage.textContent = message; | |
| // Update icon based on type | |
| const icon = toast.querySelector('i'); | |
| icon.className = type === 'success' ? 'fas fa-check-circle mr-2 text-green-400' : 'fas fa-exclamation-circle mr-2 text-yellow-400'; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Sample data for affirmations | |
| let affirmations = [ | |
| { id: 1, text: "I am confident and capable in everything I do", category: "confidence", audio: null }, | |
| { id: 2, text: "My body is healthy and full of energy", category: "health", audio: null }, | |
| { id: 3, text: "Abundance flows freely to me", category: "wealth", audio: null }, | |
| { id: 4, text: "I am worthy of love and respect", category: "love", audio: null } | |
| ]; | |
| // Sample data for mixes | |
| let mixes = [ | |
| { id: 1, name: "Morning Motivation", affirmations: [1, 2], interval: 5, background: "rain" }, | |
| { id: 2, name: "Evening Relaxation", affirmations: [3, 4], interval: 8, background: "ocean" } | |
| ]; | |
| // Sample data for saved beats | |
| let savedBeats = [ | |
| { id: 1, name: "Deep Meditation", baseFreq: 200, beatFreq: 6, waveform: "sine", duration: 20 }, | |
| { id: 2, name: "Focus Boost", baseFreq: 300, beatFreq: 18, waveform: "square", duration: 30 } | |
| ]; | |
| // Render affirmations | |
| function renderAffirmations(filterCategory = 'all') { | |
| const grid = document.getElementById('affirmations-grid'); | |
| grid.innerHTML = ''; | |
| const filtered = filterCategory === 'all' | |
| ? affirmations | |
| : affirmations.filter(a => a.category === filterCategory); | |
| if (filtered.length === 0) { | |
| grid.innerHTML = ` | |
| <div class="col-span-full text-center py-8 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-comment-slash text-3xl mb-2"></i> | |
| <p>No affirmations found</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| filtered.forEach(affirmation => { | |
| const card = document.createElement('div'); | |
| card.className = `affirmation-card bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden border-l-4 ${ | |
| affirmation.category === 'confidence' ? 'border-indigo-500' : | |
| affirmation.category === 'health' ? 'border-green-500' : | |
| affirmation.category === 'wealth' ? 'border-purple-500' : | |
| 'border-yellow-500' | |
| }`; | |
| card.innerHTML = ` | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <span class="px-2 py-1 rounded-full text-xs font-medium ${ | |
| affirmation.category === 'confidence' ? 'bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200' : | |
| affirmation.category === 'health' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : | |
| affirmation.category === 'wealth' ? 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200' : | |
| 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200' | |
| }">${affirmation.category.charAt(0).toUpperCase() + affirmation.category.slice(1)}</span> | |
| <div class="flex space-x-2"> | |
| <button class="edit-affirmation-btn text-gray-500 hover:text-indigo-500 dark:hover:text-indigo-400" data-id="${affirmation.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete-affirmation-btn text-gray-500 hover:text-red-500 dark:hover:text-red-400" data-id="${affirmation.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <p class="text-gray-700 dark:text-gray-300 mb-4">${affirmation.text}</p> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center space-x-2"> | |
| <button class="play-affirmation-btn bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded-lg text-sm" data-id="${affirmation.id}"> | |
| <i class="fas fa-play mr-1"></i>Play | |
| </button> | |
| <button class="loop-affirmation-btn bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 px-3 py-1 rounded-lg text-sm" data-id="${affirmation.id}"> | |
| <i class="fas fa-redo mr-1"></i>Loop | |
| </button> | |
| </div> | |
| <span class="text-xs text-gray-500 dark:text-gray-400">ID: ${affirmation.id}</span> | |
| </div> | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| // Add event listeners to buttons | |
| document.querySelectorAll('.play-affirmation-btn').forEach(btn => { | |
| btn.addEventListener('click', () => playAffirmation(btn.getAttribute('data-id'))); | |
| }); | |
| document.querySelectorAll('.loop-affirmation-btn').forEach(btn => { | |
| btn.addEventListener('click', () => loopAffirmation(btn.getAttribute('data-id'))); | |
| }); | |
| document.querySelectorAll('.edit-affirmation-btn').forEach(btn => { | |
| btn.addEventListener('click', () => editAffirmation(btn.getAttribute('data-id'))); | |
| }); | |
| document.querySelectorAll('.delete-affirmation-btn').forEach(btn => { | |
| btn.addEventListener('click', () => deleteAffirmation(btn.getAttribute('data-id'))); | |
| }); | |
| } | |
| // Play affirmation | |
| function playAffirmation(id) { | |
| const affirmation = affirmations.find(a => a.id == id); | |
| showToast(`Playing affirmation: "${affirmation.text.substring(0, 20)}..."`); | |
| // In a real app, this would play the audio | |
| } | |
| // Loop affirmation | |
| function loopAffirmation(id) { | |
| const affirmation = affirmations.find(a => a.id == id); | |
| showToast(`Looping affirmation: "${affirmation.text.substring(0, 20)}..."`); | |
| // In a real app, this would loop the audio | |
| } | |
| // Edit affirmation | |
| function editAffirmation(id) { | |
| const affirmation = affirmations.find(a => a.id == id); | |
| document.getElementById('affirmation-text').value = affirmation.text; | |
| document.getElementById('affirmation-category').value = affirmation.category; | |
| // Show the modal | |
| document.getElementById('add-affirmation-modal').classList.remove('hidden'); | |
| // Change the form to update mode | |
| const form = document.getElementById('affirmation-form'); | |
| form.setAttribute('data-mode', 'edit'); | |
| form.setAttribute('data-id', id); | |
| } | |
| // Delete affirmation | |
| function deleteAffirmation(id) { | |
| if (confirm('Are you sure you want to delete this affirmation?')) { | |
| affirmations = affirmations.filter(a => a.id != id); | |
| renderAffirmations(); | |
| showToast('Affirmation deleted successfully'); | |
| } | |
| } | |
| // Category filter | |
| document.querySelectorAll('.category-filter').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const category = btn.getAttribute('data-category'); | |
| renderAffirmations(category); | |
| // Update active filter button | |
| document.querySelectorAll('.category-filter').forEach(b => { | |
| b.classList.remove('font-semibold', 'scale-105'); | |
| }); | |
| btn.classList.add('font-semibold', 'scale-105'); | |
| }); | |
| }); | |
| // Add affirmation modal | |
| document.getElementById('add-affirmation-btn').addEventListener('click', () => { | |
| // Reset form | |
| document.getElementById('affirmation-form').reset(); | |
| document.getElementById('affirmation-form').setAttribute('data-mode', 'add'); | |
| document.getElementById('add-affirmation-modal').classList.remove('hidden'); | |
| }); | |
| // Close affirmation modal | |
| document.getElementById('close-affirmation-modal').addEventListener('click', () => { | |
| document.getElementById('add-affirmation-modal').classList.add('hidden'); | |
| }); | |
| document.getElementById('cancel-affirmation').addEventListener('click', () => { | |
| document.getElementById('add-affirmation-modal').classList.add('hidden'); | |
| }); | |
| // Record button | |
| document.getElementById('record-btn').addEventListener('click', () => { | |
| const recordingIndicator = document.getElementById('recording-indicator'); | |
| if (recordingIndicator.classList.contains('hidden')) { | |
| // Start recording | |
| recordingIndicator.classList.remove('hidden'); | |
| document.getElementById('record-btn').innerHTML = '<i class="fas fa-stop mr-2"></i>Stop'; | |
| showToast('Recording started'); | |
| } else { | |
| // Stop recording | |
| recordingIndicator.classList.add('hidden'); | |
| document.getElementById('record-btn').innerHTML = '<i class="fas fa-microphone mr-2"></i>Record'; | |
| showToast('Recording saved'); | |
| } | |
| }); | |
| // Affirmation form submission | |
| document.getElementById('affirmation-form').addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const form = e.target; | |
| const mode = form.getAttribute('data-mode'); | |
| const text = document.getElementById('affirmation-text').value; | |
| const category = document.getElementById('affirmation-category').value; | |
| if (mode === 'add') { | |
| // Add new affirmation | |
| const newId = affirmations.length > 0 ? Math.max(...affirmations.map(a => a.id)) + 1 : 1; | |
| affirmations.push({ | |
| id: newId, | |
| text, | |
| category, | |
| audio: null | |
| }); | |
| showToast('Affirmation added successfully'); | |
| } else { | |
| // Update existing affirmation | |
| const id = form.getAttribute('data-id'); | |
| const index = affirmations.findIndex(a => a.id == id); | |
| if (index !== -1) { | |
| affirmations[index] = { | |
| ...affirmations[index], | |
| text, | |
| category | |
| }; | |
| showToast('Affirmation updated successfully'); | |
| } | |
| } | |
| renderAffirmations(); | |
| document.getElementById('add-affirmation-modal').classList.add('hidden'); | |
| }); | |
| // Render mixes | |
| function renderMixes() { | |
| const grid = document.getElementById('mixes-grid'); | |
| grid.innerHTML = ''; | |
| if (mixes.length === 0) { | |
| grid.innerHTML = ` | |
| <div class="col-span-full text-center py-8 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-music text-3xl mb-2"></i> | |
| <p>No mixes created yet</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| mixes.forEach(mix => { | |
| const card = document.createElement('div'); | |
| card.className = 'bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden'; | |
| // Get affirmation texts for display | |
| const mixAffirmations = mix.affirmations.map(id => { | |
| const aff = affirmations.find(a => a.id === id); | |
| return aff ? aff.text.substring(0, 20) + (aff.text.length > 20 ? '...' : '') : 'Deleted'; | |
| }); | |
| card.innerHTML = ` | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="font-medium">${mix.name}</h3> | |
| <div class="flex space-x-2"> | |
| <button class="edit-mix-btn text-gray-500 hover:text-indigo-500 dark:hover:text-indigo-400" data-id="${mix.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete-mix-btn text-gray-500 hover:text-red-500 dark:hover:text-red-400" data-id="${mix.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <div class="text-sm text-gray-600 dark:text-gray-300 mb-1">Affirmations:</div> | |
| <ul class="text-xs text-gray-500 dark:text-gray-400 space-y-1"> | |
| ${mixAffirmations.map(a => `<li class="flex items-center"><i class="fas fa-check-circle mr-2 text-green-500"></i>${a}</li>`).join('')} | |
| </ul> | |
| </div> | |
| <div class="flex flex-wrap gap-2 text-xs mb-3"> | |
| <span class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">Interval: ${mix.interval}s</span> | |
| <span class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">Background: ${mix.background}</span> | |
| </div> | |
| <button class="play-mix-btn w-full bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-2 rounded-lg text-sm" data-id="${mix.id}"> | |
| <i class="fas fa-play mr-1"></i>Play Mix | |
| </button> | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| }); | |
| // Add event listeners to buttons | |
| document.querySelectorAll('.play-mix-btn').forEach(btn => { | |
| btn.addEventListener('click', () => playMix(btn.getAttribute('data-id'))); | |
| }); | |
| document.querySelectorAll('.edit-mix-btn').forEach(btn => { | |
| btn.addEventListener('click', () => editMix(btn.getAttribute('data-id'))); | |
| }); | |
| document.querySelectorAll('.delete-mix-btn').forEach(btn => { | |
| btn.addEventListener('click', () => deleteMix(btn.getAttribute('data-id'))); | |
| }); | |
| } | |
| // Play mix | |
| function playMix(id) { | |
| const mix = mixes.find(m => m.id == id); | |
| showToast(`Playing mix: "${mix.name}"`); | |
| // In a real app, this would play the mix | |
| } | |
| // Edit mix | |
| function editMix(id) { | |
| const mix = mixes.find(m => m.id == id); | |
| document.getElementById('mix-name').value = mix.name; | |
| document.getElementById('mix-interval').value = mix.interval; | |
| document.getElementById('mix-background').value = mix.background; | |
| // Populate affirmations list | |
| const affirmationsList = document.getElementById('mix-affirmations-list'); | |
| affirmationsList.innerHTML = ''; | |
| affirmations.forEach(affirmation => { | |
| const div = document.createElement('div'); | |
| div.className = 'flex items-center'; | |
| const checkbox = document.createElement('input'); | |
| checkbox.type = 'checkbox'; | |
| checkbox.id = `affirmation-${affirmation.id}`; | |
| checkbox.value = affirmation.id; | |
| checkbox.className = 'mr-2'; | |
| if (mix.affirmations.includes(affirmation.id)) { | |
| checkbox.checked = true; | |
| } | |
| const label = document.createElement('label'); | |
| label.htmlFor = `affirmation-${affirmation.id}`; | |
| label.className = 'text-sm'; | |
| label.textContent = affirmation.text.substring(0, 30) + (affirmation.text.length > 30 ? '...' : ''); | |
| div.appendChild(checkbox); | |
| div.appendChild(label); | |
| affirmationsList.appendChild(div); | |
| }); | |
| // Show the modal | |
| document.getElementById('add-mix-modal').classList.remove('hidden'); | |
| // Change the form to update mode | |
| const form = document.getElementById('mix-form'); | |
| form.setAttribute('data-mode', 'edit'); | |
| form.setAttribute('data-id', id); | |
| } | |
| // Delete mix | |
| function deleteMix(id) { | |
| if (confirm('Are you sure you want to delete this mix?')) { | |
| mixes = mixes.filter(m => m.id != id); | |
| renderMixes(); | |
| showToast('Mix deleted successfully'); | |
| } | |
| } | |
| // Add mix modal | |
| document.getElementById('add-mix-btn').addEventListener('click', () => { | |
| // Reset form | |
| document.getElementById('mix-form').reset(); | |
| document.getElementById('mix-form').setAttribute('data-mode', 'add'); | |
| // Populate affirmations list | |
| const affirmationsList = document.getElementById('mix-affirmations-list'); | |
| affirmationsList.innerHTML = ''; | |
| affirmations.forEach(affirmation => { | |
| const div = document.createElement('div'); | |
| div.className = 'flex items-center'; | |
| const checkbox = document.createElement('input'); | |
| checkbox.type = 'checkbox'; | |
| checkbox.id = `affirmation-${affirmation.id}`; | |
| checkbox.value = affirmation.id; | |
| checkbox.className = 'mr-2'; | |
| const label = document.createElement('label'); | |
| label.htmlFor = `affirmation-${affirmation.id}`; | |
| label.className = 'text-sm'; | |
| label.textContent = affirmation.text.substring(0, 30) + (affirmation.text.length > 30 ? '...' : ''); | |
| div.appendChild(checkbox); | |
| div.appendChild(label); | |
| affirmationsList.appendChild(div); | |
| }); | |
| document.getElementById('add-mix-modal').classList.remove('hidden'); | |
| }); | |
| // Close mix modal | |
| document.getElementById('close-mix-modal').addEventListener('click', () => { | |
| document.getElementById('add-mix-modal').classList.add('hidden'); | |
| }); | |
| document.getElementById('cancel-mix').addEventListener('click', () => { | |
| document.getElementById('add-mix-modal').classList.add('hidden'); | |
| }); | |
| // Mix form submission | |
| document.getElementById('mix-form').addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const form = e.target; | |
| const mode = form.getAttribute('data-mode'); | |
| const name = document.getElementById('mix-name').value; | |
| const interval = parseInt(document.getElementById('mix-interval').value); | |
| const background = document.getElementById('mix-background').value; | |
| // Get selected affirmations | |
| const selectedAffirmations = []; | |
| document.querySelectorAll('#mix-affirmations-list input[type="checkbox"]:checked').forEach(checkbox => { | |
| selectedAffirmations.push(parseInt(checkbox.value)); | |
| }); | |
| if (mode === 'add') { | |
| // Add new mix | |
| const newId = mixes.length > 0 ? Math.max(...mixes.map(m => m.id)) + 1 : 1; | |
| mixes.push({ | |
| id: newId, | |
| name, | |
| affirmations: selectedAffirmations, | |
| interval, | |
| background | |
| }); | |
| showToast('Mix created successfully'); | |
| } else { | |
| // Update existing mix | |
| const id = form.getAttribute('data-id'); | |
| const index = mixes.findIndex(m => m.id == id); | |
| if (index !== -1) { | |
| mixes[index] = { | |
| id, | |
| name, | |
| affirmations: selectedAffirmations, | |
| interval, | |
| background | |
| }; | |
| showToast('Mix updated successfully'); | |
| } | |
| } | |
| renderMixes(); | |
| document.getElementById('add-mix-modal').classList.add('hidden'); | |
| }); | |
| // Binaural Beats Controls | |
| let audioContext; | |
| let oscillatorLeft; | |
| let oscillatorRight; | |
| let gainNode; | |
| let pannerNode; | |
| let isPlaying = false; | |
| // Initialize audio context on user interaction | |
| function initAudioContext() { | |
| if (!audioContext) { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| gainNode = audioContext.createGain(); | |
| gainNode.gain.value = 0.5; | |
| gainNode.connect(audioContext.destination); | |
| pannerNode = audioContext.createStereoPanner(); | |
| pannerNode.pan.value = 0; | |
| pannerNode.connect(gainNode); | |
| showToast('Audio context initialized'); | |
| } | |
| } | |
| // Play binaural beat | |
| function playBinauralBeat() { | |
| if (isPlaying) return; | |
| initAudioContext(); | |
| const baseFrequency = parseFloat(document.getElementById('base-frequency').value); | |
| const beatFrequency = parseFloat(document.getElementById('beat-frequency').value); | |
| const waveform = document.getElementById('waveform').value; | |
| oscillatorLeft = audioContext.createOscillator(); | |
| oscillatorRight = audioContext.createOscillator(); | |
| oscillatorLeft.type = waveform; | |
| oscillatorRight.type = waveform; | |
| oscillatorLeft.frequency.value = baseFrequency; | |
| oscillatorRight.frequency.value = baseFrequency + beatFrequency; | |
| oscillatorLeft.connect(pannerNode); | |
| oscillatorRight.connect(pannerNode); | |
| oscillatorLeft.start(); | |
| oscillatorRight.start(); | |
| isPlaying = true; | |
| document.getElementById('play-beat-btn').innerHTML = '<i class="fas fa-pause"></i>'; | |
| // Update UI | |
| updateFrequencyDisplay(baseFrequency, beatFrequency); | |
| updateBrainwaveState(beatFrequency); | |
| showToast(`Playing binaural beat at ${beatFrequency}Hz`); | |
| } | |
| // Stop binaural beat | |
| function stopBinauralBeat() { | |
| if (!isPlaying) return; | |
| if (oscillatorLeft) { | |
| oscillatorLeft.stop(); | |
| oscillatorLeft = null; | |
| } | |
| if (oscillatorRight) { | |
| oscillatorRight.stop(); | |
| oscillatorRight = null; | |
| } | |
| isPlaying = false; | |
| document.getElementById('play-beat-btn').innerHTML = '<i class="fas fa-play"></i>'; | |
| // Reset UI | |
| document.getElementById('current-frequency').textContent = '0 Hz'; | |
| document.getElementById('brainwave-state').textContent = 'None'; | |
| document.getElementById('brainwave-description').textContent = 'No frequency selected'; | |
| showToast('Binaural beat stopped'); | |
| } | |
| // Toggle play/stop | |
| document.getElementById('play-beat-btn').addEventListener('click', () => { | |
| if (isPlaying) { | |
| stopBinauralBeat(); | |
| } else { | |
| playBinauralBeat(); | |
| } | |
| }); | |
| document.getElementById('stop-beat-btn').addEventListener('click', stopBinauralBeat); | |
| // Update frequency display | |
| function updateFrequencyDisplay(baseFreq, beatFreq) { | |
| document.getElementById('current-frequency').textContent = `${beatFreq} Hz`; | |
| document.getElementById('base-frequency-value').textContent = `${baseFreq} Hz`; | |
| document.getElementById('beat-frequency-value').textContent = `${beatFreq} Hz`; | |
| } | |
| // Update brainwave state | |
| function updateBrainwaveState(frequency) { | |
| let state, description, colorClass; | |
| if (frequency >= 0.5 && frequency < 4) { | |
| state = 'Delta'; | |
| description = 'Deep sleep, healing, transcendence'; | |
| colorClass = 'bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200'; | |
| } else if (frequency >= 4 && frequency < 8) { | |
| state = 'Theta'; | |
| description = 'Visualization, trance, dreaming states'; | |
| colorClass = 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200'; | |
| } else if (frequency >= 8 && frequency < 12) { | |
| state = 'Alpha'; | |
| description = 'Meditation, creativity, relaxation'; | |
| colorClass = 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200'; | |
| } else if (frequency >= 12 && frequency < 30) { | |
| state = 'Beta'; | |
| description = 'Alertness, concentration, active thinking'; | |
| colorClass = 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200'; | |
| } else if (frequency >= 30) { | |
| state = 'Gamma'; | |
| description = 'Peak experiences, insight, synchronization'; | |
| colorClass = 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200'; | |
| } else { | |
| state = 'None'; | |
| description = 'No frequency selected'; | |
| colorClass = 'bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200'; | |
| } | |
| const stateElement = document.getElementById('brainwave-state'); | |
| stateElement.textContent = state; | |
| stateElement.className = `px-2 py-1 rounded-full text-xs font-medium ${colorClass}`; | |
| document.getElementById('brainwave-description').textContent = description; | |
| } | |
| // Slider controls | |
| document.getElementById('base-frequency').addEventListener('input', (e) => { | |
| const value = e.target.value; | |
| document.getElementById('base-frequency-value').textContent = `${value} Hz`; | |
| if (isPlaying) { | |
| oscillatorLeft.frequency.value = value; | |
| oscillatorRight.frequency.value = value + parseFloat(document.getElementById('beat-frequency').value); | |
| updateFrequencyDisplay(value, parseFloat(document.getElementById('beat-frequency').value)); | |
| } | |
| }); | |
| document.getElementById('beat-frequency').addEventListener('input', (e) => { | |
| const value = e.target.value; | |
| document.getElementById('beat-frequency-value').textContent = `${value} Hz`; | |
| if (isPlaying) { | |
| oscillatorRight.frequency.value = parseFloat(document.getElementById('base-frequency').value) + value; | |
| updateFrequencyDisplay(parseFloat(document.getElementById('base-frequency').value), value); | |
| updateBrainwaveState(value); | |
| } else { | |
| updateBrainwaveState(value); | |
| } | |
| }); | |
| document.getElementById('volume').addEventListener('input', (e) => { | |
| const value = e.target.value; | |
| document.getElementById('volume-value').textContent = `${Math.round(value * 100)}%`; | |
| if (gainNode) { | |
| gainNode.gain.value = value; | |
| } | |
| }); | |
| document.getElementById('balance').addEventListener('input', (e) => { | |
| if (pannerNode) { | |
| pannerNode.pan.value = e.target.value; | |
| } | |
| }); | |
| // Preset buttons | |
| document.querySelectorAll('.preset-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const frequency = btn.getAttribute('data-frequency'); | |
| const state = btn.getAttribute('data-state'); | |
| document.getElementById('beat-frequency').value = frequency; | |
| document.getElementById('beat-frequency-value').textContent = `${frequency} Hz`; | |
| if (isPlaying) { | |
| oscillatorRight.frequency.value = parseFloat(document.getElementById('base-frequency').value) + parseFloat(frequency); | |
| } | |
| updateBrainwaveState(frequency); | |
| showToast(`Preset loaded: ${state.charAt(0).toUpperCase() + state.slice(1)} waves`); | |
| }); | |
| }); | |
| // Save beat | |
| document.getElementById('save-beat-btn').addEventListener('click', () => { | |
| const name = prompt('Enter a name for this binaural beat:'); | |
| if (!name) return; | |
| const newId = savedBeats.length > 0 ? Math.max(...savedBeats.map(b => b.id)) + 1 : 1; | |
| savedBeats.push({ | |
| id: newId, | |
| name, | |
| baseFreq: parseFloat(document.getElementById('base-frequency').value), | |
| beatFreq: parseFloat(document.getElementById('beat-frequency').value), | |
| waveform: document.getElementById('waveform').value, | |
| duration: parseInt(document.getElementById('duration').value) | |
| }); | |
| renderSavedBeats(); | |
| showToast('Binaural beat saved'); | |
| }); | |
| // Render saved beats | |
| function renderSavedBeats() { | |
| const list = document.getElementById('saved-beats-list'); | |
| if (savedBeats.length === 0) { | |
| list.innerHTML = ` | |
| <div class="text-center py-4 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-music text-2xl mb-2"></i> | |
| <p>No saved beats yet</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| list.innerHTML = ''; | |
| savedBeats.forEach(beat => { | |
| const div = document.createElement('div'); | |
| div.className = 'bg-gray-100 dark:bg-gray-700 rounded-lg p-3 flex justify-between items-center'; | |
| div.innerHTML = ` | |
| <div> | |
| <div class="font-medium">${beat.name}</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400"> | |
| ${beat.baseFreq}Hz + ${beat.beatFreq}Hz • ${beat.waveform} • ${beat.duration}min | |
| </div> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button class="load-beat-btn text-indigo-500 dark:text-indigo-400" data-id="${beat.id}"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| <button class="delete-beat-btn text-gray-500 hover:text-red-500 dark:hover:text-red-400" data-id="${beat.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| `; | |
| list.appendChild(div); | |
| }); | |
| // Add event listeners | |
| document.querySelectorAll('.load-beat-btn').forEach(btn => { | |
| btn.addEventListener('click', () => loadBeat(btn.getAttribute('data-id'))); | |
| }); | |
| document.querySelectorAll('.delete-beat-btn').forEach(btn => { | |
| btn.addEventListener('click', () => deleteBeat(btn.getAttribute('data-id'))); | |
| }); | |
| } | |
| // Load beat | |
| function loadBeat(id) { | |
| const beat = savedBeats.find(b => b.id == id); | |
| document.getElementById('base-frequency').value = beat.baseFreq; | |
| document.getElementById('base-frequency-value').textContent = `${beat.baseFreq} Hz`; | |
| document.getElementById('beat-frequency').value = beat.beatFreq; | |
| document.getElementById('beat-frequency-value').textContent = `${beat.beatFreq} Hz`; | |
| document.getElementById('waveform').value = beat.waveform; | |
| document.getElementById('duration').value = beat.duration; | |
| updateBrainwaveState(beat.beatFreq); | |
| showToast(`Loaded beat: ${beat.name}`); | |
| } | |
| // Delete beat | |
| function deleteBeat(id) { | |
| if (confirm('Are you sure you want to delete this saved beat?')) { | |
| savedBeats = savedBeats.filter(b => b.id != id); | |
| renderSavedBeats(); | |
| showToast('Beat deleted'); | |
| } | |
| } | |
| // Initialize the app | |
| document.addEventListener('DOMContentLoaded', () => { | |
| renderAffirmations(); | |
| renderMixes(); | |
| renderSavedBeats(); | |
| // Set default values for binaural beats | |
| updateBrainwaveState(10); // Default to Alpha | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Bakingbad/mindful-voice" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |