Update pages/6_records.py
Browse files- 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.
|
| 25 |
-
transform:scale(1.06);
|
| 26 |
-
}
|
| 27 |
[data-testid="stAppViewContainer"]{
|
| 28 |
-
background:linear-gradient(160deg,rgba(3,1,0,0.
|
| 29 |
-
|
| 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 |
-
/* ββ
|
| 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:
|
| 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 |
-
/* ββ
|
| 79 |
-
.
|
| 80 |
-
background:rgba(
|
| 81 |
-
|
| 82 |
-
.
|
| 83 |
-
|
| 84 |
-
border-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
.
|
| 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 |
-
/* ββ
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
| 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;
|
| 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 |
-
/* ββ
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
| 118 |
</style>
|
| 119 |
""", unsafe_allow_html=True)
|
| 120 |
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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
|
| 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 |
-
"
|
| 421 |
-
|
| 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'>
|
| 448 |
-
"<div style='font-size:1rem;color:rgba(255,255,255,0.
|
| 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(
|
| 462 |
dots = "".join(
|
| 463 |
-
'<circle cx="' + str(
|
|
|
|
| 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.
|
| 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.
|
| 472 |
-
"<text x='200' y='77' fill='rgba(255,255,255,0.
|
| 473 |
"</svg></div>", unsafe_allow_html=True)
|
| 474 |
for r in reversed(ex_records[-5:]):
|
| 475 |
is_best = r["value"] == best["value"]
|
| 476 |
-
badge = "
|
| 477 |
color = "#fbbf24" if is_best else "#fff"
|
| 478 |
st.markdown(
|
| 479 |
-
"<div style='display:flex;justify-content:space-between;padding:
|
| 480 |
"border-bottom:1px solid rgba(255,255,255,0.06);font-size:0.90rem'>"
|
| 481 |
-
"<span style='color:rgba(255,255,255,0.
|
| 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 β
|
| 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:
|
| 509 |
"<span style='width:16px;height:1.5px;background:#E50914;display:block'></span>"
|
| 510 |
-
"
|
| 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 |
-
|
| 546 |
-
|
| 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 |
-
"
|
| 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 |
-
|
| 573 |
-
|
| 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[
|
| 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 |
-
|
| 591 |
-
|
| 592 |
for i,v in enumerate(_vals_m))
|
| 593 |
-
delta
|
| 594 |
-
dcol
|
| 595 |
-
dsym
|
| 596 |
-
short = metric.replace(" (cm)","")
|
| 597 |
-
|
| 598 |
st.markdown(
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 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'>
|
| 621 |
-
"<div style='font-size:1rem;color:rgba(255,255,255,0.
|
| 622 |
"</div>", unsafe_allow_html=True)
|
| 623 |
|
| 624 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 625 |
-
# TAB 3: 1RM CALCULATOR β
|
| 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 |
-
"
|
| 633 |
-
unsafe_allow_html=True
|
| 634 |
-
)
|
| 635 |
|
| 636 |
-
#
|
| 637 |
st.markdown(
|
| 638 |
-
"<div style='background:rgba(8,4,2,0.
|
| 639 |
-
"border-radius:14px;padding:18px 24px;margin-bottom:20px
|
| 640 |
-
"<div style='font-size:0.
|
| 641 |
-
"color:rgba(251,191,36,0.
|
| 642 |
-
"<div style='
|
| 643 |
-
"
|
| 644 |
-
"
|
| 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='
|
| 669 |
-
"
|
|
|
|
| 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
|
| 683 |
-
epley
|
| 684 |
-
brzycki
|
| 685 |
-
lander
|
| 686 |
-
avg_1rm
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
pcts = [
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
(
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
"
|
| 699 |
-
|
| 700 |
-
|
| 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:
|
| 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'>
|
| 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:
|
| 726 |
-
"<div style='font-size:0.
|
| 727 |
-
"Average of
|
| 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:
|
| 731 |
"<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
|
| 732 |
-
"color:rgba(255,255,255,0.
|
| 733 |
-
"<div style='font-family:Bebas Neue,sans-serif;font-size:1.
|
| 734 |
"<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
|
| 735 |
-
"border-radius:
|
| 736 |
"<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
|
| 737 |
-
"color:rgba(255,255,255,0.
|
| 738 |
-
"<div style='font-family:Bebas Neue,sans-serif;font-size:1.
|
| 739 |
"<div style='background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.10);"
|
| 740 |
-
"border-radius:
|
| 741 |
"<div style='font-size:0.60rem;letter-spacing:2px;text-transform:uppercase;"
|
| 742 |
-
"color:rgba(255,255,255,0.
|
| 743 |
-
"<div style='font-family:Bebas Neue,sans-serif;font-size:1.
|
| 744 |
"</div>"
|
| 745 |
-
"<div style='font-size:0.
|
|
|
|
| 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 |
-
"
|
| 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.
|
| 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;
|
| 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'>
|
| 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'>
|
| 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("
|
| 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'>🏆</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 = " 🏆 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'>📏</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'>💪 " + 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'>🎯 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'>🏆 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("🔒 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:
|