Upload 8 files
Browse files- main.py +275 -0
- static/css/styles.css +117 -0
- static/data/food_items.json +42 -0
- static/js/app.js +247 -0
- templates/dashboard.html +0 -0
- templates/index.html +99 -0
- templates/login.html +0 -0
- templates/register.html +0 -0
main.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify, render_template
|
| 2 |
+
import requests
|
| 3 |
+
import os
|
| 4 |
+
import math
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
app = Flask(__name__)
|
| 8 |
+
app.static_folder = 'static'
|
| 9 |
+
|
| 10 |
+
# Configure logging
|
| 11 |
+
logging.basicConfig(level=logging.DEBUG)
|
| 12 |
+
|
| 13 |
+
# Configuration
|
| 14 |
+
USDA_API_ENDPOINT = "https://api.nal.usda.gov/fdc/v1"
|
| 15 |
+
USDA_API_KEY = os.environ.get('USDA_API_KEY')
|
| 16 |
+
|
| 17 |
+
# Error messages
|
| 18 |
+
INVALID_INPUT_ERROR = "Invalid input"
|
| 19 |
+
MISSING_REQUIRED_FIELDS_ERROR = "Missing required fields"
|
| 20 |
+
FAILED_TO_FETCH_NUTRIENT_DATA_ERROR = "Failed to fetch nutrient data"
|
| 21 |
+
|
| 22 |
+
@app.route('/')
|
| 23 |
+
def index():
|
| 24 |
+
return render_template('index.html')
|
| 25 |
+
|
| 26 |
+
@app.route('/api/calculate-metrics', methods=['POST'])
|
| 27 |
+
def calculate_metrics():
|
| 28 |
+
data = request.json
|
| 29 |
+
app.logger.debug(f"Received data for calculate_metrics: {data}")
|
| 30 |
+
|
| 31 |
+
required_fields = ['age', 'gender', 'heightFeet', 'heightInches', 'weight', 'targetWeight', 'waist', 'neck', 'hip', 'steps', 'standingHours']
|
| 32 |
+
missing_fields = [field for field in required_fields if field not in data or data[field] is None]
|
| 33 |
+
|
| 34 |
+
if missing_fields:
|
| 35 |
+
app.logger.error(f"Missing required fields: {', '.join(missing_fields)}")
|
| 36 |
+
return jsonify({"error": MISSING_REQUIRED_FIELDS_ERROR, "missing_fields": missing_fields}), 400
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
age = int(data['age'])
|
| 40 |
+
gender = data['gender']
|
| 41 |
+
height_feet = int(data['heightFeet'])
|
| 42 |
+
height_inches = int(data['heightInches'])
|
| 43 |
+
weight = float(data['weight']) # in kg
|
| 44 |
+
target_weight = float(data['targetWeight']) # in kg
|
| 45 |
+
waist = float(data['waist']) # in cm
|
| 46 |
+
neck = float(data['neck']) # in cm
|
| 47 |
+
hip = float(data['hip']) # in cm
|
| 48 |
+
steps = int(data['steps'])
|
| 49 |
+
standing_hours = float(data['standingHours'])
|
| 50 |
+
|
| 51 |
+
# Convert height to cm
|
| 52 |
+
height = (height_feet * 30.48) + (height_inches * 2.54) # Convert to cm
|
| 53 |
+
|
| 54 |
+
except (ValueError, KeyError) as e:
|
| 55 |
+
app.logger.error(f"Invalid input: {str(e)}")
|
| 56 |
+
return jsonify({"error": INVALID_INPUT_ERROR, "details": str(e)}), 400
|
| 57 |
+
|
| 58 |
+
if age <= 0 or height <= 0 or weight <= 0 or target_weight <= 0 or waist <= 0 or neck <= 0 or hip <= 0 or steps < 0 or standing_hours < 0:
|
| 59 |
+
app.logger.error("Invalid input values")
|
| 60 |
+
return jsonify({"error": INVALID_INPUT_ERROR, "details": "Input values must be positive numbers"}), 400
|
| 61 |
+
|
| 62 |
+
if gender not in ['male', 'female', 'other']:
|
| 63 |
+
app.logger.error("Invalid gender")
|
| 64 |
+
return jsonify({"error": INVALID_INPUT_ERROR, "details": "Gender must be 'male', 'female', or 'other'"}), 400
|
| 65 |
+
|
| 66 |
+
# Calculate BMI
|
| 67 |
+
bmi = weight / ((height / 100) ** 2)
|
| 68 |
+
|
| 69 |
+
# Calculate body fat percentage (using U.S. Navy method)
|
| 70 |
+
if gender == 'male':
|
| 71 |
+
body_fat = 86.010 * math.log10(waist - neck) - 70.041 * math.log10(height) + 36.76
|
| 72 |
+
elif gender == 'female':
|
| 73 |
+
body_fat = 163.205 * math.log10(waist + hip - neck) - 97.684 * math.log10(height) - 78.387
|
| 74 |
+
else:
|
| 75 |
+
# For 'other' gender, use an average of male and female calculations
|
| 76 |
+
body_fat_male = 86.010 * math.log10(waist - neck) - 70.041 * math.log10(height) + 36.76
|
| 77 |
+
body_fat_female = 163.205 * math.log10(waist + hip - neck) - 97.684 * math.log10(height) - 78.387
|
| 78 |
+
body_fat = (body_fat_male + body_fat_female) / 2
|
| 79 |
+
|
| 80 |
+
# Calculate lean body mass
|
| 81 |
+
lean_body_mass = weight * (1 - (body_fat / 100))
|
| 82 |
+
|
| 83 |
+
# Calculate recommended calorie intake (using Mifflin-St Jeor Equation)
|
| 84 |
+
if gender == 'male':
|
| 85 |
+
bmr = 10 * weight + 6.25 * height - 5 * age + 5
|
| 86 |
+
elif gender == 'female':
|
| 87 |
+
bmr = 10 * weight + 6.25 * height - 5 * age - 161
|
| 88 |
+
else:
|
| 89 |
+
# For 'other' gender, use an average of male and female calculations
|
| 90 |
+
bmr_male = 10 * weight + 6.25 * height - 5 * age + 5
|
| 91 |
+
bmr_female = 10 * weight + 6.25 * height - 5 * age - 161
|
| 92 |
+
bmr = (bmr_male + bmr_female) / 2
|
| 93 |
+
|
| 94 |
+
# Adjust for activity level
|
| 95 |
+
activity_factor = 1.2 + (steps / 10000) * 0.1 + (standing_hours / 24) * 0.1
|
| 96 |
+
recommended_calories = bmr * activity_factor
|
| 97 |
+
|
| 98 |
+
# Calculate time to reach target weight
|
| 99 |
+
weight_difference = abs(weight - target_weight)
|
| 100 |
+
daily_calorie_deficit = 500 # Assuming a 500 calorie deficit per day
|
| 101 |
+
days_to_target = (weight_difference * 7700) / daily_calorie_deficit # 7700 calories ≈ 1 kg of body fat
|
| 102 |
+
|
| 103 |
+
response = {
|
| 104 |
+
'bmi': round(bmi, 2),
|
| 105 |
+
'bodyFatPercentage': round(body_fat, 2),
|
| 106 |
+
'leanBodyMass': round(lean_body_mass, 2),
|
| 107 |
+
'recommendedCalories': round(recommended_calories),
|
| 108 |
+
'timeToTargetWeight': f"{round(days_to_target)} days"
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
app.logger.debug(f"Calculated metrics: {response}")
|
| 112 |
+
return jsonify(response)
|
| 113 |
+
|
| 114 |
+
@app.route('/api/personalized-recommendations', methods=['POST'])
|
| 115 |
+
def get_personalized_recommendations():
|
| 116 |
+
data = request.json
|
| 117 |
+
app.logger.debug(f"Received data for personalized_recommendations: {data}")
|
| 118 |
+
|
| 119 |
+
required_fields = ['age', 'gender', 'height', 'weight', 'targetWeight', 'bmi', 'bodyFatPercentage', 'recommendedCalories', 'steps', 'standingHours']
|
| 120 |
+
missing_fields = [field for field in required_fields if field not in data or data[field] is None]
|
| 121 |
+
if missing_fields:
|
| 122 |
+
app.logger.error(f"Missing required fields: {', '.join(missing_fields)}")
|
| 123 |
+
return jsonify({"error": MISSING_REQUIRED_FIELDS_ERROR, "missing_fields": missing_fields}), 400
|
| 124 |
+
|
| 125 |
+
try:
|
| 126 |
+
age = int(data['age'])
|
| 127 |
+
gender = data['gender']
|
| 128 |
+
height = float(data['height'])
|
| 129 |
+
weight = float(data['weight'])
|
| 130 |
+
target_weight = float(data['targetWeight'])
|
| 131 |
+
bmi = float(data['bmi'])
|
| 132 |
+
body_fat_percentage = float(data['bodyFatPercentage'])
|
| 133 |
+
recommended_calories = int(data['recommendedCalories'])
|
| 134 |
+
steps = int(data['steps'])
|
| 135 |
+
standing_hours = float(data['standingHours'])
|
| 136 |
+
except (ValueError, KeyError) as e:
|
| 137 |
+
app.logger.error(f"Invalid input: {str(e)}")
|
| 138 |
+
return jsonify({"error": INVALID_INPUT_ERROR, "details": str(e)}), 400
|
| 139 |
+
|
| 140 |
+
# Diet recommendations
|
| 141 |
+
diet_recommendations = []
|
| 142 |
+
if bmi < 18.5:
|
| 143 |
+
diet_recommendations.append("Increase calorie intake with nutrient-dense foods to reach a healthy weight")
|
| 144 |
+
diet_recommendations.append("Focus on foods high in healthy fats, such as avocados, nuts, and olive oil")
|
| 145 |
+
diet_recommendations.append("Incorporate protein-rich foods like lean meats, fish, eggs, and legumes")
|
| 146 |
+
elif 18.5 <= bmi < 25:
|
| 147 |
+
diet_recommendations.append("Maintain a balanced diet with a focus on whole foods")
|
| 148 |
+
diet_recommendations.append("Ensure adequate intake of fruits, vegetables, whole grains, and lean proteins")
|
| 149 |
+
diet_recommendations.append("Monitor portion sizes to maintain your healthy weight")
|
| 150 |
+
elif 25 <= bmi < 30:
|
| 151 |
+
diet_recommendations.append("Slightly reduce calorie intake and focus on nutrient-dense, low-calorie foods")
|
| 152 |
+
diet_recommendations.append("Increase fiber intake through vegetables, fruits, and whole grains")
|
| 153 |
+
diet_recommendations.append("Choose lean proteins and limit saturated fats")
|
| 154 |
+
else:
|
| 155 |
+
diet_recommendations.append("Reduce calorie intake and focus on whole, unprocessed foods")
|
| 156 |
+
diet_recommendations.append("Prioritize vegetables, lean proteins, and complex carbohydrates")
|
| 157 |
+
diet_recommendations.append("Avoid sugary drinks and high-calorie snacks")
|
| 158 |
+
|
| 159 |
+
if (gender == 'male' and body_fat_percentage > 25) or (gender == 'female' and body_fat_percentage > 32) or (gender == 'other' and body_fat_percentage > 28):
|
| 160 |
+
diet_recommendations.append("Increase protein intake to support lean muscle mass")
|
| 161 |
+
diet_recommendations.append("Consider adding a protein shake or Greek yogurt as a snack")
|
| 162 |
+
diet_recommendations.append("Include more fish, chicken, turkey, or plant-based proteins in your meals")
|
| 163 |
+
|
| 164 |
+
diet_recommendations.append(f"Aim for {recommended_calories} calories per day")
|
| 165 |
+
diet_recommendations.append("Include a variety of colorful fruits and vegetables in your diet")
|
| 166 |
+
diet_recommendations.append("Stay hydrated by drinking at least 8 glasses of water daily")
|
| 167 |
+
diet_recommendations.append("Limit processed foods and choose whole grains over refined grains")
|
| 168 |
+
|
| 169 |
+
if weight > target_weight:
|
| 170 |
+
diet_recommendations.append("Create a calorie deficit of 500 calories per day to lose weight")
|
| 171 |
+
diet_recommendations.append("Use smaller plates to help control portion sizes")
|
| 172 |
+
diet_recommendations.append("Start meals with a salad or vegetable soup to increase satiety")
|
| 173 |
+
elif weight < target_weight:
|
| 174 |
+
diet_recommendations.append("Increase your calorie intake by 500 calories per day to gain weight")
|
| 175 |
+
diet_recommendations.append("Add healthy, calorie-dense foods like nuts, seeds, and dried fruits to your meals")
|
| 176 |
+
diet_recommendations.append("Consider drinking smoothies made with fruits, oats, and protein powder")
|
| 177 |
+
else:
|
| 178 |
+
diet_recommendations.append("Maintain your current calorie intake to maintain your weight")
|
| 179 |
+
diet_recommendations.append("Practice mindful eating and listen to your body's hunger and fullness cues")
|
| 180 |
+
|
| 181 |
+
# Exercise recommendations
|
| 182 |
+
exercise_recommendations = []
|
| 183 |
+
if steps < 5000:
|
| 184 |
+
exercise_recommendations.append("Gradually increase your daily step count to at least 7,500 steps")
|
| 185 |
+
exercise_recommendations.append("Take short walks during breaks or after meals")
|
| 186 |
+
exercise_recommendations.append("Use stairs instead of elevators when possible")
|
| 187 |
+
elif 5000 <= steps < 10000:
|
| 188 |
+
exercise_recommendations.append("Aim to reach 10,000 steps per day for better health")
|
| 189 |
+
exercise_recommendations.append("Try brisk walking or light jogging to increase step count")
|
| 190 |
+
exercise_recommendations.append("Consider using a treadmill desk or walking meetings")
|
| 191 |
+
else:
|
| 192 |
+
exercise_recommendations.append("Great job on your step count! Consider adding more intense exercises")
|
| 193 |
+
exercise_recommendations.append("Incorporate interval training or hill walks to challenge yourself")
|
| 194 |
+
exercise_recommendations.append("Set new step goals to maintain motivation")
|
| 195 |
+
|
| 196 |
+
if standing_hours < 2:
|
| 197 |
+
exercise_recommendations.append("Try to increase your standing time to at least 2-4 hours per day")
|
| 198 |
+
exercise_recommendations.append("Use a standing desk or elevate your workstation for part of the day")
|
| 199 |
+
exercise_recommendations.append("Take phone calls while standing or walking")
|
| 200 |
+
elif 2 <= standing_hours < 4:
|
| 201 |
+
exercise_recommendations.append("Good job on standing! Aim to increase your standing time to 4-6 hours per day")
|
| 202 |
+
exercise_recommendations.append("Alternate between sitting and standing every 30-60 minutes")
|
| 203 |
+
exercise_recommendations.append("Try gentle exercises or stretches while standing")
|
| 204 |
+
else:
|
| 205 |
+
exercise_recommendations.append("Excellent standing habits! Maintain your current standing routine")
|
| 206 |
+
exercise_recommendations.append("Incorporate balance exercises or yoga poses while standing")
|
| 207 |
+
exercise_recommendations.append("Consider a treadmill desk for light walking while working")
|
| 208 |
+
|
| 209 |
+
exercise_recommendations.append("Include strength training exercises at least 2-3 times per week")
|
| 210 |
+
exercise_recommendations.append("Aim for at least 150 minutes of moderate-intensity aerobic activity per week")
|
| 211 |
+
exercise_recommendations.append("Don't forget to stretch before and after exercises to improve flexibility")
|
| 212 |
+
|
| 213 |
+
if weight > target_weight:
|
| 214 |
+
exercise_recommendations.append("Incorporate high-intensity interval training (HIIT) to boost fat burning")
|
| 215 |
+
exercise_recommendations.append("Try circuit training to combine strength and cardio exercises")
|
| 216 |
+
exercise_recommendations.append("Consider joining group fitness classes for motivation and guidance")
|
| 217 |
+
elif weight < target_weight:
|
| 218 |
+
exercise_recommendations.append("Focus on compound exercises and progressive overload to build muscle mass")
|
| 219 |
+
exercise_recommendations.append("Incorporate resistance band exercises for muscle growth")
|
| 220 |
+
exercise_recommendations.append("Ensure adequate rest between workouts for muscle recovery and growth")
|
| 221 |
+
else:
|
| 222 |
+
exercise_recommendations.append("Mix cardio and strength training to maintain your current weight and improve overall fitness")
|
| 223 |
+
exercise_recommendations.append("Try new activities or sports to keep your routine interesting")
|
| 224 |
+
exercise_recommendations.append("Set performance-based goals to stay motivated")
|
| 225 |
+
|
| 226 |
+
if age > 50:
|
| 227 |
+
exercise_recommendations.append("Include balance and flexibility exercises to maintain mobility")
|
| 228 |
+
exercise_recommendations.append("Consider low-impact activities like swimming or cycling to protect joints")
|
| 229 |
+
|
| 230 |
+
response = {
|
| 231 |
+
'dietRecommendations': diet_recommendations,
|
| 232 |
+
'exerciseRecommendations': exercise_recommendations
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
app.logger.debug(f"Generated recommendations: {response}")
|
| 236 |
+
return jsonify(response)
|
| 237 |
+
|
| 238 |
+
@app.route('/api/search-food', methods=['GET'])
|
| 239 |
+
def search_food():
|
| 240 |
+
query = request.args.get('query', '')
|
| 241 |
+
if not query:
|
| 242 |
+
return jsonify({"error": "Missing query parameter"}), 400
|
| 243 |
+
|
| 244 |
+
try:
|
| 245 |
+
response = requests.get(
|
| 246 |
+
f"{USDA_API_ENDPOINT}/foods/search",
|
| 247 |
+
params={
|
| 248 |
+
"api_key": USDA_API_KEY,
|
| 249 |
+
"query": query,
|
| 250 |
+
"dataType": ["Survey (FNDDS)"],
|
| 251 |
+
"pageSize": 10
|
| 252 |
+
}
|
| 253 |
+
)
|
| 254 |
+
response.raise_for_status()
|
| 255 |
+
data = response.json()
|
| 256 |
+
|
| 257 |
+
results = []
|
| 258 |
+
for food in data.get('foods', []):
|
| 259 |
+
nutrients = {nutrient['nutrientName']: nutrient['value'] for nutrient in food.get('foodNutrients', [])}
|
| 260 |
+
results.append({
|
| 261 |
+
'description': food['description'],
|
| 262 |
+
'calories': nutrients.get('Energy', 0),
|
| 263 |
+
'protein': nutrients.get('Protein', 0),
|
| 264 |
+
'carbs': nutrients.get('Carbohydrate, by difference', 0),
|
| 265 |
+
'fat': nutrients.get('Total lipid (fat)', 0)
|
| 266 |
+
})
|
| 267 |
+
|
| 268 |
+
return jsonify(results)
|
| 269 |
+
|
| 270 |
+
except requests.RequestException as e:
|
| 271 |
+
app.logger.error(f"Error fetching food data: {str(e)}")
|
| 272 |
+
return jsonify({"error": "Failed to fetch food data"}), 500
|
| 273 |
+
|
| 274 |
+
if __name__ == '__main__':
|
| 275 |
+
app.run(host='0.0.0.0', port=5000, debug=True)
|
static/css/styles.css
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: Arial, sans-serif;
|
| 3 |
+
line-height: 1.6;
|
| 4 |
+
margin: 0;
|
| 5 |
+
padding: 20px;
|
| 6 |
+
max-width: 800px;
|
| 7 |
+
margin: 0 auto;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
h1, h2, h3 {
|
| 11 |
+
color: #333;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
form {
|
| 15 |
+
margin-bottom: 20px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
label {
|
| 19 |
+
display: block;
|
| 20 |
+
margin-bottom: 5px;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
input, select {
|
| 24 |
+
width: 100%;
|
| 25 |
+
padding: 5px;
|
| 26 |
+
margin-bottom: 10px;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
button {
|
| 30 |
+
background-color: #4CAF50;
|
| 31 |
+
color: white;
|
| 32 |
+
padding: 10px 15px;
|
| 33 |
+
border: none;
|
| 34 |
+
cursor: pointer;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
button:hover {
|
| 38 |
+
background-color: #45a049;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
ul {
|
| 42 |
+
list-style-type: none;
|
| 43 |
+
padding: 0;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
li {
|
| 47 |
+
cursor: pointer;
|
| 48 |
+
padding: 5px;
|
| 49 |
+
margin-bottom: 5px;
|
| 50 |
+
background-color: #f0f0f0;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
li:hover {
|
| 54 |
+
background-color: #e0e0e0;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
canvas {
|
| 58 |
+
border: 1px solid #ddd;
|
| 59 |
+
margin-top: 20px;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.flex-container {
|
| 63 |
+
display: flex;
|
| 64 |
+
justify-content: space-between;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.flex-container > div {
|
| 68 |
+
flex: 1;
|
| 69 |
+
margin-right: 20px;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
#visualization {
|
| 73 |
+
text-align: center;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
#bmi-display {
|
| 77 |
+
margin-top: 20px;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.notification {
|
| 81 |
+
position: fixed;
|
| 82 |
+
top: 20px;
|
| 83 |
+
right: 20px;
|
| 84 |
+
padding: 10px 20px;
|
| 85 |
+
border-radius: 5px;
|
| 86 |
+
color: white;
|
| 87 |
+
font-weight: bold;
|
| 88 |
+
opacity: 0.9;
|
| 89 |
+
transition: opacity 0.3s ease-in-out;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.notification.success {
|
| 93 |
+
background-color: #4CAF50;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.notification.info {
|
| 97 |
+
background-color: #2196F3;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.notification.error {
|
| 101 |
+
background-color: #f44336;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
@media (max-width: 600px) {
|
| 105 |
+
body {
|
| 106 |
+
padding: 10px;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.flex-container {
|
| 110 |
+
flex-direction: column;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.flex-container > div {
|
| 114 |
+
margin-right: 0;
|
| 115 |
+
margin-bottom: 20px;
|
| 116 |
+
}
|
| 117 |
+
}
|
static/data/food_items.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"Apple": {
|
| 3 |
+
"calories": 95,
|
| 4 |
+
"effect": "decrease"
|
| 5 |
+
},
|
| 6 |
+
"Banana": {
|
| 7 |
+
"calories": 105,
|
| 8 |
+
"effect": "decrease"
|
| 9 |
+
},
|
| 10 |
+
"Chicken Breast": {
|
| 11 |
+
"calories": 165,
|
| 12 |
+
"effect": "decrease"
|
| 13 |
+
},
|
| 14 |
+
"Pizza Slice": {
|
| 15 |
+
"calories": 285,
|
| 16 |
+
"effect": "increase"
|
| 17 |
+
},
|
| 18 |
+
"Salad": {
|
| 19 |
+
"calories": 100,
|
| 20 |
+
"effect": "decrease"
|
| 21 |
+
},
|
| 22 |
+
"Cheeseburger": {
|
| 23 |
+
"calories": 300,
|
| 24 |
+
"effect": "increase"
|
| 25 |
+
},
|
| 26 |
+
"Yogurt": {
|
| 27 |
+
"calories": 150,
|
| 28 |
+
"effect": "decrease"
|
| 29 |
+
},
|
| 30 |
+
"Ice Cream": {
|
| 31 |
+
"calories": 270,
|
| 32 |
+
"effect": "increase"
|
| 33 |
+
},
|
| 34 |
+
"Broccoli": {
|
| 35 |
+
"calories": 55,
|
| 36 |
+
"effect": "decrease"
|
| 37 |
+
},
|
| 38 |
+
"French Fries": {
|
| 39 |
+
"calories": 365,
|
| 40 |
+
"effect": "increase"
|
| 41 |
+
}
|
| 42 |
+
}
|
static/js/app.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
+
const userForm = document.getElementById('user-form');
|
| 3 |
+
const selectedFoodList = document.getElementById('selected-food-list');
|
| 4 |
+
const canvas = document.getElementById('belly-fat-canvas');
|
| 5 |
+
const ctx = canvas.getContext('2d');
|
| 6 |
+
const searchInput = document.getElementById('food-search');
|
| 7 |
+
const searchResults = document.getElementById('search-results');
|
| 8 |
+
|
| 9 |
+
let selectedItems = [];
|
| 10 |
+
let userMetrics = {};
|
| 11 |
+
let recommendedCalories = 2000; // Default value, will be updated after user submits metrics
|
| 12 |
+
|
| 13 |
+
function updateUserMetrics() {
|
| 14 |
+
userMetrics.age = parseInt(document.getElementById('age').value);
|
| 15 |
+
userMetrics.gender = document.getElementById('gender').value;
|
| 16 |
+
userMetrics.heightFeet = parseInt(document.getElementById('height-feet').value);
|
| 17 |
+
userMetrics.heightInches = parseInt(document.getElementById('height-inches').value);
|
| 18 |
+
userMetrics.weight = parseFloat(document.getElementById('weight').value);
|
| 19 |
+
userMetrics.targetWeight = parseFloat(document.getElementById('target-weight').value);
|
| 20 |
+
userMetrics.waist = parseFloat(document.getElementById('waist').value);
|
| 21 |
+
userMetrics.neck = parseFloat(document.getElementById('neck').value);
|
| 22 |
+
userMetrics.hip = parseFloat(document.getElementById('hip').value);
|
| 23 |
+
userMetrics.steps = parseInt(document.getElementById('steps').value);
|
| 24 |
+
userMetrics.standingHours = parseFloat(document.getElementById('standing-hours').value);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
userForm.addEventListener('submit', (e) => {
|
| 28 |
+
e.preventDefault();
|
| 29 |
+
updateUserMetrics();
|
| 30 |
+
calculateMetrics();
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
function calculateMetrics() {
|
| 34 |
+
console.log('calculateMetrics function called');
|
| 35 |
+
const heightInCm = (userMetrics.heightFeet * 30.48) + (userMetrics.heightInches * 2.54);
|
| 36 |
+
const weightInKg = userMetrics.weight * 0.45359237;
|
| 37 |
+
const targetWeightInKg = userMetrics.targetWeight * 0.45359237;
|
| 38 |
+
const waistInCm = userMetrics.waist * 2.54;
|
| 39 |
+
const neckInCm = userMetrics.neck * 2.54;
|
| 40 |
+
const hipInCm = userMetrics.hip * 2.54;
|
| 41 |
+
|
| 42 |
+
const metricsData = {
|
| 43 |
+
age: userMetrics.age,
|
| 44 |
+
gender: userMetrics.gender,
|
| 45 |
+
heightFeet: userMetrics.heightFeet,
|
| 46 |
+
heightInches: userMetrics.heightInches,
|
| 47 |
+
weight: weightInKg,
|
| 48 |
+
targetWeight: targetWeightInKg,
|
| 49 |
+
waist: waistInCm,
|
| 50 |
+
neck: neckInCm,
|
| 51 |
+
hip: hipInCm,
|
| 52 |
+
steps: userMetrics.steps,
|
| 53 |
+
standingHours: userMetrics.standingHours
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
console.log('Sending metrics data:', metricsData);
|
| 57 |
+
fetch('/api/calculate-metrics', {
|
| 58 |
+
method: 'POST',
|
| 59 |
+
headers: {
|
| 60 |
+
'Content-Type': 'application/json',
|
| 61 |
+
},
|
| 62 |
+
body: JSON.stringify(metricsData),
|
| 63 |
+
})
|
| 64 |
+
.then(response => {
|
| 65 |
+
console.log('Response status:', response.status);
|
| 66 |
+
return response.json();
|
| 67 |
+
})
|
| 68 |
+
.then(data => {
|
| 69 |
+
console.log('Received metrics data:', data);
|
| 70 |
+
if (data.error) {
|
| 71 |
+
console.error('Error calculating metrics:', data.error);
|
| 72 |
+
showNotification(`Error: ${data.error}`, 'error');
|
| 73 |
+
return;
|
| 74 |
+
}
|
| 75 |
+
document.getElementById('bmi-value').textContent = data.bmi;
|
| 76 |
+
document.getElementById('recommended-calories').textContent = data.recommendedCalories;
|
| 77 |
+
document.getElementById('body-fat-percentage').textContent = data.bodyFatPercentage + '%';
|
| 78 |
+
document.getElementById('lean-body-mass').textContent = (data.leanBodyMass * 2.20462).toFixed(2) + ' lbs';
|
| 79 |
+
document.getElementById('target-weight-time').textContent = data.timeToTargetWeight;
|
| 80 |
+
recommendedCalories = data.recommendedCalories;
|
| 81 |
+
updateBellyFatVisualization({ calories: 0, protein: 0, carbs: 0, fat: 0 });
|
| 82 |
+
getPersonalizedRecommendations(data);
|
| 83 |
+
})
|
| 84 |
+
.catch(error => {
|
| 85 |
+
console.error('Error:', error);
|
| 86 |
+
showNotification(`Error: ${error.message}`, 'error');
|
| 87 |
+
});
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
function getPersonalizedRecommendations(metricsData) {
|
| 91 |
+
const recommendationData = {
|
| 92 |
+
...userMetrics,
|
| 93 |
+
...metricsData,
|
| 94 |
+
height: (userMetrics.heightFeet * 30.48) + (userMetrics.heightInches * 2.54)
|
| 95 |
+
};
|
| 96 |
+
|
| 97 |
+
console.log('Sending recommendation data:', recommendationData);
|
| 98 |
+
fetch('/api/personalized-recommendations', {
|
| 99 |
+
method: 'POST',
|
| 100 |
+
headers: {
|
| 101 |
+
'Content-Type': 'application/json',
|
| 102 |
+
},
|
| 103 |
+
body: JSON.stringify(recommendationData),
|
| 104 |
+
})
|
| 105 |
+
.then(response => response.json())
|
| 106 |
+
.then(data => {
|
| 107 |
+
console.log('Received recommendations:', data);
|
| 108 |
+
if (data.error) {
|
| 109 |
+
console.error('Error getting recommendations:', data.error);
|
| 110 |
+
showNotification(`Error: ${data.error}`, 'error');
|
| 111 |
+
return;
|
| 112 |
+
}
|
| 113 |
+
displayRecommendations(data);
|
| 114 |
+
})
|
| 115 |
+
.catch(error => {
|
| 116 |
+
console.error('Error:', error);
|
| 117 |
+
showNotification(`Error: ${error.message}`, 'error');
|
| 118 |
+
});
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
function displayRecommendations(recommendations) {
|
| 122 |
+
const dietRecommendationsList = document.getElementById('diet-recommendations-list');
|
| 123 |
+
const exerciseRecommendationsList = document.getElementById('exercise-recommendations-list');
|
| 124 |
+
|
| 125 |
+
dietRecommendationsList.innerHTML = '';
|
| 126 |
+
exerciseRecommendationsList.innerHTML = '';
|
| 127 |
+
|
| 128 |
+
recommendations.dietRecommendations.forEach(recommendation => {
|
| 129 |
+
const li = document.createElement('li');
|
| 130 |
+
li.textContent = recommendation;
|
| 131 |
+
dietRecommendationsList.appendChild(li);
|
| 132 |
+
});
|
| 133 |
+
|
| 134 |
+
recommendations.exerciseRecommendations.forEach(recommendation => {
|
| 135 |
+
const li = document.createElement('li');
|
| 136 |
+
li.textContent = recommendation;
|
| 137 |
+
exerciseRecommendationsList.appendChild(li);
|
| 138 |
+
});
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
function searchFood() {
|
| 142 |
+
const query = searchInput.value.trim();
|
| 143 |
+
if (query.length < 2) return;
|
| 144 |
+
|
| 145 |
+
fetch(`/api/search-food?query=${encodeURIComponent(query)}`)
|
| 146 |
+
.then(response => response.json())
|
| 147 |
+
.then(data => {
|
| 148 |
+
displaySearchResults(data);
|
| 149 |
+
})
|
| 150 |
+
.catch(error => console.error('Error:', error));
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
function displaySearchResults(results) {
|
| 154 |
+
searchResults.innerHTML = '';
|
| 155 |
+
results.forEach(item => {
|
| 156 |
+
const li = document.createElement('li');
|
| 157 |
+
li.textContent = item.description;
|
| 158 |
+
li.addEventListener('click', () => addFoodItem(item));
|
| 159 |
+
searchResults.appendChild(li);
|
| 160 |
+
});
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
function addFoodItem(item) {
|
| 164 |
+
selectedItems.push(item);
|
| 165 |
+
updateSelectedFoodList();
|
| 166 |
+
updateBellyFatVisualization();
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
function updateSelectedFoodList() {
|
| 170 |
+
selectedFoodList.innerHTML = '';
|
| 171 |
+
selectedItems.forEach(item => {
|
| 172 |
+
const li = document.createElement('li');
|
| 173 |
+
li.textContent = `${item.description} (${item.calories} kcal)`;
|
| 174 |
+
selectedFoodList.appendChild(li);
|
| 175 |
+
});
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
function updateBellyFatVisualization() {
|
| 179 |
+
const totalNutrients = calculateTotalNutrients();
|
| 180 |
+
|
| 181 |
+
// Clear the canvas
|
| 182 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 183 |
+
|
| 184 |
+
// Draw yellow plate
|
| 185 |
+
ctx.beginPath();
|
| 186 |
+
ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 3, 0, 2 * Math.PI);
|
| 187 |
+
ctx.fillStyle = 'yellow';
|
| 188 |
+
ctx.fill();
|
| 189 |
+
|
| 190 |
+
// Draw green circles for food items
|
| 191 |
+
const foodItems = Math.min(selectedItems.length, 20);
|
| 192 |
+
for (let i = 0; i < foodItems; i++) {
|
| 193 |
+
const angle = (i / foodItems) * 2 * Math.PI;
|
| 194 |
+
const radius = canvas.width / 4;
|
| 195 |
+
const x = canvas.width / 2 + radius * Math.cos(angle);
|
| 196 |
+
const y = canvas.height / 2 + radius * Math.sin(angle);
|
| 197 |
+
|
| 198 |
+
ctx.beginPath();
|
| 199 |
+
ctx.arc(x, y, 10, 0, 2 * Math.PI);
|
| 200 |
+
ctx.fillStyle = 'green';
|
| 201 |
+
ctx.fill();
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
// Draw stack graph
|
| 205 |
+
drawStackGraph(totalNutrients);
|
| 206 |
+
|
| 207 |
+
// Update total calories display
|
| 208 |
+
document.getElementById('total-calories').textContent = totalNutrients.calories;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
function calculateTotalNutrients() {
|
| 212 |
+
return selectedItems.reduce((total, item) => {
|
| 213 |
+
total.calories += item.calories || 0;
|
| 214 |
+
total.protein += item.protein || 0;
|
| 215 |
+
total.carbs += item.carbs || 0;
|
| 216 |
+
total.fat += item.fat || 0;
|
| 217 |
+
return total;
|
| 218 |
+
}, { calories: 0, protein: 0, carbs: 0, fat: 0 });
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
function drawStackGraph(nutrients) {
|
| 222 |
+
const barWidth = 40;
|
| 223 |
+
const barHeight = 100;
|
| 224 |
+
const startX = canvas.width - barWidth - 10;
|
| 225 |
+
const startY = canvas.height - 10;
|
| 226 |
+
|
| 227 |
+
const totalMacros = nutrients.protein + nutrients.carbs + nutrients.fat;
|
| 228 |
+
const proteinHeight = (nutrients.protein / totalMacros) * barHeight;
|
| 229 |
+
const carbsHeight = (nutrients.carbs / totalMacros) * barHeight;
|
| 230 |
+
const fatHeight = (nutrients.fat / totalMacros) * barHeight;
|
| 231 |
+
|
| 232 |
+
ctx.fillStyle = 'red';
|
| 233 |
+
ctx.fillRect(startX, startY - fatHeight, barWidth, fatHeight);
|
| 234 |
+
|
| 235 |
+
ctx.fillStyle = 'blue';
|
| 236 |
+
ctx.fillRect(startX, startY - fatHeight - carbsHeight, barWidth, carbsHeight);
|
| 237 |
+
|
| 238 |
+
ctx.fillStyle = 'green';
|
| 239 |
+
ctx.fillRect(startX, startY - fatHeight - carbsHeight - proteinHeight, barWidth, proteinHeight);
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
// Add these event listeners
|
| 243 |
+
searchInput.addEventListener('input', searchFood);
|
| 244 |
+
|
| 245 |
+
// Call updateBellyFatVisualization initially to draw the empty plate
|
| 246 |
+
updateBellyFatVisualization();
|
| 247 |
+
});
|
templates/dashboard.html
ADDED
|
File without changes
|
templates/index.html
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Belly Fat Visualizer</title>
|
| 7 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<h1>Belly Fat Visualizer</h1>
|
| 11 |
+
|
| 12 |
+
<div id="user-metrics">
|
| 13 |
+
<h2>Enter Your Metrics</h2>
|
| 14 |
+
<form id="user-form">
|
| 15 |
+
<label for="age">Age:</label>
|
| 16 |
+
<input type="number" id="age" required>
|
| 17 |
+
|
| 18 |
+
<label for="gender">Gender:</label>
|
| 19 |
+
<select id="gender" required>
|
| 20 |
+
<option value="male">Male</option>
|
| 21 |
+
<option value="female">Female</option>
|
| 22 |
+
<option value="other">Other</option>
|
| 23 |
+
</select>
|
| 24 |
+
|
| 25 |
+
<label for="height-feet">Height (feet):</label>
|
| 26 |
+
<input type="number" id="height-feet" required>
|
| 27 |
+
|
| 28 |
+
<label for="height-inches">Height (inches):</label>
|
| 29 |
+
<input type="number" id="height-inches" required>
|
| 30 |
+
|
| 31 |
+
<label for="weight">Weight (lbs):</label>
|
| 32 |
+
<input type="number" id="weight" required>
|
| 33 |
+
|
| 34 |
+
<label for="target-weight">Target Weight (lbs):</label>
|
| 35 |
+
<input type="number" id="target-weight" required>
|
| 36 |
+
|
| 37 |
+
<label for="waist">Waist Circumference (inches):</label>
|
| 38 |
+
<input type="number" id="waist" required>
|
| 39 |
+
|
| 40 |
+
<label for="neck">Neck Circumference (inches):</label>
|
| 41 |
+
<input type="number" id="neck" required>
|
| 42 |
+
|
| 43 |
+
<label for="hip">Hip Circumference (inches):</label>
|
| 44 |
+
<input type="number" id="hip" required>
|
| 45 |
+
|
| 46 |
+
<label for="steps">Daily Steps:</label>
|
| 47 |
+
<input type="number" id="steps" required>
|
| 48 |
+
|
| 49 |
+
<label for="standing-hours">Standing Hours:</label>
|
| 50 |
+
<input type="number" id="standing-hours" required>
|
| 51 |
+
|
| 52 |
+
<button type="submit">Calculate</button>
|
| 53 |
+
</form>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div id="food-selection">
|
| 57 |
+
<h2>Select Food Items</h2>
|
| 58 |
+
<div class="search-container">
|
| 59 |
+
<input type="text" id="food-search" placeholder="Search for food...">
|
| 60 |
+
<ul id="search-results"></ul>
|
| 61 |
+
</div>
|
| 62 |
+
<div class="flex-container">
|
| 63 |
+
<div>
|
| 64 |
+
<h3>Selected Food Items:</h3>
|
| 65 |
+
<ul id="selected-food-list"></ul>
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
<div>
|
| 69 |
+
<h3>Total Calories: <span id="total-calories">0</span></h3>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<div id="visualization">
|
| 74 |
+
<h2>Belly Fat Visualization</h2>
|
| 75 |
+
<canvas id="belly-fat-canvas" width="300" height="300"></canvas>
|
| 76 |
+
<div id="health-metrics">
|
| 77 |
+
<h3>Your BMI: <span id="bmi-value"></span></h3>
|
| 78 |
+
<h3>Recommended Daily Calories: <span id="recommended-calories"></span></h3>
|
| 79 |
+
<h3>Body Fat Percentage: <span id="body-fat-percentage"></span></h3>
|
| 80 |
+
<h3>Lean Body Mass: <span id="lean-body-mass"></span></h3>
|
| 81 |
+
<h3>Time to Reach Target Weight: <span id="target-weight-time"></span></h3>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div id="recommendations">
|
| 86 |
+
<h2>Personalized Recommendations</h2>
|
| 87 |
+
<div id="diet-recommendations">
|
| 88 |
+
<h3>Diet Recommendations:</h3>
|
| 89 |
+
<ul id="diet-recommendations-list"></ul>
|
| 90 |
+
</div>
|
| 91 |
+
<div id="exercise-recommendations">
|
| 92 |
+
<h3>Exercise Recommendations:</h3>
|
| 93 |
+
<ul id="exercise-recommendations-list"></ul>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
|
| 97 |
+
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
| 98 |
+
</body>
|
| 99 |
+
</html>
|
templates/login.html
ADDED
|
File without changes
|
templates/register.html
ADDED
|
File without changes
|