diff --git a/api/ai_analyze.php b/api/ai_analyze.php
new file mode 100644
index 0000000..4d52db5
--- /dev/null
+++ b/api/ai_analyze.php
@@ -0,0 +1,51 @@
+ 'No text provided']);
+ http_response_code(400);
+ exit;
+}
+
+$prompt = "You are a nutrition expert. Analyze the following food description and provide the estimated nutritional information.
+The user is from Bulgaria, so understand Bulgarian food names (e.g., 'banitsa', 'shopska salad', 'lyutenitsa', 'kebapche', 'tarator') and language.
+If the description is in Bulgarian, translate the 'entry_name' to English but use the Bulgarian context for nutritional estimation.
+Return only a JSON object with the following keys:
+- 'entry_name': A short, descriptive name of the food (in English).
+- 'calories': Estimated total calories (integer).
+- 'protein': Estimated protein in grams (integer).
+- 'creatine': Estimated creatine in grams (float, usually 0 unless it's a supplement).
+
+If the user mentions a supplement like 'creatine', ensure to include it.
+If multiple items are mentioned, provide the total for all of them.
+
+Food description: \"$text\"";
+
+$response = LocalAIApi::createResponse([
+ 'input' => [
+ ['role' => 'system', 'content' => 'You are a nutrition expert that returns only JSON. You understand Bulgarian and international cuisine.'],
+ ['role' => 'user', 'content' => $prompt],
+ ],
+]);
+
+if (!empty($response['success'])) {
+ $decoded = LocalAIApi::decodeJsonFromResponse($response);
+ if ($decoded) {
+ echo json_encode($decoded);
+ } else {
+ $rawText = LocalAIApi::extractText($response);
+ if (preg_match('/{.*}/s', $rawText, $matches)) {
+ echo $matches[0];
+ } else {
+ echo json_encode(['error' => 'Failed to parse AI response', 'raw' => $rawText]);
+ http_response_code(500);
+ }
+ }
+} else {
+ echo json_encode(['error' => $response['error'] ?? 'AI request failed']);
+ http_response_code(500);
+}
\ No newline at end of file
diff --git a/api/analysis.php b/api/analysis.php
new file mode 100644
index 0000000..b05e2b7
--- /dev/null
+++ b/api/analysis.php
@@ -0,0 +1,30 @@
+ [],
+ 'weight' => [],
+ 'water' => []
+];
+
+// Get nutrition (last X days)
+$stmt = $pdo->prepare("SELECT DATE(created_at) as log_date, SUM(calories) as total_calories, SUM(protein) as total_protein, SUM(creatine) as total_creatine FROM nutrition_logs WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) GROUP BY log_date ORDER BY log_date ASC");
+$stmt->execute([$days]);
+$data['nutrition'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+// Get weight (last X days)
+$stmt = $pdo->prepare("SELECT weight, logged_at FROM weight_logs WHERE logged_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) ORDER BY logged_at ASC");
+$stmt->execute([$days]);
+$data['weight'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+// Get water (last X days)
+$stmt = $pdo->prepare("SELECT amount, logged_at FROM water_logs WHERE logged_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) ORDER BY logged_at ASC");
+$stmt->execute([$days]);
+$data['water'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+echo json_encode($data);
diff --git a/api/logs.php b/api/logs.php
new file mode 100644
index 0000000..8aecdc5
--- /dev/null
+++ b/api/logs.php
@@ -0,0 +1,89 @@
+query("SELECT goal_key, goal_value FROM user_goals");
+ $goals = [];
+ while ($row = $stmt->fetch()) {
+ $goals[$row['goal_key']] = (float)$row['goal_value'];
+ }
+
+ // Get today's nutrition logs
+ $stmt = $pdo->prepare("SELECT SUM(calories) as calories, SUM(protein) as protein, SUM(creatine) as creatine FROM nutrition_logs WHERE DATE(created_at) = ?");
+ $stmt->execute([$today]);
+ $stats = $stmt->fetch();
+
+ // Get today's water
+ $stmtW = $pdo->prepare("SELECT SUM(amount) as amount FROM water_logs WHERE logged_at = ?");
+ $stmtW->execute([$today]);
+ $waterStats = $stmtW->fetch();
+
+ $consumed = [
+ 'calories' => (int)($stats['calories'] ?? 0),
+ 'protein' => (int)($stats['protein'] ?? 0),
+ 'creatine' => (float)($stats['creatine'] ?? 0),
+ 'water' => (float)($waterStats['amount'] ?? 0)
+ ];
+
+ echo json_encode([
+ 'goals' => $goals,
+ 'consumed' => $consumed,
+ 'remaining' => [
+ 'calories' => max(0, ($goals['calories'] ?? 0) - $consumed['calories']),
+ 'protein' => max(0, ($goals['protein'] ?? 0) - $consumed['protein']),
+ 'creatine' => max(0, ($goals['creatine'] ?? 0) - $consumed['creatine']),
+ 'water' => max(0, ($goals['water'] ?? 0) - $consumed['water'])
+ ]
+ ]);
+ exit;
+}
+
+if ($action === 'add_log') {
+ $input = json_decode(file_get_contents('php://input'), true);
+
+ if (empty($input['entry_name'])) {
+ echo json_encode(['error' => 'Name is required']);
+ http_response_code(400);
+ exit;
+ }
+
+ $stmt = $pdo->prepare("INSERT INTO nutrition_logs (entry_name, calories, protein, creatine) VALUES (?, ?, ?, ?)");
+ $stmt->execute([
+ $input['entry_name'],
+ (int)($input['calories'] ?? 0),
+ (int)($input['protein'] ?? 0),
+ (float)($input['creatine'] ?? 0)
+ ]);
+
+ echo json_encode(['success' => true]);
+ exit;
+}
+
+if ($action === 'get_recent') {
+ $stmt = $pdo->query("SELECT * FROM nutrition_logs ORDER BY created_at DESC LIMIT 10");
+ echo json_encode($stmt->fetchAll());
+ exit;
+}
+
+if ($action === 'update_goals') {
+ $input = json_decode(file_get_contents('php://input'), true);
+
+ $stmt = $pdo->prepare("INSERT INTO user_goals (goal_key, goal_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE goal_value = VALUES(goal_value)");
+
+ $allowedKeys = ['calories', 'protein', 'creatine', 'water', 'weight'];
+ foreach ($input as $key => $value) {
+ if (in_array($key, $allowedKeys)) {
+ $stmt->execute([$key, (float)$value]);
+ }
+ }
+
+ echo json_encode(['success' => true]);
+ exit;
+}
\ No newline at end of file
diff --git a/api/water.php b/api/water.php
new file mode 100644
index 0000000..4adcbdd
--- /dev/null
+++ b/api/water.php
@@ -0,0 +1,42 @@
+ false, 'error' => 'Invalid amount']);
+ exit;
+ }
+
+ try {
+ // Find existing record for today
+ $stmt = $pdo->prepare("SELECT id, amount FROM water_logs WHERE logged_at = ?");
+ $stmt->execute([$date]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if ($row) {
+ $newAmount = $row['amount'] + $amount;
+ $stmtUpdate = $pdo->prepare("UPDATE water_logs SET amount = ? WHERE id = ?");
+ $stmtUpdate->execute([$newAmount, $row['id']]);
+ } else {
+ $stmtInsert = $pdo->prepare("INSERT INTO water_logs (amount, logged_at) VALUES (?, ?)");
+ $stmtInsert->execute([$amount, $date]);
+ }
+ echo json_encode(['success' => true]);
+ } catch (PDOException $e) {
+ echo json_encode(['success' => false, 'error' => $e->getMessage()]);
+ }
+} elseif ($method === 'GET') {
+ $date = $_GET['date'] ?? date('Y-m-d');
+ $stmt = $pdo->prepare("SELECT amount FROM water_logs WHERE logged_at = ?");
+ $stmt->execute([$date]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ echo json_encode(['amount' => $row ? $row['amount'] : 0]);
+}
diff --git a/api/weight.php b/api/weight.php
new file mode 100644
index 0000000..fefa8dd
--- /dev/null
+++ b/api/weight.php
@@ -0,0 +1,28 @@
+ false, 'error' => 'Invalid weight']);
+ exit;
+ }
+
+ try {
+ $stmt = $pdo->prepare("INSERT INTO weight_logs (weight, logged_at) VALUES (?, ?) ON DUPLICATE KEY UPDATE weight = VALUES(weight)");
+ $stmt->execute([$weight, $date]);
+ echo json_encode(['success' => true]);
+ } catch (PDOException $e) {
+ echo json_encode(['success' => false, 'error' => $e->getMessage()]);
+ }
+} elseif ($method === 'GET') {
+ $stmt = $pdo->query("SELECT weight, logged_at FROM weight_logs ORDER BY logged_at DESC LIMIT 30");
+ echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
+}
diff --git a/assets/css/custom.css b/assets/css/custom.css
index 50e0502..1dd9c4d 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,302 +1,168 @@
+:root {
+ --primary: #3b82f6;
+ --primary-hover: #2563eb;
+ --bg: #0f172a;
+ --surface: #1e293b;
+ --border: #334155;
+ --text: #f8fafc;
+ --text-muted: #94a3b8;
+ --success: #10b981;
+ --info: #06b6d4;
+ --warning: #f59e0b;
+ --radius: 12px;
+ --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+}
+
body {
- background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
- background-size: 400% 400%;
- animation: gradient 15s ease infinite;
- color: #212529;
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- font-size: 14px;
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
+ background-color: var(--bg);
+ color: var(--text);
margin: 0;
- min-height: 100vh;
+ padding: 0;
+ -webkit-font-smoothing: antialiased;
}
-.main-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 100vh;
- width: 100%;
+.container-mobile {
+ max-width: 480px;
+ margin: 0 auto;
+ padding: 24px 16px;
+ padding-bottom: 80px; /* footer space */
+}
+
+header {
+ margin-bottom: 24px;
+}
+
+.card-stat {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
padding: 20px;
- box-sizing: border-box;
- position: relative;
- z-index: 1;
+ margin-bottom: 16px;
+ box-shadow: var(--card-shadow);
+ transition: transform 0.2s ease;
}
-@keyframes gradient {
- 0% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 100% 50%;
- }
- 100% {
- background-position: 0% 50%;
- }
+.stat-label {
+ font-size: 0.7rem;
+ color: var(--text-muted);
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
}
-.chat-container {
- width: 100%;
- max-width: 600px;
- background: rgba(255, 255, 255, 0.85);
- border: 1px solid rgba(255, 255, 255, 0.3);
- border-radius: 20px;
- display: flex;
- flex-direction: column;
- height: 85vh;
- box-shadow: 0 20px 40px rgba(0,0,0,0.2);
- backdrop-filter: blur(15px);
- -webkit-backdrop-filter: blur(15px);
- overflow: hidden;
-}
-
-.chat-header {
- padding: 1.5rem;
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
- background: rgba(255, 255, 255, 0.5);
+.stat-value {
+ font-size: 1.75rem;
font-weight: 700;
- font-size: 1.1rem;
+ margin: 4px 0;
+ color: var(--text);
+}
+
+.progress-thin {
+ height: 6px;
+ background-color: var(--border);
+ border-radius: 4px;
+ overflow: hidden;
+ margin-top: 8px;
+}
+
+.progress-bar-inner {
+ height: 100%;
+ background-color: var(--primary);
+ transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.3s ease;
+}
+
+.btn-primary-custom {
+ background-color: var(--primary);
+ color: white;
+ border: none;
+ border-radius: var(--radius);
+ padding: 14px 24px;
+ font-weight: 600;
+ width: 100%;
+ transition: all 0.2s ease;
+}
+
+.btn-primary-custom:hover {
+ background-color: var(--primary-hover);
+}
+
+.log-item {
display: flex;
justify-content: space-between;
align-items: center;
+ padding: 14px;
+ background: var(--surface);
+ border-radius: var(--radius);
+ margin-bottom: 8px;
+ border: 1px solid var(--border);
}
-.chat-messages {
- flex: 1;
- overflow-y: auto;
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 1.25rem;
-}
-
-/* Custom Scrollbar */
-::-webkit-scrollbar {
- width: 6px;
-}
-
-::-webkit-scrollbar-track {
- background: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.3);
- border-radius: 10px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.5);
-}
-
-.message {
- max-width: 85%;
- padding: 0.85rem 1.1rem;
- border-radius: 16px;
- line-height: 1.5;
- font-size: 0.95rem;
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
- animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
-}
-
-@keyframes fadeIn {
- from { opacity: 0; transform: translateY(20px) scale(0.95); }
- to { opacity: 1; transform: translateY(0) scale(1); }
-}
-
-.message.visitor {
- align-self: flex-end;
- background: linear-gradient(135deg, #212529 0%, #343a40 100%);
- color: #fff;
- border-bottom-right-radius: 4px;
-}
-
-.message.bot {
- align-self: flex-start;
- background: #ffffff;
- color: #212529;
- border-bottom-left-radius: 4px;
-}
-
-.chat-input-area {
- padding: 1.25rem;
- background: rgba(255, 255, 255, 0.5);
- border-top: 1px solid rgba(0, 0, 0, 0.05);
-}
-
-.chat-input-area form {
- display: flex;
- gap: 0.75rem;
-}
-
-.chat-input-area input {
- flex: 1;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- padding: 0.75rem 1rem;
- outline: none;
- background: rgba(255, 255, 255, 0.9);
- transition: all 0.3s ease;
-}
-
-.chat-input-area input:focus {
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
-}
-
-.chat-input-area button {
- background: #212529;
- color: #fff;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.3s ease;
-}
-
-.chat-input-area button:hover {
- background: #000;
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
-}
-
-/* Background Animations */
-.bg-animations {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- overflow: hidden;
- pointer-events: none;
-}
-
-.blob {
- position: absolute;
- width: 500px;
- height: 500px;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 50%;
- filter: blur(80px);
- animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
-}
-
-.blob-1 {
- top: -10%;
- left: -10%;
- background: rgba(238, 119, 82, 0.4);
-}
-
-.blob-2 {
- bottom: -10%;
- right: -10%;
- background: rgba(35, 166, 213, 0.4);
- animation-delay: -7s;
- width: 600px;
- height: 600px;
-}
-
-.blob-3 {
- top: 40%;
- left: 30%;
- background: rgba(231, 60, 126, 0.3);
- animation-delay: -14s;
- width: 450px;
- height: 450px;
-}
-
-@keyframes move {
- 0% { transform: translate(0, 0) rotate(0deg) scale(1); }
- 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
- 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
- 100% { transform: translate(0, 0) rotate(360deg) scale(1); }
-}
-
-.admin-link {
- font-size: 14px;
- color: #fff;
- text-decoration: none;
- background: rgba(0, 0, 0, 0.2);
- padding: 0.5rem 1rem;
- border-radius: 8px;
- transition: all 0.3s ease;
-}
-
-.admin-link:hover {
- background: rgba(0, 0, 0, 0.4);
- text-decoration: none;
-}
-
-/* Admin Styles */
-.admin-container {
- max-width: 900px;
- margin: 3rem auto;
- padding: 2.5rem;
- background: rgba(255, 255, 255, 0.85);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border-radius: 24px;
- box-shadow: 0 20px 50px rgba(0,0,0,0.15);
- border: 1px solid rgba(255, 255, 255, 0.4);
- position: relative;
- z-index: 1;
-}
-
-.admin-container h1 {
- margin-top: 0;
- color: #212529;
- font-weight: 800;
-}
-
-.table {
- width: 100%;
- border-collapse: separate;
- border-spacing: 0 8px;
- margin-top: 1.5rem;
-}
-
-.table th {
- background: transparent;
- border: none;
- padding: 1rem;
- color: #6c757d;
- font-weight: 600;
- text-transform: uppercase;
+.log-details {
font-size: 0.75rem;
- letter-spacing: 1px;
+ color: var(--text-muted);
}
-.table td {
- background: #fff;
- padding: 1rem;
- border: none;
-}
-
-.table tr td:first-child { border-radius: 12px 0 0 12px; }
-.table tr td:last-child { border-radius: 0 12px 12px 0; }
-
-.form-group {
- margin-bottom: 1.25rem;
-}
-
-.form-group label {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: 600;
- font-size: 0.9rem;
+.modal-content {
+ background-color: var(--surface);
+ color: var(--text);
+ border-radius: 20px;
+ border: 1px solid var(--border);
}
.form-control {
- width: 100%;
- padding: 0.75rem 1rem;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- background: #fff;
- transition: all 0.3s ease;
- box-sizing: border-box;
+ background-color: var(--bg);
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ padding: 12px 16px;
+ color: var(--text);
}
.form-control:focus {
- outline: none;
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
-}
\ No newline at end of file
+ background-color: var(--bg);
+ border-color: var(--primary);
+ color: var(--text);
+ box-shadow: none;
+}
+
+/* Tabs styling */
+.nav-pills {
+ background: var(--surface);
+ padding: 4px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+}
+
+.nav-pills .nav-link {
+ color: var(--text-muted);
+ border-radius: calc(var(--radius) - 4px);
+ font-weight: 600;
+ transition: all 0.2s;
+ font-size: 0.85rem;
+}
+
+.nav-pills .nav-link.active {
+ background-color: var(--bg);
+ color: var(--primary);
+ box-shadow: 0 4px 6px -1px rgba(0,0,0,0.3);
+}
+
+.btn-outline-secondary { border-color: var(--border); color: var(--text-muted); }
+.btn-outline-secondary:hover { background-color: var(--border); color: var(--text); }
+
+.bg-success-subtle { background-color: rgba(16, 185, 129, 0.1) !important; }
+.bg-secondary-subtle { background-color: rgba(51, 65, 85, 0.3) !important; }
+
+#reminders-row::-webkit-scrollbar { display: none; }
+
+.btn-xs { padding: 0.25rem 0.5rem; font-size: 0.7rem; }
+
+/* Custom colors for stats */
+.text-info { color: var(--info) !important; }
+.text-warning { color: var(--warning) !important; }
+.bg-info { background-color: var(--info) !important; }
+.bg-warning { background-color: var(--warning) !important; }
+
+canvas {
+ max-width: 100% !important;
+}
diff --git a/assets/js/main.js b/assets/js/main.js
index d349598..0b027fc 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,39 +1,383 @@
-document.addEventListener('DOMContentLoaded', () => {
- const chatForm = document.getElementById('chat-form');
- const chatInput = document.getElementById('chat-input');
- const chatMessages = document.getElementById('chat-messages');
+document.addEventListener('DOMContentLoaded', function() {
+ // Elements
+ const statsContainer = document.getElementById('stats-container');
+ const logsList = document.getElementById('recent-logs-list');
+ const addLogForm = document.getElementById('addLogForm');
+ const updateGoalsForm = document.getElementById('updateGoalsForm');
+ const weightForm = document.getElementById('weightForm');
+
+ // AI Elements
+ const btnAnalyzeAI = document.getElementById('btnAnalyzeAI');
+ const aiInput = document.getElementById('aiInput');
+ const aiFeedback = document.getElementById('aiFeedback');
+ const aiBtnText = document.getElementById('aiBtnText');
+ const aiBtnSpinner = document.getElementById('aiBtnSpinner');
+ const manualTab = document.getElementById('manual-tab');
+
+ // Tab Elements
+ const analysisTab = document.getElementById('analysis-tab');
+ const periodWeekly = document.getElementById('periodWeekly');
+ const periodMonthly = document.getElementById('periodMonthly');
- const appendMessage = (text, sender) => {
- const msgDiv = document.createElement('div');
- msgDiv.classList.add('message', sender);
- msgDiv.textContent = text;
- chatMessages.appendChild(msgDiv);
- chatMessages.scrollTop = chatMessages.scrollHeight;
- };
+ let weightChart = null;
+ let caloriesChart = null;
- chatForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const message = chatInput.value.trim();
- if (!message) return;
+ // Initial fetch
+ refreshAll();
- appendMessage(message, 'visitor');
- chatInput.value = '';
+ function refreshAll() {
+ fetchStats();
+ fetchRecentLogs();
+ fetchWater();
+ fetchWeight();
+ }
- try {
- const response = await fetch('api/chat.php', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ message })
+ // --- NUTRITION & STATS ---
+ function fetchStats() {
+ fetch('api/logs.php?action=get_stats')
+ .then(res => res.json())
+ .then(data => {
+ const { goals, consumed, remaining } = data;
+
+ // Update Goals (UI)
+ setText('cal-goal', goals.calories);
+ setText('pro-goal', goals.protein);
+ setText('cre-goal', goals.creatine || 0);
+
+ // Update Consumed (UI)
+ setText('cal-consumed', consumed.calories);
+ setText('pro-consumed', consumed.protein);
+ setText('cre-consumed', consumed.creatine || 0);
+
+ // Update Left (UI)
+ setText('cal-left', Math.max(0, remaining.calories));
+ setText('pro-left', Math.max(0, remaining.protein));
+ // setText('cre-left', Math.max(0, (goals.creatine || 0) - (consumed.creatine || 0)));
+
+ // Update Progress (UI)
+ updateProgress('cal-progress', consumed.calories, goals.calories);
+ updateProgress('pro-progress', consumed.protein, goals.protein);
+
+ // Supplements status
+ const creGoal = parseFloat(goals.creatine || 0);
+ const creCons = parseFloat(consumed.creatine || 0);
+ updateProgress('cre-progress-health', creCons, creGoal);
+ const creBadge = document.getElementById('cre-status');
+ if (creCons >= creGoal && creGoal > 0) {
+ creBadge.innerText = 'Taken';
+ creBadge.className = 'badge bg-success';
+ } else {
+ creBadge.innerText = 'Not taken';
+ creBadge.className = 'badge bg-danger';
+ }
+
+ // Pre-fill goal form
+ document.getElementById('goalCalories').value = goals.calories;
+ document.getElementById('goalProtein').value = goals.protein;
+ document.getElementById('goalCreatine').value = goals.creatine;
+ document.getElementById('goalWater').value = goals.water || 2.5;
+
+ updateReminders(consumed, goals);
});
- const data = await response.json();
-
- // Artificial delay for realism
- setTimeout(() => {
- appendMessage(data.reply, 'bot');
- }, 500);
- } catch (error) {
- console.error('Error:', error);
- appendMessage("Sorry, something went wrong. Please try again.", 'bot');
+ }
+
+ function updateProgress(id, consumed, goal) {
+ const el = document.getElementById(id);
+ if (!el) return;
+ const percent = goal > 0 ? Math.min(100, (consumed / goal) * 100) : 0;
+ el.style.width = percent + '%';
+ if (percent >= 100) {
+ el.style.backgroundColor = 'var(--success)';
+ } else {
+ // Keep original if not success (some bars have specific colors)
+ if (!el.classList.contains('bg-info')) {
+ el.style.backgroundColor = 'var(--primary)';
+ }
}
+ }
+
+ function setText(id, val) {
+ const el = document.getElementById(id);
+ if (el) el.innerText = val;
+ }
+
+ function fetchRecentLogs() {
+ fetch('api/logs.php?action=get_recent')
+ .then(res => res.json())
+ .then(data => {
+ if (data.length === 0) {
+ logsList.innerHTML = '
';
+ return;
+ }
+ logsList.innerHTML = data.map(log => `
+
+
+
${log.entry_name}
+
${log.calories} kcal · ${log.protein}g protein ${log.creatine > 0 ? '· ' + log.creatine + 'g creatine' : ''}
+
+
${new Date(log.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
+
+ `).join('');
+ });
+ }
+
+ // --- WATER ---
+ function fetchWater() {
+ fetch('api/water.php')
+ .then(res => res.json())
+ .then(data => {
+ const amount = data.amount || 0;
+ setText('water-consumed', amount.toFixed(2));
+ fetch('api/logs.php?action=get_stats')
+ .then(res => res.json())
+ .then(stats => {
+ const goal = stats.goals.water || 2.5;
+ setText('water-goal', goal);
+ updateProgress('water-progress', amount, goal);
+ });
+ });
+ }
+
+ document.querySelectorAll('.water-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const amount = parseFloat(btn.dataset.amount);
+ logWater(amount);
+ });
+ });
+
+ document.getElementById('btnAddWater').addEventListener('click', () => {
+ logWater(0.25); // Default add 250ml
+ });
+
+ function logWater(amount) {
+ fetch('api/water.php', {
+ method: 'POST',
+ body: JSON.stringify({ amount: amount })
+ }).then(() => fetchWater());
+ }
+
+ // --- WEIGHT ---
+ function fetchWeight() {
+ fetch('api/weight.php')
+ .then(res => res.json())
+ .then(data => {
+ if (data.length > 0) {
+ const last = data[0];
+ setText('weight-current', last.weight);
+ setText('weight-last-date', 'Last logged: ' + last.logged_at);
+ document.getElementById('weightInput').value = last.weight;
+ }
+ });
+ }
+
+ weightForm.addEventListener('submit', function(e) {
+ e.preventDefault();
+ const weight = document.getElementById('weightInput').value;
+ fetch('api/weight.php', {
+ method: 'POST',
+ body: JSON.stringify({ weight: weight })
+ }).then(res => res.json())
+ .then(res => {
+ if (res.success) {
+ bootstrap.Modal.getInstance(document.getElementById('weightModal')).hide();
+ fetchWeight();
+ }
+ });
+ });
+
+ // --- REMINDERS ---
+ function updateReminders(consumed, goals) {
+ const row = document.getElementById('reminders-row');
+ row.innerHTML = '';
+
+ const items = [
+ { label: 'Water', current: consumed.water || 0, goal: goals.water || 2.5, unit: 'L', color: 'info' },
+ { label: 'Protein', current: consumed.protein, goal: goals.protein, unit: 'g', color: 'primary' },
+ { label: 'Creatine', current: consumed.creatine || 0, goal: goals.creatine || 5, unit: 'g', color: 'warning' }
+ ];
+
+ // Fetch water separately for reminders since it's not in get_stats usually
+ fetch('api/water.php').then(res => res.json()).then(waterData => {
+ items[0].current = waterData.amount || 0;
+
+ items.forEach(item => {
+ const done = item.current >= item.goal && item.goal > 0;
+ const div = document.createElement('div');
+ div.className = `badge ${done ? 'bg-success-subtle text-success' : 'bg-secondary-subtle text-muted'} border border-opacity-10 px-3 py-2 rounded-pill flex-shrink-0 d-flex align-items-center gap-2`;
+ div.innerHTML = `
+
+ ${item.label}: ${done ? 'Done' : (item.goal - item.current).toFixed(1) + item.unit + ' left'}
+ `;
+ row.appendChild(div);
+ });
+ });
+ }
+
+ // --- ANALYSIS & CHARTS ---
+ analysisTab.addEventListener('shown.bs.tab', () => {
+ renderAnalysis();
+ });
+
+ periodWeekly.addEventListener('change', renderAnalysis);
+ periodMonthly.addEventListener('change', renderAnalysis);
+
+ function renderAnalysis() {
+ const type = periodMonthly.checked ? 'monthly' : 'weekly';
+ fetch(`api/analysis.php?type=${type}`)
+ .then(res => res.json())
+ .then(data => {
+ initWeightChart(data.weight);
+ initCaloriesChart(data.nutrition);
+ });
+ }
+
+ function initWeightChart(weightData) {
+ const ctx = document.getElementById('weightChart').getContext('2d');
+ if (weightChart) weightChart.destroy();
+
+ weightChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: weightData.map(d => d.logged_at),
+ datasets: [{
+ label: 'Weight (kg)',
+ data: weightData.map(d => d.weight),
+ borderColor: '#f59e0b',
+ backgroundColor: 'rgba(245, 158, 11, 0.1)',
+ fill: true,
+ tension: 0.4
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: { legend: { display: false } },
+ scales: {
+ y: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } },
+ x: { grid: { display: false }, ticks: { color: '#94a3b8' } }
+ }
+ }
+ });
+ }
+
+ function initCaloriesChart(nutritionData) {
+ const ctx = document.getElementById('caloriesChart').getContext('2d');
+ if (caloriesChart) caloriesChart.destroy();
+
+ // Get goal from stats
+ fetch('api/logs.php?action=get_stats').then(res => res.json()).then(stats => {
+ const goal = stats.goals.calories;
+ caloriesChart = new Chart(ctx, {
+ type: 'bar',
+ data: {
+ labels: nutritionData.map(d => d.log_date),
+ datasets: [{
+ label: 'Consumed',
+ data: nutritionData.map(d => d.total_calories),
+ backgroundColor: nutritionData.map(d => d.total_calories >= goal ? '#10b981' : '#3b82f6'),
+ borderRadius: 4
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: { display: false },
+ annotation: {
+ annotations: {
+ line1: {
+ type: 'line',
+ yMin: goal,
+ yMax: goal,
+ borderColor: 'rgba(255, 255, 255, 0.5)',
+ borderWidth: 2,
+ borderDash: [6, 6]
+ }
+ }
+ }
+ },
+ scales: {
+ y: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } },
+ x: { grid: { display: false }, ticks: { color: '#94a3b8' } }
+ }
+ }
+ });
+ });
+ }
+
+ // --- FORM HANDLERS ---
+ addLogForm.addEventListener('submit', function(e) {
+ e.preventDefault();
+ const data = {
+ entry_name: document.getElementById('logName').value,
+ calories: document.getElementById('logCalories').value || 0,
+ protein: document.getElementById('logProtein').value || 0,
+ creatine: document.getElementById('logCreatine').value || 0
+ };
+
+ fetch('api/logs.php?action=add_log', {
+ method: 'POST',
+ body: JSON.stringify(data)
+ }).then(res => res.json())
+ .then(res => {
+ if (res.success) {
+ bootstrap.Modal.getInstance(document.getElementById('addLogModal')).hide();
+ addLogForm.reset();
+ aiInput.value = '';
+ refreshAll();
+ }
+ });
+ });
+
+ btnAnalyzeAI.addEventListener('click', function() {
+ const text = aiInput.value.trim();
+ if (!text) return;
+
+ aiBtnText.textContent = 'Analyzing...';
+ aiBtnSpinner.classList.remove('d-none');
+ btnAnalyzeAI.disabled = true;
+
+ fetch('api/ai_analyze.php', {
+ method: 'POST',
+ body: JSON.stringify({ text: text })
+ })
+ .then(res => res.json())
+ .then(data => {
+ document.getElementById('logName').value = data.entry_name;
+ document.getElementById('logCalories').value = data.calories;
+ document.getElementById('logProtein').value = data.protein;
+ document.getElementById('logCreatine').value = data.creatine;
+ new bootstrap.Tab(document.getElementById('manual-tab')).show();
+ resetAIButton();
+ })
+ .catch(() => resetAIButton());
+ });
+
+ function resetAIButton() {
+ aiBtnText.textContent = 'Analyze with AI';
+ aiBtnSpinner.classList.add('d-none');
+ btnAnalyzeAI.disabled = false;
+ }
+
+ updateGoalsForm.addEventListener('submit', function(e) {
+ e.preventDefault();
+ const data = {
+ calories: document.getElementById('goalCalories').value,
+ protein: document.getElementById('goalProtein').value,
+ creatine: document.getElementById('goalCreatine').value,
+ water: document.getElementById('goalWater').value,
+ weight: 75 // placeholder or add input if needed
+ };
+
+ fetch('api/logs.php?action=update_goals', {
+ method: 'POST',
+ body: JSON.stringify(data)
+ }).then(res => res.json())
+ .then(res => {
+ if (res.success) {
+ bootstrap.Modal.getInstance(document.getElementById('goalsModal')).hide();
+ refreshAll();
+ }
+ });
});
});
diff --git a/index.php b/index.php
index 7205f3d..0f7cfb5 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,285 @@
-
-
-
-
-
- New Style
-exec("INSERT IGNORE INTO user_goals (goal_key, goal_value) VALUES ('calories', 2500), ('protein', 130), ('creatine', 5), ('water', 2.5), ('weight', 75)");
+
+$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Modern nutrition tracking for athletes.';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
-
-
-
-
+
+
+
+
+
+ Nutrition Pulse
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
-
Loading…
+
+
+
+
+
Nutrition Pulse
+
= date('l, M d') ?>
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calories
+ 0 kcal left
+
+
0 / 0
+
+
+
+
+
+
+ Protein
+ 0g left
+
+
0g / 0g
+
+
+
+
+
+
+
+
+
Today's History
+
+
+
No entries today yet.
+
+
+
+
+
+
+
+
+
+
+ Water Intake
+ 0 / 0 L
+
+
+
+
+
+
+
+
+
+
+
+ Current Weight
+ Last logged: --
+
+
+ 0
+ kg
+
+
+
+
+
+
+
Supplements
+
+
+ Creatine
+ Not taken
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Calories vs Goal
+
+
+
+
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
-
-
+
+
+
+
+
+
+
+
New Log
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file