help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • Blog
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
fitness-coach-bot
/
static
/
js
RSK World
fitness-coach-bot
Fitness Coach Bot - Python + Flask + SQLAlchemy + Workout Plans + Exercise Guidance + Health Tracking + AI Fitness Coach
js
  • analytics_dashboard.js11.7 KB
  • app.js11.7 KB
  • pose_detection.js21.3 KB
  • voice_recognition.js8 KB
analytics_dashboard.jsapp.jsindex.htmlpose_detection.js
static/js/analytics_dashboard.js
Raw Download
Find: Go to:
/**
 * Advanced Analytics Dashboard with Visualizations
 * Author: RSK World (https://rskworld.in)
 * Founded by: Molla Samser
 * Designer & Tester: Rima Khatun
 * Contact: help@rskworld.in, +91 93305 39277
 * Year: 2026
 */

class AnalyticsDashboard {
    constructor() {
        this.charts = {};
        this.data = {};
        this.init();
    }

    async init() {
        await this.loadChartsLibrary();
        this.loadAnalyticsData();
        this.setupEventListeners();
    }

    async loadChartsLibrary() {
        // Load Chart.js
        if (typeof Chart === 'undefined') {
            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js';
            document.head.appendChild(script);
            await new Promise(resolve => script.onload = resolve);
        }
    }

    async loadAnalyticsData() {
        try {
            const response = await fetch('/api/analytics?timeframe=30');
            const data = await response.json();
            
            if (data.success) {
                this.data = data.analytics;
                this.renderAllCharts();
            }
        } catch (error) {
            console.error('Error loading analytics:', error);
        }
    }

    renderAllCharts() {
        this.renderWorkoutFrequencyChart();
        this.renderProgressChart();
        this.renderStrengthChart();
        this.renderNutritionChart();
        this.renderRecoveryChart();
        this.renderRadarChart();
    }

    renderWorkoutFrequencyChart() {
        const ctx = document.getElementById('workoutFrequencyChart');
        if (!ctx) return;

        const categories = this.data.categories || {};
        const workout = categories.workout || {};

        this.charts.workoutFrequency = new Chart(ctx, {
            type: 'line',
            data: {
                labels: this.generateDateLabels(30),
                datasets: [{
                    label: 'Workouts per Week',
                    data: this.generateWorkoutData(),
                    borderColor: 'rgb(75, 192, 192)',
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    tension: 0.4,
                    fill: true
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'Workout Frequency Trend'
                    },
                    legend: {
                        display: true
                    }
                },
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            stepSize: 1
                        }
                    }
                }
            }
        });
    }

    renderProgressChart() {
        const ctx = document.getElementById('progressChart');
        if (!ctx) return;

        const body = this.data.categories?.body || {};
        const metrics = body.body_metrics || {};

        this.charts.progress = new Chart(ctx, {
            type: 'bar',
            data: {
                labels: ['Weight', 'Body Fat %', 'Muscle Mass'],
                datasets: [{
                    label: 'Current',
                    data: [
                        metrics.weight?.current || 0,
                        metrics.body_fat?.current || 0,
                        metrics.muscle_mass?.current || 0
                    ],
                    backgroundColor: [
                        'rgba(54, 162, 235, 0.8)',
                        'rgba(255, 99, 132, 0.8)',
                        'rgba(75, 192, 192, 0.8)'
                    ]
                }, {
                    label: 'Start',
                    data: [
                        metrics.weight?.start || 0,
                        metrics.body_fat?.start || 0,
                        metrics.muscle_mass?.start || 0
                    ],
                    backgroundColor: [
                        'rgba(54, 162, 235, 0.4)',
                        'rgba(255, 99, 132, 0.4)',
                        'rgba(75, 192, 192, 0.4)'
                    ]
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'Body Composition Progress'
                    }
                },
                scales: {
                    y: {
                        beginAtZero: true
                    }
                }
            }
        });
    }

    renderStrengthChart() {
        const ctx = document.getElementById('strengthChart');
        if (!ctx) return;

        const strength = this.data.categories?.strength || {};
        const exercises = strength.strength_exercises || {};

        const exerciseNames = Object.keys(exercises);
        const improvements = exerciseNames.map(name => exercises[name].improvement || 0);

        this.charts.strength = new Chart(ctx, {
            type: 'line',
            data: {
                labels: exerciseNames.map(name => name.replace('_', ' ').toUpperCase()),
                datasets: [{
                    label: 'Strength Improvement %',
                    data: improvements,
                    borderColor: 'rgb(255, 99, 132)',
                    backgroundColor: 'rgba(255, 99, 132, 0.2)',
                    tension: 0.4,
                    fill: true
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'Strength Progress by Exercise'
                    }
                },
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            callback: function(value) {
                                return value + '%';
                            }
                        }
                    }
                }
            }
        });
    }

    renderNutritionChart() {
        const ctx = document.getElementById('nutritionChart');
        if (!ctx) return;

        const nutrition = this.data.categories?.nutrition || {};
        const macros = nutrition.macro_distribution || {};

        this.charts.nutrition = new Chart(ctx, {
            type: 'doughnut',
            data: {
                labels: ['Protein', 'Carbs', 'Fats'],
                datasets: [{
                    data: [
                        macros.protein || 0,
                        macros.carbs || 0,
                        macros.fat || 0
                    ],
                    backgroundColor: [
                        'rgba(255, 99, 132, 0.8)',
                        'rgba(54, 162, 235, 0.8)',
                        'rgba(255, 206, 86, 0.8)'
                    ]
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'Macronutrient Distribution'
                    },
                    legend: {
                        position: 'bottom'
                    }
                }
            }
        });
    }

    renderRecoveryChart() {
        const ctx = document.getElementById('recoveryChart');
        if (!ctx) return;

        const recovery = this.data.categories?.recovery || {};
        const sleep = recovery.sleep_data || {};

        this.charts.recovery = new Chart(ctx, {
            type: 'radar',
            data: {
                labels: ['Sleep Duration', 'Sleep Quality', 'Consistency', 'Recovery Score'],
                datasets: [{
                    label: 'Recovery Metrics',
                    data: [
                        (sleep.avg_duration || 0) * 10, // Scale to 0-100
                        sleep.avg_quality || 0,
                        sleep.consistency || 0,
                        recovery.recovery_score || 0
                    ],
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    borderColor: 'rgb(75, 192, 192)',
                    pointBackgroundColor: 'rgb(75, 192, 192)'
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'Recovery Overview'
                    }
                },
                scales: {
                    r: {
                        beginAtZero: true,
                        max: 100
                    }
                }
            }
        });
    }

    renderRadarChart() {
        const ctx = document.getElementById('overallRadarChart');
        if (!ctx) return;

        const categories = this.data.categories || {};

        this.charts.overall = new Chart(ctx, {
            type: 'radar',
            data: {
                labels: ['Workout', 'Strength', 'Cardio', 'Nutrition', 'Recovery', 'Body'],
                datasets: [{
                    label: 'Overall Fitness Score',
                    data: [
                        categories.workout?.score || 0,
                        categories.strength?.score || 0,
                        categories.cardio?.score || 0,
                        categories.nutrition?.score || 0,
                        categories.recovery?.score || 0,
                        categories.body?.score || 0
                    ],
                    backgroundColor: 'rgba(153, 102, 255, 0.2)',
                    borderColor: 'rgb(153, 102, 255)',
                    pointBackgroundColor: 'rgb(153, 102, 255)'
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'Overall Fitness Profile'
                    }
                },
                scales: {
                    r: {
                        beginAtZero: true,
                        max: 100
                    }
                }
            }
        });
    }

    generateDateLabels(days) {
        const labels = [];
        const today = new Date();
        for (let i = days - 1; i >= 0; i--) {
            const date = new Date(today);
            date.setDate(date.getDate() - i);
            labels.push(date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }));
        }
        return labels;
    }

    generateWorkoutData() {
        // Generate sample data - in real app, would come from backend
        const data = [];
        for (let i = 0; i < 30; i++) {
            data.push(Math.floor(Math.random() * 7) + 1);
        }
        return data;
    }

    setupEventListeners() {
        const timeframeSelect = document.getElementById('timeframeSelect');
        if (timeframeSelect) {
            timeframeSelect.addEventListener('change', (e) => {
                this.loadAnalyticsData(e.target.value);
            });
        }
    }

    exportReport() {
        // Export analytics report as PDF/image
        window.print();
    }
}

// Initialize dashboard when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
    if (document.getElementById('analyticsDashboard')) {
        window.analyticsDashboard = new AnalyticsDashboard();
    }
});
363 lines•11.7 KB
javascript
static/js/app.js
Raw Download
Find: Go to:
/**
 * Fitness Coach Bot - Frontend JavaScript
 * Author: RSK World (https://rskworld.in)
 * Founded by: Molla Samser
 * Designer & Tester: Rima Khatun
 * Contact: help@rskworld.in, +91 93305 39277
 * Year: 2026
 */

class FitnessCoachBot {
    constructor() {
        this.init();
        this.loadUserProfile();
        this.loadHealthTips();
        this.updateStats();
    }

    init() {
        // Get DOM elements
        this.chatMessages = document.getElementById('chatMessages');
        this.messageInput = document.getElementById('messageInput');
        this.sendButton = document.getElementById('sendButton');
        this.profileForm = document.getElementById('profileForm');
        this.refreshTipsBtn = document.getElementById('refreshTips');
        
        // Event listeners
        this.sendButton.addEventListener('click', () => this.sendMessage());
        this.messageInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                this.sendMessage();
            }
        });
        
        this.profileForm.addEventListener('submit', (e) => {
            e.preventDefault();
            this.saveUserProfile();
        });
        
        this.refreshTipsBtn.addEventListener('click', () => this.loadHealthTips());
        
        // Quick suggestion buttons
        document.querySelectorAll('.quick-suggestion').forEach(button => {
            button.addEventListener('click', () => {
                this.messageInput.value = button.textContent;
                this.sendMessage();
            });
        });
        
        // Feature cards
        document.querySelectorAll('.feature-card').forEach(card => {
            card.addEventListener('click', () => {
                const feature = card.querySelector('h6').textContent;
                this.handleFeatureClick(feature);
            });
        });
    }

    async sendMessage() {
        const message = this.messageInput.value.trim();
        if (!message) return;

        // Add user message to chat
        this.addMessage(message, 'user');
        this.messageInput.value = '';
        
        // Show typing indicator
        this.showTypingIndicator();
        
        try {
            // Send message to backend
            const response = await fetch('/api/chat', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ message: message })
            });
            
            const data = await response.json();
            
            // Hide typing indicator
            this.hideTypingIndicator();
            
            if (data.success) {
                // Add bot response to chat
                this.addMessage(data.response, 'bot');
            } else {
                this.addMessage('Sorry, I encountered an error. Please try again.', 'bot');
            }
        } catch (error) {
            this.hideTypingIndicator();
            this.addMessage('Sorry, I\'m having trouble connecting. Please check your internet connection.', 'bot');
            console.error('Error sending message:', error);
        }
    }

    addMessage(content, sender) {
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${sender}-message mb-3 fade-in`;
        
        const avatarClass = sender === 'bot' ? 'bot-avatar' : 'user-avatar';
        const iconClass = sender === 'bot' ? 'fas fa-robot text-primary' : 'fas fa-user text-success';
        
        messageDiv.innerHTML = `
            <div class="d-flex">
                <div class="${avatarClass} me-2">
                    <i class="${iconClass}"></i>
                </div>
                <div class="message-content ${sender === 'user' ? 'bg-primary text-white' : 'bg-light'} rounded p-3">
                    <p class="mb-0">${content}</p>
                    <small class="${sender === 'user' ? 'text-white-50' : 'text-muted'}">${new Date().toLocaleTimeString()}</small>
                </div>
            </div>
        `;
        
        this.chatMessages.appendChild(messageDiv);
        this.scrollToBottom();
    }

    showTypingIndicator() {
        const typingDiv = document.createElement('div');
        typingDiv.className = 'typing-indicator active mb-3';
        typingDiv.id = 'typingIndicator';
        typingDiv.innerHTML = `
            <div class="d-flex">
                <div class="bot-avatar me-2">
                    <i class="fas fa-robot text-primary"></i>
                </div>
                <div class="message-content bg-light rounded p-3">
                    <div class="typing-dots">
                        <span></span>
                        <span></span>
                        <span></span>
                    </div>
                </div>
            </div>
        `;
        
        this.chatMessages.appendChild(typingDiv);
        this.scrollToBottom();
    }

    hideTypingIndicator() {
        const typingIndicator = document.getElementById('typingIndicator');
        if (typingIndicator) {
            typingIndicator.remove();
        }
    }

    scrollToBottom() {
        this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
    }

    async saveUserProfile() {
        const profileData = {
            name: document.getElementById('userName').value,
            age: document.getElementById('userAge').value,
            weight: document.getElementById('userWeight').value,
            fitness_goal: document.getElementById('userGoal').value
        };

        try {
            const response = await fetch('/api/user/profile', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(profileData)
            });
            
            const data = await response.json();
            
            if (data.success) {
                this.showNotification('Profile saved successfully!', 'success');
                this.updateStats();
            } else {
                this.showNotification('Error saving profile. Please try again.', 'error');
            }
        } catch (error) {
            this.showNotification('Error saving profile. Please check your connection.', 'error');
            console.error('Error saving profile:', error);
        }
    }

    async loadUserProfile() {
        try {
            const response = await fetch('/api/user/profile');
            const data = await response.json();
            
            if (data.success && data.user) {
                const user = data.user;
                document.getElementById('userName').value = user.name || '';
                document.getElementById('userAge').value = user.age || '';
                document.getElementById('userWeight').value = user.weight || '';
                document.getElementById('userGoal').value = user.fitness_goal || '';
            }
        } catch (error) {
            console.error('Error loading profile:', error);
        }
    }

    async loadHealthTips() {
        try {
            const response = await fetch('/api/health-tips');
            const data = await response.json();
            
            if (data.success && data.tips) {
                const tipsContainer = document.getElementById('healthTips');
                tipsContainer.innerHTML = '';
                
                data.tips.forEach(tip => {
                    const tipDiv = document.createElement('div');
                    tipDiv.className = 'tip-item mb-2 fade-in';
                    tipDiv.innerHTML = `<small class="text-muted">💡 ${tip.title}</small>`;
                    tipsContainer.appendChild(tipDiv);
                });
            }
        } catch (error) {
            console.error('Error loading health tips:', error);
            // Fallback tips
            this.showFallbackTips();
        }
    }

    showFallbackTips() {
        const fallbackTips = [
            '💡 Drink at least 8 glasses of water daily',
            '🥗 Include protein in every meal',
            '😴 Get 7-9 hours of quality sleep',
            '🏃 Take regular movement breaks',
            '🧘 Practice stress management daily'
        ];
        
        const tipsContainer = document.getElementById('healthTips');
        tipsContainer.innerHTML = '';
        
        fallbackTips.forEach(tip => {
            const tipDiv = document.createElement('div');
            tipDiv.className = 'tip-item mb-2 fade-in';
            tipDiv.innerHTML = `<small class="text-muted">${tip}</small>`;
            tipsContainer.appendChild(tipDiv);
        });
    }

    async updateStats() {
        // Simulated stats - in real app, these would come from backend
        const stats = {
            workouts: Math.floor(Math.random() * 50) + 10,
            streak: Math.floor(Math.random() * 30) + 1,
            calories: Math.floor(Math.random() * 5000) + 1000,
            goals: Math.floor(Math.random() * 20) + 5
        };
        
        // Animate counter updates
        this.animateCounter('workoutCount', stats.workouts);
        this.animateCounter('streakCount', stats.streak);
        this.animateCounter('caloriesCount', stats.calories);
        this.animateCounter('goalsCount', stats.goals);
    }

    animateCounter(elementId, targetValue) {
        const element = document.getElementById(elementId);
        const duration = 1000;
        const step = targetValue / (duration / 16);
        let currentValue = 0;
        
        const timer = setInterval(() => {
            currentValue += step;
            if (currentValue >= targetValue) {
                currentValue = targetValue;
                clearInterval(timer);
            }
            element.textContent = Math.floor(currentValue);
        }, 16);
    }

    handleFeatureClick(feature) {
        const messages = {
            'Workout Plans': 'Can you create a personalized workout plan for me?',
            'Exercise Guidance': 'Show me proper form for basic exercises',
            'Progress Tracking': 'How can I track my fitness progress effectively?',
            'Health Tips': 'Give me some nutrition and health advice'
        };
        
        this.messageInput.value = messages[feature] || `Tell me about ${feature}`;
        this.sendMessage();
    }

    showNotification(message, type) {
        const notification = document.createElement('div');
        notification.className = `${type}-message position-fixed top-0 start-50 translate-middle-x mt-3`;
        notification.style.zIndex = '9999';
        notification.innerHTML = `
            <div class="d-flex align-items-center">
                <i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} me-2"></i>
                ${message}
            </div>
        `;
        
        document.body.appendChild(notification);
        
        setTimeout(() => {
            notification.remove();
        }, 3000);
    }
}

// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
    new FitnessCoachBot();
});

// Service Worker for PWA capabilities (optional)
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js')
            .then((registration) => {
                console.log('SW registered: ', registration);
            })
            .catch((registrationError) => {
                console.log('SW registration failed: ', registrationError);
            });
    });
}
325 lines•11.7 KB
javascript
templates/index.html
Raw Download
Find: Go to:
<!DOCTYPE html>
<html lang="en">
<head>
    <!--
    Fitness Coach Bot - Main Interface
    Author: RSK World (https://rskworld.in)
    Founded by: Molla Samser
    Designer & Tester: Rima Khatun
    Contact: help@rskworld.in, +91 93305 39277
    Year: 2026
    -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fitness Coach Bot - Your Personal AI Fitness Trainer</title>
    <meta name="description" content="AI-powered fitness coaching chatbot for workout plans, exercise guidance, and health tracking">
    <meta name="keywords" content="fitness coach, workout plans, exercise guidance, health tracking, AI chatbot">
    <meta name="author" content="RSK World">
    
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Font Awesome -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <!-- Custom CSS -->
    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/advanced-features.css') }}" rel="stylesheet">
</head>
<body class="bg-light">
    <!-- Header -->
    <header class="bg-primary text-white py-3 shadow">
        <div class="container">
            <div class="row align-items-center">
                <div class="col-md-8">
                    <h1 class="h3 mb-0">
                        <i class="fas fa-dumbbell me-2"></i>
                        Fitness Coach Bot
                    </h1>
                    <p class="mb-0 small">Your Personal AI Fitness Trainer</p>
                </div>
                <div class="col-md-4 text-md-end">
                    <span class="badge bg-success me-2">Online</span>
                    <small>Powered by RSK World</small>
                </div>
            </div>
        </div>
    </header>

    <!-- Main Content -->
    <main class="container my-4">
        <div class="row">
            <!-- Chat Section -->
            <div class="col-lg-8 mb-4">
                <div class="card shadow-sm h-100">
                    <div class="card-header bg-white">
                        <h5 class="mb-0">
                            <i class="fas fa-comments me-2 text-primary"></i>
                            Chat with Your Fitness Coach
                        </h5>
                    </div>
                    <div class="card-body p-0">
                        <!-- Chat Messages -->
                        <div id="chatMessages" class="chat-messages p-3" style="height: 400px; overflow-y: auto;">
                            <div class="message bot-message mb-3">
                                <div class="d-flex">
                                    <div class="bot-avatar me-2">
                                        <i class="fas fa-robot text-primary"></i>
                                    </div>
                                    <div class="message-content bg-light rounded p-3">
                                        <p class="mb-0">Hello! I'm your fitness coach bot. I'm here to help you with workout plans, exercise guidance, and health tracking. What would you like to know today?</p>
                                    </div>
                                </div>
                            </div>
                        </div>
                        
                        <!-- Chat Input -->
                        <div class="chat-input p-3 border-top">
                            <div class="input-group">
                                <input type="text" id="messageInput" class="form-control" placeholder="Ask about workouts, nutrition, or fitness goals..." maxlength="500">
                                <button class="btn btn-primary" type="button" id="sendButton">
                                    <i class="fas fa-paper-plane"></i> Send
                                </button>
                            </div>
                            <div class="mt-2">
                                <small class="text-muted">Quick suggestions:</small>
                                <div class="mt-1">
                                    <button class="btn btn-sm btn-outline-secondary me-1 mb-1 quick-suggestion">Create workout plan</button>
                                    <button class="btn btn-sm btn-outline-secondary me-1 mb-1 quick-suggestion">Nutrition advice</button>
                                    <button class="btn btn-sm btn-outline-secondary me-1 mb-1 quick-suggestion">Track progress</button>
                                    <button class="btn btn-sm btn-outline-secondary me-1 mb-1 quick-suggestion">Motivation</button>
                                </div>
                                <div class="mt-2">
                                    <button class="btn btn-sm btn-outline-info" id="voiceToggleButton" title="Voice Commands">
                                        <i class="fas fa-microphone"></i> Voice Command
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Sidebar -->
            <div class="col-lg-4">
                <!-- User Profile -->
                <div class="card shadow-sm mb-4">
                    <div class="card-header bg-white">
                        <h6 class="mb-0">
                            <i class="fas fa-user me-2 text-primary"></i>
                            Your Profile
                        </h6>
                    </div>
                    <div class="card-body">
                        <form id="profileForm">
                            <div class="mb-3">
                                <label for="userName" class="form-label">Name</label>
                                <input type="text" class="form-control form-control-sm" id="userName" placeholder="Your name">
                            </div>
                            <div class="row">
                                <div class="col-6 mb-3">
                                    <label for="userAge" class="form-label">Age</label>
                                    <input type="number" class="form-control form-control-sm" id="userAge" placeholder="25">
                                </div>
                                <div class="col-6 mb-3">
                                    <label for="userWeight" class="form-label">Weight (kg)</label>
                                    <input type="number" class="form-control form-control-sm" id="userWeight" placeholder="70">
                                </div>
                            </div>
                            <div class="mb-3">
                                <label for="userGoal" class="form-label">Fitness Goal</label>
                                <select class="form-select form-select-sm" id="userGoal">
                                    <option value="">Select goal...</option>
                                    <option value="weight_loss">Weight Loss</option>
                                    <option value="muscle_gain">Muscle Gain</option>
                                    <option value="endurance">Endurance</option>
                                    <option value="strength">Strength</option>
                                    <option value="general_fitness">General Fitness</option>
                                </select>
                            </div>
                            <button type="submit" class="btn btn-primary btn-sm w-100">Save Profile</button>
                        </form>
                    </div>
                </div>

                <!-- Quick Stats -->
                <div class="card shadow-sm mb-4">
                    <div class="card-header bg-white">
                        <h6 class="mb-0">
                            <i class="fas fa-chart-line me-2 text-primary"></i>
                            Quick Stats
                        </h6>
                    </div>
                    <div class="card-body">
                        <div class="row text-center">
                            <div class="col-6 mb-3">
                                <div class="stat-item">
                                    <h4 class="text-primary mb-0" id="workoutCount">0</h4>
                                    <small class="text-muted">Workouts</small>
                                </div>
                            </div>
                            <div class="col-6 mb-3">
                                <div class="stat-item">
                                    <h4 class="text-success mb-0" id="streakCount">0</h4>
                                    <small class="text-muted">Day Streak</small>
                                </div>
                            </div>
                            <div class="col-6">
                                <div class="stat-item">
                                    <h4 class="text-info mb-0" id="caloriesCount">0</h4>
                                    <small class="text-muted">Calories</small>
                                </div>
                            </div>
                            <div class="col-6">
                                <div class="stat-item">
                                    <h4 class="text-warning mb-0" id="goalsCount">0</h4>
                                    <small class="text-muted">Goals Met</small>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Health Tips -->
                <div class="card shadow-sm">
                    <div class="card-header bg-white">
                        <h6 class="mb-0">
                            <i class="fas fa-lightbulb me-2 text-warning"></i>
                            Health Tips
                        </h6>
                    </div>
                    <div class="card-body">
                        <div id="healthTips">
                            <div class="tip-item mb-2">
                                <small class="text-muted">💡 Drink at least 8 glasses of water daily</small>
                            </div>
                            <div class="tip-item mb-2">
                                <small class="text-muted">🥗 Include protein in every meal</small>
                            </div>
                            <div class="tip-item">
                                <small class="text-muted">😴 Get 7-9 hours of quality sleep</small>
                            </div>
                        </div>
                        <button class="btn btn-sm btn-outline-primary mt-2" id="refreshTips">
                            <i class="fas fa-sync-alt"></i> More Tips
                        </button>
                    </div>
                </div>
            </div>
        </div>

        <!-- Features Section -->
        <div class="row mt-4">
            <div class="col-12">
                <div class="card shadow-sm">
                    <div class="card-header bg-white">
                        <h5 class="mb-0">
                            <i class="fas fa-star me-2 text-warning"></i>
                            Features
                        </h5>
                    </div>
                    <div class="card-body">
                        <div class="row">
                            <div class="col-md-3 mb-3">
                                <div class="feature-card text-center p-3">
                                    <i class="fas fa-clipboard-list fa-2x text-primary mb-2"></i>
                                    <h6>Workout Plans</h6>
                                    <small class="text-muted">Personalized workout routines</small>
                                </div>
                            </div>
                            <div class="col-md-3 mb-3">
                                <div class="feature-card text-center p-3">
                                    <i class="fas fa-running fa-2x text-success mb-2"></i>
                                    <h6>Exercise Guidance</h6>
                                    <small class="text-muted">Proper form and technique</small>
                                </div>
                            </div>
                            <div class="col-md-3 mb-3">
                                <div class="feature-card text-center p-3">
                                    <i class="fas fa-chart-bar fa-2x text-info mb-2"></i>
                                    <h6>Progress Tracking</h6>
                                    <small class="text-muted">Monitor your improvements</small>
                                </div>
                            </div>
                            <div class="col-md-3 mb-3">
                                <div class="feature-card text-center p-3">
                                    <i class="fas fa-heart fa-2x text-danger mb-2"></i>
                                    <h6>Health Tips</h6>
                                    <small class="text-muted">Nutrition and wellness advice</small>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Voice Recognition Indicator -->
        <div id="voiceListeningIndicator" class="voice-listening-indicator position-fixed" style="display: none; bottom: 100px; right: 30px; background: rgba(0,0,0,0.8); color: white; padding: 15px 20px; border-radius: 20px; z-index: 999;">
            <div class="d-flex align-items-center">
                <div class="spinner-border spinner-border-sm text-primary me-2" role="status"></div>
                <span>Listening...</span>
            </div>
        </div>

        <!-- Pose Detection Container -->
        <div id="poseDetectionContainer" class="mt-4" style="display: none;">
            <div class="card shadow-sm">
                <div class="card-header bg-white">
                    <h5 class="mb-0">
                        <i class="fas fa-video me-2 text-primary"></i>
                        Pose Detection & Form Correction
                    </h5>
                </div>
                <div class="card-body">
                    <div id="poseFeedback" class="alert alert-info mb-3"></div>
                    <div class="row">
                        <div class="col-md-8">
                            <div id="poseCanvasContainer" class="pose-detection-container"></div>
                        </div>
                        <div class="col-md-4">
                            <div class="pose-stats p-3">
                                <h6 class="mb-3">Live Stats</h6>
                                <div class="rep-counter mb-3">
                                    <div class="mb-2">Reps: <span id="repCounter" class="text-primary fw-bold">0</span></div>
                                    <div class="form-score mb-2">Form Score: <span id="formScore" class="text-warning fw-bold">0%</span></div>
                                    <div>Exercise: <span id="currentExercise" class="text-info">None</span></div>
                                </div>
                                <button class="btn btn-danger w-100" onclick="if(window.poseDetector) window.poseDetector.stopDetection()">
                                    <i class="fas fa-stop"></i> Stop Detection
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <!-- Footer -->
    <footer class="bg-dark text-white py-4 mt-5">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <h6>Fitness Coach Bot</h6>
                    <p class="small mb-0">Your AI-powered personal fitness trainer for workout plans, exercise guidance, and health tracking.</p>
                </div>
                <div class="col-md-6 text-md-end">
                    <p class="small mb-0">
                        © 2026 RSK World. All rights reserved.<br>
                        Developed by: Molla Samser | Designed & Tested by: Rima Khatun<br>
                        Contact: <a href="mailto:help@rskworld.in" class="text-white">help@rskworld.in</a> | +91 93305 39277
                    </p>
                </div>
            </div>
        </div>
    </footer>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Chart.js for Analytics -->
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
    <!-- Custom JS -->
    <script src="{{ url_for('static', filename='js/app.js') }}"></script>
    <script src="{{ url_for('static', filename='js/voice_recognition.js') }}"></script>
    <script src="{{ url_for('static', filename='js/pose_detection.js') }}"></script>
    <script src="{{ url_for('static', filename='js/analytics_dashboard.js') }}"></script>
</body>
</html>
327 lines•17.3 KB
markup
static/js/pose_detection.js
Raw Download
Find: Go to:
/**
 * Real-time Pose Detection for Fitness Coach Bot
 * Author: RSK World (https://rskworld.in)
 * Founded by: Molla Samser
 * Designer & Tester: Rima Khatun
 * Contact: help@rskworld.in, +91 93305 39277
 * Year: 2026
 */

class PoseDetector {
    constructor() {
        this.model = null;
        this.video = null;
        this.canvas = null;
        this.ctx = null;
        this.isDetecting = false;
        this.currentExercise = null;
        this.repCount = 0;
        this.formScore = 0;
        this.feedback = [];
        this.exerciseStates = {};
        
        // Exercise-specific pose configurations
        this.exerciseConfigs = {
            'squats': {
                keyPoints: ['left_hip', 'right_hip', 'left_knee', 'right_knee', 'left_ankle', 'right_ankle'],
                idealAngles: {
                    'down': 90,
                    'up': 170
                },
                tolerance: 15,
                feedbackMessages: {
                    'good': 'Great form! Keep it up!',
                    'knees_forward': 'Keep your knees behind your toes',
                    'depth': 'Go deeper - aim for parallel',
                    'back_straight': 'Keep your back straight'
                }
            },
            'pushups': {
                keyPoints: ['left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow', 'left_wrist', 'right_wrist'],
                idealAngles: {
                    'down': 90,
                    'up': 170
                },
                tolerance: 15,
                feedbackMessages: {
                    'good': 'Perfect push-up form!',
                    'depth': 'Lower your chest more',
                    'full_extension': 'Extend arms fully at the top',
                    'core_tight': 'Keep your core engaged'
                }
            },
            'plank': {
                keyPoints: ['left_shoulder', 'right_shoulder', 'left_hip', 'right_hip', 'left_ankle', 'right_ankle'],
                idealAngles: {
                    'hold': 180
                },
                tolerance: 10,
                feedbackMessages: {
                    'good': 'Excellent plank position!',
                    'hips_high': 'Lower your hips',
                    'hips_low': 'Raise your hips',
                    'back_straight': 'Maintain straight line from head to heels'
                }
            }
        };
        
        this.init();
    }
    
    async init() {
        try {
            // Load TensorFlow.js and PoseNet models
            await this.loadModels();
            this.setupCamera();
            this.setupCanvas();
            console.log('Pose detection initialized successfully');
        } catch (error) {
            console.error('Error initializing pose detection:', error);
            this.showFallbackMessage();
        }
    }
    
    async loadModels() {
        // Load TensorFlow.js
        const script = document.createElement('script');
        script.src = 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js';
        document.head.appendChild(script);
        
        await new Promise(resolve => script.onload = resolve);
        
        // Load PoseNet
        const poseScript = document.createElement('script');
        poseScript.src = 'https://cdn.jsdelivr.net/npm/@tensorflow-models/posenet@2.2.2/dist/posenet.min.js';
        document.head.appendChild(poseScript);
        
        await new Promise(resolve => poseScript.onload = resolve);
        
        // Load the model
        this.model = await posenet.load({
            architecture: 'MobileNetV1',
            outputStride: 16,
            inputResolution: { width: 640, height: 480 },
            multiplier: 0.75
        });
    }
    
    setupCamera() {
        this.video = document.createElement('video');
        this.video.width = 640;
        this.video.height = 480;
        
        // Get user media
        navigator.mediaDevices.getUserMedia({
            video: { 
                width: 640, 
                height: 480,
                facingMode: 'user'
            }
        }).then(stream => {
            this.video.srcObject = stream;
            this.video.play();
        }).catch(error => {
            console.error('Error accessing camera:', error);
            this.showCameraError();
        });
    }
    
    setupCanvas() {
        this.canvas = document.getElementById('poseCanvas');
        if (!this.canvas) {
            this.canvas = document.createElement('canvas');
            this.canvas.id = 'poseCanvas';
            this.canvas.width = 640;
            this.canvas.height = 480;
            document.getElementById('poseDetectionContainer').appendChild(this.canvas);
        }
        
        this.ctx = this.canvas.getContext('2d');
    }
    
    async startDetection(exercise) {
        if (!this.model) {
            console.error('Model not loaded yet');
            return;
        }
        
        this.currentExercise = exercise;
        this.isDetecting = true;
        this.repCount = 0;
        this.formScore = 0;
        this.exerciseStates = {
            lastPosition: 'up',
            inPosition: false,
            repStartTime: null
        };
        
        this.updateUI();
        this.detectLoop();
    }
    
    stopDetection() {
        this.isDetecting = false;
        this.saveWorkoutData();
        this.showResults();
    }
    
    async detectLoop() {
        if (!this.isDetecting) return;
        
        try {
            const pose = await this.model.estimateSinglePose(this.video, {
                flipHorizontal: true
            });
            
            this.drawPose(pose);
            this.analyzePose(pose);
            
        } catch (error) {
            console.error('Error in pose detection:', error);
        }
        
        requestAnimationFrame(() => this.detectLoop());
    }
    
    drawPose(pose) {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        
        // Draw video
        this.ctx.save();
        this.ctx.scale(-1, 1);
        this.ctx.translate(-this.canvas.width, 0);
        this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
        this.ctx.restore();
        
        // Draw keypoints
        pose.keypoints.forEach(keypoint => {
            if (keypoint.score > 0.5) {
                this.ctx.beginPath();
                this.ctx.arc(keypoint.position.x, keypoint.position.y, 5, 0, 2 * Math.PI);
                this.ctx.fillStyle = '#00FF00';
                this.ctx.fill();
            }
        });
        
        // Draw skeleton
        const adjacentKeyPoints = posenet.getAdjacentKeyPoints(pose.keypoints, 0.5);
        adjacentKeyPoints.forEach(keypoints => {
            this.drawSegment(keypoints[0].position, keypoints[1].position);
        });
        
        // Draw exercise-specific indicators
        this.drawExerciseIndicators(pose);
    }
    
    drawSegment(p1, p2) {
        this.ctx.beginPath();
        this.ctx.moveTo(p1.x, p1.y);
        this.ctx.lineTo(p2.x, p2.y);
        this.ctx.lineWidth = 2;
        this.ctx.strokeStyle = '#00FF00';
        this.ctx.stroke();
    }
    
    drawExerciseIndicators(pose) {
        if (!this.currentExercise) return;
        
        const config = this.exerciseConfigs[this.currentExercise];
        if (!config) return;
        
        // Draw angle indicators for key joints
        if (this.currentExercise === 'squats') {
            this.drawKneeAngles(pose);
        } else if (this.currentExercise === 'pushups') {
            this.drawElbowAngles(pose);
        } else if (this.currentExercise === 'plank') {
            this.drawBodyLine(pose);
        }
    }
    
    drawKneeAngles(pose) {
        const leftKneeAngle = this.calculateAngle(
            this.getKeypoint(pose, 'left_hip'),
            this.getKeypoint(pose, 'left_knee'),
            this.getKeypoint(pose, 'left_ankle')
        );
        
        const rightKneeAngle = this.calculateAngle(
            this.getKeypoint(pose, 'right_hip'),
            this.getKeypoint(pose, 'right_knee'),
            this.getKeypoint(pose, 'right_ankle')
        );
        
        // Draw angle text
        this.ctx.fillStyle = '#FFD700';
        this.ctx.font = '16px Arial';
        this.ctx.fillText(`L: ${Math.round(leftKneeAngle)}°`, 50, 50);
        this.ctx.fillText(`R: ${Math.round(rightKneeAngle)}°`, 50, 80);
    }
    
    drawElbowAngles(pose) {
        const leftElbowAngle = this.calculateAngle(
            this.getKeypoint(pose, 'left_shoulder'),
            this.getKeypoint(pose, 'left_elbow'),
            this.getKeypoint(pose, 'left_wrist')
        );
        
        const rightElbowAngle = this.calculateAngle(
            this.getKeypoint(pose, 'right_shoulder'),
            this.getKeypoint(pose, 'right_elbow'),
            this.getKeypoint(pose, 'right_wrist')
        );
        
        // Draw angle text
        this.ctx.fillStyle = '#FFD700';
        this.ctx.font = '16px Arial';
        this.ctx.fillText(`L: ${Math.round(leftElbowAngle)}°`, 50, 50);
        this.ctx.fillText(`R: ${Math.round(rightElbowAngle)}°`, 50, 80);
    }
    
    drawBodyLine(pose) {
        const shoulder = this.getKeypoint(pose, 'left_shoulder');
        const hip = this.getKeypoint(pose, 'left_hip');
        const ankle = this.getKeypoint(pose, 'left_ankle');
        
        if (shoulder && hip && ankle) {
            this.ctx.beginPath();
            this.ctx.moveTo(shoulder.position.x, shoulder.position.y);
            this.ctx.lineTo(hip.position.x, hip.position.y);
            this.ctx.lineTo(ankle.position.x, ankle.position.y);
            this.ctx.strokeStyle = '#FF6B6B';
            this.ctx.lineWidth = 3;
            this.ctx.stroke();
        }
    }
    
    analyzePose(pose) {
        if (!this.currentExercise) return;
        
        const config = this.exerciseConfigs[this.currentExercise];
        if (!config) return;
        
        // Calculate exercise-specific metrics
        let metrics = {};
        
        if (this.currentExercise === 'squats') {
            metrics = this.analyzeSquat(pose);
        } else if (this.currentExercise === 'pushups') {
            metrics = this.analyzePushup(pose);
        } else if (this.currentExercise === 'plank') {
            metrics = this.analyzePlank(pose);
        }
        
        // Update rep count and form score
        this.updateRepCount(metrics);
        this.updateFormScore(metrics);
        this.provideFeedback(metrics);
    }
    
    analyzeSquat(pose) {
        const leftKneeAngle = this.calculateAngle(
            this.getKeypoint(pose, 'left_hip'),
            this.getKeypoint(pose, 'left_knee'),
            this.getKeypoint(pose, 'left_ankle')
        );
        
        const rightKneeAngle = this.calculateAngle(
            this.getKeypoint(pose, 'right_hip'),
            this.getKeypoint(pose, 'right_knee'),
            this.getKeypoint(pose, 'right_ankle')
        );
        
        const avgKneeAngle = (leftKneeAngle + rightKneeAngle) / 2;
        
        // Determine position
        let position = 'up';
        if (avgKneeAngle < 100) {
            position = 'down';
        } else if (avgKneeAngle < 150) {
            position = 'middle';
        }
        
        // Check form
        const formIssues = [];
        if (avgKneeAngle > 110 && position === 'down') {
            formIssues.push('depth');
        }
        
        return {
            position,
            avgKneeAngle,
            leftKneeAngle,
            rightKneeAngle,
            formIssues,
            isGoodForm: formIssues.length === 0 && Math.abs(avgKneeAngle - 90) < 20
        };
    }
    
    analyzePushup(pose) {
        const leftElbowAngle = this.calculateAngle(
            this.getKeypoint(pose, 'left_shoulder'),
            this.getKeypoint(pose, 'left_elbow'),
            this.getKeypoint(pose, 'left_wrist')
        );
        
        const rightElbowAngle = this.calculateAngle(
            this.getKeypoint(pose, 'right_shoulder'),
            this.getKeypoint(pose, 'right_elbow'),
            this.getKeypoint(pose, 'right_wrist')
        );
        
        const avgElbowAngle = (leftElbowAngle + rightElbowAngle) / 2;
        
        // Determine position
        let position = 'up';
        if (avgElbowAngle < 100) {
            position = 'down';
        } else if (avgElbowAngle < 150) {
            position = 'middle';
        }
        
        // Check form
        const formIssues = [];
        if (avgElbowAngle > 110 && position === 'down') {
            formIssues.push('depth');
        }
        
        return {
            position,
            avgElbowAngle,
            leftElbowAngle,
            rightElbowAngle,
            formIssues,
            isGoodForm: formIssues.length === 0 && Math.abs(avgElbowAngle - 90) < 20
        };
    }
    
    analyzePlank(pose) {
        const shoulder = this.getKeypoint(pose, 'left_shoulder');
        const hip = this.getKeypoint(pose, 'left_hip');
        const ankle = this.getKeypoint(pose, 'left_ankle');
        
        if (!shoulder || !hip || !ankle) {
            return { isGoodForm: false, formIssues: ['no_pose'] };
        }
        
        // Calculate body line angle
        const bodyAngle = this.calculateAngle(shoulder, hip, ankle);
        
        // Check form
        const formIssues = [];
        if (bodyAngle < 170) {
            formIssues.push('hips_low');
        } else if (bodyAngle > 190) {
            formIssues.push('hips_high');
        }
        
        return {
            bodyAngle,
            formIssues,
            isGoodForm: formIssues.length === 0
        };
    }
    
    updateRepCount(metrics) {
        const currentState = this.exerciseStates;
        const currentPosition = metrics.position;
        
        if (currentState.lastPosition === 'up' && currentPosition === 'down') {
            currentState.inPosition = true;
            currentState.repStartTime = Date.now();
        } else if (currentState.lastPosition === 'down' && currentPosition === 'up' && currentState.inPosition) {
            this.repCount++;
            currentState.inPosition = false;
            this.onRepComplete();
        }
        
        currentState.lastPosition = currentPosition;
    }
    
    updateFormScore(metrics) {
        if (metrics.isGoodForm) {
            this.formScore = Math.min(100, this.formScore + 1);
        } else {
            this.formScore = Math.max(0, this.formScore - 2);
        }
    }
    
    provideFeedback(metrics) {
        const config = this.exerciseConfigs[this.currentExercise];
        if (!config) return;
        
        let feedback = '';
        
        if (metrics.isGoodForm) {
            feedback = config.feedbackMessages.good;
        } else {
            // Provide specific feedback based on form issues
            for (const issue of metrics.formIssues) {
                if (config.feedbackMessages[issue]) {
                    feedback = config.feedbackMessages[issue];
                    break;
                }
            }
        }
        
        if (feedback && feedback !== this.lastFeedback) {
            this.showFeedback(feedback);
            this.lastFeedback = feedback;
        }
    }
    
    calculateAngle(pointA, pointB, pointC) {
        if (!pointA || !pointB || !pointC) return 0;
        
        const radians = Math.atan2(pointC.position.y - pointB.position.y, 
                                   pointC.position.x - pointB.position.x) -
                         Math.atan2(pointA.position.y - pointB.position.y, 
                                   pointA.position.x - pointB.position.x);
        let angle = Math.abs(radians * 180.0 / Math.PI);
        
        if (angle > 180.0) {
            angle = 360 - angle;
        }
        
        return angle;
    }
    
    getKeypoint(pose, partName) {
        return pose.keypoints.find(kp => kp.part === partName);
    }
    
    updateUI() {
        // Update rep counter
        const repElement = document.getElementById('repCounter');
        if (repElement) {
            repElement.textContent = this.repCount;
        }
        
        // Update form score
        const formElement = document.getElementById('formScore');
        if (formElement) {
            formElement.textContent = `${Math.round(this.formScore)}%`;
        }
        
        // Update exercise name
        const exerciseElement = document.getElementById('currentExercise');
        if (exerciseElement) {
            exerciseElement.textContent = this.currentExercise || 'None';
        }
    }
    
    showFeedback(message) {
        const feedbackElement = document.getElementById('poseFeedback');
        if (feedbackElement) {
            feedbackElement.textContent = message;
            feedbackElement.className = 'alert alert-info';
            
            // Auto-hide after 3 seconds
            setTimeout(() => {
                feedbackElement.textContent = '';
                feedbackElement.className = '';
            }, 3000);
        }
    }
    
    onRepComplete() {
        // Haptic feedback if available
        if (navigator.vibrate) {
            navigator.vibrate(200);
        }
        
        // Visual feedback
        const repElement = document.getElementById('repCounter');
        if (repElement) {
            repElement.classList.add('rep-complete');
            setTimeout(() => {
                repElement.classList.remove('rep-complete');
            }, 500);
        }
    }
    
    saveWorkoutData() {
        const workoutData = {
            exercise: this.currentExercise,
            reps: this.repCount,
            avgFormScore: this.formScore,
            duration: Date.now() - (this.exerciseStates.startTime || Date.now()),
            timestamp: new Date().toISOString()
        };
        
        // Save to localStorage
        let workouts = JSON.parse(localStorage.getItem('poseWorkouts') || '[]');
        workouts.push(workoutData);
        localStorage.setItem('poseWorkouts', JSON.stringify(workouts));
        
        // Send to server
        fetch('/api/pose-workout', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(workoutData)
        }).catch(error => {
            console.error('Error saving workout data:', error);
        });
    }
    
    showResults() {
        const resultsModal = document.getElementById('workoutResults');
        if (resultsModal) {
            document.getElementById('finalReps').textContent = this.repCount;
            document.getElementById('finalFormScore').textContent = `${Math.round(this.formScore)}%`;
            
            // Show achievement badges
            this.showAchievements();
            
            resultsModal.style.display = 'block';
        }
    }
    
    showAchievements() {
        const achievements = [];
        
        if (this.repCount >= 50) {
            achievements.push({ name: 'Rep Master', icon: '🏆' });
        } else if (this.repCount >= 25) {
            achievements.push({ name: 'Rep Champion', icon: '🥇' });
        } else if (this.repCount >= 10) {
            achievements.push({ name: 'Rep Hero', icon: '🥈' });
        }
        
        if (this.formScore >= 90) {
            achievements.push({ name: 'Form Perfectionist', icon: '⭐' });
        }
        
        const achievementsElement = document.getElementById('achievements');
        if (achievementsElement) {
            achievementsElement.innerHTML = achievements.map(a => 
                `<div class="achievement">
                    <span class="achievement-icon">${a.icon}</span>
                    <span class="achievement-name">${a.name}</span>
                </div>`
            ).join('');
        }
    }
    
    showFallbackMessage() {
        const container = document.getElementById('poseDetectionContainer');
        if (container) {
            container.innerHTML = `
                <div class="alert alert-warning">
                    <h4>Camera Access Required</h4>
                    <p>For pose detection, please allow camera access and use a device with a camera.</p>
                    <p>You can still use all other features of the fitness coach bot!</p>
                </div>
            `;
        }
    }
    
    showCameraError() {
        const container = document.getElementById('poseDetectionContainer');
        if (container) {
            container.innerHTML = `
                <div class="alert alert-danger">
                    <h4>Camera Error</h4>
                    <p>Unable to access camera. Please check your camera permissions.</p>
                </div>
            `;
        }
    }
}

// Initialize pose detector when page loads
document.addEventListener('DOMContentLoaded', () => {
    window.poseDetector = new PoseDetector();
});

// Export for use in other scripts
window.PoseDetector = PoseDetector;
641 lines•21.3 KB
javascript

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer