PageSourceSearch

https://my-diet-tracker.pages.dev/app.js

js my-diet-tracker.pages.dev collected 2026-10-03 05:40:36 UTC 110,672 bytes, 1,977 lines download raw bytes

1"use strict";
2
3const STORAGE_KEY = "myDietTracker.v1";
4const CLOUD_CONFIG_KEY = "myDietTracker.cloudConfig";
5const CLOUD_SESSION_KEY = "myDietTracker.cloudSession";
6const LOCAL_STATE_PRESENT_AT_LAUNCH = localStorage.getItem(STORAGE_KEY) !== null;
7
8const mealLabels = {
9  breakfast: "الفطار",
10  snack1: "سناك 1",
11  lunch: "الغداء",
12  snack2: "سناك 2",
13  dinner: "العشاء"
14};
15
16const unitLabels = {
17  g: "جم",
18  ml: "مل",
19  piece: "قطعة",
20  serving: "حصة",
21  tbsp: "ملعقة كبيرة",
22  cup: "كوب"
23};
24
25const viewTitles = {
26  dashboard: "اليوم",
27  add: "تسجيل وجبة",
28  mealplanner: "حاسبة الوجبة",
29  explore: "استكشف الأطعمة",
30  foods: "مكتبتي",
31  calculator: "حاسبة السعرات",
32  history: "السجل",
33  progress: "الوزن والتقدم",
34  settings: "الإعدادات"
35};
36
37const defaultCategories = [
38  { id: "protein", name: "بروتينات", icon: "P", custom: false },
39  { id: "carbs", name: "نشويات", icon: "C", custom: false },
40  { id: "dairy", name: "ألبان وأجبان", icon: "D", custom: false },
41  { id: "legumes", name: "بقوليات", icon: "L", custom: false },
42  { id: "vegetables", name: "خضار", icon: "V", custom: false },
43  { id: "fruits", name: "فاكهة", icon: "F", custom: false },
44  { id: "fats", name: "دهون ومكسرات", icon: "N", custom: false },
45  { id: "drinks", name: "مشروبات", icon: "B", custom: false },
46  { id: "egyptian", name: "أكلات مصرية", icon: "E", custom: false },
47  { id: "meals", name: "وجبات وأكل بيتي", icon: "M", custom: false },
48  { id: "supplements", name: "مكملات", icon: "S", custom: false },
49  { id: "uncategorized", name: "غير مصنف", icon: "?", custom: false }
50];
51
52const defaultFoods = [
53  food("base-qareesh", "جبنة قريش", 100, "g", 100, 12, 4, 4, 0, "dairy", true),
54  food("base-egg-boiled", "بيض مسلوق", 1, "piece", 78, 6.3, 0.6, 5.3, 0, "protein", true),
55  food("base-egg-white", "بياض بيضة", 1, "piece", 17, 3.6, 0.2, 0.1, 0, "protein"),
56  food("base-rice-cooked", "رز أبيض مطبوخ", 100, "g", 130, 2.7, 28.2, 0.3, 0.4, "carbs", true),
57  food("base-rice-raw", "أرز أبيض ني", 100, "g", 365, 7.1, 80, 0.7, 1.3, "carbs"),
58  food("base-brown-rice", "أرز بني مطبوخ", 100, "g", 123, 2.7, 25.6, 1, 1.6, "carbs"),
59  researchedFood("egy-rice-sayadiya", "رز صيادية", 100, "g", 170, 3.2, 31, 3.6, 1.1, "egyptian", "تقدير وصفة مصرية مبني على مكونات USDA", "متوسط رز مطهو ببصل محمر وكمية زيت معتدلة؛ كمية الزيت والبصل تغيّر القيمة."),
60  researchedFood("egy-rice-koshari-lentils", "رز كشري (رز وعدس)", 100, "g", 158, 5.1, 29, 2.4, 3.2, "egyptian", "تقدير وصفة مصرية مبني على مكونات USDA", "رز وعدس مطبوخان بكمية زيت معتدلة؛ لا يشمل مكرونة أو حمص أو دقة الكشري الكامل."),
61  researchedFood("egy-rice-tomato", "رز بالطماطم", 100, "g", 148, 2.8, 28, 2.9, 1.2, "egyptian", "تقدير وصفة منزلية مبني على مكونات USDA", "متوسط رز مطهو بالطماطم وكمية زيت معتدلة."),
62  researchedFood("egy-rice-tomato-potato", "رز بالطماطم والبطاطس", 100, "g", 137, 2.7, 25.5, 2.8, 1.6, "egyptian", "تقدير وصفة منزلية مبني على مكونات USDA", "القيمة لكل 100 جم من الطبق النهائي؛ تختلف حسب نسبة الرز للبطاطس وكمية الزيت."),
63  researchedFood("base-chicken-cooked", "صدر فراخ مشوي بدون جلد", 100, "g", 165, 31, 0, 3.6, 0, "protein", "USDA FoodData Central", "القيمة لكل 100 جم بعد الطهي، بدون جلد أو صوص.", true),
64  researchedFood("base-chicken-raw", "صدر فراخ ني بدون جلد", 100, "g", 120, 22.5, 0, 2.6, 0, "protein", "USDA FoodData Central", "القيمة لكل 100 جم قبل الطهي.") ,
65  researchedFood("base-chicken-thigh", "ورك فراخ منزوع الجلد مطهو", 100, "g", 209, 26, 0, 10.9, 0, "protein", "USDA FoodData Central", "القيمة لكل 100 جم من اللحم المطهو دون جلد."),
66  food("base-beef", "لحم بقري قليل الدهن مطهو", 100, "g", 217, 26, 0, 12, 0, "protein"),
67  food("base-minced-beef", "لحم مفروم متوسط الدهن مطهو", 100, "g", 250, 26, 0, 17, 0, "protein"),
68  food("base-tuna", "تونة مصفاة بالماء", 100, "g", 116, 25.5, 0, 0.8, 0, "protein", true),
69  food("base-salmon", "سلمون مشوي", 100, "g", 206, 22, 0, 12, 0, "protein"),
70  food("base-shrimp", "جمبري مطهو", 100, "g", 99, 24, 0.2, 0.3, 0, "protein"),
71  food("base-baladi", "رغيف عيش بلدي متوسط", 1, "piece", 250, 8, 50, 2, 4, "carbs", true),
72  food("base-half-baladi", "نصف رغيف عيش بلدي", 1, "piece", 125, 4, 25, 1, 2, "carbs"),
73  food("base-toast", "توست أسمر", 1, "piece", 75, 3, 13, 1.1, 2, "carbs"),
74  food("base-oats", "شوفان جاف", 100, "g", 389, 16.9, 66.3, 6.9, 10.6, "carbs", true),
75  food("base-yogurt", "زبادي طبيعي كامل الدسم", 100, "g", 61, 3.5, 4.7, 3.3, 0, "dairy"),
76  food("base-greek-yogurt", "زبادي يوناني قليل الدسم", 100, "g", 73, 9.5, 3.9, 2, 0, "dairy", true),
77  food("base-milk", "لبن نصف دسم", 100, "ml", 50, 3.4, 4.8, 1.8, 0, "dairy"),
78  food("base-skim-milk", "لبن خالي الدسم", 100, "ml", 35, 3.4, 5, 0.1, 0, "dairy"),
79  food("base-cheddar", "جبنة شيدر", 100, "g", 403, 25, 1.3, 33, 0, "dairy"),
80  researchedFood("base-potato", "بطاطس مسلوقة بدون إضافات", 100, "g", 87, 1.9, 20.1, 0.1, 1.8, "carbs", "USDA FoodData Central", "القيمة لكل 100 جم بعد السلق، دون زيت أو زبدة."),
81  researchedFood("potato-fried", "بطاطس محمرة / مقلية", 100, "g", 312, 3.4, 41.4, 14.7, 3.8, "carbs", "USDA FoodData Central", "متوسط بطاطس مقلية في الزيت؛ نوع الزيت ومدة القلي يغيران القيمة."),
82  researchedFood("potato-baked", "بطاطس مشوية بدون زيت", 100, "g", 93, 2.5, 21.2, 0.1, 2.2, "carbs", "USDA FoodData Central", "القيمة لكل 100 جم من البطاطس المشوية دون إضافات."),
83  egyptianFood("potato-wedges-light", "بطاطس ودجز بالفرن - زيت خفيف", 145, 2.5, 24, 4.5, 2.5),
84  egyptianFood("potato-mashed-milk", "بطاطس بيوريه باللبن", 113, 1.9, 16.8, 4.2, 1.5),
85  egyptianFood("egy-potato-tomato", "بطاطس مطبوخة بالصلصة", 105, 2, 15, 4.5, 2),
86  researchedFood("potato-chips", "شيبسي بطاطس", 100, "g", 536, 7, 53, 35, 4.8, "carbs", "USDA FoodData Central", "متوسط رقائق البطاطس المقلية؛ راجع عبوة المنتج عند توفرها."),
87  food("base-sweet-potato", "بطاطا حلوة مشوية", 100, "g", 90, 2, 20.7, 0.2, 3.3, "carbs"),
88  food("base-pasta", "مكرونة مسلوقة", 100, "g", 158, 5.8, 30.9, 0.9, 1.8, "carbs"),
89  food("base-bulgur", "برغل مطبوخ", 100, "g", 83, 3.1, 18.6, 0.2, 4.5, "carbs"),
90  food("base-beans", "فول مدمس بدون إضافات", 100, "g", 110, 7.6, 19.7, 0.4, 5.4, "legumes"),
91  food("base-lentils", "عدس مطبوخ", 100, "g", 116, 9, 20.1, 0.4, 7.9, "legumes"),
92  food("base-chickpeas", "حمص مطبوخ", 100, "g", 164, 8.9, 27.4, 2.6, 7.6, "legumes"),
93  fruitFood("base-banana", "موز", 89, 1.1, 22.8, 0.3, 2.6),
94  fruitFood("base-apple", "تفاح بالقشر", 52, 0.3, 13.8, 0.2, 2.4),
95  fruitFood("base-orange", "برتقال", 47, 0.9, 11.8, 0.1, 2.4),
96  fruitFood("base-strawberry", "فراولة", 32, 0.7, 7.7, 0.3, 2),
97  researchedFood("base-dates", "تمرة مجففة متوسطة", 1, "piece", 23, 0.2, 6.2, 
970, 0.6, "fruits", "USDA FoodData Central", "حصة تقريبية لتمرة متوسطة؛ وزن الحبة يختلف حسب النوع."),
98  fruitFood("fruit-pear", "كمثرى", 57, 0.4, 15.2, 0.1, 3.1),
99  fruitFood("fruit-peach", "خوخ", 39, 0.9, 9.5, 0.3, 1.5),
100  fruitFood("fruit-apricot", "مشمش", 48, 1.4, 11.1, 0.4, 2),
101  fruitFood("fruit-mango", "مانجو", 60, 0.8, 15, 0.4, 1.6),
102  fruitFood("fruit-guava", "جوافة", 68, 2.6, 14.3, 1, 5.4),
103  fruitFood("fruit-grapes", "عنب", 69, 0.7, 18.1, 0.2, 0.9),
104  fruitFood("fruit-watermelon", "بطيخ", 30, 0.6, 7.6, 0.2, 0.4),
105  fruitFood("fruit-cantaloupe", "شمام / كنتالوب", 34, 0.8, 8.2, 0.2, 0.9),
106  fruitFood("fruit-pomegranate", "رمان - حبوب", 83, 1.7, 18.7, 1.2, 4),
107  fruitFood("fruit-kiwi", "كيوي", 61, 1.1, 14.7, 0.5, 3),
108  fruitFood("fruit-pineapple", "أناناس", 50, 0.5, 13.1, 0.1, 1.4),
109  fruitFood("fruit-plum", "برقوق", 46, 0.7, 11.4, 0.3, 1.4),
110  fruitFood("fruit-cherry", "كرز حلو", 63, 1.1, 16, 0.2, 2.1),
111  fruitFood("fruit-fig", "تين طازج", 74, 0.8, 19.2, 0.3, 2.9),
112  fruitFood("fruit-date-dried-100", "تمر مجفف", 277, 1.8, 75, 0.2, 6.7),
113  researchedFood("fruit-date-fresh", "بلح رطب", 100, "g", 142, 1.3, 37.2, 0.2, 3.5, "fruits", "جداول تركيب أغذية إقليمية", "قيمة متوسطة؛ تختلف كثيرًا باختلاف درجة النضج والنوع."),
114  fruitFood("fruit-tangerine", "يوسفي", 53, 0.8, 13.3, 0.3, 1.8),
115  fruitFood("fruit-grapefruit", "جريب فروت", 42, 0.8, 10.7, 0.1, 1.6),
116  fruitFood("fruit-lemon", "ليمون", 29, 1.1, 9.3, 0.3, 2.8),
117  fruitFood("fruit-avocado", "أفوكادو", 160, 2, 8.5, 14.7, 6.7),
118  fruitFood("fruit-coconut", "جوز هند طازج", 354, 3.3, 15.2, 33.5, 9),
119  fruitFood("fruit-prickly-pear", "تين شوكي", 41, 0.7, 9.6, 0.5, 3.6),
120  fruitFood("fruit-persimmon", "كاكا", 70, 0.6, 18.6, 0.2, 3.6),
121  fruitFood("fruit-mulberry", "توت", 43, 1.4, 9.8, 0.4, 1.7),
122  fruitFood("fruit-blueberry", "توت أزرق", 57, 0.7, 14.5, 0.3, 2.4),
123  fruitFood("fruit-blackberry", "توت أسود", 43, 1.4, 9.6, 0.5, 5.3),
124  fruitFood("fruit-raspberry", "توت العليق", 52, 1.2, 11.9, 0.7, 6.5),
125  fruitFood("fruit-papaya", "بابايا", 43, 0.5, 10.8, 0.3, 1.7),
126  fruitFood("fruit-passion", "باشن فروت", 97, 2.2, 23.4, 0.7, 10.4),
127  fruitFood("fruit-lychee", "ليتشي", 66, 0.8, 16.5, 0.4, 1.3),
128  fruitFood("fruit-loquat", "بشملة / أسكدنيا", 47, 0.4, 12.1, 0.2, 1.7),
129  fruitFood("fruit-quince", "سفرجل", 57, 0.4, 15.3, 0.1, 1.9),
130  researchedFood("fruit-custard-apple", "قشطة / تفاح سكري", 100, "g", 94, 2.1, 23.6, 0.3, 4.4, "fruits", "جداول تركيب أغذية دولية", "القيم للجزء المأكول وقد تختلف حسب الصنف."),
131  food("base-cucumber", "خيار", 100, "g", 15, 0.7, 3.6, 0.1, 0.5, "vegetables"),
132  food("base-tomato", "طماطم", 100, "g", 18, 0.9, 3.9, 0.2, 1.2, "vegetables"),
133  food("base-lettuce", "خس", 100, "g", 15, 1.4, 2.9, 0.2, 1.3, "vegetables"),
134  food("base-broccoli", "بروكلي مطهو", 100, "g", 35, 2.4, 7.2, 0.4, 3.3, "vegetables"),
135  food("base-mixed-salad", "سلطة خضراء بدون زيت", 100, "g", 25, 1.2, 5, 0.2, 2, "vegetables", true),
136  food("base-olive-oil", "زيت زيتون", 1, "tbsp", 119, 0, 0, 13.5, 0, "fats"),
137  food("base-peanut-butter", "زبدة فول سوداني", 100, "g", 588, 25, 20, 50, 6, "fats"),
138  food("base-almonds", "لوز", 100, "g", 579, 21.2, 21.6, 49.9, 12.5, "fats"),
139  food("base-peanuts", "فول سوداني محمص", 100, "g", 585, 24.4, 21.5, 49.7, 8.4, "fats"),
140  food("base-whey", "واي بروتين - سكوب تقريبي", 1, "serving", 120, 24, 3, 2, 0, "supplements"),
141  food("base-coffee", "قهوة سادة", 1, "cup", 3, 0.3, 0, 0, 0, "drinks"),
142  food("base-green-tea", "شاي أخضر سادة", 1, "cup", 2, 0, 0.4, 0, 0, "drinks"),
143  food("base-herbal-tea", "مشروب أعشاب سادة", 1, "cup", 2, 0, 0.4, 0, 0, "drinks"),
144  food("base-americano", "قهوة أمريكانو سادة", 1, "cup", 5, 0.3, 0.7, 0, 0, "drinks"),
145  food("base-espresso", "إسبريسو سادة", 1, "cup", 3, 0.2, 
1450.5, 0, 0, "drinks"),
146  egyptianFood("egy-chicken-pane-fried", "فراخ بانيه مقلية", 270, 22, 17, 13, 1),
147  egyptianFood("egy-chicken-pane-oven", "فراخ بانيه في الفرن أو إير فراير", 220, 24, 15, 7.5, 1),
148  researchedFood("chicken-grilled-skin", "فراخ مشوية بالجلد - لحم مختلط", 100, "g", 239, 27, 0, 14, 0, "protein", "USDA FoodData Central", "متوسط لحم الفراخ المشوي مع الجلد؛ القطعة المختارة تؤثر على الدهون."),
149  egyptianFood("egy-chicken-oven-noskin", "فراخ في الفرن بدون جلد", 185, 28, 0, 7.5, 0),
150  egyptianFood("egy-chicken-boiled-noskin", "فراخ مسلوقة بدون جلد", 170, 28, 0, 6, 0),
151  egyptianFood("egy-chicken-potato-stew", "فراخ مع بطاطس مطبوخة بالصلصة", 145, 10, 14, 5.5, 2),
152  egyptianFood("egy-chicken-shawarma", "شاورما فراخ منزلية", 210, 24, 4, 11, 0.5),
153  egyptianFood("egy-chicken-kofta", "كفتة فراخ", 190, 22, 5, 9, 0.5),
154  egyptianFood("egy-shish-tawook", "شيش طاووق مشوي", 175, 27, 3, 6, 0.3),
155  researchedFood("fish-tilapia-grilled", "سمك بلطي مشوي", 100, "g", 128, 26, 0, 2.7, 0, "protein", "USDA FoodData Central", "القيمة لكل 100 جم من اللحم المطهو دون زيت إضافي."),
156  egyptianFood("egy-tilapia-fried", "سمك بلطي مقلي", 215, 23, 8, 10.5, 0.5),
157  egyptianFood("egy-mullet-grilled", "سمك بوري مشوي", 160, 24, 0, 7, 0),
158  egyptianFood("egy-fish-sayadiya", "سمك صيادية بالأرز", 175, 12, 25, 3.5, 1),
159  egyptianFood("egy-green-beans", "فاصوليا خضراء بالصلصة", 80, 2.5, 10, 3.5, 3.5),
160  egyptianFood("egy-zucchini-tomato", "كوسة مطبوخة بالصلصة", 65, 2, 7, 3.5, 2),
161  egyptianFood("egy-white-beans", "فاصوليا بيضاء بالصلصة", 125, 7, 20, 2.5, 6),
162  egyptianFood("egy-pasta-red-sauce", "مكرونة بالصلصة الحمراء", 150, 5, 28, 2.5, 2),
163  egyptianFood("egy-shakshuka", "شكشوكة مصرية", 110, 6, 5, 7, 1.5),
164  egyptianFood("egy-eggs-pastrami", "بيض بالبسطرمة", 210, 15, 3, 15, 0),
165  egyptianFood("egy-liver-grilled", "كبدة مشوية", 175, 26, 3, 6, 0),
166  egyptianFood("egy-beef-boiled", "لحمة مسلوقة", 230, 26, 0, 14, 0),
167  egyptianFood("egy-beef-vegetable-stew", "يخنة لحمة بالخضار", 150, 14, 7, 7, 2),
168  egyptianFood("egy-rice-kofta", "كفتة أرز", 195, 11, 20, 8, 1),
169  egyptianFood("egy-okra-plain", "بامية سادة مطبوخة - زيت متوسط", 75, 2, 8, 4, 3),
170  egyptianFood("egy-okra-meat", "بامية باللحمة", 115, 7, 7, 7, 2.5),
171  egyptianFood("egy-peas-carrots", "بسلة بالجزر - زيت متوسط", 95, 4, 15, 2.5, 4),
172  egyptianFood("egy-peas-meat", "بسلة باللحمة", 125, 8, 12, 5, 3.5),
173  egyptianFood("egy-taro", "قلقاس بالسلق", 110, 2.5, 18, 3.5, 3),
174  egyptianFood("egy-molokhia", "ملوخية مصرية بالتقلية - زيت متوسط", 70, 3, 7, 4, 2.5),
175  egyptianFood("egy-spinach", "سبانخ مطبوخة بالطماطم", 65, 3, 7, 3, 3),
176  egyptianFood("egy-moussaka", "مسقعة مصرية", 120, 3, 12, 7, 4),
177  egyptianFood("egy-moussaka-meat", "مسقعة باللحمة المفرومة", 155, 8, 10, 10, 3),
178  egyptianFood("egy-mahshi-grape", "محشي ورق عنب", 165, 3.5, 25, 6, 3),
179  egyptianFood("egy-mahshi-cabbage", "محشي كرنب", 145, 3.5, 23, 5, 3),
180  egyptianFood("egy-mahshi-zucchini", "محشي كوسة", 130, 3, 20, 4.5, 2.5),
181  egyptianFood("egy-mahshi-eggplant", "محشي باذنجان", 135, 3, 20, 5, 3),
182  egyptianFood("egy-mahshi-pepper", "محشي فلفل", 130, 3, 20, 4.5, 2.5),
183  egyptianFood("egy-mahshi-mixed", "محشي مشكل", 145, 3.5, 23, 5, 3),
184  egyptianFood("egy-mombar", "ممبار (منبار)", 240, 10, 22, 13, 1.5),
185  egyptianFood("egy-macaroni-bechamel", "مكرونة بشاميل", 200, 8, 23, 8.5, 1.5),
186  egyptianFood("egy-lasagna-meat", "لازانيا باللحمة", 190, 10, 21, 8, 1.5),
187  egyptianFood("egy-roqaq-meat", "رقاق باللحمة", 260, 10, 28, 12, 1),
188  egyptianFood("egy-fatta-meat", "فتة مصرية باللحمة", 220, 10, 27, 8, 1),
189  egyptianFood("egy-kebab-halla", "كباب حلة", 180, 20, 4, 9, 1),
190  egyptianFood("egy-dawood-basha", "كفتة داود باشا", 185, 15, 8, 10, 1.5),
191  egyptianFood("egy-liver-alex", "كبدة إسكندراني - زيت متوسط", 185, 22, 4, 9, 1),
192  egyptianFood("egy-liver-onion", "كبدة مشوحة بالبصل", 175, 23, 5, 7.5, 1),
193  egyptianFood("egy-sausage-alex", "سجق إسكندراني", 280, 15, 7, 22, 1.5),
194  egyptianFood("egy-hawawshi", "حواوشي", 275, 13, 28, 12, 2),
195  egyptianFood("egy-potato-meat", "صينية بطاطس باللحمة", 145, 8, 15, 6, 2),
196  egyptianFood("egy-potato-chicken", "صينية بطاطس بالفراخ", 155, 11, 14, 6.5, 2),
197  egyptianFood("egy-orzo-soup", "شوربة لسان عصفور", 85, 2.5, 13, 2.5, 1),
198  egyptianFood("egy-lentil-soup", "شوربة عدس مصرية", 105, 5, 17, 2.5, 4),
199  egyptianFood("egy-rice-vermicelli", "رز بالشعرية مطبوخ", 155, 3, 30, 2.5, 0.7),
200  egyptianFood("egy-roz-moammar", "أرز معمر", 190, 5, 25, 8, 0.5),
201  egyptianFood("egy-bessara", "بصارة", 115, 6, 14, 4, 5),
202  egyptianFood("egy-fava-oil", "فول بالزيت والليمون", 145, 7.5, 18, 5, 5),
203  egyptianFood("egy-pigeon-stuffed", "حمام محشي", 240, 17, 13, 13, 0.5),
204  egyptianFood("egy-trotters", "كوارع مطبوخة", 220, 25, 2, 13, 0),
205  food("base-tea", "شاي سادة بدون سكر", 1, "cup", 2, 0, 0.5, 0, 0, "drinks"),
206  researchedFood("drink-tea-milk", "شاي بلبن - بدون سكر", 1, "cup", 52, 3.4, 5.3, 1.8, 0, "drinks", "تقدير من شاي سادة + 100 مل لبن نصف دسم", "الكوب محسوب بشاي وماء مع نحو 100 مل لبن نصف دسم. اختر ملاعق السكر عند تسجيل المشروب لتُضاف تلقائيًا."),
207  food("base-instant-coffee", "نسكافيه سادة بدون إضافات", 1, "cup", 4, 0.2, 0.7, 0, 0, "drinks"),
208  food("base-sugar-tsp", "سكر أبيض - ملعقة صغيرة", 1, "serving", 16, 0, 4, 0, 0, "drinks"),
209  food("base-koshari", "كشري متوسط بدون إضافات زائدة", 100, "g", 170, 6, 31, 2.5, 4, "meals"),
210  food("base-falafel", "طعمية مقلية متوسطة", 1, "piece", 65, 2.5, 5.5, 3.8, 1.5, "meals"),
211  food("base-grilled-kofta", "كفتة مشوية", 100, "g", 245, 23, 2, 16, 0, "meals")
212];
213
214function food(id, name, basis, unit, calories, protein, carbs, fat, fiber, categoryId, favorite = false) {
215  return {
216    id, name, basis, unit, calories, protein, carbs, fat, fiber,
217    categoryId, favorite, custom: false, brand: "", notes: "", source: "قاعدة البرنامج"
218  };
219}
220
221function researchedFood(id, name, basis, unit, calories, protein, carbs, fat, fiber, categoryId, source, notes = "", favorite = false) {
222  const item = food(id, name, basis, unit, calories, protein, carbs, fat, fiber, categoryId, favorite);
223  item.source = source;
224  item.notes = notes;
225  return item;
226}
227
228function fruitFood(id, name, calories, protein, carbs, fat, fiber) {
229  return researchedFood(
230    id, name, 100, "g", calories, protein, carbs, fat, fiber, "fruits",
231    "USDA FoodData Central",
232    "القيم لكل 100 جم من الجزء المأكول، من دون القشر أو البذور غير المأكولة حيث ينطبق."
233  );
234}
235
236function egyptianFood(id, name, calories, protein, carbs, fat, fiber) {
237  const item = food(id, name, 100, "g", calories, protein, carbs, fat, fiber, "egyptian");
238  item.notes = "تقدير منزلي متوسط لكل 100 جم؛ قد يتغير بنحو 20–35% حسب الزيت والسمن ونسبة اللحمة والماء وطريقة التسوية. استخدم وصفة مخصصة لو تعرف مكونات طبختك.";
239  item.source = "وصفة مصرية معيارية + مراجع مكونات";
240  return item;
241}
242
243const initialState = {
244  version: 7,
245  updatedAt: new Date().toISOString(),
246  theme: "light",
247  profile: {
248    age: 19,
249    sex: "unspecified",
250    height: 168,
251    weight: 120,
252    workouts: 4,
253    dailyActivity: "sedentary"
254  },
255  goalProfiles: {
256    default: { calories: 2400, protein: 160, carbs: 250, fat: 80, water: 3 },
257    training: { calories: 2500, protein: 170, carbs: 280, fat: 75, water: 3.5 },
258    rest: { calories: 2300, protein: 170, carbs: 210, fat: 80, water: 3 }
259  },
260  dayModes: {},
261  dayGoalOverrides: {},
262  categories: defaultCategories,
263  foods: defaultFoods,
264  logs: {},
265  water: {},
266  weights: [{ id: makeId(), date: todayISO(), value: 120, note: "الوزن المبدئي" }]
267};
268
269let state = loadState();
270let deferredInstallPrompt = null;
271let toastTimer = null;
272let cloudSyncTimer = null;
273let isApplyingRemote = false;
274let compareIds = [];
275let usdaSearchResults = [];
276let lastCalculatorResult = null;
277let mealDraftEntries = [];
278let customDrinkTarget = "log";
279let cloudConfig = loadJson(CLOUD_CONFIG_KEY, { url: "", anonKey: "" });
280let cloudSession = loadJson(CLOUD_SESSION_KEY, null);
281
282const $ = (selector) => document.querySelector(selector);
283const $$ = (selector) => [...document.querySelectorAll(selector)];
284
285function makeId() {
286  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
287}
288
289function todayISO() {
290  const d = new Date();
291  const offset = d.getTimezoneOffset();
292  return new Date(d.getTime() - offset * 60_000).toISOString().slice(0, 10);
293}
294
295function addDays(dateISO, amount) {
296  const d = new Date(`${dateISO}T12:00:00`);
297  d.setDate(d.getDate() + amount);
298  return d.toISOString().slice(0, 10);
299}
300
301function formatNumber(value, maximumFractionDigits = 0) {
302  return new Intl.NumberFormat("ar-EG", { maximumFractionDigits }).format(Number(value) || 0);
303}
304
305function formatDate(dateISO, options = { weekday: "long", day: "numeric", month: "long" }) {
306  return new Intl.DateTimeFormat("ar-EG", options).format(new Date(`${dateISO}T12:00:00`));
307}
308
309function deepClone(value) {
310  return JSON.parse(JSON.stringify(value));
311}
312
313function escapeHtml(value) {
314  return String(value ?? "")
315    .replaceAll("&", "&")
316    .replaceAll("<", "&lt;")
317    .replaceAll(">", "&gt;")
318    .replaceAll('"', "&quot;")
319    .replaceAll("'", "&#039;");
320}
321
322function optionalNumber(value) {
323  if (value === "" || value === null || value === undefined) return null;
324  const num = Number(value);
325  return Number.isFinite(num) ? num : null;
326}
327
328function loadJson(key, fallback) {
329  try {
330    const raw = localStorage.getItem(key);
331    return raw ? JSON.parse(raw) : fallback;
332  } catch {
333    return fallback;
334  }
335}
336
337function loadState() {
338  try {
339    const raw = localStorage.getItem(STORAGE_KEY);
340    if (!raw) return deepClone(initialState);
341    return normalizeState(JSON.parse(raw));
342  } catch (error) {
343    console.error("Could not load saved data", error);
344    return deepClone(initialState);
345  }
346}
347
348function normalizeState(saved) {
349  const merged = deepClone(initialState);
350  const previousVersion = Number(saved?.version) || 1;
351  merged.version = 7;
352  merged.updatedAt = saved.updatedAt || new Date().toISOString();
353  merged.theme = saved.theme === "dark" ? "dark" : "light";
354  merged.profile = { ...merged.profile, ...(saved.profile || {}) };
355
356  if (saved.goalProfiles) {
357    merged.goalProfiles = {
358      default: { ...merged.goalProfiles.default, ...(saved.goalProfiles.default || {}) },
359      training: { ...merged.goalProfiles.training, ...(saved.goalProfiles.training || {}) },
360      rest: { ...merged.goalProfiles.rest, ...(saved.goalProfiles.rest || {}) }
361    };
362  } else if (saved.goals) {
363    merged.goalProfiles.default = { ...merged.goalProfiles.default, ...saved.goals };
364  }
365
366  merged.dayModes = saved.dayModes && typeof saved.dayModes === "object" ? saved.dayModes : {};
367  merged.dayGoalOverrides = saved.dayGoalOverrides && typeof saved.dayGoalOverrides === "object" ? saved.dayGoalOverrides : {};
368  merged.logs = saved.logs && typeof saved.logs === "object" ? saved.logs : {};
369  merged.water = saved.water && typeof saved.water === "object" ? saved.water : {};
370  merged.weights = Array.isArray(saved.weights) ? saved.weights : merged.weights;
371
372  // النسختان السابقتان كانتا تحفظان الماء بعدد الأكواب. من الإصدار 3 أصبحت الوحدة لتر.
373  if (previousVersion < 3) {
374    const minimumTargets = { default: 3, training: 3.5, rest: 3 };
375    Object.keys(merged.goalProfiles).forEach((profileName) => {
376      const oldValue = Number(merged.goalProfiles[profileName].water) || 0;
377      const liters = oldValue > 5 ? oldValue / 4 : oldValue;
378      merged.goalProfiles[profileName].water = Math.max(minimumTargets[profileName] || 3, liters || 0);
379    });
380    Object.keys(merged.dayGoalOverrides).forEach((date) => {
381      const oldValue = Number(merged.dayGoalOverrides[date]?.water) || 0;
382      if (oldValue) merged.dayGoalOverrides[date].water = oldValue > 5 ? oldValue / 4 : oldValue;
383    });
384    merged.water = Object.fromEntries(Object.entries(merged.water).map(([date, value]) => [date, Math.round(((Number(value) || 0) / 4) * 100) / 100]));
385  }
386
387  const savedCategories = Array.isArray(saved.categories) ? saved.categories : [];
388  const customCategories = savedCategories.filter((category) => category.custom && !defaultCategories.some((base) => base.id === category.id));
389  merged.categories = defaultCategories.map((base) => ({ ...base, ...(savedCategories.find((category) => category.id === base.id) || {}) })).concat(customCategories);
390
391  const savedFoods = Array.isArray(saved.foods) ? saved.foods : [];
392  const customFoods = savedFoods.filter((item) => item.custom).map(normalizeFood);
393  const savedBaseMap = new Map(savedFoods.filter((item) => !item.custom).map((item) => [item.id, item]));
394  merged.foods = defaultFoods.map((base) => {
395    const savedBase = savedBaseMap.get(base.id) || {};
396    return normalizeFood({ ...base, favorite: savedBase.favorite ?? base.favorite });
397  }).concat(customFoods);
398  return merged;
399}
400
401function normalizeFood(item) {
402  return {
403    id: item.id || makeId(),
404    name: item.name || "طعام بدون اسم",
405    basis: Number(item.basis) > 0 ? Number(item.basis) : 100,
406    unit: unitLabels[item.unit] ? item.unit : "g",
407    calories: Number(item.calories) || 0,
408    protein: optionalNumber(item.protein),
409    carbs: optionalNumber(item.carbs),
410    fat: optionalNumber(item.fat),
411    fiber: optionalNumber(item.fiber),
412    categoryId: item.categoryId || guessCategory(item.name),
413    favorite: Boolean(item.favorite),
414    custom: Boolean(item.custom),
415    brand: item.brand || "",
416    notes: item.notes || "",
417    source: item.source || (item.custom ? "أضافه المستخدم" : "قاعدة البرنامج"),
418    sourceId: item.sourceId || ""
419  };
420}
421
422function guessCategory(name = "") {
423  const text = name.toLowerCase();
424  if (/فراخ|لحم|تونة|بيض|سمك|سلمون|جمبري|كفتة/.test(text)) return "protein";
425  if (/جبن|زبادي|لبن/.test(text)) return "dairy";
426  if (/رز|أرز|عيش|توست|مكرونة|بطاط|شوفان|برغل/.test(text)) return "carbs";
427  if (/فول|عدس|حمص/.test(text)) return "legumes";
428  if (/خيار|طماطم|سلطة|خس|بروكلي/.test(text)) return "vegetables";
429  if (/موز|تفاح|برتقال|فراولة|تمر/.test(text)) return "fruits";
430  if (/زيت|لوز|سوداني|مكسرات/.test(text)) return "fats";
431  if (/شاي|قهوة|نسكافيه|إسبريسو|امريكانو|أمريكانو|أعشاب|مشروب/.test(text)) return "drinks";
432  if (/بامية|بسلة|قلقاس|ملوخية|محشي|ممبار|منبار|بشاميل|لازانيا|رقاق|فتة|كباب حلة|داود باشا|كبدة|سجق|حواوشي|صينية بطاطس|لسان عصفور|أرز معمر|رز معمر|رز صيادية|رز كشري|رز بالطماطم|بصارة|حمام محشي|كوارع/.test(text)) return "egyptian";
433  return "uncategorized";
434}
435
436function saveState({ scheduleSync = true } = {}) {
437  state.updatedAt = new Date().toISOString();
438  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
439  updateStorageStatus();
440  if (scheduleSync && !isApplyingRemote) scheduleCloudPush();
441}
442
443function showToast(message) {
444  const toast = $("#toast");
445  toast.textContent = message;
446  toast.classList.add("show");
447  clearTimeout(toastTimer);
448  toastTimer = setTimeout(() => toast.classList.remove("show"), 2800);
449}
450
451function getFood(foodId) {
452  return state.foods.find((item) => item.id === foodId);
453}
454
455function getCategory(categoryId) {
456  return state.categories.find((item) => item.id === categoryId) || state.categories.find((item) => item.id === "uncategorized");
457}
458
459function calculateNutrition(item, quantity) {
460  const factor = Number(quantity) / Number(item?.basis || 1);
461  return {
462    calories: (Number(item?.calories) || 0) * factor,
463    protein: (Number(item?.protein) || 0) * factor,
464    carbs: (Number(item?.carbs) || 0) * factor,
465    fat: (Number(item?.fat) || 0) * factor,
466    fiber: (Number(item?.fiber) || 0) * factor
467  };
468}
469
470function getEntries(dateISO) {
471  return Array.isArray(state.logs[dateISO]) ? state.logs[dateISO] : [];
472}
473
474function getDayTotals(dateISO) {
475  return getEntries(dateISO).reduce((total, entry) => {
476    const item = getFood(entry.foodId) || entry.foodSnapshot;
477    if (!item) return total;
478    const nutrition = calculateNutrition(item, entry.quantity);
479    Object.keys(total).forEach((key) => { total[key] += nutrition[key] || 0; });
480    return total;
481  }, { calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0 });
482}
483
484function getGoalsForDate(dateISO) {
485  if (state.dayGoalOverrides[dateISO]) {
486    return { ...state.dayGoalOverrides[dateISO], mode: "custom", label: "هدف مخصص" };
487  }
488  const mode = state.dayModes[dateISO] || "default";
489  const labels = { default: "الهدف الأساسي", training: "يوم تمرين", rest: "يوم راحة" };
490  return { ...state.goalProfiles[mode], mode, label: labels[mode] || labels.default };
491}
492
493function percentage(value, goal) {
494  if (!goal) return 0;
495  return Math.max(0, Math.min(100, (value / goal) * 100));
496}
497
498function setProgress(element, value) {
499  if (element) element.style.width = `${Math.min(100, Math.max(0, value))}%`;
500}
501
502function applyTheme() {
503  document.documentElement.dataset.theme = state.theme;
504  $("#themeToggle").textContent = state.theme === "dark" ? "الوضع الفاتح" : "الوضع الداكن";
505}
506
507function switchView(view) {
508  $$(".view").forEach((section) => section.classList.toggle("active-view", section.id === `${view}View`));
509  $$('[data-view]').forEach((button) => button.classList.toggle("active", button.dataset.view === view));
510  $("#pageTitle").textContent = viewTitles[view] || "My Diet Tracker";
511  window.scrollTo({ top: 0, behavior: "smooth" });
512  if (view === "mealplanner") renderMealPlanner();
513  if (view === "explore") renderExplore();
514  if (view === "calculator") populateCalculatorFromProfile(false);
515  if (view === "progress") renderProgress();
516  if (view === "settings") renderSettings();
517}
518
519function renderDashboard() {
520  const date = todayISO();
521  const totals = getDayTotals(date);
522  const goals = getGoalsForDate(date);
523  const remaining = goals.calories - totals.calories;
524  const consumedPercent = percentage(totals.calories, goals.calories);
525
526  $("#heroRemaining").textContent = formatNumber(Math.abs(remaining));
527  $("#heroRemainingLabel").textContent = remaining >= 0 ? " سعر متبقي" : " سعر فوق الهدف";
528  $("#heroSummary").textContent = totals.calories
529    ? `أكلت ${formatNumber(totals.calories)} من ${formatNumber(goals.calories)} سعر اليوم.`
530    : "ابدأ بتسجيل أول وجبة اليوم.";
531  $("#dayModeBadge").textContent = goals.label;
532  $("#ringCalories").textContent = formatNumber(totals.calories);
533  $("#ringGoal").textContent = formatNumber(goals.calories);
534  $("#calorieRing").style.setProperty("--percent", consumedPercent);
535
536  renderMacroStat("protein", totals.protein, goals.protein);
537  renderMacroStat("carbs", totals.carbs, goals.carbs);
538  renderMacroStat("fat", totals.fat, goals.fat);
539
540  const water = Number(state.water[date]) || 0;
541  $("#waterStat").textContent = `${formatNumber(water, 2)} / ${formatNumber(goals.water, 2)} لتر`;
542  setProgress($("#waterBar"), percentage(water, goals.water));
543
544  $("#todayMeals").innerHTML = renderMealGroups(date);
545  renderWeekSummary();
546  renderRemainingSuggestions();
547  renderDashboardEnergy();
548}
549
550function renderMacroStat(key, value, goal) {
551  const ids = { protein: ["proteinStat", "proteinBar"], carbs: ["carbsStat", "carbsBar"], fat: ["fatStat", "fatBar"] };
552  $(`#${ids[key][0]}`).textContent = `${formatNumber(value, 1)} / ${formatNumber(goal)} جم`;
553  setProgress($(`#${ids[key][1]}`), percentage(value, goal));
554}
555
556function renderMealGroups(dateISO) {
557  const entries = getEntries(dateISO);
558  if (!entries.length) return `<div class="empty-state"><strong>لا توجد وجبات مسجلة</strong><span>أضف أول طعام لتبدأ حساب يومك.</span></div>`;
559
560  return Object.keys(mealLabels).map((mealType) => {
561    const mealEntries = entries.filter((entry) => entry.mealType === mealType);
562    if (!mealEntries.length) return "";
563    const mealCalories = mealEntries.reduce((sum, entry) => {
564      const item = getFood(entry.foodId) || entry.foodSnapshot;
565      return sum + calculateNutrition(item, entry.quantity).calories;
566    }, 0);
567    const rows = mealEntries.map((entry) => {
568      const item = getFood(entry.foodId) || entry.foodSnapshot;
569      if (!item) return "";
570      const nutrition = calculateNutrition(item, entry.quantity);
571      const incomplete = item.protein === null || item.carbs === null || item.fat === null;
572      return `<div class="food-entry">
573        <div class="food-entry-info"><strong>${escapeHtml(item.name)}</strong><span>${formatNumber(entry.quantity, 1)} ${unitLabels[item.unit] || item.unit}${incomplete ? " • الماكروز غير مكتملة" : ` • ${formatNumber(nutrition.protein, 1)} جم بروتين`}</span></div>
574        <span class="entry-kcal">${formatNumber(nutrition.calories)} سعر</span>
575        <button class="icon-button" data-delete-log="${entry.id}" data-date="${dateISO}" aria-label="حذف">×</button>
576      </div>`;
577    }).join("");
578    return `<section class="meal-group"><header class="meal-group-header"><h4>${mealLabels[mealType]}</h4><span>${formatNumber(mealCalories)} سعر</span></header>${rows}</section>`;
579  }).join("");
580}
581
582function renderWeekSummary() {
583  const today = todayISO();
584  const dates = Array.from({ length: 7 }, (_, index) => addDays(today, index - 6));
585  const values = dates.map((date) => getDayTotals(date).calories);
586  const logged = values.filter((value) => value > 0);
587  const average = logged.length ? logged.reduce((sum, value) => sum + value, 0) / logged.length : 0;
588  const max = Math.max(...values, ...dates.map((date) => getGoalsForDate(date).calories), 1);
589
590  $("#weeklyAverage").textContent = formatNumber(average);
591  $("#daysLogged").textContent = formatNumber(logged.length);
592  const sortedWeights = [...state.weights].sort((a, b) => a.date.localeCompare(b.date));
593  $("#latestWeight").textContent = sortedWeights.length ? `${formatNumber(sortedWeights.at(-1).value, 1)} كجم` : "—";
594
595  $("#weekBars").innerHTML = dates.map((date, index) => {
596    const height = Math.max(3, (values[index] / max) * 145);
597    const label = new Intl.DateTimeFormat("ar-EG", { weekday: "short" }).format(new Date(`${date}T12:00:00`));
598    return `<div class="week-bar"><b>${values[index] ? formatNumber(values[index]) : "—"}</b><i style="height:${height}px"></i><span>${label}</span></div>`;
599  }).join("");
600}
601
602function renderRemainingSuggestions() {
603  const date = todayISO();
604  const totals = getDayTotals(date);
605  const goals = getGoalsForDate(date);
606  const remainingCalories = Math.max(0, goals.calories - totals.calories);
607  const remainingProtein = Math.max(0, goals.protein - totals.protein);
608  const candidates = state.foods
609    .filter((item) => item.categoryId !== "drinks" && item.calories > 0 && item.calories <= Math.max(remainingCalories, 150) * 1.1)
610    .map((item) => {
611      const nutrition = calculateNutrition(item, item.basis);
612      const proteinScore = remainingProtein > 0 ? Math.min(nutrition.protein, remainingProtein) * 8 : nutrition.protein * 2;
613      const calorieFit = remainingCalories > 0 ? 100 - Math.min(100, Math.abs(remainingCalories - nutrition.calories) / Math.max(remainingCalories, 1) * 100) : 0;
614      return { item, nutrition, score: proteinScore + calorieFit + (item.favorite ? 15 : 0) };
615    })
616    .sort((a, b) => b.score - a.score)
617    .slice(0, 4);
618
619  if (!candidates.length || remainingCalories <= 0) {
620    $("#remainingSuggestions").innerHTML = `<div class="empty-state"><strong>${remainingCalories <= 0 ? "وصلت لهدف السعرات" : "لا توجد اقتراحات الآن"}</strong><span>راجع يومك أو ابحث في قسم الاستكشاف.</span></div>`;
621    return;
622  }
623
624  $("#remainingSuggestions").innerHTML = candidates.map(({ item, nutrition }) => `<div class="suggestion-item"><div><strong>${escapeHtml(item.name)}</strong><span>${formatNumber(nutrition.calories)} سعر • ${formatNumber(nutrition.protein, 1)} جم بروتين لكل ${formatNumber(item.basis, 1)} ${unitLabels[item.unit]}</span></div><button class="ghost-button compact" data-quick-add="${item.id}">إضافة</button></div>`).join("");
625}
626
627function renderDashboardEnergy() {
628  const result = calculateProfileEnergy();
629  if (!result) {
630    $("#dashboardEnergySummary").innerHTML = `<div><strong>—</strong><span>BMR</span></div><div><strong>—</strong><span>الثبات</span></div><div><strong>${formatNumber(getGoalsForDate(todayISO()).calories)}</strong><span>هدف اليوم</span></div>`;
631    return;
632  }
633  $("#dashboardEnergySummary").innerHTML = `<div><strong>${formatNumber(result.bmr)}</strong><span>BMR</span></div><div><strong>${formatNumber(result.tdee)}</strong><span>
633ثبات تقديري</span></div><div><strong>${formatNumber(getGoalsForDate(todayISO()).calories)}</strong><span>هدف اليوم</span></div>`;
634}
635
636function calculateProfileEnergy() {
637  const { sex, age, height, weight, workouts, dailyActivity } = state.profile;
638  if (!['male', 'female'].includes(sex)) return null;
639  let activity = 1.2;
640  if (dailyActivity === "sedentary") activity = workouts >= 5 ? 1.5 : workouts >= 3 ? 1.45 : workouts >= 1 ? 1.35 : 1.2;
641  if (dailyActivity === "light") activity = workouts >= 5 ? 1.6 : workouts >= 3 ? 1.52 : 1.4;
642  if (dailyActivity === "active") activity = workouts >= 5 ? 1.75 : workouts >= 3 ? 1.65 : 1.55;
643  const bmr = calculateBmr(sex, age, height, weight);
644  return { bmr, tdee: bmr * activity, activity };
645}
646
647function populateFoodSelect(search = "", selectedId = null) {
648  const normalized = search.trim().toLowerCase();
649  const foods = [...state.foods]
650    .filter((item) => item.categoryId !== "drinks")
651    .filter((item) => !normalized || `${item.name} ${item.brand}`.toLowerCase().includes(normalized))
652    .sort((a, b) => Number(b.favorite) - Number(a.favorite) || a.name.localeCompare(b.name, "ar"));
653
654  $("#foodSelect").innerHTML = foods.length
655    ? foods.map((item) => `<option value="${item.id}">${item.favorite ? "★ " : ""}${escapeHtml(item.name)} — ${formatNumber(item.calories, 1)} سعر/${formatNumber(item.basis, 1)} ${unitLabels[item.unit]}</option>`).join("")
656    : `<option value="">لا توجد نتيجة — أضف الطعام من مكتبتك</option>`;
657
658  if (selectedId && foods.some((item) => item.id === selectedId)) $("#foodSelect").value = selectedId;
659  updateQuantityHint();
660  updateNutritionPreview();
661}
662
663function updateQuantityHint() {
664  const item = getFood($("#foodSelect").value);
665  $("#quantityHint").textContent = item ? `(الوحدة: ${unitLabels[item.unit]}، القيمة الأساسية: ${formatNumber(item.basis, 1)})` : "";
666  if (item && !$("#foodQuantity").dataset.userEdited) $("#foodQuantity").value = item.basis;
667}
668
669function updateNutritionPreview() {
670  const item = getFood($("#foodSelect").value);
671  const quantity = Number($("#foodQuantity").value);
672  const nutrition = item && quantity > 0 ? calculateNutrition(item, quantity) : { calories: 0, protein: 0, carbs: 0, fat: 0 };
673  $("#previewCalories").textContent = formatNumber(nutrition.calories, 1);
674  $("#previewProtein").textContent = item?.protein === null ? "غير معروف" : `${formatNumber(nutrition.protein, 1)} جم`;
675  $("#previewCarbs").textContent = item?.carbs === null ? "غير معروف" : `${formatNumber(nutrition.carbs, 1)} جم`;
676  $("#previewFat").textContent = item?.fat === null ? "غير معروف" : `${formatNumber(nutrition.fat, 1)} جم`;
677}
678
679function populateDraftFoodSelect(search = "", selectedId = null) {
680  const normalized = search.trim().toLowerCase();
681  const foods = [...state.foods]
682    .filter((item) => item.categoryId !== "drinks")
683    .filter((item) => !normalized || `${item.name} ${item.brand}`.toLowerCase().includes(normalized))
684    .sort((a, b) => Number(b.favorite) - Number(a.favorite) || a.name.localeCompare(b.name, "ar"));
685
686  $("#draftFoodSelect").innerHTML = foods.length
687    ? foods.map((item) => `<option value="${item.id}">${item.favorite ? "★ " : ""}${escapeHtml(item.name)} — ${formatNumber(item.calories, 1)} سعر/${formatNumber(item.basis, 1)} ${unitLabels[item.unit]}</option>`).join("")
688    : `<option value="">لا توجد نتيجة</option>`;
689
690  if (selectedId && foods.some((item) => item.id === selectedId)) $("#draftFoodSelect").value = selectedId;
691  updateDraftQuantityHint();
692  updateDraftNutritionPreview();
693}
694
695function updateDraftQuantityHint() {
696  const item = getFood($("#draftFoodSelect").value);
697  $("#draftQuantityHint").textContent = item ? `(الوحدة: ${unitLabels[item.unit]}، القيمة الأساسية: ${formatNumber(item.basis, 1)})` : "";
698  if (item && !$("#draftFoodQuantity").dataset.userEdited) $("#draftFoodQuantity").value = item.basis;
699}
700
701function updateDraftNutritionPreview() {
702  const item = getFood($("#draftFoodSelect").value);
703  const quantity = Number($("#draftFoodQuantity").value);
704  const nutrition = item && quantity > 0 ? calculateNutrition(item, quantity) : { calories: 0, protein: 0, carbs: 0, fat: 0 };
705  $("#draftPreviewCalories").textContent = formatNumber(nutrition.calories, 1);
706  $("#draftPreviewProtein").textContent = item?.protein === null ? "غير معروف" : `${formatNumber(nutrition.protein, 1)} جم`;
707  $("#draftPreviewCarbs").textContent = item?.carbs === null ? "غير معروف" : `${formatNumber(nutrition.carbs, 1)} جم`;
708  $("#draftPreviewFat").textContent = item?.fat === null ? "غير معروف" : `${formatNumber(nutrition.fat, 1)} جم`;
709}
710
711function resetCustomDrinkForm() {
712  const form = $("#customDrinkForm");
713  if (form) form.reset();
714}
715
716function openCustomDrinkDialog(target = "log") {
717  customDrinkTarget = target === "draft" ? "draft" : "log";
718  resetCustomDrinkForm();
719  const dialog = $("#customDrinkDialog");
720  if (dialog && typeof dialog.showModal === "function") dialog.showModal();
721  setTimeout(() => $("#customDrinkName")?.focus(), 50);
722}
723
724function closeCustomDrinkDialog() {
725  const dialog = $("#customDrinkDialog");
726  if (dialog?.open) dialog.close();
727}
728
729function saveCustomDrink() {
730  const name = $("#customDrinkName").value.trim();
731  const caloriesValue = $("#customDrinkCalories").value;
732  if (!name) return showToast("اكتب اسم المشروب.");
733  if (caloriesValue === "" || Number(caloriesValue) < 0) return showToast("اكتب سعرات الكوب.");
734
735  const servingMl = optionalNumber($("#customDrinkMl").value);
736  const notesParts = [];
737  const userNotes = $("#customDrinkNotes").value.trim();
738  if (servingMl !== null) notesParts.push(`حجم الكوب التقريبي: ${formatNumber(servingMl, 0)} مل.`);
739  if (userNotes) notesParts.push(userNotes);
740
741  const item = normalizeFood({
742    id: makeId(),
743    name,
744    calories: Number(caloriesValue),
745    basis: 1,
746    unit: "cup",
747    protein: optionalNumber($("#customDrinkProtein").value),
748    carbs: optionalNumber($("#customDrinkCarbs").value),
749    fat: optionalNumber($("#customDrinkFat").value),
750    fiber: optionalNumber($("#customDrinkFiber").value),
751    categoryId: "drinks",
752    brand: $("#customDrinkBrand").value.trim(),
753    notes: notesParts.join(" "),
754    favorite: $("#customDrinkFavorite").checked,
755    custom: true,
756    source: "أضافه المستخدم"
757  });
758
759  state.foods.push(item);
760  saveState();
761  populateDrinkSelects();
762  renderFoods();
763  renderExplore();
764
765  if (customDrinkTarget === "draft") {
766    $("#draftDrinkSelect").value = item.id;
767    updateDrinkPreview(true);
768  } else {
769    $("#drinkSelect").value = item.id;
770    updateDrinkPreview(false);
771  }
772
773  closeCustomDrinkDialog();
774  resetCustomDrinkForm();
775  showToast(`تم حفظ ${item.name} في قائمة المشروبات.`);
776}
777
778function getDrinkFoods() {
779  return state.foods
780    .filter((item) => item.categoryId === "drinks" && item.id !== "base-sugar-tsp")
781    .sort((a, b) => a.name.localeCompare(b.name, "ar"));
782}
783
784function populateDrinkSelects() {
785  const drinks = getDrinkFoods();
786  const options = drinks.map((item) => `<option value="${item.id}">${escapeHtml(item.name)} — ${formatNumber(item.calories, 1)} سعر/كوب</option>`).join("");
787  ["#drinkSelect", "#draftDrinkSelect"].forEach((selector) => {
788    const element = $(selector);
789    if (!element) return;
790    const current = element.value;
791    element.innerHTML = options || `<option value="">لا توجد مشروبات</option>`;
792    if (drinks.some((item) => item.id === current)) element.value = current;
793  });
794  updateDrinkPreview(false);
795  updateDrinkPreview(true);
796}
797
798function calculateDrinkNutrition(item, cups, sugarPerCup) {
799  const base = calculateNutrition(item, cups);
800  const sugarTsp = Math.max(0, Number(sugarPerCup) || 0) * Math.max(0, Number(cups) || 0);
801  return {
802    calories: base.calories + sugarTsp * 16,
803    protein: base.protein,
804    carbs: base.carbs + sugarTsp * 4,
805    fat: base.fat,
806    fiber: base.fiber,
807    sugarTsp
808  };
809}
810
811function updateDrinkPreview(draft = false) {
812  const select = $(draft ? "#draftDrinkSelect" : "#drinkSelect");
813  const cupsInput = $(draft ? "#draftDrinkCups" : "#drinkCups");
814  const sugarInput = $(draft ? "#draftDrinkSugar" : "#drinkSugar");
815  const preview = $(draft ? "#draftDrinkPreview" : "#drinkPreview");
816  const item = getFood(select?.value);
817  const cups = Number(cupsInput?.value) || 0;
818  const sugar = Number(sugarInput?.value) || 0;
819  const nutrition = item ? calculateDrinkNutrition(item, cups, sugar) : { calories: 0, carbs: 0 };
820  if (preview) preview.innerHTML = `<strong>${formatNumber(nutrition.calories, 1)} سعر</strong><span>${formatNumber(nutrition.carbs, 1)} جم كارب • ${formatNumber(sugar, 1)} ملعقة سكر لكل كوب</span>`;
821}
822
823function buildDrinkSnapshot(item, sugarPerCup) {
824  const sugar = Math.max(0, Number(sugarPerCup) || 0);
825  return normalizeFood({
826    id: `drink-${item.id}-${String(sugar).replace(".", "-")}`,
827    name: `${item.name}${sugar > 0 ? ` + ${formatNumber(sugar, 1)} ملعقة سكر/كوب` : " بدون سكر"}`,
828    basis: 1,
829    unit: "cup",
830    calories: Number(item.calories) + sugar * 16,
831    protein: item.protein,
832    carbs: (Number(item.carbs) || 0) + sugar * 4,
833    fat: item.fat,
834    fiber: item.fiber,
835    categoryId: "drinks",
836    custom: false,
837    source: "حاسبة المشروبات",
838    notes: "السكر محسوب بملعقة صغيرة تقريبًا 4 جم."
839  });
840}
841
842function addDrinkToLog() {
843  const date = $("#logDate").value || todayISO();
844  const item = getFood($("#drinkSelect").value);
845  const cups = Number($("#drinkCups").value);
846  const sugar = Number($("#drinkSugar").value) || 0;
847  if (!item || !cups || cups <= 0) return showToast("اختر المشروب وعدد الأكواب.");
848  const snapshot = buildDrinkSnapshot(item, sugar);
849  const entry = { id: makeId(), mealType: $("#mealType").value, foodId: "", foodSnapshot: snapshot, quantity: cups, createdAt: new Date().toISOString() };
850  state.logs[date] = getEntries(date).concat(entry);
851  saveState();
852  renderSelectedDay(); renderDashboard(); renderHistory();
853  showToast(`تمت إضافة ${snapshot.name}.`);
854}
855
856function addDrinkToDraft() {
857  const item = getFood($("#draftDrinkSelect").value);
858  const cups = Number($("#draftDrinkCups").value);
859  const sugar = Number($("#draftDrinkSugar").value) || 0;
860  if (!item || !cups || cups <= 0) return showToast("اختر المشروب وعدد الأكواب.");
861  const snapshot = buildDrinkSnapshot(item, sugar);
862  mealDraftEntries.push({ id: makeId(), foodId: "", foodSnapshot: snapshot, quantity: cups, temporary: true });
863  renderMealPlanner();
864  showToast("تمت إضافة المشروب للوجبة التجريبية فقط.");
865}
866
867function getDraftItem(entry) {
868  return (entry.foodId && getFood(entry.foodId)) || entry.foodSnapshot;
869}
870
871function getDraftTotals() {
872  return mealDraftEntries.reduce((total, entry) => {
873    const item = getDraftItem(entry);
874    if (!item) return total;
875    const nutrition = calculateNutrition(item, entry.quantity);
876    Object.keys(total).forEach((key) => { total[key] += nutrition[key] || 0; });
877    return total;
878  }, { calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0 });
879}
880
881function renderMealPlanner() {
882  if (!$("#draftFoodSelect")) return;
883  if (!$("#draftFoodSelect").options.length) populateDraftFoodSelect();
884  const date = $("#draftCommitDate").value || todayISO();
885  const dayTotals = getDayTotals(date);
886  const goals = getGoalsForDate(date);
887  const totals = getDraftTotals();
888  const currentRemaining = goals.calories - dayTotals.calories;
889  const afterMeal = dayTotals.calories + totals.calories;
890  const remainingAfter = goals.calories - afterMeal;
891  const fitClass = totals.calories === 0 ? "" : remainingAfter >= 0 ? "good" : "warn";
892
893  $("#draftGoalSummary").className = `meal-fit-card ${fitClass}`;
894  $("#draftGoalSummary").innerHTML = `<span>المتاح في ${formatDate(date, { day: "numeric", month: "short" })}</span><strong>${formatNumber(Math.max(0, currentRemaining))} سعر</strong><small>${totals.calories ? (remainingAfter >= 0 ? `بعد الوجبة سيتبقى ${formatNumber(remainingAfter)} سعر` : `الوجبة ستتجاوز الهدف بـ ${formatNumber(Math.abs(remainingAfter))}
894 سعر`) : "ابدأ بإضافة مكونات للوجبة التجريبية"}</small>`;
895
896  $("#draftMealTotals").innerHTML = `<div><span>السعرات</span><strong>${formatNumber(totals.calories, 1)}</strong></div><div><span>البروتين</span><strong>${formatNumber(totals.protein, 1)} جم</strong></div><div><span>الكارب</span><strong>${formatNumber(totals.carbs, 1)} جم</strong></div><div><span>الدهون</span><strong>${formatNumber(totals.fat, 1)} جم</strong></div>`;
897
898  $("#draftMealItems").innerHTML = mealDraftEntries.length ? mealDraftEntries.map((entry) => {
899    const item = getDraftItem(entry);
900    const nutrition = calculateNutrition(item, entry.quantity);
901    const detail = item.protein === null && item.carbs === null && item.fat === null
902      ? "القيم التفصيلية غير معروفة"
903      : `${formatNumber(nutrition.protein, 1)} بروتين • ${formatNumber(nutrition.carbs, 1)} كارب • ${formatNumber(nutrition.fat, 1)} دهون`;
904    return `<div class="draft-item"><div><strong>${escapeHtml(item.name)}</strong><span>${formatNumber(entry.quantity, 1)} ${unitLabels[item.unit] || item.unit} • ${detail}</span></div><b>${formatNumber(nutrition.calories, 1)} سعر</b><button class="icon-button" data-delete-draft="${entry.id}" aria-label="حذف من التجربة">×</button></div>`;
905  }).join("") : `<div class="empty-state"><strong>الوجبة التجريبية فارغة</strong><span>أضف مكونًا أو أكثر، ولن يُسجل أي شيء في يومك الآن.</span></div>`;
906
907  $("#commitDraftMeal").disabled = mealDraftEntries.length === 0;
908}
909
910function addExistingFoodToDraft() {
911  const item = getFood($("#draftFoodSelect").value);
912  const quantity = Number($("#draftFoodQuantity").value);
913  if (!item || !quantity || quantity <= 0) return showToast("اختر طعامًا وكمية صحيحة.");
914  mealDraftEntries.push({ id: makeId(), foodId: item.id, foodSnapshot: deepClone(item), quantity, temporary: false });
915  renderMealPlanner();
916  showToast(`تمت إضافة ${item.name} للتجربة فقط.`);
917}
918
919function addCustomFoodToDraft() {
920  const name = $("#draftCustomName").value.trim();
921  const calories = Number($("#draftCustomCalories").value);
922  if (!name) return showToast("اكتب اسم المكوّن.");
923  if (!Number.isFinite(calories) || calories < 0) return showToast("اكتب سعرات صحيحة.");
924  const item = normalizeFood({
925    id: `draft-${makeId()}`, name, basis: 1, unit: "serving", calories,
926    protein: optionalNumber($("#draftCustomProtein").value),
927    carbs: optionalNumber($("#draftCustomCarbs").value),
928    fat: optionalNumber($("#draftCustomFat").value),
929    fiber: null, categoryId: "uncategorized", custom: true, source: "مكوّن تجريبي"
930  });
931  mealDraftEntries.push({ id: makeId(), foodId: "", foodSnapshot: item, quantity: 1, temporary: true });
932  $("#draftCustomForm").reset();
933  renderMealPlanner();
934  showToast("تمت إضافة المكوّن المؤقت للتجربة.");
935}
936
937function commitDraftMeal() {
938  if (!mealDraftEntries.length) return showToast("أضف مكونات للوجبة أولًا.");
939  const date = $("#draftCommitDate").value || todayISO();
940  const mealType = $("#draftCommitMeal").value;
941  const entries = mealDraftEntries.map((draft) => ({
942    id: makeId(), mealType, foodId: draft.foodId || "", foodSnapshot: deepClone(getDraftItem(draft)),
943    quantity: draft.quantity, createdAt: new Date().toISOString()
944  }));
945  state.logs[date] = getEntries(date).concat(entries);
946  saveState();
947  const count = entries.length;
948  mealDraftEntries = [];
949  renderAll();
950  showToast(`تمت إضافة الوجبة (${count} مكونات) إلى يومك.`);
951}
952
953function renderSelectedDay() {
954  const date = $("#logDate").value || todayISO();
955  const totals = getDayTotals(date);
956  const goals = getGoalsForDate(date);
957  $("#selectedDayTitle").textContent = formatDate(date);
958  $("#selectedDaySummary").innerHTML = summaryCards(totals, goals);
959  $("#selectedDayEntries").innerHTML = renderMealGroups(date);
960}
961
962function summaryCards(totals, goals) {
963  return `<div><span>السعرات</span><strong>${formatNumber(totals.calories)} / ${formatNumber(goals.calories)}</strong></div><div><span>البروتين</span><strong>${formatNumber(totals.protein, 1)} جم</strong></div><div><span>الكارب</span><strong>${formatNumber(totals.carbs, 1)} جم</strong></div><div><span>الدهون</span><strong>${formatNumber(totals.fat, 1)} جم</strong></div>`;
964}
965
966function populateCategorySelects() {
967  const options = `<option value="all">كل التصنيفات</option>${state.categories.map((category) => `<option value="${category.id}">${escapeHtml(category.name)}</option>`).join("")}`;
968  const noAllOptions = state.categories.map((category) => `<option value="${category.id}">${escapeHtml(category.name)}</option>`).join("");
969  ["#exploreCategory", "#foodsListCategory"].forEach((selector) => {
970    const current = $(selector).value;
971    $(selector).innerHTML = options;
972    if ([...$(selector).options].some((option) => option.value === current)) $(selector).value = current;
973  });
974  const customCurrent = $("#customCategory").value;
975  $("#customCategory").innerHTML = noAllOptions;
976  if ([...$("#customCategory").options].some((option) => option.value === customCurrent)) $("#customCategory").value = customCurrent;
977}
978
979function renderFoods() {
980  populateCategorySelects();
981  const search = $("#foodsListSearch").value.trim().toLowerCase();
982  const category = $("#foodsListCategory").value || "all";
983  const items = [...state.foods]
984    .filter((item) => (!search || `${item.name} ${item.brand}`.toLowerCase().includes(search)) && (category === "all" || item.categoryId === category))
985    .sort((a, b) => Number(b.favorite) - Number(a.favorite) || Number(b.custom) - Number(a.custom) || a.name.localeCompare(b.name, "ar"));
986
987  $("#foodsList").innerHTML = items.length ? items.map((item) => {
988    const categoryItem = getCategory(item.categoryId);
989    const macroText = [
990      item.protein === null ? null : `بروتين ${formatNumber(item.protein, 1)}`,
991      item.carbs === null ? null : `كارب ${formatNumber(item.carbs, 1)}`,
992      item.fat === null ? null : `دهون ${formatNumber(item.fat, 1)}`
993    ].filter(Boolean).join(" • ") || "القيم التفصيلية غير مسجلة";
994    return `<div class="food-row"><div class="food-row-main"><div class="food-icon">${escapeHtml(categoryItem.icon)}</div><div><strong>${item.favorite ? "★ " : ""}${escapeHtml(item.name)}</strong><small>${formatNumber(item.calories, 1)} سعر لكل ${formatNumber(item.basis, 1)} ${unitLabels[item.unit]} • ${macroText}</small><span class="badge">${escapeHtml(categoryItem.name)}${item.custom ? " • مخصص" : ""}</span></div></div><div class="food-actions"><button class="icon-button" data-quick-add="${item.id}" title="إضافة لليوم">+</button><button class="icon-button" data-toggle-favorite="${item.id}" title="المفضلة">${item.favorite ? "★" : "☆"}</button>${item.custom ? `<button class="icon-button" data-edit-food="${item.id}" title="تعديل">✎</button><button class="icon-button" data-delete-food="${item.id}" title="حذف">×</button>` : ""}</div></div>`;
995  }).join("") : `<div class="empty-state"><strong>لا توجد أطعمة مطابقة</strong><span>غيّر البحث أو أضف طعامًا جديدًا.</span></div>`;
996}
997
998function resetCustomFoodForm() {
999  $("#customFoodForm").reset();
1000  $("#editingFoodId").value = "";
1001  $("#customFoodBasis").value = 100;
1002  $("#customFoodUnit").value = "g";
1003  $("#customCategory").value = "uncategorized";
1004  $("#foodFormTitle").textContent = "طعام جديد";
1005  $("#cancelFoodEdit").hidden = true;
1006  $("#macroCheck").hidden = true;
1007}
1008
1009function editFood(foodId) {
1010  const item = getFood(foodId);
1011  if (!item || !item.custom) return;
1012  switchView("foods");
1013  $("#editingFoodId").value = item.id;
1014  $("#customFoodName").value = item.name;
1015  $("#customCalories").value = item.calories;
1016  $("#customFoodBasis").value = item.basis;
1017  $("#customFoodUnit").value = item.unit;
1018  $("#customProtein").value = item.protein ?? "";
1019  $("#customCarbs").value = item.carbs ?? "";
1020  $("#customFat").value = item.fat ?? "";
1021  $("#customFiber").value = item.fiber ?? "";
1022  $("#customCategory").value = item.categoryId;
1023  $("#customBrand").value = item.brand || "";
1024  $("#customNotes").value = item.notes || "";
1025  $("#customFavorite").checked = item.favorite;
1026  $("#foodFormTitle").textContent = "تعديل الطعام";
1027  $("#cancelFoodEdit").hidden = false;
1028  const details = $(".advanced-details");
1029  if (details) details.open = true;
1030  window.scrollTo({ top: 0, behavior: "smooth" });
1031  updateMacroCheck();
1032}
1033
1034function updateMacroCheck() {
1035  const calories = optionalNumber($("#customCalories").value);
1036  const protein = optionalNumber($("#customProtein").value);
1037  const carbs = optionalNumber($("#customCarbs").value);
1038  const fat = optionalNumber($("#customFat").value);
1039  const box = $("#macroCheck");
1040  if (calories === null || protein === null || carbs === null || fat === null) {
1041    box.hidden = true;
1042    return;
1043  }
1044  const calculated = protein * 4 + carbs * 4 + fat * 9;
1045  const difference = Math.abs(calories - calculated);
1046  if (difference < Math.max(20, calories * .12)) {
1047    box.hidden = true;
1048    return;
1049  }
1050  box.hidden = false;
1051  box.textContent = `تنبيه: السعرات المحسوبة من الماكروز تساوي تقريبًا ${formatNumber(calculated)} سعر، بينما أدخلت ${formatNumber(calories)}. يمكن الحفظ، لكن راجع ملصق المنتج.`;
1052}
1053
1054function renderExplore() {
1055  populateCategorySelects();
1056  const search = $("#exploreSearch").value.trim().toLowerCase();
1057  const category = $("#exploreCategory").value || "all";
1058  const filter = $("#exploreFilter").value;
1059  const sort = $("#exploreSort").value;
1060  const remaining = Math.max(0, getGoalsForDate(todayISO()).calories - getDayTotals(todayISO()).calories);
1061
1062  let items = state.foods.filter((item) => {
1063    if (search && !`${item.name} ${item.brand} ${getCategory(item.categoryId).name}`.toLowerCase().includes(search)) return false;
1064    if (category !== "all" && item.categoryId !== category) return false;
1065    const nutrition = calculateNutrition(item, item.basis);
1066    if (filter === "highProtein" && nutrition.protein < 15) return false;
1067    if (filter === "lowCalorie" && nutrition.calories > 150) return false;
1068    if (filter === "lowFat" && (item.fat === null || nutrition.fat > 5)) return false;
1069    if (filter === "highFiber" && (item.fiber === null || nutrition.fiber < 3)) return false;
1070    if (filter === "favorites" && !item.favorite) return false;
1071    return true;
1072  });
1073
1074  const score = (item) => {
1075    const n = calculateNutrition(item, item.basis);
1076    const fit = remaining ? Math.max(0, 80 - Math.abs(remaining - n.calories) / Math.max(remaining, 1) * 80) : 0;
1077    return fit + n.protein * 4 + (item.favorite ? 12 : 0) - n.fat * .4;
1078  };
1079
1080  if (sort === "recommended") items.sort((a, b) => score(b) - score(a));
1081  if (sort === "proteinDesc") items.sort((a, b) => (b.protein || 0) - (a.protein || 0));
1082  if (sort === "caloriesAsc") items.sort((a, b) => a.calories - b.calories);
1083  if (sort === "name") items.sort((a, b) => a.name.localeCompare(b.name, "ar"));
1084
1085  $("#exploreCount").textContent = `${formatNumber(items.length)} طعام متاح`;
1086  $("#exploreFoods").innerHTML = items.length ? items.map(renderExploreCard).join("") : `<div class="empty-state"><strong>لا توجد نتائج</strong><span>جرّب بحثًا أو فلترًا مختلفًا.</span></div>`;
1087  renderCompare();
1088}
1089
1090function renderExploreCard(item) {
1091  const nutrition = calculateNutrition(item, item.basis);
1092  const tags = getFoodTags(item, nutrition);
1093  const compared = compareIds.includes(item.id);
1094  return `<article class="food-card"><div class="food-card-head"><div><h4>${item.favorite ? "★ " : ""}${escapeHtml(item.name)}</h4><p>${escapeHtml(getCategory(item.categoryId).name)} • لكل ${formatNumber(item.basis, 1)} ${unitLabels[item.unit]}</p></div><span class="badge">${escapeHtml(item.source || "قاعدة البرنامج")}</span></div><div class="food-card-macros"><div><span>سعرات</span><strong>${formatNumber(nutrition.calories)}</strong></div><div><span>بروتين</span><strong>${item.protein === null ? "—" : formatNumber(nutrition.protein, 1)}</strong></div><div><span>كارب</span><strong>${item.carbs === null ? "—" : formatNumber(nutrition.carbs, 1)}</strong></div><div><span>دهون</span><strong>${item.fat === null ? "—" : formatNumber(nutrition.fat, 1)}</strong></div></div>${item.notes ? `<p class="food-source-note">${escapeHtml(item.notes)}</p>` : ""}<div class="food-tags">${tags.map((tag) => `<span class="food-tag">${tag}</span>`).join("")}</div><div class="food-card-actions"><button class="primary-button compact" data-quick-add="${item.id}">أضف ليومي</button><button class="ghost-button compact" data-compare-food="${item.id}">${compared ? "إزالة" : "قارن"}</button></div></article>`;
1095}
1096
1097function getFoodTags(item, nutrition) {
1098  const tags = [];
1099  if (nutrition.protein >= 20) tags.push("بروتين مرتفع");
1100  if (nutrition.calories <= 100) tags.push("سعرات منخفضة");
1101  if (item.fat !== null && nutrition.fat <= 3) tags.push("قليل الدهون");
1102  if (item.fiber !== null && nutrition.fiber >= 3) tags.push("ألياف جيدة");
1103  if (nutrition.calories >= 350) tags.push("كثافة سعرية مرتفعة");
1104  if (!tags.length) tags.push("كمية مرنة");
1105  return tags.slice(0, 3);
1106}
1107
1108function renderCompare() {
1109  const items = compareIds.map(getFood).filter(Boolean);
1110  if (!items.length) {
1111    $("#compareFoods").innerHTML = `<div class="empty-state"><strong>لم تختر أطعمة</strong><span>اضغط «قارن» على أي طعام.</span></div>`;
1112    return;
1113  }
1114  $("#compareFoods").innerHTML = items.map((item) => {
1115    const n = calculateNutrition(item, item.basis);
1116    return `<div class="compare-item"><strong>${escapeHtml(item.name)}</strong><span>${formatNumber(n.calories)} سعر • ${item.protein === null ? "بروتين غير معروف" : `${formatNumber(n.protein, 1)} جم بروتين`} • ${item.fat === null ? "دهون غير معروفة" : `${formatNumber(n.fat, 1)} جم دهون`}</span></div>`;
1117  }).join("");
1118}
1119
1120function renderUsdaResults() {
1121  if (!usdaSearchResults.length) return;
1122  $("#usdaResults").innerHTML = usdaSearchResults.map((item, index) => `<div class="external-item"><strong>${escapeHtml(item.name)}</strong><small>${formatNumber(item.calories, 1)} سعر • بروتين ${formatNumber(item.protein, 1)} • كارب ${formatNumber(item.carbs, 1)} • دهون ${formatNumber(item.fat, 1)} لكل 100 جم</small><button class="ghost-button compact" data-import-usda="${index}">أضفه لمكتبتي</button></div>`).join("");
1123}
1124
1125async function searchUsdaFoods() {
1126  const query = $("#usdaQuery").value.trim();
1127  if (!query) return showToast("اكتب اسم الطعام بالإنجليزية أولًا.");
1128  const key = $("#usdaApiKey").value.trim() || "DEMO_KEY";
1129  $("#searchUsda").disabled = true;
1130  $("#searchUsda").textContent = "جارٍ البحث...";
1131  try {
1132    const response = await fetch(`https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${encodeURIComponent(key)}&query=${encodeURIComponent(query)}&pageSize=10`);
1133    if (!response.ok) throw new Error(`USDA ${response.status}`);
1134    const data = await response.json();
1135    usdaSearchResults = (data.foods || []).map((result) => {
1136      const nutrients = result.foodNutrients || [];
1137      const getNutrient = (number, names) => {
1138        const match = nutrients.find((n) => String(n.nutrientNumber || n.number || "") === String(number)) || nutrients.find((n) => names.some((name) => String(n.nutrientName || n.name || "").toLowerCase().includes(name)));
1139        return Number(match?.value) || 0;
1140      };
1141      return {
1142        name: result.description || "USDA Food",
1143        calories: getNutrient(1008, ["energy"]),
1144        protein: getNutrient(1003, ["protein"]),
1145        carbs: getNutrient(1005, ["carbohydrate"]),
1146        fat: getNutrient(1004, ["total lipid", "total fat"]),
1147        fiber: getNutrient(1079, ["fiber"]),
1148        sourceId: String(result.fdcId || "")
1149      };
1150    }).filter((item) => item.calories || item.protein || item.carbs || item.fat);
1151    $("#usdaResults").innerHTML = usdaSearchResults.length ? "" : `<div class="mini-note">لم تظهر نتائج غذائية قابلة للاستخدام.</div>`;
1152    renderUsdaResults();
1153  } catch (error) {
1154    console.error(error);
1155    $("#usdaResults").innerHTML = `<div class="mini-note">تعذر الاتصال بقاعدة USDA. جرّب لاحقًا أو استخدم مفتاح API خاصًا بك.</div>`;
1156  } finally {
1157    $("#searchUsda").disabled = false;
1158    $("#searchUsda").textContent = "بحث خارجي";
1159  }
1160}
1161
1162function importUsdaFood(index) {
1163  const imported = usdaSearchResults[index];
1164  if (!imported) return;
1165  state.foods.push(normalizeFood({
1166    id: makeId(), name: imported.name, basis: 100, unit: "g", calories: imported.calories,
1167    protein: imported.protein, carbs: imported.carbs, fat: imported.fat, fiber: imported.fiber,
1168    categoryId: guessCategory(imported.name), favorite: false, custom: true,
1169    source: "USDA FoodData Central", sourceId: imported.sourceId, notes: "تم الاستيراد من البحث الخارجي"
1170  }));
1171  saveState();
1172  renderFoods();
1173  renderExplore();
1174  populateFoodSelect();
1175  showToast("تمت إضافة الطعام إلى مكتبتك.");
1176}
1177
1178function calculateBmr(sex, age, height, weight) {
1179  const base = 10 * Number(weight) + 6.25 * Number(height) - 5 * Number(age);
1180  return sex === "male" ? base + 5 : base - 161;
1181}
1182
1183function populateCalculatorFromProfile(force = true) {
1184  if (!force && $("#calcAge").value) return;
1185  $("#calcSex").value = state.profile.sex === "female" ? "female" : "male";
1186  $("#calcAge").value = state.profile.age;
1187  $("#calcHeight").value = state.profile.height;
1188  $("#calcWeight").value = state.profile.weight;
1189  let activity = 1.5;
1190  if (state.profile.dailyActivity === "sedentary" && state.profile.workouts < 3) activity = 1.375;
1191  if (state.profile.dailyActivity === "light") activity = state.profile.workouts >= 3 ? 1.55 : 1.375;
1192  if (state.profile.dailyActivity === "active") activity = state.profile.workouts >= 5 ? 1.725 : 1.55;
1193  $("#calcActivity").value = String(activity);
1194}
1195
1196function calculateEnergyFromForm() {
1197  const sex = $("#calcSex").value;
1198  const age = Number($("#calcAge").value);
1199  const height = Number($("#calcHeight").value);
1200  const weight = Number($("#calcWeight").value);
1201  const activity = Number($("#calcActivity").value);
1202  const goalType = $("#calcGoalType").value;
1203  const mode = $("#calcAdjustmentMode").value;
1204  const adjustmentInput = Math.max(0, Number($("#calcAdjustment").value) || 0);
1205  const bmr = calculateBmr(sex, age, height, weight);
1206  const tdee = bmr * activity;
1207  const customAdjustment = goalType === "maintain" ? 0 : mode === "percent" ? tdee * adjustmentInput / 100 : adjustmentInput;
1208  const customTarget = goalType === "deficit" ? tdee - customAdjustment : goalType === "surplus" ? tdee + customAdjustment : tdee;
1209
1210  // اقتراحات تلقائية محافظة كبداية، مع إظهار نطاق بدل ادعاء رقم قطعي.
1211  const lossLow = tdee * 0.90;      // عجز 10%
1212  const lossRecommended = tdee * 0.85; // عجز 15%
1213  const lossHigh = tdee * 0.80;     // عجز 20%
1214  const gainLow = tdee * 1.05;      // فائض 5%
1215  const gainRecommended = tdee * 1.075; // فائض 7.5%
1216  const gainHigh = tdee * 1.10;     // فائض 10%
1217
1218  const targets = {
1219    maintain: tdee,
1220    loss: lossRecommended,
1221    gain: gainRecommended,
1222    custom: customTarget
1223  };
1224  const selectedTargetKey = goalType === "deficit" ? "custom" : goalType === "surplus" ? "custom" : "maintain";
1225  return {
1226    sex, age, height, weight, activity, goalType, mode, adjustmentInput,
1227    bmr, tdee, customAdjustment, customTarget, targets, selectedTargetKey,
1228    lossRange: { min: lossHigh, recommended: lossRecommended, max: lossLow },
1229    gainRange: { min: gainLow, recommended: gainRecommended, max: gainHigh }
1230  };
1231}
1232
1233function selectedCalculatorTarget(result = lastCalculatorResult) {
1234  if (!result) return 0;
1235  return Number(result.targets?.[result.selectedTargetKey]) || Number(result.customTarget) || Number(result.tdee) || 0;
1236}
1237
1238function targetMeta(result, key) {
1239  const target = Number(result.targets[key]);
1240  const delta = target - result.tdee;
1241  const weeklyKg = Math.abs(delta) * 7 / 7700;
1242  if (key === "loss") return { label: "نزول مقترح", detail: `عجز 15% تقريبًا • ${formatNumber(weeklyKg, 2)} كجم/أسبوع نظريًا` };
1243  if (key === "gain") return { label: "زيادة مقترحة", detail: `فائض 7.5% تقريبًا • ${formatNumber(weeklyKg, 2)} كجم/أسبوع نظريًا` };
1244  if (key === "custom") return { label: "اختيارك المخصص", detail: result.goalType === "maintain" ? "ثبات" : `${delta < 0 ? "عجز" : "فائض"} ${formatNumber(Math.abs(delta))} سعر • ${formatNumber(weeklyKg, 2)} كجم/أسبوع نظريًا` };
1245  return { label: "سعرات الثبات", detail: "تقدير المحافظة على متوسط الوزن" };
1246}
1247
1248function renderCalculatorResults(result) {
1249  lastCalculatorResult = result;
1250  const selectedKey = result.selectedTargetKey || "maintain";
1251  const selected = selectedCalculatorTarget(result);
1252  const selectedDelta = selected - result.tdee;
1253  const selectedPercent = result.tdee ? Math.abs(selectedDelta) / result.tdee * 100 : 0;
1254  let level = "good";
1255  let title = "اقتراح تلقائي قابل للمراجعة";
1256  let advice = "ابدأ بالرقم المقترح وراقب متوسط الوزن والطاقة والجوع لمدة أسبوعين إلى ثلاثة، ثم عدّل 100–200 سعر حسب النتيجة الفعلية.";
1257
1258  if (selectedDelta < 0 && (selectedPercent > 25 || Math.abs(selectedDelta) > 1000)) {
1259    level = "danger";
1260    title = "العجز المختار شديد";
1261    advice = "هذا العجز قد يصعب الاستمرار عليه ويؤثر في الأداء والتغذية. اختر اقتراح النزول المحافظ أو راجع مختصًا، خصوصًا مع أي حالة صحية.";
1262  } else if (selectedDelta > 0 && selectedPercent > 15) {
1263    level = "warn";
1264    title = "الفائض المختار كبير نسبيًا";
1265    advice = "زيادة السعرات بهذا القدر قد ترفع اكتساب الدهون. اقتراح الزيادة المحافظ يبدأ عادةً بفائض أصغر ثم يُعدّل حسب متوسط الوزن والأداء.";
1266  }
1267
1268  const targetCard = (key, value, titleText, smallText) => `
1269    <button type="button" class="result-card selectable ${selectedKey === key ? "featured" : ""}" data-select-calculator-target="${key}">
1270      <span>${titleText}</span><strong>${formatNumber(value)}</strong><small>${smallText}</small>
1271    </button>`;
1272
1273  const customMeta = targetMeta(result, "custom");
1274  const selectedMeta = targetMeta(result, selectedKey);
1275  $("#calculatorResults").className = "calculator-results";
1276  $("#calculatorResults").innerHTML = `
1277    <div class="bmr-summary"><div><span>BMR الحرق الأساسي</span><strong>${formatNumber(result.bmr)}</strong><small>سعر/يوم في الراحة التامة</small></div></div>
1278    <div class="recommendation-heading"><strong>اقتراحات التطبيق تلقائيًا</strong><span>اضغط على الخطة التي تريد اعتمادها</span></div>
1279    <div class="result-grid recommendation-grid">
1280      ${targetCard("maintain", result.targets.maintain, "الثبات", "حافظ على متوسط الوزن")}
1281      ${targetCard("loss", result.targets.loss, "النزول المقترح", `النطاق ${formatNumber(result.lossRange.min)}–${formatNumber(result.lossRange.max)}`)}
1282      ${targetCard("gain", result.targets.gain, "الزيادة المقترحة", `النطاق ${formatNumber(result.gainRange.min)}–${formatNumber(result.gainRange.max)}`)}
1283      ${targetCard("custom", result.targets.custom, customMeta.label, customMeta.detail)}
1284    </div>
1285    <div class="selected-target-line"><span>الخطة المختارة الآن</span><strong>${selectedMeta.label}: ${formatNumber(selected)} سعر/يوم</strong></div>
1286    <div class="advice-box ${level}"><strong>${title}</strong><p>${advice}</p><p>الثبات والحركة الفعلية تقديرات؛ النتيجة الأدق تُراجع من اتجاه الوزن المسجل، وليست ضمانًا لمعدل تغير محدد.</p></div>
1287    <div class="result-actions"><button class="primary-button" data-apply-calculator="default">اعتمد المختار كهدف أساسي</button><button class="secondary-button" data-apply-calculator="training">لهدف التمرين</button><button class="secondary-button" data-apply-calculator="rest">لهدف الراحة</button></div>`;
1288}
1289
1290function selectCalculatorTarget(key) {
1291  if (!lastCalculatorResult?.targets || !(key in lastCalculatorResult.targets)) return;
1292  lastCalculatorResult.selectedTargetKey = key;
1293  renderCalculatorResults(lastCalculatorResult);
1294}
1295
1296function applyCalculatorResult(profileName) {
1297  if (!lastCalculatorResult) return;
1298  const target = selectedCalculatorTarget(lastCalculatorResult);
1299  state.goalProfiles[profileName].calories = Math.max(500, Math.round(target / 10) * 10);
1300  saveState();
1301  renderSettings(profileName);
1302  renderDashboard();
1303  showToast("تم اعتماد الخطة المختارة في الهدف المحدد.");
1304}
1305
1306function renderHistory() {
1307  const date = $("#historyDate").value || todayISO();
1308  const totals = getDayTotals(date);
1309  $("#historySummary").innerHTML = summaryCards(totals, getGoalsForDate(date));
1310  $("#historyMeals").innerHTML = renderMealGroups(date);
1311}
1312
1313function renderProgress() {
1314  const entries = [...state.weights].sort((a, b) => a.date.localeCompare(b.date));
1315  const start = entries[0]?.value;
1316  const current = entries.at(-1)?.value;
1317  const change = start !== undefined && current !== undefined ? current - start : null;
1318  $("#startWeight").textContent = start !== undefined ? `${formatNumber(start, 1)} كجم` : "—";
1319  $("#currentWeight").textContent = current !== undefined ? `${formatNumber(current, 1)} كجم` : "—";
1320  $("#weightChange").textContent = change !== null ? `${change > 0 ? "+" : ""}${formatNumber(change, 1)} كجم` : "—";
1321  $("#weightEntries").innerHTML = entries.length ? [...entries].reverse().map((entry) => `<div class="weight-row"><div><strong>${formatNumber(entry.value, 1)} كجم</strong><small>${formatDate(entry.date, { day: "numeric", month: "long", year: "numeric" })}${entry.note ? ` • ${escapeHtml(entry.note)}` : ""}</small></div><span>${entry.date}</span><button class="icon-button" data-delete-weight="${entry.id}">×</button></div>`).join("") : `<div class="empty-state"><strong>لا توجد قياسات</strong></div>`;
1322  drawWeightChart(entries);
1323}
1324
1325function drawWeightChart(entries) {
1326  const canvas = $("#weightChart");
1327  const rect = canvas.getBoundingClientRect();
1328  const ratio = window.devicePixelRatio || 1;
1329  const width = Math.max(300, rect.width || 600);
1330  const height = 260;
1331  canvas.width = width * ratio;
1332  canvas.height = height * ratio;
1333  const ctx = canvas.getContext("2d");
1334  ctx.scale(ratio, ratio);
1335  ctx.clearRect(0, 0, width, height);
1336  const styles = getComputedStyle(document.documentElement);
1337  const muted = styles.getPropertyValue("--muted").trim();
1338  const line = styles.getPropertyValue("--line").trim();
1339  const primary = styles.getPropertyValue("--primary-2").trim();
1340  ctx.font = "12px Segoe UI";
1341  ctx.fillStyle = muted;
1342  if (entries.length < 2) {
1343    ctx.textAlign = "center";
1344    ctx.fillText("أضف قياسين أو أكثر لعرض الاتجاه", width / 2, height / 2);
1345    return;
1346  }
1347  const values = entries.map((entry) => Number(entry.value));
1348  const min = Math.min(...values) - 1;
1349  const max = Math.max(...values) + 1;
1350  const pad = { top: 25, right: 20, bottom: 38, left: 46 };
1351  const chartW = width - pad.left - pad.right;
1352  const chartH = height - pad.top - pad.bottom;
1353  ctx.strokeStyle = line;
1354  ctx.lineWidth = 1;
1355  for (let i = 0; i <= 4; i++) {
1356    const y = pad.top + chartH * i / 4;
1357    ctx.beginPath();
1357 ctx.moveTo(pad.left, y); ctx.lineTo(width - pad.right, y); ctx.stroke();
1358    const label = max - (max - min) * i / 4;
1359    ctx.fillStyle = muted; ctx.textAlign = "right"; ctx.fillText(label.toFixed(1), pad.left - 7, y + 4);
1360  }
1361  const points = entries.map((entry, index) => ({
1362    x: pad.left + chartW * index / Math.max(1, entries.length - 1),
1363    y: pad.top + (max - Number(entry.value)) / Math.max(.1, max - min) * chartH,
1364    entry
1365  }));
1366  ctx.strokeStyle = primary; ctx.lineWidth = 3; ctx.beginPath();
1367  points.forEach((point, index) => index ? ctx.lineTo(point.x, point.y) : ctx.moveTo(point.x, point.y));
1368  ctx.stroke();
1369  points.forEach((point, index) => {
1370    ctx.fillStyle = primary; ctx.beginPath(); ctx.arc(point.x, point.y, 4, 0, Math.PI * 2); ctx.fill();
1371    if (index === 0 || index === points.length - 1 || entries.length <= 5) {
1372      ctx.fillStyle = muted; ctx.textAlign = "center"; ctx.fillText(point.entry.date.slice(5), point.x, height - 14);
1373    }
1374  });
1375}
1376
1377function renderSettings(preferredGoalProfile = null) {
1378  $("#profileAge").value = state.profile.age;
1379  $("#profileSex").value = state.profile.sex;
1380  $("#profileHeight").value = state.profile.height;
1381  $("#profileWeight").value = state.profile.weight;
1382  $("#profileWorkouts").value = state.profile.workouts;
1383  $("#profileDailyActivity").value = state.profile.dailyActivity;
1384
1385  const profileName = preferredGoalProfile || $("#activeGoalProfile").value || "default";
1386  $("#activeGoalProfile").value = profileName;
1387  $$(".goal-tab").forEach((button) => button.classList.toggle("active", button.dataset.goalProfile === profileName));
1388  const goals = state.goalProfiles[profileName];
1389  $("#goalCalories").value = goals.calories;
1390  $("#goalProtein").value = goals.protein;
1391  $("#goalCarbs").value = goals.carbs;
1392  $("#goalFat").value = goals.fat;
1393  $("#goalWater").value = goals.water;
1394
1395  $("#supabaseUrl").value = cloudConfig.url || "";
1396  $("#supabaseAnonKey").value = cloudConfig.anonKey || "";
1397  $("#cloudEmail").value = cloudSession?.user?.email || "";
1398  renderCloudStatus();
1399}
1400
1401function deleteLog(date, id) {
1402  state.logs[date] = getEntries(date).filter((entry) => entry.id !== id);
1403  if (!state.logs[date].length) delete state.logs[date];
1404  saveState();
1405  renderDashboard(); renderSelectedDay(); renderHistory(); renderExplore();
1406}
1407
1408function copyDay(source, target) {
1409  const sourceEntries = getEntries(source);
1410  if (!sourceEntries.length) { showToast("اليوم السابق لا يحتوي على وجبات."); return false; }
1411  const copies = sourceEntries.map((entry) => ({ ...deepClone(entry), id: makeId(), createdAt: new Date().toISOString() }));
1412  state.logs[target] = getEntries(target).concat(copies);
1413  saveState();
1414  showToast("تم نسخ الوجبات بنجاح.");
1415  renderDashboard(); renderSelectedDay(); renderHistory();
1416  return true;
1417}
1418
1419function prepareFoodForLog(foodId) {
1420  const item = getFood(foodId);
1421  if (!item) return;
1422  switchView("add");
1423  if (item.categoryId === "drinks") {
1424    populateDrinkSelects();
1425    $("#drinkSelect").value = item.id;
1426    $("#drinkCups").value = 1;
1427    $("#drinkSugar").value = 0;
1428    updateDrinkPreview(false);
1429    return;
1430  }
1431  $("#foodSearch").value = item.name;
1432  $("#foodQuantity").dataset.userEdited = "";
1433  populateFoodSelect(item.name, item.id);
1434  $("#foodQuantity").value = item.basis;
1435  updateNutritionPreview();
1436}
1437
1438function confirmAction(title, message) {
1439  const dialog = $("#confirmDialog");
1440  $("#confirmTitle").textContent = title;
1441  $("#confirmMessage").textContent = message;
1442  dialog.showModal();
1443  return new Promise((resolve) => dialog.addEventListener("close", () => resolve(dialog.returnValue === "confirm"), { once: true }));
1444}
1445
1446function downloadFile(filename, content, type) {
1447  const blob = new Blob([content], { type });
1448  const url = URL.createObjectURL(blob);
1449  const link = document.createElement("a");
1450  link.href = url; link.download = filename; link.click();
1451  setTimeout(() => URL.revokeObjectURL(url), 1000);
1452}
1453
1454function exportCsv() {
1455  const rows = [["date", "meal", "food", "quantity", "unit", "calories", "protein", "carbs", "fat"]];
1456  Object.keys(state.logs).sort().forEach((date) => {
1457    getEntries(date).forEach((entry) => {
1458      const item = getFood(entry.foodId) || entry.foodSnapshot;
1459      if (!item) return;
1460      const n = calculateNutrition(item, entry.quantity);
1461      rows.push([date, mealLabels[entry.mealType], item.name, entry.quantity, unitLabels[item.unit], n.calories.toFixed(1), n.protein.toFixed(1), n.carbs.toFixed(1), n.fat.toFixed(1)]);
1462    });
1463  });
1464  const csv = "\uFEFF" + rows.map((row) => row.map((cell) => `"${String(cell ?? "").replaceAll('"', '""')}"`).join(",")).join("\n");
1465  downloadFile(`my-diet-tracker-${todayISO()}.csv`, csv, "text/csv;charset=utf-8");
1466}
1467
1468function openTodayGoalDialog() {
1469  const date = todayISO();
1470  const override = state.dayGoalOverrides[date];
1471  const mode = override ? "custom" : (state.dayModes[date] || "default");
1472  $("#todayGoalMode").value = mode;
1473  const goals = override || state.goalProfiles[mode === "custom" ? "default" : mode];
1474  $("#todayGoalCalories").value = goals.calories;
1475  $("#todayGoalProtein").value = goals.protein;
1476  $("#todayGoalCarbs").value = goals.carbs;
1477  $("#todayGoalFat").value = goals.fat;
1478  $("#todayGoalWater").value = goals.water;
1479  toggleTodayCustomFields();
1480  $("#todayGoalDialog").showModal();
1481}
1482
1483function toggleTodayCustomFields() {
1484  const custom = $("#todayGoalMode").value === "custom";
1485  $("#todayCustomGoalFields").style.display = custom ? "grid" : "none";
1486  if (!custom) {
1487    const goals = state.goalProfiles[$("#todayGoalMode").value];
1488    if (goals) {
1489      $("#todayGoalCalories").value = goals.calories;
1490      $("#todayGoalProtein").value = goals.protein;
1491      $("#todayGoalCarbs").value = goals.carbs;
1492      $("#todayGoalFat").value = goals.fat;
1493      $("#todayGoalWater").value = goals.water;
1494    }
1495  }
1496}
1497
1498function saveTodayGoal() {
1499  const date = todayISO();
1500  const mode = $("#todayGoalMode").value;
1501  if (mode === "custom") {
1502    state.dayGoalOverrides[date] = {
1503      calories: Number($("#todayGoalCalories").value), protein: Number($("#todayGoalProtein").value),
1504      carbs: Number($("#todayGoalCarbs").value), fat: Number($("#todayGoalFat").value), water: Number($("#todayGoalWater").value)
1505    };
1506    delete state.dayModes[date];
1507  } else {
1508    state.dayModes[date] = mode;
1509    delete state.dayGoalOverrides[date];
1510  }
1511  saveState();
1512  renderDashboard(); renderSelectedDay(); renderExplore();
1513  showToast("تم تعديل هدف اليوم.");
1514}
1515
1516function resetTodayGoal() {
1517  const date = todayISO();
1518  delete state.dayModes[date];
1519  delete state.dayGoalOverrides[date];
1520  saveState();
1521  $("#todayGoalDialog").close();
1522  renderDashboard(); renderSelectedDay(); renderExplore();
1523  showToast("عاد اليوم إلى الهدف الأساسي.");
1524}
1525
1526function updateStorageStatus() {
1527  const connected = Boolean(cloudSession?.access_token && cloudConfig.url && cloudConfig.anonKey);
1528  $("#storageStatus").textContent = connected ? "حفظ محلي + مزامنة سحابية" : "الحفظ المحلي يعمل";
1529  $("#syncStatusButton").textContent = connected ? "سحابي" : "محلي";
1530}
1531
1532function loadCloudConfigFromForm() {
1533  cloudConfig = { url: $("#supabaseUrl").value.trim().replace(/\/$/, ""), anonKey: $("#supabaseAnonKey").value.trim() };
1534  localStorage.setItem(CLOUD_CONFIG_KEY, JSON.stringify(cloudConfig));
1535  renderCloudStatus();
1536  showToast("تم حفظ إعداد الاتصال.");
1537}
1538
1539function cloudConfigured() {
1540  return Boolean(cloudConfig.url && cloudConfig.anonKey);
1541}
1542
1543async function authRequest(path, body) {
1544  if (!cloudConfigured()) throw new Error("أدخل رابط Supabase والمفتاح أولًا.");
1545  const response = await fetch(`${cloudConfig.url}${path}`, {
1546    method: "POST",
1547    headers: { "Content-Type": "application/json", apikey: cloudConfig.anonKey },
1548    body: JSON.stringify(body)
1549  });
1550  const data = await response.json().catch(() => ({}));
1551  if (!response.ok) throw new Error(data.msg || data.message || data.error_description || data.error || "تعذر الاتصال");
1552  return data;
1553}
1554
1555async function cloudLogin() {
1556  try {
1557    const data = await authRequest("/auth/v1/token?grant_type=password", { email: $("#cloudEmail").value.trim(), password: $("#cloudPassword").value });
1558    cloudSession = data;
1559    localStorage.setItem(CLOUD_SESSION_KEY, JSON.stringify(cloudSession));
1560    $("#cloudPassword").value = "";
1561    renderCloudStatus();
1562    await reconcileCloudOnLogin();
1563    showToast("تم تسجيل الدخول والمزامنة.");
1564  } catch (error) {
1565    setCloudMessage(error.message, true);
1566  }
1567}
1568
1569async function cloudSignup() {
1570  try {
1571    const data = await authRequest("/auth/v1/signup", { email: $("#cloudEmail").value.trim(), password: $("#cloudPassword").value });
1572    $("#cloudPassword").value = "";
1573    if (data.access_token) {
1574      cloudSession = data;
1575      localStorage.setItem(CLOUD_SESSION_KEY, JSON.stringify(cloudSession));
1576      await pushStateToCloud(false);
1577      showToast("تم إنشاء الحساب وربط البيانات.");
1578    } else {
1579      showToast("تم إنشاء الحساب. افحص بريدك لتأكيده ثم سجّل الدخول.");
1580    }
1581    renderCloudStatus();
1582  } catch (error) {
1583    setCloudMessage(error.message, true);
1584  }
1585}
1586
1587function cloudLogout() {
1588  cloudSession = null;
1589  localStorage.removeItem(CLOUD_SESSION_KEY);
1590  renderCloudStatus();
1591  showToast("تم تسجيل الخروج. الحفظ المحلي مستمر.");
1592}
1593
1594async function refreshCloudSession() {
1595  if (!cloudSession?.refresh_token) throw new Error("انتهت الجلسة. سجّل الدخول مجددًا.");
1596  const data = await authRequest("/auth/v1/token?grant_type=refresh_token", { refresh_token: cloudSession.refresh_token });
1597  cloudSession = data;
1598  localStorage.setItem(CLOUD_SESSION_KEY, JSON.stringify(cloudSession));
1599  renderCloudStatus();
1600  return data;
1601}
1602
1603async function cloudRest(path, options = {}, retry = true) {
1604  if (!cloudConfigured() || !cloudSession?.access_token) throw new Error("سجّل الدخول أولًا.");
1605  const response = await fetch(`${cloudConfig.url}/rest/v1/${path}`, {
1606    ...options,
1607    headers: {
1608      apikey: cloudConfig.anonKey,
1609      Authorization: `Bearer ${cloudSession.access_token}`,
1610      "Content-Type": "application/json",
1611      ...(options.headers || {})
1612    }
1613  });
1614  if (response.status === 401 && retry) {
1615    await refreshCloudSession();
1616    return cloudRest(path, options, false);
1617  }
1618  const text = await response.text();
1619  let data = null;
1620  try { data = text ? JSON.parse(text) : null; } catch { data = text ? { message: text } : null; }
1621  if (!response.ok) throw new Error(data?.message || data?.hint || `خطأ سحابي ${response.status}`);
1622  return data;
1623}
1624
1625async function pushStateToCloud(silent = true) {
1626  if (!cloudConfigured() || !cloudSession?.user?.id) return;
1627  try {
1628    await cloudRest("user_data?on_conflict=user_id", {
1629      method: "POST",
1630      headers: { Prefer: "resolution=merge-duplicates,return=minimal" },
1631      body: JSON.stringify([{ user_id: cloudSession.user.id, data: state, updated_at: state.updatedAt }])
1632    });
1633    setCloudMessage(`آخر رفع: ${new Date().toLocaleTimeString("ar-EG", { hour: "2-digit", minute: "2-digit" })}`);
1634    if (!silent) showToast("تم رفع البيانات إلى السحابة.");
1635  } catch (error) {
1636    setCloudMessage(error.message, true);
1637    if (!silent) showToast("تعذر رفع البيانات.");
1638  }
1639}
1640
1641async function fetchCloudState() {
1642  const rows = await cloudRest(`user_data?user_id=eq.${encodeURIComponent(cloudSession.user.id)}&select=data,updated_at`, { method: "GET" });
1643  return Array.isArray(rows) && rows[0] ? rows[0] : null;
1644}
1645
1646async function pullStateFromCloud(confirmReplace = true) {
1647  try {
1648    const row = await fetchCloudState();
1649    if (!row?.data) {
1650      showToast("لا توجد بيانات سحابية بعد. ارفع بيانات هذا الجهاز أولًا.");
1651      return;
1652    }
1653    if (confirmReplace) {
1654      const ok = await confirmAction("تنزيل بيانات السحابة", "سيتم استبدال البيانات المحلية الحالية بالنسخة السحابية.");
1655      if (!ok) return;
1656    }
1657    isApplyingRemote = true;
1658    state = normalizeState(row.data);
1659    localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
1660    initializeValues(); renderAll();
1661    isApplyingRemote = false;
1662    setCloudMessage("تم تنزيل أحدث نسخة سحابية.");
1663    showToast("تم تنزيل البيانات السحابية.");
1664  } catch (error) {
1665    isApplyingRemote = false;
1666    setCloudMessage(error.message, true);
1667  }
1668}
1669
1670async function reconcileCloudOnLogin() {
1671  const row = await fetchCloudState();
1672  if (!row?.data) {
1673    await pushStateToCloud(true);
1674    return;
1675  }
1676
1677  // On a genuinely new device, always download the existing cloud copy first.
1678  // This prevents the fresh default state from overwriting real cloud data.
1679  if (!LOCAL_STATE_PRESENT_AT_LAUNCH) {
1680    await pullStateFromCloud(false);
1681    return;
1682  }
1683
1684  const cloudTime = new Date(row.updated_at || row.data.updatedAt || 0).getTime();
1685  const localTime = new Date(state.updatedAt || 0).getTime();
1686  if (cloudTime > localTime) await pullStateFromCloud(false);
1687  else await pushStateToCloud(true);
1688}
1689
1690function scheduleCloudPush() {
1691  if (!cloudConfigured() || !cloudSession?.access_token) return;
1692  clearTimeout(cloudSyncTimer);
1693  cloudSyncTimer = setTimeout(() => pushStateToCloud(true), 1800);
1694}
1695
1696function setCloudMessage(message, error = false) {
1697  const element = $("#cloudMessage");
1698  element.textContent = message;
1699  element.style.color = error ? "var(--danger)" : "var(--muted)";
1700}
1701
1702function renderCloudStatus() {
1703  const connected = Boolean(cloudSession?.access_token && cloudSession?.user);
1704  $("#cloudBadge").textContent = connected ? `متصل: ${cloudSession.user.email || "حساب"}` : "غير متصل";
1705  $("#cloudBadge").classList.toggle("connected", connected);
1706  $("#cloudLogout").hidden = !connected;
1707  $("#cloudLogin").hidden = connected;
1708  $("#cloudSignup").hidden = connected;
1709  $("#pushCloud").disabled = !connected;
1710  $("#pullCloud").disabled = !connected;
1711  updateStorageStatus();
1712}
1713
1714function setupEventListeners() {
1715  $$('[data-view]').forEach((button) => button.addEventListener("click", () => switchView(button.dataset.view)));
1716  $$('[data-go]').forEach((button) => button.addEventListener("click", () => switchView(button.dataset.go)));
1717
1718  $("#themeToggle").addEventListener("click", () => {
1719    state.theme = state.theme === "dark" ? "light" : "dark";
1720    applyTheme(); saveState(); renderProgress();
1721  });
1722
1723  $("#addWater").addEventListener("click", () => { const date = todayISO(); state.water[date] = Math.round(((Number(state.water[date]) || 0) + 0.25) * 100) / 100; saveState(); renderDashboard(); });
1724  $("#removeWater").addEventListener("click", () => { const date = todayISO(); state.water[date] = Math.max(0, Math.round(((Number(state.water[date]) || 0) - 0.25) * 100) / 100); saveState(); renderDashboard(); });
1725  $("#repeatYesterdayBtn").addEventListener("click", () => copyDay(addDays(todayISO(), -1), todayISO()));
1726  $("#editTodayGoal").addEventListener("click", openTodayGoalDialog);
1727  $("#todayGoalMode").addEventListener("change", toggleTodayCustomFields);
1728  $("#todayGoalForm").addEventListener("submit", (event) => { event.preventDefault(); saveTodayGoal(); $("#todayGoalDialog").close(); });
1729  $("#resetTodayGoal").addEventListener("click", resetTodayGoal);
1730
1731  $("#foodSearch").addEventListener("input", (event) => populateFoodSelect(event.target.value));
1732  $("#foodSelect").addEventListener("change", () => { $("#foodQuantity").dataset.userEdited = ""; updateQuantityHint(); updateNutritionPreview(); });
1733  $("#foodQuantity").addEventListener("input", () => { $("#foodQuantity").dataset.userEdited = "1"; updateNutritionPreview(); });
1734  $("#logDate").addEventListener("change", renderSelectedDay);
1735  $$('[data-open-custom-drink]').forEach((button) => button.addEventListener("click", () => openCustomDrinkDialog(button.dataset.openCustomDrink)));
1736  $("#customDrinkForm").addEventListener("submit", (event) => { event.preventDefault(); saveCustomDrink(); });
1737  $("#closeCustomDrinkDialog").addEventListener("click", closeCustomDrinkDialog);
1738  $("#cancelCustomDrink").addEventListener("click", closeCustomDrinkDialog);
1739  ["#drinkSelect", "#drinkCups", "#drinkSugar"].forEach((selector) => $(selector).addEventListener(selector === "#drinkSelect" ? "change" : "input", () => updateDrinkPreview(false)));
1740  $("#logDrinkForm").addEventListener("submit", (event) => { event.preventDefault(); addDrinkToLog(); });
1741  ["#draftDrinkSelect", "#draftDrinkCups", "#draftDrinkSugar"].forEach((selector) => $(selector).addEventListener(selector === "#draftDrinkSelect" ? "change" : "input", () => updateDrinkPreview(true)));
1742  $("#draftDrinkForm").addEventListener("submit", (event) => { event.preventDefault(); addDrinkToDraft(); });
1743  $("#draftFoodSearch").addEventListener("input", (event) => populateDraftFoodSelect(event.target.value));
1744  $("#draftFoodSelect").addEventListener("change", () => { $("#draftFoodQuantity").dataset.userEdited = "";
1744 updateDraftQuantityHint(); updateDraftNutritionPreview(); });
1745  $("#draftFoodQuantity").addEventListener("input", () => { $("#draftFoodQuantity").dataset.userEdited = "1"; updateDraftNutritionPreview(); });
1746  $("#draftFoodForm").addEventListener("submit", (event) => { event.preventDefault(); addExistingFoodToDraft(); });
1747  $("#draftCustomForm").addEventListener("submit", (event) => { event.preventDefault(); addCustomFoodToDraft(); });
1748  $("#draftCommitDate").addEventListener("change", renderMealPlanner);
1749  $("#clearDraftMeal").addEventListener("click", () => { mealDraftEntries = []; renderMealPlanner(); showToast("تم مسح الوجبة التجريبية."); });
1750  $("#commitDraftMeal").addEventListener("click", commitDraftMeal);
1751
1752  $("#logFoodForm").addEventListener("submit", (event) => {
1753    event.preventDefault();
1754    const date = $("#logDate").value;
1755    const item = getFood($("#foodSelect").value);
1756    const quantity = Number($("#foodQuantity").value);
1757    if (!item || !date || !quantity || quantity <= 0) return;
1758    const entry = { id: makeId(), mealType: $("#mealType").value, foodId: item.id, foodSnapshot: deepClone(item), quantity, createdAt: new Date().toISOString() };
1759    state.logs[date] = getEntries(date).concat(entry);
1760    saveState();
1761    showToast(`تمت إضافة ${item.name}.`);
1762    renderSelectedDay(); renderDashboard(); renderHistory(); renderExplore();
1763  });
1764
1765  ["#customCalories", "#customProtein", "#customCarbs", "#customFat"].forEach((selector) => $(selector).addEventListener("input", updateMacroCheck));
1766  $("#customFoodForm").addEventListener("submit", (event) => {
1767    event.preventDefault();
1768    const editingId = $("#editingFoodId").value;
1769    const data = normalizeFood({
1770      id: editingId || makeId(),
1771      name: $("#customFoodName").value.trim(),
1772      calories: Number($("#customCalories").value),
1773      basis: Number($("#customFoodBasis").value) || 100,
1774      unit: $("#customFoodUnit").value || "g",
1775      protein: optionalNumber($("#customProtein").value),
1776      carbs: optionalNumber($("#customCarbs").value),
1777      fat: optionalNumber($("#customFat").value),
1778      fiber: optionalNumber($("#customFiber").value),
1779      categoryId: $("#customCategory").value || "uncategorized",
1780      brand: $("#customBrand").value.trim(),
1781      notes: $("#customNotes").value.trim(),
1782      favorite: $("#customFavorite").checked,
1783      custom: true,
1784      source: "أضافه المستخدم"
1785    });
1786    if (!data.name) return showToast("اكتب اسم الطعام.");
1787    if (editingId) {
1788      const index = state.foods.findIndex((item) => item.id === editingId);
1789      if (index >= 0) state.foods[index] = data;
1790    } else state.foods.push(data);
1791    saveState(); resetCustomFoodForm(); renderFoods(); renderExplore(); populateFoodSelect();
1792    showToast(editingId ? "تم تعديل الطعام." : "تمت إضافة الطعام. يمكنك استخدامه الآن.");
1793  });
1794  $("#cancelFoodEdit").addEventListener("click", resetCustomFoodForm);
1795  $("#addCategoryButton").addEventListener("click", () => {
1796    const name = $("#newCategoryName").value.trim();
1797    if (!name) return showToast("اكتب اسم التصنيف أولًا.");
1798    const existing = state.categories.find((category) => category.name.toLowerCase() === name.toLowerCase());
1799    if (existing) { $("#customCategory").value = existing.id; return showToast("التصنيف موجود بالفعل."); }
1800    const id = `cat-${makeId()}`;
1801    state.categories.push({ id, name, icon: name.slice(0, 1), custom: true });
1802    saveState(); populateCategorySelects(); $("#customCategory").value = id; $("#newCategoryName").value = ""; renderFoods(); renderExplore();
1803    showToast("تمت إضافة التصنيف.");
1804  });
1805  $("#foodsListSearch").addEventListener("input", renderFoods);
1806  $("#foodsListCategory").addEventListener("change", renderFoods);
1807
1808  ["#exploreSearch", "#exploreCategory", "#exploreFilter", "#exploreSort"].forEach((selector) => $(selector).addEventListener(selector === "#exploreSearch" ? "input" : "change", renderExplore));
1809  $("#clearCompare").addEventListener("click", () => { compareIds = []; renderExplore(); });
1810  $("#searchUsda").addEventListener("click", searchUsdaFoods);
1811
1812  $("#calculatorForm").addEventListener("submit", (event) => { event.preventDefault(); renderCalculatorResults(calculateEnergyFromForm()); });
1813  $("#loadMyProfile").addEventListener("click", () => populateCalculatorFromProfile(true));
1814  $("#calcGoalType").addEventListener("change", updateCalculatorAdjustmentUi);
1815  $("#calcAdjustmentMode").addEventListener("change", updateCalculatorAdjustmentUi);
1816
1817  $("#historyDate").addEventListener("change", renderHistory);
1818  $("#copyPreviousDay").addEventListener("click", () => { const target = $("#historyDate").value; if (target) copyDay(addDays(target, -1), target); });
1819
1820  $("#weightForm").addEventListener("submit", (event) => {
1821    event.preventDefault();
1822    const date = $("#weightDate").value;
1823    const value = Number($("#weightValue").value);
1824    const note = $("#weightNote").value.trim();
1825    const existing = state.weights.find((entry) => entry.date === date);
1826    if (existing) { existing.value = value; existing.note = note; }
1827    else state.weights.push({ id: makeId(), date, value, note });
1828    state.profile.weight = value;
1829    saveState(); renderProgress(); renderDashboard(); renderSettings(); showToast(existing ? "تم تحديث وزن اليوم." : "تم حفظ الوزن.");
1830    $("#weightNote").value = "";
1831  });
1832
1833  $("#profileForm").addEventListener("submit", (event) => {
1834    event.preventDefault();
1835    state.profile = {
1836      age: Number($("#profileAge").value), sex: $("#profileSex").value, height: Number($("#profileHeight").value),
1837      weight: Number($("#profileWeight").value), workouts: Number($("#profileWorkouts").value), dailyActivity: $("#profileDailyActivity").value
1838    };
1839    saveState(); renderDashboard(); showToast("تم حفظ بياناتك.");
1840  });
1841  $$(".goal-tab").forEach((button) => button.addEventListener("click", () => renderSettings(button.dataset.goalProfile)));
1842  $("#goalsForm").addEventListener("submit", (event) => {
1843    event.preventDefault();
1844    const profileName = $("#activeGoalProfile").value;
1845    state.goalProfiles[profileName] = {
1846      calories: Number($("#goalCalories").value), protein: Number($("#goalProtein").value), carbs: Number($("#goalCarbs").value),
1847      fat: Number($("#goalFat").value), water: Number($("#goalWater").value)
1848    };
1849    saveState(); renderDashboard(); renderExplore(); showToast("تم حفظ الهدف.");
1850  });
1851
1852  $("#saveCloudConfig").addEventListener("click", loadCloudConfigFromForm);
1853  $("#cloudLogin").addEventListener("click", cloudLogin);
1854  $("#cloudSignup").addEventListener("click", cloudSignup);
1855  $("#cloudLogout").addEventListener("click", cloudLogout);
1856  $("#pushCloud").addEventListener("click", () => pushStateToCloud(false));
1857  $("#pullCloud").addEventListener("click", () => pullStateFromCloud(true));
1858
1859  $("#exportBackup").addEventListener("click", () => downloadFile(`my-diet-tracker-backup-${todayISO()}.json`, JSON.stringify(state, null, 2), "application/json"));
1860  $("#exportCsv").addEventListener("click", exportCsv);
1861  $("#importBackup").addEventListener("change", async (event) => {
1862    const file = event.target.files[0];
1863    if (!file) return;
1864    try {
1865      const parsed = JSON.parse(await file.text());
1866      const ok = await confirmAction("استرجاع النسخة", "سيتم استبدال البيانات الحالية بمحتوى النسخة الاحتياطية.");
1867      if (ok) { state = normalizeState(parsed); saveState(); initializeValues(); renderAll(); showToast("تم استرجاع النسخة بنجاح."); }
1868    } catch { showToast("الملف غير صالح أو تالف."); }
1869    event.target.value = "";
1870  });
1871  $("#resetData").addEventListener("click", async () => {
1872    const ok = await confirmAction("مسح كل البيانات", "سيتم مسح الوجبات والأطعمة المخصصة والأوزان من هذا الجهاز. إعداد الاتصال السحابي لن يُمسح.");
1873    if (ok) { state = deepClone(initialState); saveState(); initializeValues(); renderAll(); showToast("تمت إعادة التطبيق لحالته الأولى."); }
1874  });
1875
1876  document.addEventListener("click", async (event) => {
1877    const quickAdd = event.target.closest("[data-quick-add]");
1878    if (quickAdd) return prepareFoodForLog(quickAdd.dataset.quickAdd);
1879    const deleteDraftButton = event.target.closest("[data-delete-draft]");
1880    if (deleteDraftButton) { mealDraftEntries = mealDraftEntries.filter((entry) => entry.id !== deleteDraftButton.dataset.deleteDraft); renderMealPlanner(); return; }
1881    const deleteLogButton = event.target.closest("[data-delete-log]");
1882    if (deleteLogButton) { const ok = await confirmAction("حذف الطعام", "هل تريد حذف هذا الطعام من اليوم؟"); if (ok) deleteLog(deleteLogButton.dataset.date, deleteLogButton.dataset.deleteLog); return; }
1883    const favoriteButton = event.target.closest("[data-toggle-favorite]");
1884    if (favoriteButton) { const item = getFood(favoriteButton.dataset.toggleFavorite); if (item) { item.favorite = !item.favorite; saveState(); renderFoods(); renderExplore(); populateFoodSelect($("#foodSearch").value); } return; }
1885    const editButton = event.target.closest("[data-edit-food]");
1886    if (editButton) return editFood(editButton.dataset.editFood);
1887    const deleteFoodButton = event.target.closest("[data-delete-food]");
1888    if (deleteFoodButton) {
1889      const item = getFood(deleteFoodButton.dataset.deleteFood);
1890      if (!item) return;
1891      const ok = await confirmAction("حذف طعام مخصص", `سيُحذف «${item.name}» من القائمة. السجلات القديمة ستظل قابلة للحساب.`);
1892      if (ok) { state.foods = state.foods.filter((foodItem) => foodItem.id !== item.id);
1892 compareIds = compareIds.filter((id) => id !== item.id); saveState(); renderFoods(); renderExplore(); populateFoodSelect(); }
1893      return;
1894    }
1895    const compareButton = event.target.closest("[data-compare-food]");
1896    if (compareButton) {
1897      const id = compareButton.dataset.compareFood;
1898      if (compareIds.includes(id)) compareIds = compareIds.filter((itemId) => itemId !== id);
1899      else if (compareIds.length < 3) compareIds.push(id);
1900      else return showToast("يمكن مقارنة 3 أطعمة بحد أقصى.");
1901      renderExplore(); return;
1902    }
1903    const importButton = event.target.closest("[data-import-usda]");
1904    if (importButton) return importUsdaFood(Number(importButton.dataset.importUsda));
1905    const selectTargetButton = event.target.closest("[data-select-calculator-target]");
1906    if (selectTargetButton) return selectCalculatorTarget(selectTargetButton.dataset.selectCalculatorTarget);
1907    const applyButton = event.target.closest("[data-apply-calculator]");
1908    if (applyButton) return applyCalculatorResult(applyButton.dataset.applyCalculator);
1909    const deleteWeightButton = event.target.closest("[data-delete-weight]");
1910    if (deleteWeightButton) { const ok = await confirmAction("حذف قياس الوزن", "هل تريد حذف هذا القياس؟"); if (ok) { state.weights = state.weights.filter((entry) => entry.id !== deleteWeightButton.dataset.deleteWeight); saveState(); renderProgress(); renderDashboard(); } }
1911  });
1912
1913  window.addEventListener("resize", debounce(() => { if ($("#progressView").classList.contains("active-view")) renderProgress(); }, 120));
1914  window.addEventListener("beforeinstallprompt", (event) => { event.preventDefault(); deferredInstallPrompt = event; $("#installButton").hidden = false; });
1915  $("#installButton").addEventListener("click", async () => { if (!deferredInstallPrompt) return; deferredInstallPrompt.prompt(); await deferredInstallPrompt.userChoice; deferredInstallPrompt = null; $("#installButton").hidden = true; });
1916}
1917
1918function updateCalculatorAdjustmentUi() {
1919  const goalType = $("#calcGoalType").value;
1920  const mode = $("#calcAdjustmentMode").value;
1921  $("#calcAdjustment").disabled = goalType === "maintain";
1922  $("#calcAdjustmentLabel").firstChild.textContent = mode === "percent" ? "النسبة %" : "عدد السعرات";
1923  if (goalType === "maintain") $("#calcAdjustment").value = 0;
1924  else if (Number($("#calcAdjustment").value) === 0) $("#calcAdjustment").value = mode === "percent" ? (goalType === "surplus" ? 8 : 15) : (goalType === "surplus" ? 250 : 500);
1925  $("#calcAdjustment").max = mode === "percent" ? 40 : 2000;
1926  $("#calcAdjustment").step = mode === "percent" ? 1 : 50;
1927}
1928
1929function debounce(fn, delay) {
1930  let timer;
1931  return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };
1932}
1933
1934function initializeValues() {
1935  const today = todayISO();
1936  $("#todayLabel").textContent = formatDate(today, { weekday: "long", day: "numeric", month: "long", year: "numeric" });
1937  $("#logDate").value = today;
1938  $("#historyDate").value = today;
1939  $("#weightDate").value = today;
1940  $("#draftCommitDate").value = today;
1941  $("#weightValue").value = state.profile.weight;
1942  applyTheme();
1943  populateCategorySelects();
1944  populateDrinkSelects();
1945  populateCalculatorFromProfile(true);
1946  updateCalculatorAdjustmentUi();
1947  updateStorageStatus();
1948}
1949
1950function renderAll() {
1951  renderDashboard();
1952  populateFoodSelect();
1953  populateDraftFoodSelect();
1954  populateDrinkSelects();
1955  renderSelectedDay();
1956  renderMealPlanner();
1957  renderFoods();
1958  renderExplore();
1959  renderHistory();
1960  renderProgress();
1961  renderSettings();
1962}
1963
1964function registerServiceWorker() {
1965  if ("serviceWorker" in navigator && location.protocol.startsWith("http")) {
1966    navigator.serviceWorker.register("sw.js").catch((error) => console.warn("Service worker registration failed", error));
1967  }
1968}
1969
1970function init() {
1971  initializeValues();
1972  setupEventListeners();
1973  renderAll();
1974  registerServiceWorker();
1975}
1976
1977document.addEventListener("DOMContentLoaded", init);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.