""" 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}
""", 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"""
{video['name']}
⏱ {video['duration']}
""", unsafe_allow_html=True) # ══════════════════════════════════════════════════════════════════════════════ # COMBINED WORKOUT DAY SECTION (pre-caution + timer per exercise + post-caution) # ══════════════════════════════════════════════════════════════════════════════ def render_full_workout_day(day_data, day_num): """ Render a complete workout day with: 1. Pre-workout cautions 2. Pre-workout stretch videos 3. Exercise cards with individual timers 4. During-workout cautions 5. Post-workout stretch videos 6. Post-workout cautions """ exercises = day_data.get("workout", []) pre_stretch = day_data.get("pre_stretch", []) post_stretch= day_data.get("post_stretch", []) muscle_grp = day_data.get("muscle_group", "Full Body") # ── Day header ──────────────────────────────────────────────────────── st.markdown(f"""
Day {day_num} — {muscle_grp}
""", 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"""
🔥
{stretch.get('name','Stretch')}
⏱ {stretch.get('duration','30s')}
""", unsafe_allow_html=True) render_stretch_videos("pre") st.markdown("
", unsafe_allow_html=True) # ── 3. EXERCISES WITH TIMERS ────────────────────────────────────────── st.markdown("""
💪 MAIN WORKOUT
""", 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"""
🧊
{stretch.get('name','Cool-down')}
⏱ {stretch.get('duration','30s')}
""", unsafe_allow_html=True) render_stretch_videos("post") # ── 6. POST-WORKOUT CAUTIONS ────────────────────────────────────────── with st.expander("✅ Post-Workout Recovery Tips", expanded=False): render_safety_cautions("post")