Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SweatSculpt Supreme - Your Digital Fitness Coach</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.waves.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap'); | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| } | |
| .glow-text { | |
| text-shadow: 0 0 10px rgba(255, 255, 255, 0.8); | |
| } | |
| .exercise-card { | |
| transition: all 0.3s ease; | |
| } | |
| .exercise-card:hover { | |
| transform: translateY(-2px); | |
| } | |
| .current-exercise { | |
| box-shadow: 0 0 15px rgba(255, 215, 0, 0.7); | |
| transform: scale(1.02); | |
| } | |
| .completed-exercise { | |
| opacity: 0.6; | |
| background-color: #f3f4f6; | |
| } | |
| .neon-timer { | |
| text-shadow: 0 0 10px #00ff9d, 0 0 20px #00ff9d; | |
| } | |
| .scroll-container { | |
| scroll-behavior: smooth; | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #888; | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #555; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen"> | |
| <div id="vanta-bg" class="fixed inset-0 -z-10"></div> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-10"> | |
| <h1 class="text-4xl md:text-5xl font-bold mb-2 glow-text">SweatSculpt Supreme</h1> | |
| <p class="text-xl text-gray-300">Your digital yoga and fitness companion</p> | |
| <p id="routine-indicator" class="text-lg text-cyan-300 mt-2"></p> <div class="flex justify-center mt-4"> | |
| <div class="w-24 h-1 bg-gradient-to-r from-blue-400 to-green-400 rounded-full"></div> | |
| </div> | |
| </header> | |
| <div class="flex flex-col lg:flex-row gap-8"> | |
| <div class="w-full lg:w-1/3 bg-gray-800 bg-opacity-70 rounded-xl p-6 shadow-lg"> | |
| <h2 class="text-2xl font-semibold mb-4 flex items-center"> | |
| <i data-feather="list" class="mr-2"></i> Workout Plan | |
| </h2> | |
| <div class="scroll-container max-h-[500px] overflow-y-auto pr-2"> | |
| <div id="exercise-list" class="space-y-3"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="w-full lg:w-2/3 bg-gray-800 bg-opacity-70 rounded-xl p-6 shadow-lg"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-semibold flex items-center"> | |
| <i data-feather="info" class="mr-2"></i> Instructions | |
| </h2> | |
| <div class="flex items-center space-x-4"> | |
| <button id="music-toggle" class="p-2 rounded-full bg-gray-700 hover:bg-gray-600 transition"> | |
| <i data-feather="music"></i> | |
| </button> | |
| <button id="tts-toggle" class="p-2 rounded-full bg-gray-700 hover:bg-gray-600 transition"> | |
| <i data-feather="volume-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="exercise-instructions" class="bg-gray-700 bg-opacity-50 rounded-lg p-4 mb-6 h-64 overflow-y-auto"> | |
| <p class="text-gray-300 italic">Select an exercise to begin or click Start Workout</p> | |
| </div> | |
| <div class="bg-gray-700 bg-opacity-50 rounded-lg p-6 mb-6"> | |
| <div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"> | |
| <div class="text-center"> | |
| <div class="text-sm text-gray-400 mb-1">Current Exercise</div> | |
| <div id="current-exercise-name" class="text-xl font-semibold">-</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-sm text-gray-400 mb-1">Time Remaining</div> | |
| <div id="timer" class="text-4xl font-bold neon-timer">00:00</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-sm text-gray-400 mb-1">Progress</div> | |
| <div id="progress-text" class="text-xl font-semibold">0/0</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <div class="flex justify-between text-sm text-gray-400 mb-1"> | |
| <span>Workout Progress</span> | |
| <span id="progress-percent">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-700 rounded-full h-3"> | |
| <div id="progress-bar" class="bg-gradient-to-r from-blue-500 to-green-500 h-3 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col sm:flex-row justify-center space-y-3 sm:space-y-0 sm:space-x-4"> | |
| <button id="start-btn" class="px-8 py-3 bg-gradient-to-r from-blue-500 to-green-500 rounded-full font-bold hover:from-blue-600 hover:to-green-600 transition transform hover:scale-105 shadow-lg"> | |
| <i data-feather="play" class="mr-2"></i> Start Workout | |
| </button> | |
| <button id="pause-btn" class="px-8 py-3 bg-yellow-500 rounded-full font-bold hover:bg-yellow-600 transition transform hover:scale-105 shadow-lg hidden"> | |
| <i data-feather="pause" class="mr-2"></i> Pause | |
| </button> | |
| <button id="skip-btn" class="px-8 py-3 bg-red-500 rounded-full font-bold hover:bg-red-600 transition transform hover:scale-105 shadow-lg hidden"> | |
| <i data-feather="skip-forward" class="mr-2"></i> Skip | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <audio id="background-music" loop> | |
| <source src="background.mp3" type="audio/mpeg"> | |
| </audio> | |
| </div> | |
| <script> | |
| // Initialize | |
| feather.replace(); | |
| // Vanta.js background | |
| VANTA.WAVES({ | |
| el: "#vanta-bg", | |
| mouseControls: true, | |
| touchControls: true, | |
| gyroControls: false, | |
| minHeight: 200.00, | |
| minWidth: 200.00, | |
| scale: 1.00, | |
| scaleMobile: 1.00, | |
| color: 0x0a0a0a, | |
| shininess: 35.00, | |
| waveHeight: 15.00, | |
| waveSpeed: 0.75, | |
| zoom: 0.65 | |
| }); | |
| // --- MODIFICATION START --- | |
| // Auto-detect morning/evening for Suryanamaskar rounds | |
| const currentHour = new Date().getHours(); | |
| const isEvening = currentHour >= 16; // Evening is defined as 4 PM (16:00) or later | |
| const suryanamaskarRounds = isEvening ? 6 : 12; | |
| // Update UI indicator | |
| const routineIndicator = document.getElementById('routine-indicator'); | |
| if (routineIndicator) { | |
| routineIndicator.textContent = `✨ ${isEvening ? 'Evening Routine' : 'Morning Routine'} Activated (${suryanamaskarRounds} Suryanamaskars)`; | |
| } | |
| // --- MODIFICATION END --- | |
| // Exercise Data | |
| const exercises = [ | |
| // Warm-up | |
| { name: "Light jogging/brisk walk", type: "warmup", duration: 300, tips: "Keep posture upright, Breathe deeply" }, | |
| { name: "Neck rotations", type: "warmup", duration: 60, tips: "Move slowly, Don't strain neck" }, | |
| { name: "Shoulder rotations", type: "warmup", duration: 60, tips: "Keep shoulders relaxed" }, | |
| { name: "Wrist rotations", type: "warmup", duration: 30, tips: "Move gently" }, | |
| { name: "Ankle rotations", type: "warmup", duration: 30, tips: "Support yourself if needed" }, | |
| { name: "Gentle stretching", type: "warmup", duration: 120, tips: "Do not bounce, Feel the stretch" }, | |
| { name: "Short break", type: "warmup", duration: 30, tips: "Relax shoulders, Sip water" }, | |
| // Suryanamaskar (Dynamic rounds based on time of day) | |
| ...Array.from({ length: suryanamaskarRounds }, (_, round) => [ | |
| { name: `Round ${round+1}: Pranamasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Hasta Uttanasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Padahastasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Ashwa Sanchalanasana (Right Leg)`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Dandasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Ashtanga Namaskara`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Bhujangasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Adho Mukha Svanasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Ashwa Sanchalanasana (Left Leg)`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Padahastasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Hasta Uttanasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" }, | |
| { name: `Round ${round+1}: Pranamasana`, type: "suryanamaskar", duration: 5, tips: "Focus on breathing, Keep core engaged" } | |
| ]).flat(), | |
| { name: "Short break", type: "warmup", duration: 30, tips: "Relax shoulders, Sip water" }, | |
| { name: "Kapalabhati", type: "postworkout", duration: 60, tips: "Focus on breath, Relax shoulders" }, | |
| { name: "Anulom Vilom", type: "postworkout", duration: 180, tips: "Inhale left, Exhale right calmly" }, | |
| { name: "Bhramari Pranayama", type: "postworkout", duration: 90, tips: "Use gentle humming sound" }, | |
| { name: "Meditation / Deep Breathing", type: "postworkout", duration: 300, tips: "Sit comfortably, Focus on stillness" }, | |
| { name: "Shavasana (Final Relaxation)", type: "postworkout", duration: 300, tips: "Completely relax body and mind" } | |
| ]; | |
| // App State | |
| let currentExerciseIndex = 0; | |
| let timerInterval; | |
| let secondsRemaining = 0; | |
| let isWorkoutRunning = false; | |
| let isPaused = false; | |
| let isTTSEnabled = true; | |
| let isMusicEnabled = false; | |
| const audioElement = document.getElementById('background-music'); | |
| const synth = window.speechSynthesis; | |
| // DOM Elements | |
| const exerciseListElement = document.getElementById('exercise-list'); | |
| const instructionsElement = document.getElementById('exercise-instructions'); | |
| const timerElement = document.getElementById('timer'); | |
| const currentExerciseNameElement = document.getElementById('current-exercise-name'); | |
| const progressTextElement = document.getElementById('progress-text'); | |
| const progressPercentElement = document.getElementById('progress-percent'); | |
| const progressBarElement = document.getElementById('progress-bar'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const pauseBtn = document.getElementById('pause-btn'); | |
| const skipBtn = document.getElementById('skip-btn'); | |
| const musicToggleBtn = document.getElementById('music-toggle'); | |
| const ttsToggleBtn = document.getElementById('tts-toggle'); | |
| // Initialize Exercise List | |
| function renderExerciseList() { | |
| exerciseListElement.innerHTML = ''; | |
| exercises.forEach((exercise, index) => { | |
| const card = document.createElement('div'); | |
| card.className = `exercise-card p-4 rounded-lg cursor-pointer transition-all ${ | |
| index < currentExerciseIndex ? 'completed-exercise' : | |
| index === currentExerciseIndex ? 'current-exercise' : 'bg-gray-700 hover:bg-gray-600' | |
| } ${ | |
| exercise.type === 'warmup' ? 'border-l-4 border-blue-500' : | |
| exercise.type === 'suryanamaskar' ? 'border-l-4 border-orange-500' : | |
| 'border-l-4 border-green-500' | |
| }`; | |
| card.innerHTML = ` | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <h3 class="font-semibold">${exercise.name}</h3> | |
| <p class="text-sm text-gray-400">${formatTime(exercise.duration)}</p> | |
| </div> | |
| ${index < currentExerciseIndex ? '<i data-feather="check" class="text-green-400"></i>' : ''} | |
| </div> | |
| `; | |
| card.addEventListener('click', () => skipToExercise(index)); | |
| exerciseListElement.appendChild(card); | |
| }); | |
| feather.replace(); | |
| } | |
| // Format time (seconds to MM:SS) | |
| function formatTime(seconds) { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = seconds % 60; | |
| return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; | |
| } | |
| // Start Workout | |
| function startWorkout() { | |
| if (isWorkoutRunning) return; | |
| isWorkoutRunning = true; | |
| startBtn.classList.add('hidden'); | |
| pauseBtn.classList.remove('hidden'); | |
| skipBtn.classList.remove('hidden'); | |
| if (isMusicEnabled) { | |
| audioElement.play().catch(e => console.log("Audio play failed:", e)); | |
| } | |
| startExercise(currentExerciseIndex); | |
| } | |
| // Start Exercise | |
| function startExercise(index) { | |
| if (index >= exercises.length) { | |
| endWorkout(); | |
| return; | |
| } | |
| currentExerciseIndex = index; | |
| const exercise = exercises[index]; | |
| // Update UI | |
| currentExerciseNameElement.textContent = exercise.name; | |
| instructionsElement.innerHTML = ` | |
| <h3 class="text-xl font-semibold mb-2">${exercise.name}</h3> | |
| <p class="text-gray-300 mb-4">${formatTime(exercise.duration)}</p> | |
| <div class="bg-gray-600 rounded-lg p-3"> | |
| <h4 class="font-semibold mb-1">Tips:</h4> | |
| <p>${exercise.tips}</p> | |
| </div> | |
| `; | |
| // Speak instructions if TTS is enabled | |
| if (isTTSEnabled) { | |
| speakInstructions(exercise); | |
| } | |
| // Start timer | |
| secondsRemaining = exercise.duration; | |
| updateTimerDisplay(); | |
| if (timerInterval) clearInterval(timerInterval); | |
| timerInterval = setInterval(updateTimer, 1000); | |
| // Update progress | |
| updateProgress(); | |
| // Scroll to current exercise | |
| scrollToCurrentExercise(); | |
| // Re-render exercise list to update highlights | |
| renderExerciseList(); | |
| } | |
| // Update Timer | |
| function updateTimer() { | |
| if (isPaused) return; | |
| secondsRemaining--; | |
| updateTimerDisplay(); | |
| if (secondsRemaining <= 0) { | |
| clearInterval(timerInterval); | |
| setTimeout(() => startExercise(currentExerciseIndex + 1), 1000); | |
| } | |
| } | |
| // Update Timer Display | |
| function updateTimerDisplay() { | |
| timerElement.textContent = formatTime(secondsRemaining); | |
| } | |
| // Skip to Exercise | |
| function skipToExercise(index) { | |
| if (!isWorkoutRunning) { | |
| currentExerciseIndex = index; | |
| const exercise = exercises[index]; | |
| currentExerciseNameElement.textContent = exercise.name; | |
| instructionsElement.innerHTML = ` | |
| <h3 class="text-xl font-semibold mb-2">${exercise.name}</h3> | |
| <p class="text-gray-300 mb-4">${formatTime(exercise.duration)}</p> | |
| <div class="bg-gray-600 rounded-lg p-3"> | |
| <h4 class="font-semibold mb-1">Tips:</h4> | |
| <p>${exercise.tips}</p> | |
| </div> | |
| `; | |
| updateProgress(); | |
| renderExerciseList(); | |
| scrollToCurrentExercise(); | |
| return; | |
| } | |
| if (index <= currentExerciseIndex) return; | |
| clearInterval(timerInterval); | |
| startExercise(index); | |
| } | |
| // Pause/Resume Workout | |
| function togglePause() { | |
| isPaused = !isPaused; | |
| if (isPaused) { | |
| pauseBtn.innerHTML = '<i data-feather="play" class="mr-2"></i> Resume'; | |
| if (isMusicEnabled) { | |
| audioElement.pause(); | |
| } | |
| } else { | |
| pauseBtn.innerHTML = '<i data-feather="pause" class="mr-2"></i> Pause'; | |
| if (isMusicEnabled) { | |
| audioElement.play().catch(e => console.log("Audio play failed:", e)); | |
| } | |
| } | |
| feather.replace(); | |
| } | |
| // Skip Current Exercise | |
| function skipCurrentExercise() { | |
| clearInterval(timerInterval); | |
| startExercise(currentExerciseIndex + 1); | |
| } | |
| // End Workout | |
| function endWorkout() { | |
| clearInterval(timerInterval); | |
| isWorkoutRunning = false; | |
| startBtn.classList.remove('hidden'); | |
| pauseBtn.classList.add('hidden'); | |
| skipBtn.classList.add('hidden'); | |
| if (isMusicEnabled) { | |
| audioElement.pause(); | |
| audioElement.currentTime = 0; | |
| } | |
| // Show completion message | |
| instructionsElement.innerHTML = ` | |
| <div class="text-center py-8"> | |
| <i data-feather="award" class="w-16 h-16 text-yellow-400 mx-auto mb-4"></i> | |
| <h3 class="text-2xl font-bold mb-2">Workout Complete!</h3> | |
| <p class="text-gray-300">Great job! You've finished your workout.</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| // Speak completion message if TTS is enabled | |
| if (isTTSEnabled) { | |
| const utterance = new SpeechSynthesisUtterance("Workout complete! Great job!"); | |
| synth.speak(utterance); | |
| } | |
| } | |
| // Update Progress | |
| function updateProgress() { | |
| const percent = Math.round((currentExerciseIndex / exercises.length) * 100); | |
| progressTextElement.textContent = `${currentExerciseIndex}/${exercises.length}`; | |
| progressPercentElement.textContent = `${percent}%`; | |
| progressBarElement.style.width = `${percent}%`; | |
| } | |
| // Scroll to Current Exercise | |
| function scrollToCurrentExercise() { | |
| const cards = document.querySelectorAll('.exercise-card'); | |
| if (cards[currentExerciseIndex]) { | |
| cards[currentExerciseIndex].scrollIntoView({ | |
| behavior: 'smooth', | |
| block: 'center' | |
| }); | |
| } | |
| } | |
| // TTS Speak Instructions | |
| function speakInstructions(exercise) { | |
| if (synth.speaking) synth.cancel(); | |
| const exerciseMsg = new SpeechSynthesisUtterance( | |
| `Now performing ${exercise.name}. ${exercise.tips}` | |
| ); | |
| synth.speak(exerciseMsg); | |
| } | |
| // Toggle Music | |
| function toggleMusic() { | |
| isMusicEnabled = !isMusicEnabled; | |
| if (isMusicEnabled) { | |
| musicToggleBtn.innerHTML = '<i data-feather="music"></i>'; | |
| if (isWorkoutRunning && !isPaused) { | |
| audioElement.play().catch(e => console.log("Audio play failed:", e)); | |
| } | |
| } else { | |
| musicToggleBtn.innerHTML = '<i data-feather="music" class="text-gray-400"></i>'; | |
| audioElement.pause(); | |
| } | |
| feather.replace(); | |
| } | |
| // Toggle TTS | |
| function toggleTTS() { | |
| isTTSEnabled = !isTTSEnabled; | |
| if (isTTSEnabled) { | |
| ttsToggleBtn.innerHTML = '<i data-feather="volume-2"></i>'; | |
| } else { | |
| ttsToggleBtn.innerHTML = '<i data-feather="volume-2" class="text-gray-400"></i>'; | |
| if (synth.speaking) synth.cancel(); | |
| } | |
| feather.replace(); | |
| } | |
| // Event Listeners | |
| startBtn.addEventListener('click', startWorkout); | |
| pauseBtn.addEventListener('click', togglePause); | |
| skipBtn.addEventListener('click', skipCurrentExercise); | |
| musicToggleBtn.addEventListener('click', toggleMusic); | |
| ttsToggleBtn.addEventListener('click', toggleTTS); | |
| // Initialize | |
| renderExerciseList(); | |
| updateProgress(); | |
| </script> | |
| </body> | |
| </html> |