"""
utils/workout_components.py — Reusable UI components for workout pages.
Includes: countdown timer, safety cautions, pre/post stretch video embeds.
"""
import streamlit as st
import json, os
# ══════════════════════════════════════════════════════════════════════════════
# SAFETY CAUTIONS
# ══════════════════════════════════════════════════════════════════════════════
PRE_WORKOUT_CAUTIONS = [
("🔥", "Always warm up for at least 5 minutes before starting"),
("💧", "Drink water before you begin — stay hydrated throughout"),
("🩺", "If you have any injury or pain, consult a doctor first"),
("👟", "Wear proper footwear and workout on a non-slip surface"),
("😴", "Never train on no sleep — rest is part of the programme"),
("🧘", "Take deep breaths — controlled breathing improves performance"),
]
DURING_WORKOUT_CAUTIONS = [
("✅", "Maintain proper form — quality over quantity always"),
("🛑", "Stop immediately if you feel sharp pain or dizziness"),
("💧", "Sip water between sets — do not chug large amounts"),
("⏱️", "Respect your rest periods — they are part of the programme"),
("🔄", "Control the movement in both directions (concentric + eccentric)"),
("📏", "Use full range of motion unless instructed otherwise"),
]
POST_WORKOUT_CAUTIONS = [
("🧊", "Cool down for 5–10 minutes — never stop suddenly"),
("🥗", "Eat a protein + carb meal within 45 minutes of finishing"),
("💧", "Rehydrate — drink at least 500ml water post workout"),
("😴", "Get 7–9 hours of sleep for muscle recovery"),
("📅", "Log your workout — tracking drives progress"),
("🚿", "Shower after workout to prevent skin irritation"),
]
def render_safety_cautions(phase="pre"):
"""
Render safety caution cards.
phase: 'pre' | 'during' | 'post'
"""
cautions_map = {
"pre": (PRE_WORKOUT_CAUTIONS, "⚠️ PRE-WORKOUT SAFETY CAUTIONS", "rgba(251,191,36,0.12)", "rgba(251,191,36,0.30)"),
"during": (DURING_WORKOUT_CAUTIONS, "🛡️ DURING WORKOUT — STAY SAFE", "rgba(239,68,68,0.10)", "rgba(239,68,68,0.28)"),
"post": (POST_WORKOUT_CAUTIONS, "✅ POST-WORKOUT RECOVERY TIPS", "rgba(34,197,94,0.10)", "rgba(34,197,94,0.28)"),
}
cautions, title, bg, border = cautions_map.get(phase, cautions_map["pre"])
items_html = "".join(
f"""
{icon}{text}
"""
for icon, text in cautions
)
st.markdown(f"""
{title}
{items_html}
""", unsafe_allow_html=True)
# ══════════════════════════════════════════════════════════════════════════════
# COUNTDOWN TIMER
# ══════════════════════════════════════════════════════════════════════════════
def render_exercise_timer(exercise_name, seconds, timer_key):
"""
Render a JavaScript countdown timer for an exercise.
exercise_name : display name of the exercise
seconds : timer duration in seconds
timer_key : unique key to avoid conflicts between multiple timers
"""
mins = seconds // 60
secs = seconds % 60
display_time = f"{mins:02d}:{secs:02d}" if mins > 0 else f"{seconds}s"
st.markdown(f"""
⏱ TIMER
{exercise_name}
{display_time}
✓ TIME'S UP! WELL DONE!
""", unsafe_allow_html=True)
# ══════════════════════════════════════════════════════════════════════════════
# STRETCH VIDEO EMBEDS
# ══════════════════════════════════════════════════════════════════════════════
def _load_stretch_videos():
"""Load stretch video data from JSON file."""
paths = [
"data/stretch_videos.json",
os.path.join(os.path.dirname(__file__), "..", "data", "stretch_videos.json"),
]
for p in paths:
if os.path.exists(p):
try:
return json.load(open(p))
except Exception:
pass
# Fallback defaults
return {
"pre_workout": [
{"name": "Full Body Warm-Up", "duration": "5 min",
"url": "https://www.youtube.com/embed/HDiHMHBpHBQ"},
],
"post_workout": [
{"name": "Full Body Cool Down", "duration": "5 min",
"url": "https://www.youtube.com/embed/qULTwquOuT4"},
]
}
def render_stretch_videos(phase="pre"):
"""
Render embedded YouTube stretch/warm-up videos.
phase: 'pre' | 'post'
"""
data = _load_stretch_videos()
key = "pre_workout" if phase == "pre" else "post_workout"
videos = data.get(key, [])
if not videos:
return
title = "🔥 PRE-WORKOUT WARM-UP VIDEOS" if phase == "pre" else "🧊 POST-WORKOUT COOL-DOWN VIDEOS"
border_color = "rgba(251,191,36,0.30)" if phase == "pre" else "rgba(34,197,94,0.28)"
header_color = "#fbbf24" if phase == "pre" else "#22c55e"
st.markdown(f"""
{title}
""", unsafe_allow_html=True)
cols = st.columns(min(len(videos), 3))
for col, video in zip(cols, videos):
with col:
st.markdown(f"""
""", unsafe_allow_html=True)
# ── 1. PRE-WORKOUT CAUTIONS ───────────────────────────────────────────
with st.expander("⚠️ Pre-Workout Safety Cautions — Read Before Starting", expanded=False):
render_safety_cautions("pre")
# ── 2. PRE-WORKOUT STRETCH VIDEOS ────────────────────────────────────
with st.expander("🔥 Warm-Up Videos — Watch Before Starting", expanded=False):
if pre_stretch:
# Use day-specific stretch videos if model provided them
for stretch in pre_stretch:
st.markdown(f"""
""", unsafe_allow_html=True)
EX_ICONS = ["🏋️","💪","🔄","⬆️","🦵","🤸","🏃","🚴","🧗","🥊","🏊","🤼"]
for idx, ex in enumerate(exercises):
name = ex.get("name", f"Exercise {idx+1}")
sets = ex.get("sets", 3)
reps = ex.get("reps", "12")
rest = ex.get("rest", "60s")
timer = ex.get("timer", 60)
notes = ex.get("notes", "Maintain proper form throughout")
icon = EX_ICONS[idx % len(EX_ICONS)]
with st.expander(f"{icon} {name} — {sets} sets × {reps} reps", expanded=False):
col_info, col_timer = st.columns([1.4, 1])
with col_info:
st.markdown(f"""
EXERCISE DETAILS
{sets}
SETS
{reps}
REPS
{rest}
REST
Form tip:
{notes}
""", unsafe_allow_html=True)
with col_timer:
render_exercise_timer(name, timer, f"d{day_num}_ex{idx}")
# ── 4. DURING-WORKOUT CAUTIONS ────────────────────────────────────────
with st.expander("🛡️ During Workout — Safety Reminders", expanded=False):
render_safety_cautions("during")
st.markdown("", unsafe_allow_html=True)
# ── 5. POST-WORKOUT STRETCH VIDEOS ───────────────────────────────────
with st.expander("🧊 Cool-Down Videos — Do These After Workout", expanded=False):
if post_stretch:
for stretch in post_stretch:
st.markdown(f"""