LakshmiNandaS commited on
Commit
6bdda0e
Β·
verified Β·
1 Parent(s): e08459f

Update pages/6_records.py

Browse files
Files changed (1) hide show
  1. pages/6_records.py +170 -480
pages/6_records.py CHANGED
@@ -4,6 +4,7 @@ import os, sys, json
4
  from datetime import date
5
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
  from auth_token import logout
 
7
 
8
  st.set_page_config(page_title="Records | FitPlan Pro", page_icon="πŸ†",
9
  layout="wide", initial_sidebar_state="collapsed")
@@ -16,109 +17,103 @@ data = st.session_state.user_data
16
 
17
  st.markdown("""
18
  <style>
19
- @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow+Condensed:wght@700;800;900&family=DM+Sans:opsz,wght@9..40,400;9..40,600;9..40,700&display=swap');
20
-
21
  [data-testid="stAppViewContainer"]::before{
22
  content:'';position:fixed;inset:0;z-index:0;
23
  background:url('https://images.unsplash.com/photo-1584735935682-2f2b69dff9d2?fm=jpg&w=1600&q=80&fit=crop') center center/cover no-repeat;
24
- filter:blur(8px) brightness(0.22) saturate(0.50);
25
- transform:scale(1.06);
26
- }
27
  [data-testid="stAppViewContainer"]{
28
- background:linear-gradient(160deg,rgba(3,1,0,0.92) 0%,rgba(4,2,0,0.85) 50%,rgba(3,1,0,0.96) 100%)!important;
29
- position:relative;
30
- }
31
- [data-testid="stAppViewContainer"]>section>div.block-container{
32
- position:relative;z-index:2;max-width:1100px!important;margin:0 auto!important;padding:0 24px 80px!important;
33
- }
34
-
35
  #MainMenu,footer,header,[data-testid="stToolbar"],[data-testid="stDecoration"],
36
  [data-testid="stSidebarNav"],[data-testid="collapsedControl"],
37
  section[data-testid="stSidebar"],button[kind="header"]{display:none!important;}
38
-
39
  html,body,.stApp{background:#050202!important;color:#fff!important;font-family:'DM Sans',sans-serif!important;}
 
 
 
40
 
41
- /* ── ALL INPUTS DARK ── */
42
- .stNumberInput>div>div>input,.stTextInput>div>div>input,
43
- .stTextArea>div>div>textarea{
44
- background:rgba(8,4,2,0.85)!important;border:1.5px solid rgba(229,9,20,0.40)!important;
45
- color:#fff!important;border-radius:12px!important;backdrop-filter:blur(20px)!important;
46
- box-shadow:0 2px 10px rgba(0,0,0,0.50)!important;font-family:'DM Sans',sans-serif!important;}
47
- .stNumberInput>div>div>input:focus,.stTextInput>div>div>input:focus{
48
- border-color:rgba(229,9,20,0.80)!important;background:rgba(15,6,3,0.95)!important;}
49
- .stNumberInput [data-testid="stNumberInputStepUp"],
50
- .stNumberInput [data-testid="stNumberInputStepDown"]{
51
- background:rgba(229,9,20,0.30)!important;border:none!important;color:#fff!important;border-radius:8px!important;}
52
-
53
- /* ── SELECTBOX DARK ── */
54
- [data-baseweb="select"]>div{
55
- background:rgba(8,4,2,0.85)!important;border:1.5px solid rgba(229,9,20,0.40)!important;
56
- border-radius:12px!important;backdrop-filter:blur(20px)!important;color:#fff!important;}
57
- [data-baseweb="select"] span,[data-baseweb="select"] div{color:#fff!important;}
58
- [data-baseweb="popover"] [role="option"]{background:rgba(10,6,4,0.96)!important;color:#fff!important;}
59
- [data-baseweb="popover"] [role="option"]:hover{background:rgba(229,9,20,0.25)!important;}
60
-
61
- /* ── WIDGET LABELS ── */
62
  [data-testid="stWidgetLabel"],[data-testid="stWidgetLabel"] p{
63
- color:#fff!important;font-size:0.90rem!important;font-weight:700!important;
64
  text-shadow:0 2px 8px rgba(0,0,0,0.99)!important;}
65
 
66
  /* ── BUTTONS ── */
67
- [data-testid="stFormSubmitButton"] button,.stFormSubmitButton>button{
68
  background:linear-gradient(135deg,#E50914,#c0000c)!important;border:none!important;
69
  color:#fff!important;border-radius:10px!important;font-weight:700!important;
70
  box-shadow:0 4px 20px rgba(229,9,20,0.45)!important;text-transform:uppercase!important;}
71
- .stButton>button{
72
- background:linear-gradient(135deg,rgba(229,9,20,0.85),rgba(160,0,10,0.90))!important;
73
  border:2px solid rgba(229,9,20,0.65)!important;color:#fff!important;border-radius:10px!important;
74
  font-family:'DM Sans',sans-serif!important;font-size:1.0rem!important;font-weight:700!important;
75
  box-shadow:0 0 12px rgba(229,9,20,0.30)!important;transition:all 0.20s!important;}
76
  .stButton>button:hover{transform:translateY(-2px)!important;box-shadow:0 0 24px rgba(229,9,20,0.60)!important;}
77
 
78
- /* ── TABS ── */
79
- .stTabs [data-baseweb="tab-list"]{
80
- background:rgba(10,5,3,0.82)!important;border-radius:10px!important;
81
- padding:4px!important;border:1px solid rgba(229,9,20,0.18)!important;}
82
- .stTabs [data-baseweb="tab"]{
83
- background:transparent!important;color:rgba(255,255,255,0.90)!important;
84
- border-radius:7px!important;font-size:0.95rem!important;font-weight:700!important;
85
- border:none!important;padding:9px 16px!important;
86
- text-shadow:0 2px 6px rgba(0,0,0,0.95)!important;}
87
- .stTabs [aria-selected="true"]{
88
- background:linear-gradient(135deg,#E50914,#c0000c)!important;
89
- color:#fff!important;box-shadow:0 3px 12px rgba(229,9,20,0.40)!important;}
90
 
91
- /* ── EXPANDERS DARK ── */
92
- .stExpander{
93
- background:rgba(8,4,2,0.82)!important;border:1.5px solid rgba(229,9,20,0.25)!important;
94
- border-radius:14px!important;backdrop-filter:blur(20px)!important;}
95
- .stExpander details summary{
96
- color:#fff!important;font-weight:700!important;font-size:1.00rem!important;
97
- text-shadow:0 2px 6px rgba(0,0,0,0.95)!important;}
 
 
 
98
 
99
  /* ── NAV BUTTONS ── */
100
  div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button{
101
  background:rgba(18,4,4,0.82)!important;border:2px solid rgba(229,9,20,0.65)!important;
102
  color:rgba(255,255,255,0.92)!important;border-radius:9px!important;
103
- font-size:0.85rem!important;font-weight:700!important;height:32px!important;
104
- min-height:32px!important;white-space:nowrap!important;}
105
  div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button:hover{
106
  background:rgba(229,9,20,0.28)!important;border-color:rgba(229,9,20,0.85)!important;color:#fff!important;}
107
  div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]:last-child>button{
108
- background:linear-gradient(135deg,#c0000c,#8b0000)!important;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- /* ── GLOBAL TEXT ── */
111
- html,body,.stApp,.stMarkdown,p,div,span,label{text-shadow:0 2px 6px rgba(0,0,0,0.98)!important;}
112
- .stMarkdown p,.stMarkdown li{color:#fff!important;font-size:1.05rem!important;line-height:1.75!important;}
113
  .nav-logo{font-family:'Bebas Neue',sans-serif;font-size:1.4rem;letter-spacing:5px;
114
  color:#E50914;text-shadow:0 0 18px rgba(229,9,20,0.45);line-height:1;}
115
 
116
- /* ── WEIGHT LOG INPUT FIX ── */
117
- [data-testid="stForm"] {background:transparent!important;border:none!important;}
 
 
 
118
  </style>
119
  """, unsafe_allow_html=True)
120
 
121
- # ── NAV ──────────────────────────────────────────────────────────────────────
 
 
 
 
 
122
  st.markdown("<div style='background:rgba(5,2,1,0.97);backdrop-filter:blur(20px);"
123
  "border-bottom:1.5px solid rgba(229,9,20,0.22);padding:5px 0;margin-bottom:16px'>",
124
  unsafe_allow_html=True)
@@ -152,7 +147,7 @@ with _n[7]:
152
  st.switch_page("app.py")
153
  st.markdown("</div>", unsafe_allow_html=True)
154
 
155
- # ── HERO ─────────────────────────────────────────────────────────────────────
156
  st.markdown("""
157
  <div style='background:linear-gradient(135deg,rgba(251,191,36,0.15),rgba(217,149,0,0.08) 40%,rgba(6,4,1,0.70));
158
  border:1px solid rgba(251,191,36,0.30);border-radius:18px;padding:28px 36px;margin-bottom:24px;
@@ -162,7 +157,7 @@ st.markdown("""
162
  <div style='font-family:Bebas Neue,sans-serif;font-size:2.4rem;font-weight:900;
163
  text-transform:uppercase;color:#fff;line-height:1;margin-bottom:6px'>
164
  Personal <span style='color:#fbbf24'>Records</span></div>
165
- <div style='font-size:1.00rem;color:rgba(255,255,255,0.80)'>
166
  Track your PRs, body measurements, and progress over time</div>
167
  </div>
168
  """, unsafe_allow_html=True)
@@ -170,232 +165,9 @@ st.markdown("""
170
  tab1, tab2, tab3, tab4 = st.tabs(["πŸ† Personal Records","πŸ“ Body Measurements","πŸ“ 1RM Calculator","πŸŽ–οΈ Achievements"])
171
 
172
  # ══════════════════════════════════════════════════════════════════════════════
173
- # TAB 1: PERSONAL RECORDS + WEIGHT LOG CHART (FIXED)
174
  # ══════════════════════════════════════════════════════════════════════════════
175
  with tab1:
176
- # ── Weight Log ───────────────────────────────────────────────────────────
177
- st.markdown(
178
- "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
179
- "color:rgba(229,9,20,0.85);margin-bottom:10px;display:flex;align-items:center;gap:8px'>"
180
- "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
181
- "βš–οΈ Weight Log</div>",
182
- unsafe_allow_html=True
183
- )
184
-
185
- if "weight_log" not in st.session_state:
186
- st.session_state.weight_log = []
187
- if not st.session_state.get("_wl_loaded"):
188
- try:
189
- from utils.db import get_weight_log as _gwl
190
- _wl = _gwl(uname)
191
- if _wl: st.session_state.weight_log = _wl
192
- except Exception: pass
193
- st.session_state._wl_loaded = True
194
-
195
- weight_log = st.session_state.weight_log
196
-
197
- wl_col1, wl_col2 = st.columns([1, 2])
198
- with wl_col1:
199
- with st.form("weight_log_form"):
200
- st.markdown(
201
- "<div style='font-size:0.80rem;font-weight:700;letter-spacing:2px;"
202
- "text-transform:uppercase;color:rgba(255,255,255,0.70);margin-bottom:6px'>"
203
- "Today's weight (kg)</div>",
204
- unsafe_allow_html=True
205
- )
206
- wl_val = st.number_input("Weight (kg)", min_value=20.0, max_value=300.0,
207
- value=float(data.get("weight", 70)),
208
- step=0.1, label_visibility="collapsed", key="wl_input")
209
- if st.form_submit_button("πŸ“ˆ Log Weight", use_container_width=True):
210
- entry = {"date": date.today().isoformat(), "weight": wl_val}
211
- # Avoid duplicate same-day entries β€” update if exists
212
- existing_dates = [e["date"] for e in weight_log]
213
- if entry["date"] in existing_dates:
214
- for e in weight_log:
215
- if e["date"] == entry["date"]:
216
- e["weight"] = wl_val
217
- else:
218
- weight_log.append(entry)
219
- weight_log.sort(key=lambda x: x["date"])
220
- st.session_state.weight_log = weight_log
221
- try:
222
- from utils.db import save_weight_log as _swl
223
- _swl(uname, weight_log)
224
- except Exception: pass
225
- st.success("Weight logged: " + str(wl_val) + " kg")
226
- st.rerun()
227
-
228
- # Stats
229
- if weight_log:
230
- start_w = weight_log[0]["weight"]
231
- curr_w = weight_log[-1]["weight"]
232
- delta_w = round(curr_w - start_w, 1)
233
- d_col = "#22c55e" if delta_w <= 0 else "#ef4444"
234
- d_sym = "β–Ό" if delta_w <= 0 else "β–²"
235
- st.markdown(
236
- f"<div style='background:rgba(8,4,2,0.85);border:1.5px solid rgba(229,9,20,0.30);"
237
- f"border-radius:14px;padding:14px 18px;margin-top:10px'>"
238
- f"<div style='display:grid;grid-template-columns:1fr 1fr;gap:10px'>"
239
- f"<div style='text-align:center'>"
240
- f"<div style='font-size:0.65rem;letter-spacing:2px;text-transform:uppercase;"
241
- f"color:rgba(255,255,255,0.50)'>Start</div>"
242
- f"<div style='font-family:Bebas Neue,sans-serif;font-size:1.6rem;color:#fff'>{start_w}kg</div>"
243
- f"</div>"
244
- f"<div style='text-align:center'>"
245
- f"<div style='font-size:0.65rem;letter-spacing:2px;text-transform:uppercase;"
246
- f"color:rgba(255,255,255,0.50)'>Current</div>"
247
- f"<div style='font-family:Bebas Neue,sans-serif;font-size:1.6rem;color:#fff'>{curr_w}kg</div>"
248
- f"</div>"
249
- f"</div>"
250
- f"<div style='text-align:center;margin-top:10px;padding-top:10px;"
251
- f"border-top:1px solid rgba(255,255,255,0.08)'>"
252
- f"<div style='font-size:0.65rem;letter-spacing:2px;text-transform:uppercase;"
253
- f"color:rgba(255,255,255,0.50)'>Total Change</div>"
254
- f"<div style='font-family:Bebas Neue,sans-serif;font-size:2rem;color:{d_col}'>"
255
- f"{d_sym} {abs(delta_w)}kg</div>"
256
- f"<div style='font-size:0.70rem;color:rgba(255,255,255,0.45);margin-top:2px'>"
257
- f"{len(weight_log)} entries logged</div>"
258
- f"</div></div>",
259
- unsafe_allow_html=True
260
- )
261
-
262
- with wl_col2:
263
- if len(weight_log) >= 2:
264
- vals = [e["weight"] for e in weight_log[-20:]]
265
- dates = [e["date"][5:] for e in weight_log[-20:]] # MM-DD
266
- N = len(vals)
267
- mn = min(vals) - 1
268
- mx = max(vals) + 1
269
- W, H = 400, 160
270
- pad_l, pad_r, pad_t, pad_b = 40, 10, 15, 30
271
-
272
- def _px(i, v):
273
- x = pad_l + i * ((W - pad_l - pad_r) / max(N-1, 1))
274
- y = pad_t + ((mx - v) / (mx - mn)) * (H - pad_t - pad_b)
275
- return round(x, 1), round(y, 1)
276
-
277
- # Grid lines
278
- grid_lines = ""
279
- for gi in range(4):
280
- gy = pad_t + gi * ((H - pad_t - pad_b) / 3)
281
- gv = round(mx - gi * ((mx - mn) / 3), 1)
282
- grid_lines += (
283
- f"<line x1='{pad_l}' y1='{round(gy,1)}' x2='{W-pad_r}' y2='{round(gy,1)}' "
284
- f"stroke='rgba(255,255,255,0.06)' stroke-width='1'/>"
285
- f"<text x='{pad_l-4}' y='{round(gy+4,1)}' fill='rgba(255,255,255,0.40)' "
286
- f"font-size='8' text-anchor='end'>{gv}</text>"
287
- )
288
-
289
- # Area fill
290
- area_pts = " ".join(f"{_px(i,v)[0]},{_px(i,v)[1]}" for i,v in enumerate(vals))
291
- first_x, first_y = _px(0, vals[0])
292
- last_x, last_y = _px(N-1, vals[-1])
293
- area_path = (
294
- f"M {first_x},{H-pad_b} "
295
- f"L {area_pts.split()[0].split(',')[0]},{area_pts.split()[0].split(',')[1]} "
296
- )
297
- for pt in area_pts.split():
298
- area_path += f"L {pt} "
299
- area_path += f"L {last_x},{H-pad_b} Z"
300
-
301
- # Line
302
- line_pts = " ".join(f"{_px(i,v)[0]},{_px(i,v)[1]}" for i,v in enumerate(vals))
303
-
304
- # Dots + labels for first, last, min, max
305
- min_i = vals.index(min(vals))
306
- max_i = vals.index(max(vals))
307
- dots_html = ""
308
- for i, v in enumerate(vals):
309
- x, y = _px(i, v)
310
- is_special = i in [0, N-1, min_i, max_i]
311
- r = 5 if is_special else 3
312
- color = "#22c55e" if v == min(vals) else ("#ef4444" if v == max(vals) else "#E50914")
313
- dots_html += f"<circle cx='{x}' cy='{y}' r='{r}' fill='{color}' stroke='rgba(0,0,0,0.5)' stroke-width='1.5'/>"
314
- if is_special:
315
- label_y = y - 8 if y > 30 else y + 16
316
- dots_html += (
317
- f"<text x='{x}' y='{label_y}' fill='#fff' font-size='8' "
318
- f"text-anchor='middle' font-weight='bold'>{v}kg</text>"
319
- )
320
-
321
- # Date labels
322
- date_labels = ""
323
- step = max(1, N // 4)
324
- for i in range(0, N, step):
325
- x, _ = _px(i, vals[i])
326
- date_labels += (
327
- f"<text x='{x}' y='{H-4}' fill='rgba(255,255,255,0.45)' "
328
- f"font-size='7' text-anchor='middle'>{dates[i]}</text>"
329
- )
330
-
331
- # Trend line
332
- avg_x = sum(range(N)) / N
333
- avg_y = sum(vals) / N
334
- num = sum((i - avg_x) * (vals[i] - avg_y) for i in range(N))
335
- den = sum((i - avg_x)**2 for i in range(N)) or 1
336
- slope = num / den
337
- t_x1, t_y1 = _px(0, vals[0] + 0 * slope - slope * 0)
338
- t_yn1 = avg_y + slope * (0 - avg_x)
339
- t_yN1 = avg_y + slope * (N-1 - avg_x)
340
- tx1, ty1 = _px(0, t_yn1)
341
- txN, tyN = _px(N-1, t_yN1)
342
- trend_color = "#22c55e" if slope <= 0 else "#ef4444"
343
-
344
- st.markdown(
345
- f"<div style='background:rgba(8,4,2,0.88);border:1.5px solid rgba(229,9,20,0.28);"
346
- f"border-radius:16px;padding:16px 20px;backdrop-filter:blur(20px)'>"
347
- f"<div style='font-size:0.75rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
348
- f"color:rgba(229,9,20,0.85);margin-bottom:12px'>πŸ“ˆ Weight Progress β€” Last {N} Entries</div>"
349
- f"<svg viewBox='0 0 {W} {H}' style='width:100%;height:{H}px'>"
350
- f"{grid_lines}"
351
- f"<defs><linearGradient id='wGrad' x1='0' y1='0' x2='0' y2='1'>"
352
- f"<stop offset='0%' stop-color='#E50914' stop-opacity='0.35'/>"
353
- f"<stop offset='100%' stop-color='#E50914' stop-opacity='0.02'/>"
354
- f"</linearGradient></defs>"
355
- f"<path d='{area_path}' fill='url(#wGrad)'/>"
356
- f"<polyline points='{line_pts}' fill='none' stroke='#E50914' stroke-width='2.5' "
357
- f"stroke-linecap='round' stroke-linejoin='round'/>"
358
- f"<line x1='{tx1}' y1='{ty1}' x2='{txN}' y2='{tyN}' "
359
- f"stroke='{trend_color}' stroke-width='1.5' stroke-dasharray='4,3' opacity='0.7'/>"
360
- f"{dots_html}"
361
- f"{date_labels}"
362
- f"</svg>"
363
- f"<div style='display:flex;gap:16px;margin-top:8px;font-size:0.72rem;"
364
- f"color:rgba(255,255,255,0.50)'>"
365
- f"<span style='display:flex;align-items:center;gap:4px'>"
366
- f"<span style='width:12px;height:2px;background:#E50914;display:block'></span>Weight</span>"
367
- f"<span style='display:flex;align-items:center;gap:4px'>"
368
- f"<span style='width:12px;height:1px;background:{trend_color};display:block;border-top:1px dashed {trend_color}'></span>Trend</span>"
369
- f"<span style='color:#22c55e'>● Low</span>"
370
- f"<span style='color:#ef4444'>● High</span>"
371
- f"</div></div>",
372
- unsafe_allow_html=True
373
- )
374
- elif len(weight_log) == 1:
375
- st.markdown(
376
- "<div style='background:rgba(8,4,2,0.82);border:1.5px solid rgba(229,9,20,0.22);"
377
- "border-radius:14px;padding:24px;text-align:center;color:rgba(255,255,255,0.60);font-size:0.90rem'>"
378
- "Log at least 2 entries to see your weight chart πŸ“ˆ</div>",
379
- unsafe_allow_html=True
380
- )
381
- else:
382
- st.markdown(
383
- "<div style='background:rgba(8,4,2,0.82);border:1.5px solid rgba(229,9,20,0.22);"
384
- "border-radius:14px;padding:40px;text-align:center'>"
385
- "<div style='font-size:2.5rem;margin-bottom:10px'>βš–οΈ</div>"
386
- "<div style='font-size:0.90rem;color:rgba(255,255,255,0.60)'>No weight entries yet.<br>Log your first one!</div>"
387
- "</div>",
388
- unsafe_allow_html=True
389
- )
390
-
391
- st.markdown("<div style='height:20px'></div>", unsafe_allow_html=True)
392
- st.markdown(
393
- "<div style='height:1px;background:linear-gradient(90deg,transparent,rgba(229,9,20,0.35),transparent);"
394
- "margin:8px 0 20px'></div>",
395
- unsafe_allow_html=True
396
- )
397
-
398
- # ── Personal Records ─────────────────────────────────────────────────────
399
  PRESET_EXERCISES = ["Bench Press","Squat","Deadlift","Overhead Press","Pull-ups",
400
  "Push-ups","Barbell Row","Dumbbell Curl","Tricep Dip","Leg Press",
401
  "Lat Pulldown","Cable Row","Plank (seconds)","Running 1km (seconds)","Custom"]
@@ -417,9 +189,8 @@ with tab1:
417
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
418
  "color:rgba(229,9,20,0.85);margin-bottom:10px;display:flex;align-items:center;gap:8px'>"
419
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
420
- "πŸ… Log New PR</div>",
421
- unsafe_allow_html=True
422
- )
423
  with st.form("pr_form"):
424
  fc1,fc2,fc3,fc4 = st.columns([3,2,2,1])
425
  with fc1: ex_choice = st.selectbox("Exercise", PRESET_EXERCISES, key="pr_ex")
@@ -444,8 +215,8 @@ with tab1:
444
  st.markdown(
445
  "<div style='text-align:center;padding:40px;background:rgba(8,4,2,0.70);"
446
  "border:1px solid rgba(229,9,20,0.15);border-radius:14px;margin-top:12px'>"
447
- "<div style='font-size:2.5rem;margin-bottom:10px'>πŸ†</div>"
448
- "<div style='font-size:1rem;color:rgba(255,255,255,0.60)'>No PRs yet. Log your first one above!</div>"
449
  "</div>", unsafe_allow_html=True)
450
  else:
451
  for ex_name, ex_records in records.items():
@@ -458,33 +229,34 @@ with tab1:
458
  dates = [r["date"][-5:] for r in ex_records[-10:]]
459
  mn, mx = min(vals)-1, max(vals)+1
460
  N = len(vals)
461
- pts = " ".join(str(round(i*(180/max(N-1,1)),1)) + "," + str(round(((mx-v)/(mx-mn))*60,1)) for i,v in enumerate(vals))
462
  dots = "".join(
463
- '<circle cx="' + str(round(i*(180/max(N-1,1)),1)) + '" cy="' + str(round(((mx-v)/(mx-mn))*60,1)) + '" r="4" fill="#E50914" stroke="rgba(0,0,0,0.5)" stroke-width="1.5"/>'
 
464
  for i,v in enumerate(vals))
465
  st.markdown(
466
- "<div style='background:rgba(8,4,2,0.80);border:1px solid rgba(229,9,20,0.20);"
467
  "border-radius:12px;padding:14px 16px;margin-bottom:10px'>"
468
  "<svg viewBox='0 0 200 80' style='width:100%;height:80px'>"
469
  "<polyline points='" + pts + "' fill='none' stroke='#E50914' stroke-width='2' stroke-linecap='round'/>"
470
  + dots +
471
- "<text x='0' y='77' fill='rgba(255,255,255,0.60)' font-size='7'>" + dates[0] + "</text>"
472
- "<text x='200' y='77' fill='rgba(255,255,255,0.60)' font-size='7' text-anchor='end'>" + dates[-1] + "</text>"
473
  "</svg></div>", unsafe_allow_html=True)
474
  for r in reversed(ex_records[-5:]):
475
  is_best = r["value"] == best["value"]
476
- badge = " πŸ† PR" if is_best else ""
477
  color = "#fbbf24" if is_best else "#fff"
478
  st.markdown(
479
- "<div style='display:flex;justify-content:space-between;padding:6px 0;"
480
  "border-bottom:1px solid rgba(255,255,255,0.06);font-size:0.90rem'>"
481
- "<span style='color:rgba(255,255,255,0.60)'>" + r["date"] + "</span>"
482
  "<span style='color:" + color + ";font-weight:700'>"
483
  + str(r["value"]) + " " + r["unit"] + badge + "</span></div>",
484
  unsafe_allow_html=True)
485
 
486
  # ══════════════════════════════════════════════════════════════════════════════
487
- # TAB 2: BODY MEASUREMENTS β€” FIXED dark theme, no white dominance
488
  # ══════════════════════════════════════════════════════════════════════════════
489
  with tab2:
490
  MEASUREMENTS = ["Chest (cm)","Waist (cm)","Hips (cm)","Left Arm (cm)","Right Arm (cm)",
@@ -505,49 +277,23 @@ with tab2:
505
 
506
  st.markdown(
507
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
508
- "color:rgba(229,9,20,0.85);margin-bottom:16px;display:flex;align-items:center;gap:8px'>"
509
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
510
- "πŸ“ Log Today's Measurements (cm)</div>",
511
- unsafe_allow_html=True
512
- )
513
-
514
- # Custom dark number inputs for measurements
515
- st.markdown("""
516
- <style>
517
- /* Force all number inputs in measurements form to be dark */
518
- [data-testid="stForm"] .stNumberInput>div>div>input {
519
- background:rgba(8,4,2,0.90)!important;
520
- border:1.5px solid rgba(229,9,20,0.45)!important;
521
- color:#fff!important;border-radius:12px!important;
522
- font-size:1.00rem!important;font-weight:600!important;
523
- }
524
- [data-testid="stForm"] [data-testid="stWidgetLabel"] p {
525
- color:#fff!important;font-size:0.85rem!important;
526
- font-weight:700!important;letter-spacing:1.5px!important;
527
- text-transform:uppercase!important;
528
- }
529
- </style>
530
- """, unsafe_allow_html=True)
531
 
 
 
 
 
532
  with st.form("meas_form"):
533
- st.markdown(
534
- "<div style='background:rgba(8,4,2,0.85);border:1.5px solid rgba(229,9,20,0.25);"
535
- "border-radius:16px;padding:20px 24px;margin-bottom:12px'>",
536
- unsafe_allow_html=True
537
- )
538
  mc1, mc2 = st.columns(2)
539
  meas_vals = {}
540
  for i, m in enumerate(MEASUREMENTS[:5]):
541
- with mc1:
542
- meas_vals[m] = st.number_input(m, min_value=0.0, step=0.5, key="meas_"+str(i))
543
  for i, m in enumerate(MEASUREMENTS[5:]):
544
- with mc2:
545
- meas_vals[m] = st.number_input(m, min_value=0.0, step=0.5, key="meas_"+str(i+5))
546
- st.markdown("</div>", unsafe_allow_html=True)
547
-
548
- if st.form_submit_button("πŸ’Ύ SAVE MEASUREMENTS", use_container_width=True):
549
- entry = {"date": date.today().isoformat(),
550
- "measurements": {k: v for k, v in meas_vals.items() if v > 0}}
551
  if entry["measurements"]:
552
  meas_list.append(entry)
553
  st.session_state.body_measurements = meas_list
@@ -557,119 +303,78 @@ with tab2:
557
  except Exception: pass
558
  st.success("βœ… Measurements saved!")
559
  st.rerun()
 
560
 
561
- # ── Progress Charts ───────────────────────────────────────────────────────
562
  if meas_list:
563
  st.markdown(
564
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
565
  "color:rgba(229,9,20,0.85);margin:20px 0 12px;display:flex;align-items:center;gap:8px'>"
566
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
567
- "πŸ“Š Progress Charts</div>",
568
- unsafe_allow_html=True
569
- )
570
  key_measures = ["Chest (cm)","Waist (cm)","Hips (cm)","Left Arm (cm)","Left Thigh (cm)"]
571
  cols = st.columns(3)
572
- chart_count = 0
573
- for ci, metric in enumerate(key_measures):
574
- vals_m = [(e["date"][5:], e["measurements"].get(metric,0))
575
- for e in meas_list if e["measurements"].get(metric,0) > 0]
576
  if len(vals_m) < 2: continue
577
- with cols[chart_count % 3]:
578
  _dates_m = [v[0] for v in vals_m[-8:]]
579
  _vals_m = [v[1] for v in vals_m[-8:]]
580
  mn2, mx2 = min(_vals_m)-1, max(_vals_m)+1
581
  N2 = len(_vals_m)
582
-
583
- def _mpx(i, v):
584
- x = 10 + i * (110/max(N2-1,1))
585
- y = 8 + ((mx2-v)/(mx2-mn2)) * 44
586
- return round(x,1), round(y,1)
587
-
588
- pts2 = " ".join(f"{_mpx(i,v)[0]},{_mpx(i,v)[1]}" for i,v in enumerate(_vals_m))
589
  dots2 = "".join(
590
- f'<circle cx="{_mpx(i,v)[0]}" cy="{_mpx(i,v)[1]}" r="3.5" '
591
- f'fill="#E50914" stroke="rgba(0,0,0,0.6)" stroke-width="1.5"/>'
592
  for i,v in enumerate(_vals_m))
593
- delta = round(_vals_m[-1] - _vals_m[0], 1)
594
- dcol = "#22c55e" if delta <= 0 else "#ef4444"
595
- dsym = "β–Ό" if delta <= 0 else "β–²"
596
- short = metric.replace(" (cm)","")
597
-
598
  st.markdown(
599
- f"<div style='background:rgba(8,4,2,0.88);border:1.5px solid rgba(229,9,20,0.22);"
600
- f"border-radius:14px;padding:14px 16px;margin-bottom:10px'>"
601
- f"<div style='font-size:0.70rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
602
- f"color:rgba(255,255,255,0.55);margin-bottom:6px'>{short}</div>"
603
- f"<div style='display:flex;align-items:baseline;gap:8px;margin-bottom:8px'>"
604
- f"<span style='font-family:Bebas Neue,sans-serif;font-size:1.8rem;color:#fff'>{_vals_m[-1]}cm</span>"
605
- f"<span style='font-size:0.80rem;color:{dcol};font-weight:700'>{dsym} {abs(delta)}cm</span>"
606
- f"</div>"
607
- f"<svg viewBox='0 0 130 60' style='width:100%;height:60px'>"
608
- f"<polyline points='{pts2}' fill='none' stroke='#E50914' stroke-width='1.8' stroke-linecap='round'/>"
609
- f"{dots2}"
610
- f"<text x='10' y='58' fill='rgba(255,255,255,0.40)' font-size='6'>{_dates_m[0]}</text>"
611
- f"<text x='120' y='58' fill='rgba(255,255,255,0.40)' font-size='6' text-anchor='end'>{_dates_m[-1]}</text>"
612
- f"</svg></div>",
613
- unsafe_allow_html=True
614
- )
615
- chart_count += 1
616
  else:
617
  st.markdown(
618
  "<div style='text-align:center;padding:40px;background:rgba(8,4,2,0.70);"
619
  "border:1px solid rgba(229,9,20,0.15);border-radius:14px;margin-top:12px'>"
620
- "<div style='font-size:2.5rem;margin-bottom:10px'>πŸ“</div>"
621
- "<div style='font-size:1rem;color:rgba(255,255,255,0.60)'>No measurements yet.</div>"
622
  "</div>", unsafe_allow_html=True)
623
 
624
  # ══════════════════════════════════════════════════════════════════════════════
625
- # TAB 3: 1RM CALCULATOR β€” FIXED formula visibility
626
  # ══════════════════════════════════════════════════════════════════════════════
627
  with tab3:
628
  st.markdown(
629
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
630
  "color:rgba(229,9,20,0.85);margin-bottom:16px;display:flex;align-items:center;gap:8px'>"
631
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
632
- "πŸ’ͺ One Rep Max Calculator</div>",
633
- unsafe_allow_html=True
634
- )
635
 
636
- # ── Formula card β€” FIXED visibility ──────────────────────────────────────
637
  st.markdown(
638
- "<div style='background:rgba(8,4,2,0.90);border:1.5px solid rgba(251,191,36,0.40);"
639
- "border-radius:14px;padding:18px 24px;margin-bottom:20px;backdrop-filter:blur(10px)'>"
640
- "<div style='font-size:0.70rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
641
- "color:rgba(251,191,36,0.80);margin-bottom:10px'>πŸ“ About 1RM</div>"
642
- "<div style='font-size:0.90rem;color:rgba(255,255,255,0.85);line-height:1.7;margin-bottom:12px'>"
643
- "The <b style='color:#fff'>One Rep Max (1RM)</b> is the maximum weight you can lift for a single repetition. "
644
- "We average 3 industry-standard formulas for accuracy.</div>"
645
- "<div style='display:flex;flex-wrap:wrap;gap:10px'>"
646
- "<div style='background:rgba(229,9,20,0.15);border:1.5px solid rgba(229,9,20,0.40);"
647
- "border-radius:10px;padding:10px 16px;flex:1;min-width:180px'>"
648
- "<div style='font-size:0.65rem;letter-spacing:2px;text-transform:uppercase;"
649
- "color:rgba(251,191,36,0.90);margin-bottom:6px;font-weight:700'>EPLEY FORMULA</div>"
650
- "<div style='font-family:monospace;font-size:1.00rem;color:#fbbf24;font-weight:700;"
651
- "background:rgba(0,0,0,0.40);padding:6px 10px;border-radius:6px;letter-spacing:1px'>"
652
- "1RM = w Γ— (1 + r Γ· 30)</div></div>"
653
- "<div style='background:rgba(229,9,20,0.15);border:1.5px solid rgba(229,9,20,0.40);"
654
- "border-radius:10px;padding:10px 16px;flex:1;min-width:180px'>"
655
- "<div style='font-size:0.65rem;letter-spacing:2px;text-transform:uppercase;"
656
- "color:rgba(251,191,36,0.90);margin-bottom:6px;font-weight:700'>BRZYCKI FORMULA</div>"
657
- "<div style='font-family:monospace;font-size:1.00rem;color:#fbbf24;font-weight:700;"
658
- "background:rgba(0,0,0,0.40);padding:6px 10px;border-radius:6px;letter-spacing:1px'>"
659
- "1RM = w Γ— 36 Γ· (37 βˆ’ r)</div></div>"
660
- "<div style='background:rgba(229,9,20,0.15);border:1.5px solid rgba(229,9,20,0.40);"
661
- "border-radius:10px;padding:10px 16px;flex:1;min-width:180px'>"
662
- "<div style='font-size:0.65rem;letter-spacing:2px;text-transform:uppercase;"
663
- "color:rgba(251,191,36,0.90);margin-bottom:6px;font-weight:700'>LANDER FORMULA</div>"
664
- "<div style='font-family:monospace;font-size:1.00rem;color:#fbbf24;font-weight:700;"
665
- "background:rgba(0,0,0,0.40);padding:6px 10px;border-radius:6px;letter-spacing:1px'>"
666
- "1RM = 100w Γ· (101.3 βˆ’ 2.67r)</div></div>"
667
  "</div>"
668
- "<div style='margin-top:10px;font-size:0.75rem;color:rgba(255,255,255,0.40)'>"
669
- "w = weight lifted (kg) Β· r = reps performed Β· result averaged across all 3 formulas</div>"
 
670
  "</div>",
671
- unsafe_allow_html=True
672
- )
673
 
674
  orm_exercises = ["Bench Press","Squat","Deadlift","Overhead Press","Barbell Row",
675
  "Pull-ups","Dumbbell Press","Leg Press","Romanian Deadlift","Cable Row","Custom"]
@@ -679,73 +384,60 @@ with tab3:
679
  with rc3: orm_weight = st.number_input("Weight (kg)", min_value=1.0, max_value=500.0, value=60.0, step=2.5, key="orm_weight")
680
  with rc4: orm_reps = st.number_input("Reps", min_value=1, max_value=30, value=5, step=1, key="orm_reps")
681
 
682
- w, r = float(orm_weight), int(orm_reps)
683
- epley = w * (1 + r/30)
684
- brzycki = w * (36/(37-r)) if r < 37 else w
685
- lander = (100*w)/(101.3 - 2.67123*r)
686
- avg_1rm = (epley + brzycki + lander) / 3
687
- ex_label = orm_custom.strip() if orm_ex=="Custom" and orm_custom.strip() else orm_ex
688
-
689
- pcts = [
690
- (100, "1 rep", "True Max", "#E50914"),
691
- (95, "1-2 reps", "Strength Max","#f97316"),
692
- (85, "4-6 reps", "Strength", "#fbbf24"),
693
- (75, "8-10 reps", "Hypertrophy", "#22c55e"),
694
- (65, "12-15 reps","Endurance", "#60a5fa"),
695
- ]
696
- _rgb_map = {
697
- "#E50914":"229,9,20","#f97316":"249,115,22","#fbbf24":"251,191,36",
698
- "#22c55e":"34,197,94","#60a5fa":"96,165,250"
699
- }
700
- pct_parts = []
701
- for pct, rng, zone, pc in pcts:
702
- _brgb = _rgb_map.get(pc, "229,9,20")
703
- _kg = round(avg_1rm * pct / 100, 1)
704
- pct_parts.append(
705
- f"<div style='background:rgba(8,4,2,0.85);border:1.5px solid rgba({_brgb},0.35);"
706
- f"border-radius:10px;padding:10px 14px;flex:1;min-width:110px;text-align:center'>"
707
- f"<div style='font-size:0.65rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
708
- f"color:{pc};margin-bottom:4px'>{pct}%</div>"
709
- f"<div style='font-family:Bebas Neue,sans-serif;font-size:1.4rem;color:#fff'>{_kg}kg</div>"
710
- f"<div style='font-size:0.65rem;color:rgba(255,255,255,0.50);margin-top:2px'>{rng}</div>"
711
- f"<div style='font-size:0.60rem;color:rgba(255,255,255,0.35)'>{zone}</div></div>"
712
- )
713
- pct_html = "".join(pct_parts)
714
 
715
  st.markdown(
716
  "<div style='background:linear-gradient(135deg,rgba(229,9,20,0.18),rgba(160,0,10,0.10) 60%,rgba(8,4,2,0.90));"
717
- "border:1.5px solid rgba(229,9,20,0.50);border-radius:18px;padding:28px 32px;margin-top:16px;"
718
  "position:relative;overflow:hidden'>"
719
  "<div style='position:absolute;top:0;left:0;right:0;height:2px;"
720
  "background:linear-gradient(90deg,transparent,#E50914,transparent)'></div>"
721
  "<div style='font-size:0.70rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
722
- "color:rgba(229,9,20,0.80);margin-bottom:8px'>πŸ’ͺ " + ex_label + " β€” Estimated 1RM</div>"
723
- "<div style='font-family:Bebas Neue,sans-serif;font-size:5rem;color:#E50914;"
724
  "letter-spacing:2px;line-height:1;margin-bottom:4px'>" + str(round(avg_1rm,1)) +
725
- " <span style='font-size:2.2rem;color:rgba(255,255,255,0.80)'>kg</span></div>"
726
- "<div style='font-size:0.80rem;color:rgba(255,255,255,0.55);margin-bottom:20px'>"
727
- "Average of Epley Β· Brzycki Β· Lander &nbsp;|&nbsp; " + str(r) + " reps @ " + str(w) + "kg</div>"
728
  "<div style='display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:20px'>"
729
  "<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
730
- "border-radius:12px;padding:12px;text-align:center'>"
731
  "<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
732
- "color:rgba(255,255,255,0.45);margin-bottom:4px'>Epley</div>"
733
- "<div style='font-family:Bebas Neue,sans-serif;font-size:1.8rem;color:#fff'>" + str(round(epley,1)) + "kg</div></div>"
734
  "<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
735
- "border-radius:12px;padding:12px;text-align:center'>"
736
  "<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
737
- "color:rgba(255,255,255,0.45);margin-bottom:4px'>Brzycki</div>"
738
- "<div style='font-family:Bebas Neue,sans-serif;font-size:1.8rem;color:#fff'>" + str(round(brzycki,1)) + "kg</div></div>"
739
  "<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
740
- "border-radius:12px;padding:12px;text-align:center'>"
741
  "<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
742
- "color:rgba(255,255,255,0.45);margin-bottom:4px'>Lander</div>"
743
- "<div style='font-family:Bebas Neue,sans-serif;font-size:1.8rem;color:#fff'>" + str(round(lander,1)) + "kg</div></div>"
744
  "</div>"
745
- "<div style='font-size:0.75rem;color:rgba(255,255,255,0.50);margin-bottom:10px;letter-spacing:1px;text-transform:uppercase'>Training % Targets</div>"
 
746
  "<div style='display:flex;gap:8px;flex-wrap:wrap'>" + pct_html + "</div></div>",
747
- unsafe_allow_html=True
748
- )
749
 
750
  # ══════════════════════════════════════════════════════════════════════════════
751
  # TAB 4: ACHIEVEMENTS
@@ -755,9 +447,7 @@ with tab4:
755
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
756
  "color:rgba(229,9,20,0.85);margin-bottom:16px;display:flex;align-items:center;gap:8px'>"
757
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
758
- "πŸŽ–οΈ Your Achievement Badges</div>",
759
- unsafe_allow_html=True
760
- )
761
  try:
762
  from utils.achievements import compute_stats, get_earned_badges, get_next_badge, render_badges_html, BADGES
763
  stats = compute_stats(uname, st.session_state)
@@ -765,14 +455,14 @@ with tab4:
765
  next_b = get_next_badge(stats)
766
 
767
  st.markdown(
768
- "<div style='background:rgba(8,4,2,0.85);border:1.5px solid rgba(229,9,20,0.30);"
769
  "border-radius:14px;padding:16px 20px;margin-bottom:20px;"
770
  "display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px'>"
771
  "<div><div style='font-family:Bebas Neue,sans-serif;font-size:2.2rem;color:#E50914;line-height:1'>"
772
  + str(len(earned)) + " / " + str(len(BADGES)) + "</div>"
773
  "<div style='font-size:0.80rem;color:rgba(255,255,255,0.60);letter-spacing:2px;text-transform:uppercase'>Badges Earned</div></div>"
774
  "<div style='text-align:right'>"
775
- "<div style='font-size:0.75rem;font-weight:600;color:rgba(255,255,255,0.55)'>Completion</div>"
776
  "<div style='height:8px;width:200px;background:rgba(255,255,255,0.08);border-radius:4px;overflow:hidden;margin-top:6px'>"
777
  "<div style='height:100%;width:" + str(int(len(earned)/max(len(BADGES),1)*100)) + "%;background:#E50914;border-radius:4px'></div>"
778
  "</div></div></div>", unsafe_allow_html=True)
@@ -783,17 +473,17 @@ with tab4:
783
  "border-radius:12px;padding:14px 18px;margin-bottom:20px;display:flex;align-items:center;gap:14px'>"
784
  "<div style='font-size:2.5rem'>" + next_b["icon"] + "</div>"
785
  "<div><div style='font-size:0.65rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
786
- "color:rgba(251,191,36,0.80);margin-bottom:4px'>🎯 Next Badge to Unlock</div>"
787
  "<div style='font-size:0.92rem;font-weight:700;color:#fff'>" + next_b["title"] + "</div>"
788
  "<div style='font-size:0.75rem;color:rgba(255,255,255,0.60);margin-top:2px'>" + next_b["desc"] + "</div>"
789
  "</div></div>", unsafe_allow_html=True)
790
 
791
  if earned:
792
- st.markdown("<div style='font-size:0.80rem;color:rgba(255,255,255,0.60);margin-bottom:12px'>πŸ† Earned Badges</div>", unsafe_allow_html=True)
793
  st.markdown(render_badges_html(earned), unsafe_allow_html=True)
794
 
795
  st.markdown("<div style='height:20px'></div>", unsafe_allow_html=True)
796
- with st.expander("πŸ”’ Locked Badges β€” " + str(len(BADGES)-len(earned)) + " remaining", expanded=False):
797
  earned_ids = {b["id"] for b in earned}
798
  locked_html = "<div style='display:flex;flex-wrap:wrap;gap:10px'>"
799
  for bid, icon, title, desc, _ in BADGES:
 
4
  from datetime import date
5
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
  from auth_token import logout
7
+ from bg_utils import apply_bg
8
 
9
  st.set_page_config(page_title="Records | FitPlan Pro", page_icon="πŸ†",
10
  layout="wide", initial_sidebar_state="collapsed")
 
17
 
18
  st.markdown("""
19
  <style>
 
 
20
  [data-testid="stAppViewContainer"]::before{
21
  content:'';position:fixed;inset:0;z-index:0;
22
  background:url('https://images.unsplash.com/photo-1584735935682-2f2b69dff9d2?fm=jpg&w=1600&q=80&fit=crop') center center/cover no-repeat;
23
+ filter:blur(8px) brightness(0.25) saturate(0.55);transform:scale(1.06);}
 
 
24
  [data-testid="stAppViewContainer"]{
25
+ background:linear-gradient(160deg,rgba(3,1,0,0.90) 0%,rgba(4,2,0,0.82) 50%,rgba(3,1,0,0.94) 100%)!important;position:relative;}
26
+ [data-testid="stAppViewContainer"]>section>div.block-container{position:relative;z-index:2;}
27
+ @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow+Condensed:wght@700;800;900&family=DM+Sans:opsz,wght@9..40,400;9..40,600;9..40,700&display=swap');
 
 
 
 
28
  #MainMenu,footer,header,[data-testid="stToolbar"],[data-testid="stDecoration"],
29
  [data-testid="stSidebarNav"],[data-testid="collapsedControl"],
30
  section[data-testid="stSidebar"],button[kind="header"]{display:none!important;}
 
31
  html,body,.stApp{background:#050202!important;color:#fff!important;font-family:'DM Sans',sans-serif!important;}
32
+ [data-testid="stAppViewContainer"]>section>div.block-container{
33
+ max-width:1100px!important;margin:0 auto!important;padding:0 24px 80px!important;
34
+ background:rgba(0,0,0,0.35)!important;border-radius:0!important;}
35
 
36
+ /* ── LABELS ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  [data-testid="stWidgetLabel"],[data-testid="stWidgetLabel"] p{
38
+ color:#fff!important;font-size:1.00rem!important;font-weight:700!important;
39
  text-shadow:0 2px 8px rgba(0,0,0,0.99)!important;}
40
 
41
  /* ── BUTTONS ── */
42
+ [data-testid="stFormSubmitButton"] button,.stFormSubmitButton>button,button[kind="secondaryFormSubmit"]{
43
  background:linear-gradient(135deg,#E50914,#c0000c)!important;border:none!important;
44
  color:#fff!important;border-radius:10px!important;font-weight:700!important;
45
  box-shadow:0 4px 20px rgba(229,9,20,0.45)!important;text-transform:uppercase!important;}
46
+ .stButton>button{background:linear-gradient(135deg,rgba(229,9,20,0.85),rgba(160,0,10,0.90))!important;
 
47
  border:2px solid rgba(229,9,20,0.65)!important;color:#fff!important;border-radius:10px!important;
48
  font-family:'DM Sans',sans-serif!important;font-size:1.0rem!important;font-weight:700!important;
49
  box-shadow:0 0 12px rgba(229,9,20,0.30)!important;transition:all 0.20s!important;}
50
  .stButton>button:hover{transform:translateY(-2px)!important;box-shadow:0 0 24px rgba(229,9,20,0.60)!important;}
51
 
52
+ /* ── FIX: DARK INPUTS β€” kills white boxes completely ── */
53
+ .stNumberInput>div>div>input,.stTextInput>div>div>input,.stTextArea>div>div>textarea{
54
+ background:rgba(8,4,2,0.90)!important;border:1.5px solid rgba(229,9,20,0.45)!important;
55
+ color:#fff!important;border-radius:12px!important;backdrop-filter:blur(28px)!important;
56
+ box-shadow:0 2px 10px rgba(0,0,0,0.60)!important;font-family:'DM Sans',sans-serif!important;}
57
+ .stNumberInput>div>div>input:focus,.stTextInput>div>div>input:focus{
58
+ border-color:rgba(229,9,20,0.85)!important;background:rgba(15,5,2,0.96)!important;}
59
+ .stNumberInput [data-testid="stNumberInputStepUp"],
60
+ .stNumberInput [data-testid="stNumberInputStepDown"]{
61
+ background:rgba(229,9,20,0.28)!important;border:none!important;color:#fff!important;border-radius:8px!important;}
 
 
62
 
63
+ /* ── FIX: DARK SELECTBOX ── */
64
+ [data-baseweb="select"]>div{background:rgba(8,4,2,0.90)!important;
65
+ border:1.5px solid rgba(229,9,20,0.40)!important;border-radius:12px!important;
66
+ backdrop-filter:blur(28px)!important;color:#fff!important;}
67
+ [data-baseweb="select"] span{color:#fff!important;}
68
+ [data-baseweb="popover"] [role="option"]{background:rgba(10,6,4,0.96)!important;color:#fff!important;}
69
+ [data-baseweb="popover"] [role="option"]:hover{background:rgba(229,9,20,0.25)!important;}
70
+
71
+ /* ── FIX: TRANSPARENT FORM ── */
72
+ [data-testid="stForm"]{background:transparent!important;border:none!important;}
73
 
74
  /* ── NAV BUTTONS ── */
75
  div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button{
76
  background:rgba(18,4,4,0.82)!important;border:2px solid rgba(229,9,20,0.65)!important;
77
  color:rgba(255,255,255,0.92)!important;border-radius:9px!important;
78
+ font-size:0.85rem!important;font-weight:700!important;padding:5px 8px!important;
79
+ height:32px!important;min-height:32px!important;white-space:nowrap!important;}
80
  div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]>button:hover{
81
  background:rgba(229,9,20,0.28)!important;border-color:rgba(229,9,20,0.85)!important;color:#fff!important;}
82
  div[data-testid="stHorizontalBlock"]:first-of-type div[data-testid="stButton"]:last-child>button{
83
+ background:linear-gradient(135deg,#c0000c,#8b0000)!important;border-color:rgba(229,9,20,0.80)!important;}
84
+
85
+ /* ── TABS ── */
86
+ .stTabs [data-baseweb="tab-list"]{background:rgba(8,4,2,0.88)!important;
87
+ border-radius:10px!important;padding:4px!important;border:1px solid rgba(229,9,20,0.18)!important;}
88
+ .stTabs [data-baseweb="tab"]{background:transparent!important;color:rgba(255,255,255,0.90)!important;
89
+ border-radius:7px!important;font-size:0.95rem!important;font-weight:700!important;
90
+ border:none!important;padding:9px 16px!important;text-shadow:0 2px 6px rgba(0,0,0,0.95)!important;}
91
+ .stTabs [aria-selected="true"]{background:linear-gradient(135deg,#E50914,#c0000c)!important;
92
+ color:#fff!important;box-shadow:0 3px 12px rgba(229,9,20,0.40)!important;}
93
+
94
+ /* ── EXPANDERS DARK ── */
95
+ .stExpander{background:rgba(8,4,2,0.82)!important;
96
+ border:1.5px solid rgba(229,9,20,0.22)!important;border-radius:14px!important;}
97
+ .stExpander details summary{color:#fff!important;font-weight:700!important;
98
+ font-size:1.00rem!important;text-shadow:0 2px 6px rgba(0,0,0,0.95)!important;}
99
 
 
 
 
100
  .nav-logo{font-family:'Bebas Neue',sans-serif;font-size:1.4rem;letter-spacing:5px;
101
  color:#E50914;text-shadow:0 0 18px rgba(229,9,20,0.45);line-height:1;}
102
 
103
+ /* ── GLOBAL VISIBILITY ── */
104
+ html,body,.stApp,.stMarkdown,p,div,span,label{text-shadow:0 2px 6px rgba(0,0,0,0.98)!important;}
105
+ .stCheckbox>label,.stCheckbox>label p{color:#fff!important;font-weight:700!important;font-size:1.05rem!important;}
106
+ .stMarkdown p,.stMarkdown li{color:#fff!important;font-size:1.05rem!important;line-height:1.75!important;}
107
+ .g-panel{background:rgba(8,4,2,0.88)!important;backdrop-filter:blur(32px)!important;}
108
  </style>
109
  """, unsafe_allow_html=True)
110
 
111
+ apply_bg(
112
+ "https://images.unsplash.com/photo-1584735935682-2f2b69dff9d2?fm=jpg&w=1600&q=80&fit=crop",
113
+ overlay="rgba(8,5,3,0.78)"
114
+ )
115
+
116
+ # ── NAV ───────────────────────────────────────────────────────────────────────
117
  st.markdown("<div style='background:rgba(5,2,1,0.97);backdrop-filter:blur(20px);"
118
  "border-bottom:1.5px solid rgba(229,9,20,0.22);padding:5px 0;margin-bottom:16px'>",
119
  unsafe_allow_html=True)
 
147
  st.switch_page("app.py")
148
  st.markdown("</div>", unsafe_allow_html=True)
149
 
150
+ # ── HERO ──────────────────────────────────────────────────────────────────────
151
  st.markdown("""
152
  <div style='background:linear-gradient(135deg,rgba(251,191,36,0.15),rgba(217,149,0,0.08) 40%,rgba(6,4,1,0.70));
153
  border:1px solid rgba(251,191,36,0.30);border-radius:18px;padding:28px 36px;margin-bottom:24px;
 
157
  <div style='font-family:Bebas Neue,sans-serif;font-size:2.4rem;font-weight:900;
158
  text-transform:uppercase;color:#fff;line-height:1;margin-bottom:6px'>
159
  Personal <span style='color:#fbbf24'>Records</span></div>
160
+ <div style='font-size:1.00rem;color:rgba(255,255,255,0.70)'>
161
  Track your PRs, body measurements, and progress over time</div>
162
  </div>
163
  """, unsafe_allow_html=True)
 
165
  tab1, tab2, tab3, tab4 = st.tabs(["πŸ† Personal Records","πŸ“ Body Measurements","πŸ“ 1RM Calculator","πŸŽ–οΈ Achievements"])
166
 
167
  # ══════════════════════════════════════════════════════════════════════════════
168
+ # TAB 1: PERSONAL RECORDS
169
  # ══════════════════════════════════════════════════════════════════════════════
170
  with tab1:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  PRESET_EXERCISES = ["Bench Press","Squat","Deadlift","Overhead Press","Pull-ups",
172
  "Push-ups","Barbell Row","Dumbbell Curl","Tricep Dip","Leg Press",
173
  "Lat Pulldown","Cable Row","Plank (seconds)","Running 1km (seconds)","Custom"]
 
189
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
190
  "color:rgba(229,9,20,0.85);margin-bottom:10px;display:flex;align-items:center;gap:8px'>"
191
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
192
+ "Log New PR</div>", unsafe_allow_html=True)
193
+
 
194
  with st.form("pr_form"):
195
  fc1,fc2,fc3,fc4 = st.columns([3,2,2,1])
196
  with fc1: ex_choice = st.selectbox("Exercise", PRESET_EXERCISES, key="pr_ex")
 
215
  st.markdown(
216
  "<div style='text-align:center;padding:40px;background:rgba(8,4,2,0.70);"
217
  "border:1px solid rgba(229,9,20,0.15);border-radius:14px;margin-top:12px'>"
218
+ "<div style='font-size:2.5rem;margin-bottom:10px'>&#127942;</div>"
219
+ "<div style='font-size:1rem;color:rgba(255,255,255,0.70)'>No PRs yet. Log your first one above!</div>"
220
  "</div>", unsafe_allow_html=True)
221
  else:
222
  for ex_name, ex_records in records.items():
 
229
  dates = [r["date"][-5:] for r in ex_records[-10:]]
230
  mn, mx = min(vals)-1, max(vals)+1
231
  N = len(vals)
232
+ pts = " ".join(str(int(i*(180/max(N-1,1)))) + "," + str(int(((mx-v)/(mx-mn))*60)) for i,v in enumerate(vals))
233
  dots = "".join(
234
+ '<circle cx="' + str(int(i*(180/max(N-1,1)))) + '" cy="' + str(int(((mx-v)/(mx-mn))*60)) +
235
+ '" r="4" fill="#E50914" stroke="rgba(0,0,0,0.5)" stroke-width="1.5"/>'
236
  for i,v in enumerate(vals))
237
  st.markdown(
238
+ "<div style='background:rgba(8,4,2,0.80);border:1px solid rgba(229,9,20,0.22);"
239
  "border-radius:12px;padding:14px 16px;margin-bottom:10px'>"
240
  "<svg viewBox='0 0 200 80' style='width:100%;height:80px'>"
241
  "<polyline points='" + pts + "' fill='none' stroke='#E50914' stroke-width='2' stroke-linecap='round'/>"
242
  + dots +
243
+ "<text x='0' y='77' fill='rgba(255,255,255,0.55)' font-size='7'>" + dates[0] + "</text>"
244
+ "<text x='200' y='77' fill='rgba(255,255,255,0.55)' font-size='7' text-anchor='end'>" + dates[-1] + "</text>"
245
  "</svg></div>", unsafe_allow_html=True)
246
  for r in reversed(ex_records[-5:]):
247
  is_best = r["value"] == best["value"]
248
+ badge = " &#127942; PR" if is_best else ""
249
  color = "#fbbf24" if is_best else "#fff"
250
  st.markdown(
251
+ "<div style='display:flex;justify-content:space-between;padding:5px 0;"
252
  "border-bottom:1px solid rgba(255,255,255,0.06);font-size:0.90rem'>"
253
+ "<span style='color:rgba(255,255,255,0.55)'>" + r["date"] + "</span>"
254
  "<span style='color:" + color + ";font-weight:700'>"
255
  + str(r["value"]) + " " + r["unit"] + badge + "</span></div>",
256
  unsafe_allow_html=True)
257
 
258
  # ══════════════════════════════════════════════════════════════════════════════
259
+ # TAB 2: BODY MEASUREMENTS β€” dark inputs, no white boxes
260
  # ══════════════════════════════════════════════════════════════════════════════
261
  with tab2:
262
  MEASUREMENTS = ["Chest (cm)","Waist (cm)","Hips (cm)","Left Arm (cm)","Right Arm (cm)",
 
277
 
278
  st.markdown(
279
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
280
+ "color:rgba(229,9,20,0.85);margin-bottom:12px;display:flex;align-items:center;gap:8px'>"
281
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
282
+ "Log Today's Measurements (cm)</div>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
+ st.markdown(
285
+ "<div style='background:rgba(8,4,2,0.88);border:1.5px solid rgba(229,9,20,0.25);"
286
+ "border-radius:16px;padding:20px 24px;margin-bottom:12px'>",
287
+ unsafe_allow_html=True)
288
  with st.form("meas_form"):
 
 
 
 
 
289
  mc1, mc2 = st.columns(2)
290
  meas_vals = {}
291
  for i, m in enumerate(MEASUREMENTS[:5]):
292
+ with mc1: meas_vals[m] = st.number_input(m, min_value=0.0, step=0.5, key="meas_"+str(i))
 
293
  for i, m in enumerate(MEASUREMENTS[5:]):
294
+ with mc2: meas_vals[m] = st.number_input(m, min_value=0.0, step=0.5, key="meas_"+str(i+5))
295
+ if st.form_submit_button("πŸ’Ύ Save Measurements", use_container_width=True):
296
+ entry = {"date":date.today().isoformat(),"measurements":{k:v for k,v in meas_vals.items() if v>0}}
 
 
 
 
297
  if entry["measurements"]:
298
  meas_list.append(entry)
299
  st.session_state.body_measurements = meas_list
 
303
  except Exception: pass
304
  st.success("βœ… Measurements saved!")
305
  st.rerun()
306
+ st.markdown("</div>", unsafe_allow_html=True)
307
 
 
308
  if meas_list:
309
  st.markdown(
310
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
311
  "color:rgba(229,9,20,0.85);margin:20px 0 12px;display:flex;align-items:center;gap:8px'>"
312
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
313
+ "Progress Charts</div>", unsafe_allow_html=True)
 
 
314
  key_measures = ["Chest (cm)","Waist (cm)","Hips (cm)","Left Arm (cm)","Left Thigh (cm)"]
315
  cols = st.columns(3)
316
+ for ci, metric in enumerate(key_measures[:3]):
317
+ vals_m = [(e["date"][-5:], e["measurements"].get(metric,0)) for e in meas_list if e["measurements"].get(metric,0)>0]
 
 
318
  if len(vals_m) < 2: continue
319
+ with cols[ci % 3]:
320
  _dates_m = [v[0] for v in vals_m[-8:]]
321
  _vals_m = [v[1] for v in vals_m[-8:]]
322
  mn2, mx2 = min(_vals_m)-1, max(_vals_m)+1
323
  N2 = len(_vals_m)
324
+ pts2 = " ".join(str(int(i*(120/max(N2-1,1)))) + "," + str(int(((mx2-v)/(mx2-mn2))*50)) for i,v in enumerate(_vals_m))
 
 
 
 
 
 
325
  dots2 = "".join(
326
+ '<circle cx="' + str(int(i*(120/max(N2-1,1)))) + '" cy="' + str(int(((mx2-v)/(mx2-mn2))*50)) +
327
+ '" r="3" fill="#E50914" stroke="rgba(0,0,0,0.5)" stroke-width="1"/>'
328
  for i,v in enumerate(_vals_m))
329
+ delta = _vals_m[-1] - _vals_m[0]
330
+ dcol = "#22c55e" if delta<=0 else "#ef4444"
331
+ dsym = "β–Ό" if delta<=0 else "β–²"
 
 
332
  st.markdown(
333
+ "<div style='background:rgba(8,4,2,0.88);border:1.5px solid rgba(229,9,20,0.22);"
334
+ "border-radius:12px;padding:12px 14px;margin-bottom:8px'>"
335
+ "<div style='font-size:0.70rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
336
+ "color:rgba(255,255,255,0.50);margin-bottom:4px'>" + metric.replace(" (cm)","") + "</div>"
337
+ "<div style='font-size:1.2rem;font-weight:700;color:#fff'>" + str(round(_vals_m[-1],1)) + " cm "
338
+ "<span style='font-size:0.85rem;color:" + dcol + "'>" + dsym + " " + str(round(abs(delta),1)) + "</span></div>"
339
+ "<svg viewBox='0 0 130 60' style='width:100%;height:60px;margin-top:6px'>"
340
+ "<polyline points='" + pts2 + "' fill='none' stroke='#E50914' stroke-width='1.5'/>"
341
+ + dots2 +
342
+ "<text x='0' y='58' fill='rgba(255,255,255,0.40)' font-size='6'>" + _dates_m[0] + "</text>"
343
+ "<text x='120' y='58' fill='rgba(255,255,255,0.40)' font-size='6' text-anchor='end'>" + _dates_m[-1] + "</text>"
344
+ "</svg></div>", unsafe_allow_html=True)
 
 
 
 
 
345
  else:
346
  st.markdown(
347
  "<div style='text-align:center;padding:40px;background:rgba(8,4,2,0.70);"
348
  "border:1px solid rgba(229,9,20,0.15);border-radius:14px;margin-top:12px'>"
349
+ "<div style='font-size:2.5rem;margin-bottom:10px'>&#128207;</div>"
350
+ "<div style='font-size:1rem;color:rgba(255,255,255,0.70)'>No measurements yet.</div>"
351
  "</div>", unsafe_allow_html=True)
352
 
353
  # ══════════════════════════════════════════════════════════════════════════════
354
+ # TAB 3: 1RM CALCULATOR β€” fixed formula visibility
355
  # ══════════════════════════════════════════════════════════════════════════════
356
  with tab3:
357
  st.markdown(
358
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
359
  "color:rgba(229,9,20,0.85);margin-bottom:16px;display:flex;align-items:center;gap:8px'>"
360
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
361
+ "One Rep Max Calculator</div>", unsafe_allow_html=True)
 
 
362
 
363
+ # FIX: Formula card β€” dark background, bright yellow formula text
364
  st.markdown(
365
+ "<div style='background:rgba(8,4,2,0.92);border:1.5px solid rgba(251,191,36,0.42);"
366
+ "border-radius:14px;padding:18px 24px;margin-bottom:20px'>"
367
+ "<div style='font-size:0.65rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
368
+ "color:rgba(251,191,36,0.85);margin-bottom:10px'>πŸ“ Epley Formula</div>"
369
+ "<div style='background:rgba(0,0,0,0.55);border-radius:10px;padding:12px 18px;margin-bottom:10px'>"
370
+ "<span style='font-family:monospace;font-size:1.15rem;font-weight:700;"
371
+ "color:#fbbf24;letter-spacing:1px'>1RM = weight Γ— (1 + reps Γ· 30)</span>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  "</div>"
373
+ "<div style='font-size:0.88rem;color:rgba(255,255,255,0.65);line-height:1.6'>"
374
+ "The <b style='color:#fff'>1RM</b> is the maximum weight you can lift for one rep. "
375
+ "We average <b style='color:#E50914'>3 formulas</b> (Epley, Brzycki, Lander) for accuracy.</div>"
376
  "</div>",
377
+ unsafe_allow_html=True)
 
378
 
379
  orm_exercises = ["Bench Press","Squat","Deadlift","Overhead Press","Barbell Row",
380
  "Pull-ups","Dumbbell Press","Leg Press","Romanian Deadlift","Cable Row","Custom"]
 
384
  with rc3: orm_weight = st.number_input("Weight (kg)", min_value=1.0, max_value=500.0, value=60.0, step=2.5, key="orm_weight")
385
  with rc4: orm_reps = st.number_input("Reps", min_value=1, max_value=30, value=5, step=1, key="orm_reps")
386
 
387
+ w, r = float(orm_weight), int(orm_reps)
388
+ epley = w * (1 + r/30)
389
+ brzycki = w * (36/(37-r)) if r < 37 else w
390
+ lander = (100*w)/(101.3 - 2.67123*r)
391
+ avg_1rm = (epley + brzycki + lander) / 3
392
+ ex_name = orm_custom.strip() if orm_ex=="Custom" and orm_custom.strip() else orm_ex
393
+
394
+ pcts = [(95,"1-2 reps","Strength Max"),(85,"4-6 reps","Strength"),
395
+ (75,"8-10 reps","Hypertrophy"),(65,"12-15 reps","Endurance")]
396
+ pct_html = "".join(
397
+ "<div style='background:rgba(8,4,2,0.88);border:1.5px solid rgba(229,9,20,0.28);"
398
+ "border-radius:10px;padding:10px 12px;flex:1;min-width:100px;text-align:center'>"
399
+ "<div style='font-size:0.65rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
400
+ "color:rgba(229,9,20,0.80);margin-bottom:4px'>" + str(pct) + "%</div>"
401
+ "<div style='font-family:Bebas Neue,sans-serif;font-size:1.4rem;color:#fff'>"
402
+ + str(round(avg_1rm*pct/100,1)) + "kg</div>"
403
+ "<div style='font-size:0.65rem;color:rgba(255,255,255,0.50);margin-top:2px'>" + rng + "</div>"
404
+ "<div style='font-size:0.60rem;color:rgba(255,255,255,0.35)'>" + zone + "</div></div>"
405
+ for pct, rng, zone in pcts)
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
  st.markdown(
408
  "<div style='background:linear-gradient(135deg,rgba(229,9,20,0.18),rgba(160,0,10,0.10) 60%,rgba(8,4,2,0.90));"
409
+ "border:1.5px solid rgba(229,9,20,0.50);border-radius:16px;padding:24px 28px;margin-top:16px;"
410
  "position:relative;overflow:hidden'>"
411
  "<div style='position:absolute;top:0;left:0;right:0;height:2px;"
412
  "background:linear-gradient(90deg,transparent,#E50914,transparent)'></div>"
413
  "<div style='font-size:0.70rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
414
+ "color:rgba(229,9,20,0.80);margin-bottom:8px'>&#128170; " + ex_name + " β€” Estimated 1RM</div>"
415
+ "<div style='font-family:Bebas Neue,sans-serif;font-size:4.5rem;color:#E50914;"
416
  "letter-spacing:2px;line-height:1;margin-bottom:4px'>" + str(round(avg_1rm,1)) +
417
+ " <span style='font-size:2rem;color:rgba(255,255,255,0.80)'>kg</span></div>"
418
+ "<div style='font-size:0.75rem;color:rgba(255,255,255,0.50);margin-bottom:20px'>"
419
+ "Average of 3 formulas | " + str(r) + " reps @ " + str(w) + "kg</div>"
420
  "<div style='display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:20px'>"
421
  "<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
422
+ "border-radius:10px;padding:10px;text-align:center'>"
423
  "<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
424
+ "color:rgba(255,255,255,0.40);margin-bottom:4px'>EPLEY</div>"
425
+ "<div style='font-family:Bebas Neue,sans-serif;font-size:1.6rem;color:#fff'>" + str(round(epley,1)) + "kg</div></div>"
426
  "<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
427
+ "border-radius:10px;padding:10px;text-align:center'>"
428
  "<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
429
+ "color:rgba(255,255,255,0.40);margin-bottom:4px'>BRZYCKI</div>"
430
+ "<div style='font-family:Bebas Neue,sans-serif;font-size:1.6rem;color:#fff'>" + str(round(brzycki,1)) + "kg</div></div>"
431
  "<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
432
+ "border-radius:10px;padding:10px;text-align:center'>"
433
  "<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
434
+ "color:rgba(255,255,255,0.40);margin-bottom:4px'>LANDER</div>"
435
+ "<div style='font-family:Bebas Neue,sans-serif;font-size:1.6rem;color:#fff'>" + str(round(lander,1)) + "kg</div></div>"
436
  "</div>"
437
+ "<div style='font-size:0.70rem;color:rgba(255,255,255,0.45);margin-bottom:10px;"
438
+ "letter-spacing:1px;text-transform:uppercase'>Training % Targets</div>"
439
  "<div style='display:flex;gap:8px;flex-wrap:wrap'>" + pct_html + "</div></div>",
440
+ unsafe_allow_html=True)
 
441
 
442
  # ══════════════════════════════════════════════════════════════════════════════
443
  # TAB 4: ACHIEVEMENTS
 
447
  "<div style='font-size:0.75rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;"
448
  "color:rgba(229,9,20,0.85);margin-bottom:16px;display:flex;align-items:center;gap:8px'>"
449
  "<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
450
+ "Your Achievement Badges</div>", unsafe_allow_html=True)
 
 
451
  try:
452
  from utils.achievements import compute_stats, get_earned_badges, get_next_badge, render_badges_html, BADGES
453
  stats = compute_stats(uname, st.session_state)
 
455
  next_b = get_next_badge(stats)
456
 
457
  st.markdown(
458
+ "<div style='background:rgba(8,4,2,0.88);border:1.5px solid rgba(229,9,20,0.28);"
459
  "border-radius:14px;padding:16px 20px;margin-bottom:20px;"
460
  "display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px'>"
461
  "<div><div style='font-family:Bebas Neue,sans-serif;font-size:2.2rem;color:#E50914;line-height:1'>"
462
  + str(len(earned)) + " / " + str(len(BADGES)) + "</div>"
463
  "<div style='font-size:0.80rem;color:rgba(255,255,255,0.60);letter-spacing:2px;text-transform:uppercase'>Badges Earned</div></div>"
464
  "<div style='text-align:right'>"
465
+ "<div style='font-size:0.75rem;color:rgba(255,255,255,0.50)'>Completion</div>"
466
  "<div style='height:8px;width:200px;background:rgba(255,255,255,0.08);border-radius:4px;overflow:hidden;margin-top:6px'>"
467
  "<div style='height:100%;width:" + str(int(len(earned)/max(len(BADGES),1)*100)) + "%;background:#E50914;border-radius:4px'></div>"
468
  "</div></div></div>", unsafe_allow_html=True)
 
473
  "border-radius:12px;padding:14px 18px;margin-bottom:20px;display:flex;align-items:center;gap:14px'>"
474
  "<div style='font-size:2.5rem'>" + next_b["icon"] + "</div>"
475
  "<div><div style='font-size:0.65rem;font-weight:700;letter-spacing:2px;text-transform:uppercase;"
476
+ "color:rgba(251,191,36,0.80);margin-bottom:4px'>&#127919; Next Badge to Unlock</div>"
477
  "<div style='font-size:0.92rem;font-weight:700;color:#fff'>" + next_b["title"] + "</div>"
478
  "<div style='font-size:0.75rem;color:rgba(255,255,255,0.60);margin-top:2px'>" + next_b["desc"] + "</div>"
479
  "</div></div>", unsafe_allow_html=True)
480
 
481
  if earned:
482
+ st.markdown("<div style='font-size:0.80rem;color:rgba(255,255,255,0.60);margin-bottom:12px'>&#127942; Earned Badges</div>", unsafe_allow_html=True)
483
  st.markdown(render_badges_html(earned), unsafe_allow_html=True)
484
 
485
  st.markdown("<div style='height:20px'></div>", unsafe_allow_html=True)
486
+ with st.expander("&#128274; Locked Badges β€” " + str(len(BADGES)-len(earned)) + " remaining", expanded=False):
487
  earned_ids = {b["id"] for b in earned}
488
  locked_html = "<div style='display:flex;flex-wrap:wrap;gap:10px'>"
489
  for bid, icon, title, desc, _ in BADGES: