diff --git a/backend/src/index.js b/backend/src/index.js index faa4fca..044336a 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -248,6 +248,61 @@ app.get('/api/days/:dayId/exercises', async (req, res) => { } }); +// Get alternative exercises for a given exercise (same muscle group) +app.get('/api/exercises/:id/alternatives', async (req, res) => { + try { + const exerciseResult = await pool.query( + 'SELECT muscle_group FROM exercises WHERE id = $1', + [req.params.id] + ); + + if (!exerciseResult.rows.length) { + return res.status(404).json({ error: 'Exercise not found' }); + } + + const muscleGroup = exerciseResult.rows[0].muscle_group; + const alternatives = await pool.query( + `SELECT id, name, muscle_group, description + FROM exercises + WHERE muscle_group = $1 AND id <> $2 + ORDER BY name`, + [muscleGroup, req.params.id] + ); + + res.json(alternatives.rows); + } catch (err) { + console.error('Error fetching alternatives:', err); + res.status(500).json({ error: 'Database error' }); + } +}); + +// Get last workout for a specific exercise id +app.get('/api/exercises/:id/last-workout', async (req, res) => { + try { + const { user_id } = req.query; + const result = await pool.query(` + WITH latest AS ( + SELECT wl.date + FROM workout_logs wl + JOIN program_exercises pe ON wl.program_exercise_id = pe.id + WHERE pe.exercise_id = $1 AND wl.user_id = $2 + ORDER BY wl.date DESC + LIMIT 1 + ) + SELECT wl.* + FROM workout_logs wl + JOIN program_exercises pe ON wl.program_exercise_id = pe.id + JOIN latest l ON wl.date = l.date + WHERE pe.exercise_id = $1 AND wl.user_id = $2 + ORDER BY wl.set_number ASC + `, [req.params.id, user_id || 1]); + res.json(result.rows); + } catch (err) { + console.error('Error fetching last workout for exercise:', err); + res.status(500).json({ error: 'Database error' }); + } +}); + // Get workout logs for a user and date app.get('/api/logs', async (req, res) => { try { diff --git a/frontend/src/App.css b/frontend/src/App.css index bc3f59c..12f7616 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -7,7 +7,7 @@ .app.loading { justify-content: center; align-items: center; - gap: 1rem; + gap: var(--space-4); } .spinner { @@ -23,10 +23,13 @@ to { transform: rotate(360deg); } } -/* Header */ +/* ============================================ + HEADER - Refined + ============================================ */ + .header { background: var(--bg-secondary); - padding: 1rem 1.25rem; + padding: var(--space-4) var(--space-5); display: flex; justify-content: space-between; align-items: center; @@ -34,17 +37,19 @@ position: sticky; top: 0; z-index: 100; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); } .header h1 { - font-size: 1.5rem; + font-size: var(--font-xl); font-weight: 700; } .week-selector { display: flex; align-items: center; - gap: 0.75rem; + gap: var(--space-3); } .week-selector button { @@ -52,13 +57,22 @@ color: var(--text-primary); width: 44px; height: 44px; - border-radius: 8px; - font-size: 1.1rem; - transition: all 0.2s; + min-width: 44px; + min-height: 44px; + border-radius: var(--radius-md); + font-size: var(--font-lg); + transition: all var(--transition-base); + border: 1px solid var(--border); } .week-selector button:hover:not(:disabled) { background: var(--accent); + border-color: var(--accent); + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.25); +} + +.week-selector button:active:not(:disabled) { + transform: scale(0.95); } .week-selector button:disabled { @@ -72,54 +86,67 @@ text-align: center; } -/* Main */ +/* ============================================ + MAIN CONTENT + ============================================ */ + .main { flex: 1; - padding: 1rem; + padding: var(--space-4); max-width: 600px; margin: 0 auto; width: 100%; } -/* Program Info */ +/* ============================================ + PROGRAM INFO + ============================================ */ + .program-info { - margin-bottom: 1.5rem; + margin-bottom: var(--space-6); } .program-info h2 { - font-size: 1.25rem; - margin-bottom: 0.5rem; + font-size: var(--font-lg); + margin-bottom: var(--space-2); color: var(--accent); } .program-info p { color: var(--text-secondary); - font-size: 0.9rem; - line-height: 1.5; + font-size: var(--font-sm); + line-height: 1.6; } -/* Days List */ +/* ============================================ + DAYS LIST - Refined Cards + ============================================ */ + .days-list h3 { - font-size: 1rem; - color: var(--text-secondary); - margin-bottom: 1rem; + font-size: var(--font-sm); + color: var(--text-muted); + margin-bottom: var(--space-4); text-transform: uppercase; letter-spacing: 0.5px; + font-weight: 600; } .day-card { background: var(--bg-card); - border-radius: 12px; - padding: 1rem; - margin-bottom: 0.75rem; + border-radius: var(--radius-lg); + padding: var(--space-4); + margin-bottom: var(--space-3); cursor: pointer; - transition: all 0.2s; - border: 1px solid transparent; + transition: all var(--transition-base); + border: 1px solid var(--border); + box-shadow: var(--shadow-card); } .day-card:hover { background: var(--bg-card-hover); border-color: var(--accent); + transform: translateY(-2px); + box-shadow: var(--shadow-md); } .day-card:active { @@ -130,141 +157,194 @@ display: flex; justify-content: space-between; align-items: center; - margin-bottom: 0.75rem; + margin-bottom: var(--space-3); } .day-number { - font-size: 0.8rem; - color: var(--text-secondary); + font-size: var(--font-xs); + color: var(--text-muted); text-transform: uppercase; + font-weight: 500; + letter-spacing: 0.5px; } .day-name { - font-size: 1.1rem; + font-size: var(--font-lg); font-weight: 600; } .day-exercises { display: flex; flex-wrap: wrap; - gap: 0.5rem; - margin-bottom: 0.75rem; + gap: var(--space-2); + margin-bottom: var(--space-3); } .exercise-tag { background: var(--bg-secondary); - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + font-size: var(--font-xs); color: var(--text-secondary); + border: 1px solid var(--border); } .exercise-tag.more { background: var(--accent); color: white; + border-color: var(--accent); } .day-action { text-align: right; color: var(--accent); font-weight: 600; - font-size: 0.9rem; + font-size: var(--font-sm); } -/* Workout View */ +/* ============================================ + WORKOUT VIEW + ============================================ */ + .workout-header { flex-direction: column; align-items: flex-start; - gap: 0.5rem; + gap: var(--space-2); } .back-btn { background: none; color: var(--accent); - font-size: 0.9rem; - padding: 0.25rem 0; + font-size: var(--font-sm); + padding: var(--space-1) 0; } .header-title h1 { - font-size: 1.25rem; + font-size: var(--font-lg); } .header-subtitle { - font-size: 0.85rem; - color: var(--text-secondary); + font-size: var(--font-sm); + color: var(--text-muted); } -/* Exercise Card */ +/* ============================================ + EXERCISE CARD - Premium + ============================================ */ + .exercise-card { background: var(--bg-card); - border-radius: 12px; - margin-bottom: 0.75rem; + border-radius: var(--radius-lg); + margin-bottom: var(--space-3); overflow: hidden; - border: 1px solid transparent; - transition: all 0.2s; + border: 1px solid var(--border); + transition: all var(--transition-base); + box-shadow: var(--shadow-card); } .exercise-card.expanded { border-color: var(--accent); + box-shadow: 0 4px 16px rgba(255, 107, 74, 0.15); +} + +.exercise-card.all-done { + border-color: var(--success); + background: var(--bg-card); } .exercise-header { - padding: 1rem; + padding: var(--space-4); display: flex; justify-content: space-between; align-items: center; cursor: pointer; } +.exercise-actions { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.swap-btn { + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-secondary); + width: 34px; + height: 34px; + border-radius: var(--radius-full); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-base); +} + +.swap-btn:hover { + color: var(--accent); + border-color: var(--accent); + background: var(--bg-tertiary); +} + +.swap-badge { + font-size: var(--font-xs); + color: var(--accent); +} + .exercise-info h3 { - font-size: 1rem; - margin-bottom: 0.25rem; + font-size: var(--font-base); + margin-bottom: var(--space-1); } .muscle-group { - font-size: 0.8rem; - color: var(--text-secondary); + font-size: var(--font-xs); + color: var(--text-muted); } .exercise-meta { display: flex; flex-direction: column; align-items: flex-end; - gap: 0.25rem; + gap: var(--space-1); } .sets-info { - font-size: 0.9rem; + font-size: var(--font-sm); color: var(--text-secondary); } .progress-badge { background: var(--bg-secondary); - padding: 0.2rem 0.5rem; - border-radius: 10px; - font-size: 0.75rem; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); + font-size: var(--font-xs); font-weight: 600; + border: 1px solid var(--border); } .progress-badge.complete { background: var(--success); - color: var(--bg-primary); + color: white; + border-color: var(--success); } -/* Exercise Body */ +/* ============================================ + EXERCISE BODY + ============================================ */ + .exercise-body { - padding: 0 1rem 1rem; + padding: 0 var(--space-4) var(--space-4); border-top: 1px solid var(--border); - padding-top: 1rem; + padding-top: var(--space-4); } .progression-hint { - background: rgba(233, 69, 96, 0.1); - border: 1px solid var(--accent); - border-radius: 8px; - padding: 0.75rem; - margin-bottom: 1rem; - font-size: 0.85rem; + background: var(--accent-subtle); + border: 1px solid rgba(255, 107, 74, 0.3); + border-radius: var(--radius-md); + padding: var(--space-3); + margin-bottom: var(--space-4); + font-size: var(--font-sm); color: var(--text-secondary); } @@ -272,90 +352,165 @@ color: var(--accent); } -/* Sets List */ +/* ============================================ + SETS LIST + ============================================ */ + .sets-list { display: flex; flex-direction: column; - gap: 0.5rem; + gap: var(--space-2); } .set-row { display: flex; - align-items: flex-start; - gap: 0.75rem; - padding: 0.75rem; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); background: var(--bg-secondary); - border-radius: 8px; - transition: all 0.2s; + border-radius: var(--radius-md); + border: 1px solid transparent; + transition: all var(--transition-base); } .set-row.completed { - background: rgba(78, 204, 163, 0.15); - border: 1px solid var(--success); + background: var(--success-subtle); + border-color: var(--success); } .set-number { - font-size: 0.85rem; - color: var(--text-secondary); - min-width: 45px; + font-size: var(--font-sm); + color: var(--text-muted); + font-weight: 500; } -.set-inputs { - flex: 1; +.set-row-top { display: flex; - align-items: flex-start; - gap: 0.75rem; + align-items: center; + justify-content: space-between; + gap: var(--space-3); } -.input-separator { - color: var(--text-secondary); +.set-controls { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-3); } -.complete-btn { +.set-metric { + background: var(--bg-card); + border-radius: var(--radius-md); + border: 1px solid var(--border); + padding: var(--space-3); + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.metric-label { + font-size: var(--font-xs); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; +} + +.metric-controls { + display: grid; + grid-template-columns: 44px 1fr 44px; + align-items: center; + gap: var(--space-2); +} + +.metric-btn { width: 44px; height: 44px; - border-radius: 50%; - background: var(--bg-card); - color: var(--text-secondary); - font-size: 1.25rem; - transition: all 0.2s; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--bg-secondary); + font-size: var(--font-lg); + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); } -.complete-btn:hover { +.metric-btn:hover { + border-color: var(--accent); + color: var(--accent); +} + +.metric-value { + display: flex; + align-items: baseline; + justify-content: center; + gap: var(--space-1); + font-size: var(--font-lg); + font-weight: 700; + min-height: 44px; + color: var(--text-primary); +} + +.metric-suffix { + font-size: var(--font-sm); + color: var(--text-muted); + font-weight: 500; +} + +.klart-btn { + width: 100%; + min-height: 52px; + border-radius: var(--radius-lg); background: var(--accent); color: white; + border: none; + font-size: var(--font-base); + font-weight: 700; + letter-spacing: 1px; + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + cursor: pointer; + transition: all var(--transition-base); } -.complete-btn.done { +.klart-btn:hover { + transform: translateY(-1px); + box-shadow: 0 10px 24px rgba(255, 107, 74, 0.25); +} + +.klart-btn.done { background: var(--success); - color: var(--bg-primary); + box-shadow: 0 10px 24px rgba(34, 197, 94, 0.3); } -/* Mobile optimizations */ +/* ============================================ + MOBILE OPTIMIZATIONS + ============================================ */ + @media (max-width: 480px) { .header { - padding: 0.75rem 1rem; + padding: var(--space-3) var(--space-4); } - + .header h1 { - font-size: 1.25rem; + font-size: var(--font-lg); } - + .main { - padding: 0.75rem; + padding: var(--space-3); } - } /* Safe area for notched phones */ @supports (padding: env(safe-area-inset-bottom)) { .main { - padding-bottom: calc(1rem + env(safe-area-inset-bottom)); + padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom)); } } /* ============================================ - DASHBOARD STYLES + DASHBOARD STYLES - Premium ============================================ */ .dashboard { @@ -368,17 +523,19 @@ flex-direction: column; justify-content: center; align-items: center; - gap: 1rem; + gap: var(--space-4); } /* Dashboard Header */ .dashboard-header { background: var(--bg-secondary); - padding: 1rem 1.25rem; + padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 100; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); } .header-top { @@ -388,66 +545,96 @@ } .header-top h1 { - font-size: 1.5rem; + font-size: var(--font-xl); font-weight: 700; } .nav-menu { display: flex; - gap: 0.25rem; + gap: var(--space-1); } .nav-btn { background: transparent; border: none; color: var(--text-muted); - padding: 0.5rem 0.75rem; - border-radius: 8px; - font-size: 0.85rem; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + font-size: var(--font-sm); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-base); + min-height: 44px; + min-width: 44px; + display: flex; + align-items: center; + justify-content: center; } .nav-btn:hover, .nav-btn.active { - background: var(--bg); - color: var(--text); + background: var(--bg-card); + color: var(--text-primary); +} + +.nav-btn.active { + color: var(--accent); } .nav-btn.logout { color: var(--text-muted); } +.nav-btn.logout:hover { + color: var(--error); +} + /* Dashboard Main */ .dashboard-main { - padding: 1rem; + padding: var(--space-4); display: flex; flex-direction: column; - gap: 1.5rem; + gap: var(--space-5); max-width: 600px; margin: 0 auto; } -/* Coach Greeting */ +/* ============================================ + COACH GREETING - Premium Card + ============================================ */ + .coach-greeting { display: flex; - gap: 1rem; - padding: 1rem; + gap: var(--space-4); + padding: var(--space-5); background: linear-gradient(135deg, var(--accent) 0%, #6366f1 100%); - border-radius: 16px; + border-radius: var(--radius-xl); color: white; + box-shadow: 0 8px 24px rgba(99, 102, 241, 0.25); + position: relative; + overflow: hidden; +} + +.coach-greeting::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(255,255,255,0.1) 0%, transparent 50%); + pointer-events: none; } .coach-avatar { - width: 52px; - height: 52px; + width: 56px; + height: 56px; + min-width: 56px; background: rgba(255,255,255,0.15); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; - color: rgba(255,255,255,0.9); + color: rgba(255,255,255,0.95); + backdrop-filter: blur(8px); + border: 2px solid rgba(255,255,255,0.2); } .coach-message { @@ -456,160 +643,192 @@ } .coach-message p { - font-size: 1.1rem; + font-size: var(--font-lg); font-weight: 500; line-height: 1.4; + position: relative; + z-index: 1; } -/* Week Calendar */ +/* ============================================ + WEEK CALENDAR - Premium + ============================================ */ + .week-calendar { - background: var(--bg-secondary); - border-radius: 16px; - padding: 1rem; + background: var(--bg-card); + border-radius: var(--radius-xl); + padding: var(--space-4); border: 1px solid var(--border); + box-shadow: var(--shadow-card); } .calendar-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .calendar-title { font-weight: 600; text-transform: capitalize; + font-size: var(--font-base); } .calendar-nav { - background: var(--bg); + background: var(--bg-secondary); border: 1px solid var(--border); width: 44px; height: 44px; - border-radius: 8px; + min-width: 44px; + min-height: 44px; + border-radius: var(--radius-md); cursor: pointer; display: flex; align-items: center; justify-content: center; - color: var(--text); - transition: all 0.2s; + color: var(--text-primary); + transition: all var(--transition-base); } .calendar-nav:hover { background: var(--accent); color: white; border-color: var(--accent); + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.25); +} + +.calendar-nav:active { + transform: scale(0.95); } .calendar-days { display: grid; grid-template-columns: repeat(7, 1fr); - gap: 0.5rem; + gap: var(--space-1); } .calendar-day { display: flex; flex-direction: column; align-items: center; - padding: 0.5rem; - border-radius: 12px; + padding: var(--space-2); + border-radius: var(--radius-md); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-base); position: relative; + min-height: 60px; + justify-content: center; } .calendar-day:hover { - background: var(--bg); + background: var(--bg-secondary); } .calendar-day.today { background: var(--accent); color: white; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); } +.calendar-day.has-workout:not(.today) { + background: var(--bg-secondary); +} .day-name { - font-size: 0.7rem; + font-size: var(--font-xs); text-transform: uppercase; color: var(--text-muted); - margin-bottom: 0.25rem; + margin-bottom: var(--space-1); + font-weight: 500; + letter-spacing: 0.5px; } .calendar-day.today .day-name { - color: rgba(255,255,255,0.8); + color: rgba(255,255,255,0.85); } .day-date { - font-size: 1rem; + font-size: var(--font-base); font-weight: 600; } .day-dot { - width: 4px; - height: 4px; + width: 5px; + height: 5px; border-radius: 50%; background: var(--success); - margin-top: 0.25rem; + margin-top: var(--space-1); } .calendar-day.today .day-dot { - background: rgba(255,255,255,0.8); + background: rgba(255,255,255,0.85); } -/* Today's Workout */ +/* ============================================ + TODAY'S WORKOUT - Premium Card + ============================================ */ + .todays-workout { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--space-4); } .todays-workout h2 { - font-size: 1.1rem; + font-size: var(--font-base); font-weight: 600; + color: var(--text-secondary); } .workout-card { - background: var(--bg-secondary); - border-radius: 16px; - padding: 1.25rem; + background: var(--bg-card); + border-radius: var(--radius-xl); + padding: var(--space-5); border: 1px solid var(--border); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-base); + box-shadow: var(--shadow-card); } .workout-card:hover { border-color: var(--accent); transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.workout-card:active { + transform: scale(0.98); } .workout-card-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .workout-card-header h3 { - font-size: 1.2rem; + font-size: var(--font-lg); font-weight: 600; } .workout-duration { - font-size: 0.85rem; + font-size: var(--font-sm); color: var(--text-muted); } .workout-exercises { display: flex; flex-direction: column; - gap: 0.5rem; - margin-bottom: 1rem; + gap: var(--space-2); + margin-bottom: var(--space-4); } .exercise-preview { display: flex; justify-content: space-between; - padding: 0.5rem 0; + padding: var(--space-2) 0; border-bottom: 1px solid var(--border); } @@ -623,7 +842,7 @@ .exercise-sets { color: var(--text-muted); - font-size: 0.9rem; + font-size: var(--font-sm); } .start-workout-btn { @@ -631,83 +850,109 @@ background: var(--accent); color: white; border: none; - padding: 1rem; - border-radius: 12px; - font-size: 1rem; + padding: var(--space-4); + border-radius: var(--radius-lg); + font-size: var(--font-base); font-weight: 600; cursor: pointer; - transition: all 0.2s; - min-height: 44px; + transition: all var(--transition-base); + min-height: 48px; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); } .start-workout-btn:hover { background: var(--accent-hover); - transform: scale(1.02); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(255, 107, 74, 0.4); } -/* Rest Day Card */ +.start-workout-btn:active { + transform: translateY(0); +} + +/* ============================================ + REST DAY CARD + ============================================ */ + .rest-day-card { - background: var(--bg-secondary); - border-radius: 16px; - padding: 2rem; + background: var(--bg-card); + border-radius: var(--radius-xl); + padding: var(--space-10); border: 1px solid var(--border); text-align: center; + box-shadow: var(--shadow-card); } .rest-icon { font-size: 3rem; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .rest-day-card h3 { - font-size: 1.2rem; - margin-bottom: 0.5rem; + font-size: var(--font-lg); + margin-bottom: var(--space-2); } .rest-day-card p { color: var(--text-muted); - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .rest-tips { display: flex; justify-content: center; - gap: 1rem; + gap: var(--space-3); + flex-wrap: wrap; } .rest-tips span { - background: var(--bg); - padding: 0.5rem 0.75rem; - border-radius: 20px; - font-size: 0.85rem; + background: var(--bg-secondary); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--font-sm); + border: 1px solid var(--border); } -/* Quick Stats */ +/* ============================================ + QUICK STATS - Premium + ============================================ */ + .quick-stats { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 0.75rem; + gap: var(--space-3); } .stat-card { - background: var(--bg-secondary); - border-radius: 12px; - padding: 1rem; + background: var(--bg-card); + border-radius: var(--radius-lg); + padding: var(--space-4); text-align: center; border: 1px solid var(--border); + box-shadow: var(--shadow-card); + transition: all var(--transition-base); +} + +.stat-card:hover { + border-color: var(--border-hover); + transform: translateY(-1px); } .stat-value { display: block; - font-size: 1.5rem; + font-size: var(--font-2xl); font-weight: 700; color: var(--accent); + line-height: 1.2; } .stat-label { - font-size: 0.75rem; + font-size: var(--font-xs); color: var(--text-muted); text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 500; + margin-top: var(--space-1); } .stat-icon { @@ -720,52 +965,190 @@ .brand-title { display: flex; align-items: center; - gap: 0.5rem; + gap: var(--space-2); } -/* Upcoming Workouts */ +/* ============================================ + UPCOMING WORKOUTS + ============================================ */ + .upcoming-workouts h2 { - font-size: 1.1rem; + font-size: var(--font-base); font-weight: 600; - margin-bottom: 0.75rem; + margin-bottom: var(--space-3); } .upcoming-list { display: flex; flex-direction: column; - gap: 0.5rem; + gap: var(--space-2); } .upcoming-item { display: flex; align-items: center; - gap: 1rem; - background: var(--bg-secondary); - padding: 1rem; - border-radius: 12px; + gap: var(--space-4); + background: var(--bg-card); + padding: var(--space-4); + border-radius: var(--radius-lg); border: 1px solid var(--border); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-base); + box-shadow: var(--shadow-card); } .upcoming-item:hover { border-color: var(--accent); + transform: translateX(4px); } .upcoming-day { font-weight: 600; width: 40px; color: var(--accent); + font-size: var(--font-sm); } .upcoming-name { flex: 1; + font-weight: 500; } .upcoming-arrow { color: var(--text-muted); } +/* ============================================ + COACH SECTION (redesigned) + ============================================ */ + +.coach-section { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.today-action { + /* Container for workout card or rest tips */ +} + +.today-workout-card { + display: flex; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, var(--accent) 0%, #6366f1 100%); + border-radius: var(--radius-xl); + padding: var(--space-5); + cursor: pointer; + transition: all var(--transition-base); + color: white; + box-shadow: 0 8px 24px rgba(99, 102, 241, 0.25); + position: relative; + overflow: hidden; +} + +.today-workout-card::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(255,255,255,0.1) 0%, transparent 50%); + pointer-events: none; +} + +.today-workout-card:hover { + transform: translateY(-2px); + box-shadow: 0 12px 32px rgba(99, 102, 241, 0.3); +} + +.today-workout-card:active { + transform: scale(0.98); +} + +.workout-info h3 { + font-size: var(--font-lg); + font-weight: 600; + margin-bottom: var(--space-1); +} + +.workout-meta { + font-size: var(--font-sm); + opacity: 0.9; +} + +.workout-action { + background: rgba(255,255,255,0.2); + width: 48px; + height: 48px; + min-width: 48px; + min-height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(8px); + border: 2px solid rgba(255,255,255,0.2); +} + +.action-arrow { + font-size: var(--font-xl); +} + +/* Rest Day Section */ +.rest-day-section { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.rest-day-section .rest-tips { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.tip-badge { + display: flex; + align-items: center; + gap: var(--space-2); + background: var(--bg-card); + border: 1px solid var(--border); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--font-sm); + transition: all var(--transition-base); +} + +.tip-badge:hover { + border-color: var(--accent); +} + +.add-workout-btn { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + background: var(--bg-card); + border: 2px dashed var(--border); + border-radius: var(--radius-xl); + padding: var(--space-5); + cursor: pointer; + color: var(--text-muted); + transition: all var(--transition-base); + font-size: var(--font-base); + min-height: 56px; +} + +.add-workout-btn:hover { + border-color: var(--accent); + color: var(--accent); + background: var(--bg-secondary); +} + +.add-icon { + font-size: var(--font-2xl); + font-weight: 300; +} + /* ============================================ PROFILE PAGE STYLES ============================================ */ @@ -782,13 +1165,13 @@ flex-direction: column; justify-content: center; align-items: center; - gap: 1rem; + gap: var(--space-4); } /* Page Header */ .page-header { background: var(--bg-secondary); - padding: 1rem 1.25rem; + padding: var(--space-4) var(--space-5); display: flex; justify-content: space-between; align-items: center; @@ -796,10 +1179,12 @@ position: sticky; top: 0; z-index: 100; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); } .page-header h1 { - font-size: 1.25rem; + font-size: var(--font-lg); font-weight: 600; } @@ -807,77 +1192,91 @@ background: transparent; border: none; color: var(--accent); - font-size: 1rem; + font-size: var(--font-base); cursor: pointer; - padding: 0.5rem; + padding: var(--space-2); display: flex; align-items: center; - gap: 0.25rem; + gap: var(--space-1); min-height: 44px; + transition: opacity var(--transition-fast); +} + +.back-btn:hover { + opacity: 0.8; } /* Page Main */ .page-main { - padding: 1rem; + padding: var(--space-4); max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; - gap: 1.5rem; + gap: var(--space-5); } /* Profile Section */ .profile-section { - background: var(--bg-secondary); - border-radius: 16px; - padding: 1.25rem; + background: var(--bg-card); + border-radius: var(--radius-xl); + padding: var(--space-5); border: 1px solid var(--border); + box-shadow: var(--shadow-card); } .section-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .section-header h2 { - font-size: 1.1rem; + font-size: var(--font-lg); font-weight: 600; } .edit-btn { - background: var(--bg); + background: var(--bg-secondary); border: 1px solid var(--border); - padding: 0.5rem 0.75rem; - border-radius: 8px; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); cursor: pointer; - font-size: 0.85rem; - color: var(--text); + font-size: var(--font-sm); + color: var(--text-primary); min-height: 44px; + transition: all var(--transition-base); +} + +.edit-btn:hover { + border-color: var(--accent); + color: var(--accent); } /* Info Grid */ .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1rem; + gap: var(--space-4); } .info-item { display: flex; flex-direction: column; - gap: 0.25rem; + gap: var(--space-1); } .info-label { - font-size: 0.75rem; + font-size: var(--font-xs); color: var(--text-muted); text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 500; } .info-value { - font-size: 1rem; + font-size: var(--font-base); font-weight: 500; } @@ -885,69 +1284,90 @@ .edit-form { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--space-4); } .form-row { display: grid; grid-template-columns: 1fr 1fr; - gap: 1rem; + gap: var(--space-4); } .form-group { display: flex; flex-direction: column; - gap: 0.5rem; + gap: var(--space-2); } .form-group label { - font-size: 0.85rem; + font-size: var(--font-sm); color: var(--text-muted); + font-weight: 500; } .form-group input, .form-group select { - padding: 0.75rem; + padding: var(--space-3) var(--space-4); border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg); - color: var(--text); - font-size: 1rem; + border-radius: var(--radius-md); + background: var(--bg-secondary); + color: var(--text-primary); + font-size: 16px; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.form-group input:hover, +.form-group select:hover { + border-color: var(--border-hover); } .form-group input:focus, .form-group select:focus { outline: none; border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-subtle); } .form-actions { display: flex; - gap: 1rem; - margin-top: 0.5rem; + gap: var(--space-3); + margin-top: var(--space-2); } .cancel-btn { flex: 1; - padding: 0.75rem; + padding: var(--space-3); border: 1px solid var(--border); - background: var(--bg); - border-radius: 8px; + background: var(--bg-secondary); + border-radius: var(--radius-md); cursor: pointer; - color: var(--text); - min-height: 44px; + color: var(--text-primary); + min-height: 48px; + transition: all var(--transition-base); +} + +.cancel-btn:hover { + border-color: var(--border-hover); } .save-btn { flex: 1; - padding: 0.75rem; + padding: var(--space-3); border: none; background: var(--accent); color: white; - border-radius: 8px; + border-radius: var(--radius-md); cursor: pointer; font-weight: 600; - min-height: 44px; + min-height: 48px; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); + transition: all var(--transition-base); +} + +.save-btn:hover:not(:disabled) { + background: var(--accent-hover); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(255, 107, 74, 0.4); } .save-btn:disabled { @@ -958,31 +1378,31 @@ .measurements-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1rem; + gap: var(--space-4); } .measurement-card { - background: var(--bg); - padding: 1rem; - border-radius: 12px; + background: var(--bg-secondary); + padding: var(--space-4); + border-radius: var(--radius-lg); display: flex; flex-direction: column; align-items: center; - gap: 0.25rem; + gap: var(--space-1); } .measurement-icon { - font-size: 1.5rem; + font-size: var(--font-xl); } .measurement-value { - font-size: 1.25rem; + font-size: var(--font-lg); font-weight: 700; color: var(--accent); } .measurement-label { - font-size: 0.75rem; + font-size: var(--font-xs); color: var(--text-muted); } @@ -990,15 +1410,15 @@ .strength-grid { display: flex; flex-direction: column; - gap: 0.75rem; + gap: var(--space-3); } .strength-card { display: flex; justify-content: space-between; - padding: 1rem; - background: var(--bg); - border-radius: 12px; + padding: var(--space-4); + background: var(--bg-secondary); + border-radius: var(--radius-lg); } .strength-exercise { @@ -1013,7 +1433,7 @@ .no-data { color: var(--text-muted); text-align: center; - padding: 1rem; + padding: var(--space-4); } /* ============================================ @@ -1022,51 +1442,54 @@ .progress-tabs { display: flex; - gap: 0.5rem; - background: var(--bg-secondary); - padding: 0.5rem; - border-radius: 12px; + gap: var(--space-2); + background: var(--bg-card); + padding: var(--space-2); + border-radius: var(--radius-lg); border: 1px solid var(--border); } .tab-btn { flex: 1; - padding: 0.75rem; + padding: var(--space-3); border: none; background: transparent; - border-radius: 8px; + border-radius: var(--radius-md); cursor: pointer; - font-size: 0.9rem; + font-size: var(--font-sm); color: var(--text-muted); - transition: all 0.2s; - min-height: 44px; + transition: all var(--transition-base); + min-height: 48px; } .tab-btn.active { background: var(--accent); color: white; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.25); } .tab-btn:hover:not(.active) { - background: var(--bg); + background: var(--bg-secondary); + color: var(--text-primary); } /* Chart Section */ .chart-section { - background: var(--bg-secondary); - border-radius: 16px; - padding: 1.25rem; + background: var(--bg-card); + border-radius: var(--radius-xl); + padding: var(--space-5); border: 1px solid var(--border); + box-shadow: var(--shadow-card); } .chart-section h2 { - font-size: 1.1rem; + font-size: var(--font-lg); font-weight: 600; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .chart-container { - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .line-chart { @@ -1077,29 +1500,29 @@ .chart-labels { display: flex; justify-content: space-between; - font-size: 0.75rem; + font-size: var(--font-xs); color: var(--text-muted); - padding: 0 0.5rem; + padding: 0 var(--space-2); } /* Strength Charts */ .strength-charts { display: flex; flex-direction: column; - gap: 2rem; + gap: var(--space-6); } .strength-chart-item h3 { - font-size: 1rem; - margin-bottom: 0.75rem; + font-size: var(--font-base); + margin-bottom: var(--space-3); } /* Progress Stats */ .progress-stats { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 0.5rem; - padding-top: 0.5rem; + gap: var(--space-2); + padding-top: var(--space-3); border-top: 1px solid var(--border); } @@ -1108,22 +1531,23 @@ } .progress-stats .stat-label { - font-size: 0.7rem; + font-size: var(--font-xs); color: var(--text-muted); text-transform: uppercase; + letter-spacing: 0.5px; } .progress-stats .stat-value { - font-size: 0.9rem; + font-size: var(--font-sm); font-weight: 600; } .trend-up { - color: #10b981; + color: var(--success); } .trend-down { - color: #ef4444; + color: var(--error); } .trend-neutral { @@ -1133,13 +1557,13 @@ /* Empty State */ .empty-state { text-align: center; - padding: 2rem; + padding: var(--space-10); } .empty-icon { font-size: 3rem; display: block; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .empty-state p { @@ -1147,8 +1571,8 @@ } .empty-hint { - font-size: 0.85rem; - margin-top: 0.5rem; + font-size: var(--font-sm); + margin-top: var(--space-2); } /* ============================================ @@ -1163,7 +1587,7 @@ .workout-page .page-header { display: grid; grid-template-columns: auto 1fr auto; - gap: 1rem; + gap: var(--space-4); align-items: center; } @@ -1172,27 +1596,119 @@ } .workout-page .header-center h1 { - font-size: 1.1rem; + font-size: var(--font-base); font-weight: 600; margin: 0; } .workout-page .header-subtitle { - font-size: 0.8rem; + font-size: var(--font-xs); color: var(--text-muted); } .workout-page .header-progress { background: var(--accent); color: white; - padding: 0.4rem 0.75rem; - border-radius: 20px; - font-size: 0.85rem; + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--font-sm); font-weight: 600; } .workout-page .workout-main { - padding-bottom: 2rem; + padding-bottom: var(--space-8); +} + +/* Rest timer */ +.rest-timer-card { + background: linear-gradient(135deg, rgba(255, 107, 74, 0.14), rgba(34, 197, 94, 0.12)); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: var(--space-4); + margin-bottom: var(--space-5); + box-shadow: var(--shadow-card); + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.rest-timer-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.rest-timer-label { + font-size: var(--font-xs); + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 600; + color: var(--text-secondary); +} + +.rest-timer-time { + font-size: 2rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 1px; +} + +.rest-timer-time.running { + color: var(--accent); +} + +.rest-timer-actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-3); +} + +.rest-timer-btn { + min-height: 48px; + border-radius: var(--radius-lg); + border: 1px solid var(--border); + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); +} + +.rest-timer-btn.primary { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +.rest-timer-btn.primary:hover { + transform: translateY(-1px); + box-shadow: 0 8px 20px rgba(255, 107, 74, 0.25); +} + +.rest-timer-btn.secondary { + background: var(--bg-card); + color: var(--text-secondary); +} + +.rest-timer-presets { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.rest-timer-chip { + padding: var(--space-2) var(--space-3); + background: var(--bg-card); + border-radius: var(--radius-full); + border: 1px solid var(--border); + font-size: var(--font-sm); + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); +} + +.rest-timer-chip:hover { + border-color: var(--accent); + color: var(--accent); } /* Progress Bar */ @@ -1200,7 +1716,7 @@ height: 4px; background: var(--border); border-radius: 2px; - margin-bottom: 1.5rem; + margin-bottom: var(--space-5); overflow: hidden; } @@ -1208,29 +1724,33 @@ height: 100%; background: linear-gradient(90deg, var(--accent), var(--success)); border-radius: 2px; - transition: width 0.3s ease; + transition: width var(--transition-slow); } -/* Uppvärmningssektion */ +/* ============================================ + WARMUP SECTION + ============================================ */ + .warmup-section { - background: var(--bg-secondary); - border-radius: 16px; + background: var(--bg-card); + border-radius: var(--radius-xl); border: 1px solid var(--border); - margin-bottom: 1.5rem; + margin-bottom: var(--space-5); overflow: hidden; - transition: all 0.3s; + transition: all var(--transition-base); + box-shadow: var(--shadow-card); } .warmup-section.completed { border-color: var(--success); - background: rgba(78, 204, 163, 0.05); + background: var(--success-subtle); } .warmup-header { display: flex; justify-content: space-between; align-items: center; - padding: 1rem 1.25rem; + padding: var(--space-4) var(--space-5); cursor: pointer; user-select: none; } @@ -1238,7 +1758,7 @@ .warmup-title { display: flex; align-items: center; - gap: 0.75rem; + gap: var(--space-3); } .warmup-icon { @@ -1248,24 +1768,25 @@ } .warmup-title h2 { - font-size: 1.1rem; + font-size: var(--font-base); font-weight: 600; margin: 0; } .warmup-progress { - background: var(--bg); - padding: 0.25rem 0.6rem; - border-radius: 12px; - font-size: 0.8rem; + background: var(--bg-secondary); + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--font-xs); color: var(--text-muted); + font-weight: 500; } .expand-icon { display: flex; align-items: center; color: var(--text-muted); - transition: transform 0.2s; + transition: transform var(--transition-base); } .expand-icon.expanded { @@ -1273,48 +1794,51 @@ } .warmup-content { - padding: 0 1.25rem 1.25rem; + padding: 0 var(--space-5) var(--space-5); } .warmup-category { - margin-bottom: 1.25rem; + margin-bottom: var(--space-5); } .warmup-category:last-of-type { - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .warmup-category h3 { - font-size: 0.85rem; + font-size: var(--font-sm); color: var(--text-muted); - margin-bottom: 0.75rem; + margin-bottom: var(--space-3); font-weight: 500; } .warmup-list { display: flex; flex-direction: column; - gap: 0.5rem; + gap: var(--space-2); } .warmup-item { display: flex; align-items: center; - gap: 0.75rem; - padding: 0.75rem; - background: var(--bg); - border-radius: 10px; + gap: var(--space-3); + padding: var(--space-3); + background: var(--bg-secondary); + border-radius: var(--radius-md); cursor: pointer; - transition: all 0.2s; - min-height: 44px; + transition: all var(--transition-base); + min-height: 48px; + border: 1px solid transparent; } .warmup-item:hover { - background: var(--bg-card-hover, var(--bg)); + background: var(--bg-tertiary); + border-color: var(--border); } .warmup-item.done { - background: rgba(78, 204, 163, 0.15); + background: var(--success-subtle); + border-color: rgba(34, 197, 94, 0.3); } .warmup-item.done .warmup-name { @@ -1323,54 +1847,57 @@ } .warmup-check { - width: 24px; - height: 24px; + width: 26px; + height: 26px; + min-width: 26px; display: flex; align-items: center; justify-content: center; border-radius: 50%; - background: var(--bg-secondary); + background: var(--bg-card); color: var(--text-muted); - font-size: 0.85rem; + font-size: var(--font-sm); flex-shrink: 0; + border: 2px solid var(--border); } .warmup-item.done .warmup-check { background: var(--success); color: white; + border-color: var(--success); } .warmup-item-icon { - font-size: 1.1rem; + font-size: var(--font-lg); flex-shrink: 0; } .warmup-name { flex: 1; - font-size: 0.95rem; + font-size: var(--font-sm); } .warmup-duration { - font-size: 0.85rem; + font-size: var(--font-sm); color: var(--text-muted); white-space: nowrap; } .warmup-done-btn { width: 100%; - padding: 1rem; - background: var(--bg); + padding: var(--space-4); + background: var(--bg-secondary); border: 2px dashed var(--border); - border-radius: 12px; + border-radius: var(--radius-lg); color: var(--text-muted); - font-size: 0.95rem; + font-size: var(--font-sm); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-base); display: flex; align-items: center; justify-content: center; - gap: 0.5rem; - min-height: 44px; + gap: var(--space-2); + min-height: 48px; } .warmup-done-btn:hover { @@ -1383,23 +1910,26 @@ border: none; color: white; font-weight: 600; + border: 2px solid var(--success); } -/* Övningssektion */ +/* ============================================ + EXERCISES SECTION + ============================================ */ + .exercises-section { - margin-bottom: 1.5rem; + margin-bottom: var(--space-5); } .exercises-section h2 { - font-size: 1.1rem; + font-size: var(--font-base); font-weight: 600; - margin-bottom: 1rem; + margin-bottom: var(--space-4); } -/* Exercise Card utökad */ .exercise-card.all-done { border-color: var(--success); - background: rgba(78, 204, 163, 0.05); + background: var(--success-subtle); } .exercise-card.all-done .exercise-info h3::after { @@ -1410,16 +1940,17 @@ /* Avsluta pass knapp */ .finish-workout-btn { width: 100%; - padding: 1.25rem; - background: var(--bg-secondary); + padding: var(--space-4); + background: var(--bg-card); border: 1px solid var(--border); - border-radius: 16px; + border-radius: var(--radius-xl); color: var(--text-muted); - font-size: 1rem; + font-size: var(--font-base); cursor: pointer; - transition: all 0.2s; - margin-top: 1rem; - min-height: 44px; + transition: all var(--transition-base); + margin-top: var(--space-4); + min-height: 52px; + box-shadow: var(--shadow-card); } .finish-workout-btn:hover { @@ -1433,33 +1964,42 @@ color: white; font-weight: 600; animation: pulse-glow 2s infinite; + box-shadow: 0 8px 24px rgba(99, 102, 241, 0.3); } @keyframes pulse-glow { 0%, 100% { - box-shadow: 0 0 0 0 rgba(233, 69, 96, 0.4); + box-shadow: 0 8px 24px rgba(99, 102, 241, 0.3); } 50% { - box-shadow: 0 0 20px 5px rgba(233, 69, 96, 0.2); + box-shadow: 0 12px 32px rgba(99, 102, 241, 0.5); } } /* Mobile optimeringar för WorkoutPage */ @media (max-width: 480px) { .workout-page .page-header { - padding: 0.75rem 1rem; + padding: var(--space-3) var(--space-4); } .workout-page .header-center h1 { - font-size: 1rem; + font-size: var(--font-sm); } .warmup-item { - padding: 0.6rem; + padding: var(--space-3); } .warmup-name { - font-size: 0.9rem; + font-size: var(--font-sm); + } + + .rest-timer-actions { + grid-template-columns: 1fr; + } + + .set-controls { + grid-template-columns: 1fr; } } @@ -1470,153 +2010,430 @@ .workout-list { display: flex; flex-direction: column; - gap: 0.75rem; + gap: var(--space-3); } .workout-card.compact { - padding: 1rem; + padding: var(--space-4); } .workout-card.compact .workout-card-header { - margin-bottom: 0.5rem; + margin-bottom: var(--space-2); } .workout-card.compact .workout-card-header h3 { - font-size: 1.1rem; + font-size: var(--font-base); } .workout-day { - font-size: 0.8rem; + font-size: var(--font-xs); color: var(--text-muted); - background: var(--bg); - padding: 0.25rem 0.5rem; - border-radius: 4px; + background: var(--bg-secondary); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + border: 1px solid var(--border); } .workout-exercises.compact { display: flex; flex-wrap: wrap; - gap: 0.5rem; + gap: var(--space-2); margin-bottom: 0; } -.exercise-tag { - background: var(--bg); - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - color: var(--text-muted); +/* ============================================ + STEPPER INPUT COMPONENT + ============================================ */ + +.stepper-wrapper { + display: flex; + flex-direction: column; + gap: var(--space-1); + width: 100%; } -.exercise-tag.more { +.stepper-label { + font-size: var(--font-xs); + color: var(--text-muted); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stepper-container { + display: flex; + align-items: center; + gap: var(--space-1); + background: var(--bg-card); + border-radius: var(--radius-md); + border: 1px solid var(--border); + padding: var(--space-1); + height: 52px; +} + +.stepper-btn { + width: 44px; + height: 44px; + min-width: 44px; + min-height: 44px; + background: var(--bg-secondary); + border: none; + border-radius: var(--radius-sm); + color: var(--text-primary); + font-size: 1.4rem; + font-weight: 300; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-base); + flex-shrink: 0; + line-height: 1; +} + +.stepper-btn:hover:not(:disabled) { background: var(--accent); color: white; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.25); +} + +.stepper-btn:active:not(:disabled) { + transform: scale(0.94); +} + +.stepper-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.stepper-input-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-1); + min-width: 0; +} + +.stepper-input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + color: var(--text-primary); + font-size: 16px; + font-weight: 600; + text-align: center; + padding: var(--space-2); + outline: none; + font-family: inherit; +} + +.stepper-input:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Remove browser native number spinners */ +.stepper-input::-webkit-outer-spin-button, +.stepper-input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.stepper-input[type='number'] { + -moz-appearance: textfield; +} + +.input-suffix { + color: var(--text-muted); + font-size: var(--font-sm); + font-weight: 500; + white-space: nowrap; + flex-shrink: 0; +} + +/* Mobile: slightly larger touch targets */ +@media (max-width: 480px) { + .stepper-container { + height: 56px; + } + + .stepper-btn { + width: 48px; + height: 48px; + min-width: 48px; + min-height: 48px; + } +} + +/* Add set button */ +.add-set-btn { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 48px; + margin-top: var(--space-2); + padding: var(--space-3) var(--space-4); + background: transparent; + border: 1px dashed var(--border); + border-radius: var(--radius-md); + color: var(--text-secondary); + font-size: var(--font-sm); + font-weight: 500; + cursor: pointer; + transition: all var(--transition-base); +} + +.add-set-btn:hover { + border-color: var(--accent); + color: var(--accent); +} + +/* Delete set button */ +.delete-set-btn { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + min-height: 44px; + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + opacity: 0.6; + transition: all var(--transition-base); + flex-shrink: 0; +} + +.delete-set-btn:hover:not(:disabled) { + color: #e53e3e; + opacity: 1; +} + +.delete-set-btn:disabled, +.delete-set-btn.disabled { + opacity: 0.2; + cursor: not-allowed; } /* ============================================ - DASHBOARD COACH SECTION (redesigned) + SET TYPE MODAL ============================================ */ -.coach-section { +.set-type-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); display: flex; - flex-direction: column; - gap: 1rem; -} - -.today-action { - /* Container for workout card or rest tips */ -} - -.today-workout-card { - display: flex; - align-items: center; - justify-content: space-between; - background: linear-gradient(135deg, var(--accent) 0%, #6366f1 100%); - border-radius: 16px; - padding: 1.25rem; - cursor: pointer; - transition: all 0.2s; - color: white; -} - -.today-workout-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3); -} - -.workout-info h3 { - font-size: 1.25rem; - font-weight: 600; - margin-bottom: 0.25rem; -} - -.workout-meta { - font-size: 0.85rem; - opacity: 0.9; -} - -.workout-action { - background: rgba(255,255,255,0.2); - width: 48px; - height: 48px; - border-radius: 50%; - display: flex; - align-items: center; + align-items: flex-end; justify-content: center; + z-index: 200; + padding-bottom: env(safe-area-inset-bottom, 0); } -.action-arrow { - font-size: 1.5rem; -} - -/* Rest Day Section */ -.rest-day-section { +.set-type-modal { + background: var(--bg-card); + border-radius: var(--radius-2xl) var(--radius-2xl) 0 0; + padding: var(--space-6) var(--space-4) var(--space-8); + width: 100%; + max-width: 600px; display: flex; flex-direction: column; - gap: 1rem; + gap: var(--space-3); + box-shadow: var(--shadow-xl); } -.rest-tips { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; +.set-type-modal h3 { + font-size: var(--font-base); + font-weight: 600; + color: var(--text-primary); + margin: 0 0 var(--space-1); + text-align: center; } -.tip-badge { +.set-type-option { display: flex; - align-items: center; - gap: 0.4rem; + flex-direction: column; + align-items: flex-start; + gap: var(--space-1); + width: 100%; + min-height: 56px; + padding: var(--space-3) var(--space-4); background: var(--bg-secondary); border: 1px solid var(--border); - padding: 0.5rem 0.75rem; - border-radius: 20px; - font-size: 0.85rem; + border-radius: var(--radius-md); + cursor: pointer; + text-align: left; + transition: all var(--transition-base); } -.add-workout-btn { +.set-type-option strong { + font-size: var(--font-base); + color: var(--text-primary); +} + +.set-type-option span { + font-size: var(--font-sm); + color: var(--text-secondary); +} + +.set-type-option:hover { + border-color: var(--accent); + background: var(--bg-tertiary); +} + +.set-type-option.dropset strong { + color: var(--accent); +} + +.set-type-cancel { + width: 100%; + min-height: 48px; + padding: var(--space-3); + background: transparent; + border: none; + color: var(--text-secondary); + font-size: var(--font-sm); + cursor: pointer; + margin-top: var(--space-1); + transition: color var(--transition-base); +} + +.set-type-cancel:hover { + color: var(--text-primary); +} + +/* ============================================ + ALTERNATIVE EXERCISES MODAL + ============================================ */ + +.alternative-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.55); + backdrop-filter: blur(4px); display: flex; + align-items: flex-end; + justify-content: center; + z-index: 220; + padding-bottom: env(safe-area-inset-bottom, 0); +} + +.alternative-modal { + background: var(--bg-card); + border-radius: var(--radius-2xl) var(--radius-2xl) 0 0; + padding: var(--space-5) var(--space-4) var(--space-7); + width: 100%; + max-width: 640px; + display: flex; + flex-direction: column; + gap: var(--space-3); + box-shadow: var(--shadow-xl); +} + +.alternative-modal-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: var(--space-3); +} + +.alternative-modal-header h3 { + font-size: var(--font-base); + margin: 0 0 var(--space-1); + color: var(--text-primary); +} + +.alternative-modal-header p { + margin: 0; + color: var(--text-secondary); + font-size: var(--font-sm); +} + +.alternative-modal-close { + border: none; + background: var(--bg-secondary); + color: var(--text-secondary); + width: 36px; + height: 36px; + border-radius: var(--radius-full); + display: inline-flex; align-items: center; justify-content: center; - gap: 0.5rem; - background: var(--bg-secondary); - border: 2px dashed var(--border); - border-radius: 16px; - padding: 1.25rem; cursor: pointer; - color: var(--text-muted); - transition: all 0.2s; - font-size: 1rem; + transition: all var(--transition-base); } -.add-workout-btn:hover { - border-color: var(--accent); - color: var(--accent); - background: var(--bg); +.alternative-modal-close:hover { + color: var(--text-primary); + background: var(--bg-tertiary); } -.add-icon { - font-size: 1.5rem; - font-weight: 300; +.alternative-modal-state { + padding: var(--space-4); + text-align: center; + color: var(--text-secondary); + font-size: var(--font-sm); + background: var(--bg-secondary); + border-radius: var(--radius-md); + border: 1px solid var(--border); +} + +.alternative-modal-state.error { + color: #e53e3e; +} + +.alternative-list { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.alternative-item { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--bg-secondary); +} + +.alternative-info { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.alternative-info strong { + font-size: var(--font-sm); + color: var(--text-primary); +} + +.alternative-info span { + font-size: var(--font-xs); + color: var(--text-secondary); +} + +.alternative-select-btn { + min-width: 72px; + padding: var(--space-2) var(--space-3); + border: none; + border-radius: var(--radius-md); + background: var(--accent); + color: white; + font-size: var(--font-sm); + cursor: pointer; + transition: transform var(--transition-base), box-shadow var(--transition-base); +} + +.alternative-select-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.25); } /* ============================================ @@ -1633,11 +2450,11 @@ flex-direction: column; justify-content: center; align-items: center; - gap: 1rem; + gap: var(--space-4); } .select-main { - padding: 1rem; + padding: var(--space-4); max-width: 600px; margin: 0 auto; } @@ -1645,47 +2462,51 @@ .select-intro { text-align: center; color: var(--text-muted); - margin-bottom: 1.5rem; - font-size: 1rem; + margin-bottom: var(--space-6); + font-size: var(--font-base); } .workout-grid { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--space-4); } .workout-select-card { display: flex; align-items: center; - gap: 1rem; - background: var(--bg-secondary); + gap: var(--space-4); + background: var(--bg-card); border: 2px solid var(--border); - border-radius: 16px; - padding: 1rem; + border-radius: var(--radius-xl); + padding: var(--space-4); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-base); position: relative; + box-shadow: var(--shadow-card); } .workout-select-card:hover { border-color: var(--workout-color, var(--accent)); transform: translateX(4px); + box-shadow: var(--shadow-md); } .workout-select-card.selected { border-color: var(--workout-color, var(--accent)); background: var(--bg); + box-shadow: 0 4px 16px rgba(255, 107, 74, 0.15); } .workout-icon { width: 56px; height: 56px; - border-radius: 14px; + min-width: 56px; + border-radius: var(--radius-lg); display: flex; align-items: center; justify-content: center; - font-size: 1.5rem; + font-size: var(--font-xl); flex-shrink: 0; } @@ -1694,33 +2515,34 @@ } .workout-details h3 { - font-size: 1.1rem; + font-size: var(--font-base); font-weight: 600; - margin-bottom: 0.25rem; + margin-bottom: var(--space-1); } .workout-exercises-count { - font-size: 0.85rem; + font-size: var(--font-sm); color: var(--text-muted); - margin-bottom: 0.5rem; + margin-bottom: var(--space-2); } .workout-preview { display: flex; flex-wrap: wrap; - gap: 0.25rem; + gap: var(--space-1); } .preview-exercise { - font-size: 0.75rem; + font-size: var(--font-xs); color: var(--text-muted); - background: var(--bg); - padding: 0.2rem 0.5rem; - border-radius: 4px; + background: var(--bg-secondary); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + border: 1px solid var(--border); } .preview-more { - font-size: 0.75rem; + font-size: var(--font-xs); color: var(--accent); } @@ -1745,9 +2567,11 @@ bottom: 0; left: 0; right: 0; - padding: 1rem; + padding: var(--space-4); background: var(--bg); border-top: 1px solid var(--border); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); } .start-btn { @@ -1755,287 +2579,25 @@ max-width: 600px; margin: 0 auto; display: block; - padding: 1rem; + padding: var(--space-4); background: var(--accent); color: white; border: none; - border-radius: 12px; - font-size: 1.1rem; + border-radius: var(--radius-lg); + font-size: var(--font-lg); font-weight: 600; cursor: pointer; - transition: all 0.2s; - min-height: 44px; + transition: all var(--transition-base); + min-height: 52px; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); } .start-btn:hover { background: var(--accent-hover); - transform: scale(1.02); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(255, 107, 74, 0.4); } -/* ============================================ - GLOBAL INPUT ACCESSIBILITY - Ensure all inputs have font-size >= 16px to prevent iOS auto-zoom - ============================================ */ - -input[type="text"], -input[type="email"], -input[type="password"], -input[type="number"], -input[type="tel"], -select, -textarea { - font-size: 16px; -} - -/* ============================================ - STEPPER INPUT COMPONENT - ============================================ */ - -.stepper-wrapper { - display: flex; - flex-direction: column; - gap: 0.4rem; - width: 100%; -} - -.stepper-label { - font-size: 0.75rem; - color: var(--text-muted); - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.stepper-container { - display: flex; - align-items: center; - gap: 0.25rem; - background: var(--bg-card); - border-radius: 8px; - border: 1px solid var(--border); - padding: 0.2rem; - height: 48px; -} - -.stepper-btn { - width: 44px; - height: 44px; - min-width: 44px; - min-height: 44px; - background: var(--bg-secondary); - border: none; - border-radius: 6px; - color: var(--text-primary); - font-size: 1.4rem; - font-weight: 300; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background 0.15s, color 0.15s; - flex-shrink: 0; - line-height: 1; -} - -.stepper-btn:hover:not(:disabled) { - background: var(--accent); - color: white; -} - -.stepper-btn:active:not(:disabled) { - transform: scale(0.94); -} - -.stepper-btn:disabled { - opacity: 0.35; - cursor: not-allowed; -} - -.stepper-input-wrapper { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - gap: 0.25rem; - min-width: 0; -} - -.stepper-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - color: var(--text-primary); - font-size: 16px; /* >= 16px prevents iOS auto-zoom */ - font-weight: 600; - text-align: center; - padding: 0.4rem 0.25rem; - outline: none; - font-family: inherit; -} - -.stepper-input:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Remove browser native number spinners */ -.stepper-input::-webkit-outer-spin-button, -.stepper-input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -.stepper-input[type='number'] { - -moz-appearance: textfield; -} - -.input-suffix { - color: var(--text-muted); - font-size: 0.8rem; - font-weight: 500; - white-space: nowrap; - flex-shrink: 0; -} - -/* Mobile: slightly larger touch targets */ -@media (max-width: 480px) { - .stepper-container { - height: 52px; - } - - .stepper-btn { - width: 48px; - height: 48px; - min-width: 48px; - min-height: 48px; - } -} - -/* Add set button */ -.add-set-btn { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - min-height: 44px; - margin-top: 0.5rem; - padding: 0.5rem 1rem; - background: transparent; - border: 1px dashed var(--border); - border-radius: 8px; - color: var(--text-secondary); - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; -} - -.add-set-btn:hover { - border-color: var(--accent); - color: var(--accent); -} - -/* Delete set button */ -.delete-set-btn { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - min-height: 44px; - background: transparent; - border: none; - color: var(--text-secondary); - cursor: pointer; - opacity: 0.6; - transition: opacity 0.15s, color 0.15s; - flex-shrink: 0; -} - -.delete-set-btn:hover:not(:disabled) { - color: #e53e3e; - opacity: 1; -} - -.delete-set-btn:disabled, -.delete-set-btn.disabled { - opacity: 0.2; - cursor: not-allowed; -} - -/* Set type modal */ -.set-type-modal-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: flex-end; - justify-content: center; - z-index: 200; - padding-bottom: env(safe-area-inset-bottom, 0); -} - -.set-type-modal { - background: var(--bg-card); - border-radius: 16px 16px 0 0; - padding: 1.5rem 1rem 2rem; - width: 100%; - max-width: 600px; - display: flex; - flex-direction: column; - gap: 0.75rem; -} - -.set-type-modal h3 { - font-size: 1rem; - font-weight: 600; - color: var(--text-primary); - margin: 0 0 0.25rem; - text-align: center; -} - -.set-type-option { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.2rem; - width: 100%; - min-height: 56px; - padding: 0.75rem 1rem; - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: 10px; - cursor: pointer; - text-align: left; - transition: border-color 0.15s; -} - -.set-type-option strong { - font-size: 1rem; - color: var(--text-primary); -} - -.set-type-option span { - font-size: 0.8rem; - color: var(--text-secondary); -} - -.set-type-option:hover { - border-color: var(--accent); -} - -.set-type-option.dropset strong { - color: var(--accent); -} - -.set-type-cancel { - width: 100%; - min-height: 44px; - padding: 0.75rem; - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 0.9rem; - cursor: pointer; - margin-top: 0.25rem; +.start-btn:active { + transform: translateY(0); } diff --git a/frontend/src/components/AlternativeModal.jsx b/frontend/src/components/AlternativeModal.jsx new file mode 100644 index 0000000..a197ed9 --- /dev/null +++ b/frontend/src/components/AlternativeModal.jsx @@ -0,0 +1,51 @@ +import { Icon } from './Icons' + +function AlternativeModal({ exercise, alternatives, loading, error, onSelect, onClose }) { + if (!exercise) return null + + return ( +
+
event.stopPropagation()}> +
+
+

Alternativa övningar

+

För {exercise.name}

+
+ +
+ + {loading && ( +
Laddar alternativ...
+ )} + + {!loading && error && ( +
{error}
+ )} + + {!loading && !error && alternatives.length === 0 && ( +
Inga alternativ hittades.
+ )} + + {!loading && !error && alternatives.length > 0 && ( +
+ {alternatives.map((alt) => ( +
+
+ {alt.name} + {alt.description || 'Ingen beskrivning tillgänglig.'} +
+ +
+ ))} +
+ )} +
+
+ ) +} + +export default AlternativeModal diff --git a/frontend/src/components/Icons.jsx b/frontend/src/components/Icons.jsx index 1f50d51..3291303 100644 --- a/frontend/src/components/Icons.jsx +++ b/frontend/src/components/Icons.jsx @@ -62,6 +62,14 @@ export const Icons = { ), + swap: ( + + + + + + + ), check: ( diff --git a/frontend/src/index.css b/frontend/src/index.css index 424d6ff..9ad03c6 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -5,39 +5,91 @@ } :root { - /* Dark fitness palette */ + /* Dark fitness palette - refined */ --bg-primary: #0a0a0f; - --bg-secondary: #0d0d12; - --bg-card: #15151b; - --bg-card-hover: #1a1a22; + --bg-secondary: #0d0d14; + --bg-tertiary: #12121a; + --bg-card: #16161f; + --bg-card-hover: #1c1c28; + --bg-elevated: #1a1a24; --bg: #0a0a0f; - /* Text colors */ + /* Text colors - better hierarchy */ --text-primary: #ffffff; --text-secondary: #a1a1aa; --text-muted: #71717a; + --text-tertiary: #52525b; --text: #ffffff; - /* Accent - energetic orange */ - --accent: #ff6b35; - --accent-hover: #ff8555; + /* Accent - refined energetic coral */ + --accent: #ff6b4a; + --accent-hover: #ff8066; + --accent-subtle: rgba(255, 107, 74, 0.15); + --accent-glow: rgba(255, 107, 74, 0.25); - /* Status colors */ + /* Status colors - refined */ --success: #22c55e; + --success-subtle: rgba(34, 197, 94, 0.15); --warning: #f59e0b; + --warning-subtle: rgba(245, 158, 11, 0.15); --error: #ef4444; + --error-subtle: rgba(239, 68, 68, 0.15); - /* Border */ - --border: #1f1f28; + /* Borders - refined */ + --border: #1f1f2a; + --border-hover: #2a2a38; + --border-accent: var(--accent-subtle); - /* Workout type colors - muted, professional */ + /* Shadows - key for enterprise feel */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -2px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.4); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.7), 0 8px 10px -6px rgba(0, 0, 0, 0.4); + --shadow-glow: 0 0 20px var(--accent-glow); + --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-elevated: 0 8px 16px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3); + + /* Workout type colors - refined */ --workout-push: #ef4444; --workout-pull: #3b82f6; --workout-legs: #22c55e; --workout-shoulders: #f59e0b; --workout-upper: #8b5cf6; --workout-lower: #06b6d4; - --workout-default: #ff6b35; + --workout-default: #ff6b4a; + + /* Typography scale */ + --font-xs: 0.75rem; + --font-sm: 0.875rem; + --font-base: 1rem; + --font-lg: 1.125rem; + --font-xl: 1.25rem; + --font-2xl: 1.5rem; + --font-3xl: 2rem; + + /* Spacing scale */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-base: 200ms ease; + --transition-slow: 300ms ease; + + /* Border radius */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 18px; + --radius-2xl: 24px; + --radius-full: 9999px; } html, body { @@ -47,10 +99,12 @@ html, body { min-height: 100vh; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + line-height: 1.5; } h1, h2, h3, h4, h5, h6 { font-weight: 700; + line-height: 1.2; } #root { @@ -62,74 +116,458 @@ button { cursor: pointer; border: none; outline: none; + font-size: var(--font-base); } input { font-family: inherit; outline: none; + font-size: var(--font-base); } -/* Scrollbar styling */ +/* Scrollbar styling - refined */ ::-webkit-scrollbar { - width: 6px; + width: 8px; + height: 8px; } ::-webkit-scrollbar-track { background: var(--bg-secondary); + border-radius: 4px; } ::-webkit-scrollbar-thumb { background: var(--border); - border-radius: 3px; + border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: var(--text-secondary); + background: var(--text-tertiary); } -/* Auth pages */ -.auth-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } -.auth-card { background: var(--bg-card); padding: 40px; border-radius: 16px; width: 100%; max-width: 400px; text-align: center; } -.auth-card h1 { font-size: 2.5rem; margin-bottom: 8px; } -.auth-card h2 { color: var(--text-secondary); font-weight: 400; margin-bottom: 24px; } -.auth-card form { display: flex; flex-direction: column; gap: 16px; } -.auth-card input { padding: 14px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 1rem; } -.auth-card input:focus { border-color: var(--accent); } -.auth-card button[type="submit"] { padding: 14px; background: var(--accent); color: white; border-radius: 8px; font-size: 1rem; font-weight: 600; transition: background 0.2s; } -.auth-card button[type="submit"]:hover:not(:disabled) { background: var(--accent-hover); } -.auth-card button:disabled { opacity: 0.6; cursor: not-allowed; } -.auth-card .error { background: rgba(233,69,96,0.15); color: var(--accent); padding: 12px; border-radius: 8px; margin-bottom: 16px; } -.auth-link { margin-top: 20px; color: var(--text-secondary); } -.auth-link a { color: var(--accent); text-decoration: none; } +/* ============================================ + AUTH PAGES - Premium First Impression + ============================================ */ -/* Onboarding */ -.onboarding { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } -.onboarding-card { background: var(--bg-card); padding: 32px; border-radius: 16px; width: 100%; max-width: 480px; } -.steps-indicator { display: flex; justify-content: center; gap: 12px; margin-bottom: 28px; } -.steps-indicator span { width: 32px; height: 32px; border-radius: 50%; background: var(--bg-secondary); display: flex; align-items: center; justify-content: center; font-size: 0.875rem; color: var(--text-secondary); } -.steps-indicator span.active { background: var(--accent); color: white; } -.step h2 { margin-bottom: 20px; text-align: center; } -.step .hint { color: var(--text-secondary); font-size: 0.875rem; margin-bottom: 16px; text-align: center; } -.field { margin-bottom: 16px; } -.field label { display: block; margin-bottom: 8px; color: var(--text-secondary); font-size: 0.875rem; } -.field input { width: 100%; padding: 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 1rem; } -.field input:focus { border-color: var(--accent); } -.btn-group { display: flex; gap: 8px; } -.btn-group.vertical { flex-direction: column; } -.btn-group button { flex: 1; padding: 12px; border-radius: 8px; background: var(--bg-secondary); color: var(--text-secondary); border: 1px solid var(--border); transition: all 0.2s; } -.btn-group button:hover { border-color: var(--accent); } -.btn-group button.active { background: var(--accent); color: white; border-color: var(--accent); } -.rm-fields { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 8px; } -.rm-fields .field { margin-bottom: 0; } -.bodyfat-result { background: rgba(78,204,163,0.15); color: var(--success); padding: 16px; border-radius: 8px; text-align: center; margin: 16px 0; } -.nav-btns { display: flex; gap: 12px; margin-top: 24px; } -.nav-btns button { flex: 1; padding: 14px; border-radius: 8px; font-size: 1rem; } -.nav-btns button:first-child { background: var(--bg-secondary); color: var(--text-secondary); } -.next-btn, .finish-btn { background: var(--accent) !important; color: white !important; font-weight: 600; } -.next-btn:hover:not(:disabled), .finish-btn:hover:not(:disabled) { background: var(--accent-hover) !important; } -button:disabled { opacity: 0.5; cursor: not-allowed; } +.auth-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-5); + background: var(--bg-primary); + position: relative; + overflow: hidden; +} + +/* Subtle background gradient */ +.auth-page::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient( + ellipse at 30% 20%, + rgba(255, 107, 74, 0.03) 0%, + transparent 50% + ), + radial-gradient( + ellipse at 70% 80%, + rgba(99, 102, 241, 0.03) 0%, + transparent 50% + ); + pointer-events: none; +} + +.auth-card { + background: var(--bg-card); + padding: var(--space-10) var(--space-8); + border-radius: var(--radius-2xl); + width: 100%; + max-width: 420px; + text-align: center; + box-shadow: var(--shadow-elevated); + border: 1px solid var(--border); + position: relative; + z-index: 1; +} + +.auth-card h1 { + font-size: var(--font-3xl); + margin-bottom: var(--space-2); + background: linear-gradient(135deg, var(--text-primary) 0%, var(--text-secondary) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.auth-card h2 { + color: var(--text-secondary); + font-weight: 500; + margin-bottom: var(--space-8); + font-size: var(--font-lg); +} + +.auth-card form { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.auth-card input { + padding: var(--space-4) var(--space-5); + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-primary); + font-size: 16px; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.auth-card input:hover { + border-color: var(--border-hover); +} + +.auth-card input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-subtle); +} + +.auth-card input::placeholder { + color: var(--text-tertiary); +} + +.auth-card button[type="submit"] { + padding: var(--space-4); + background: var(--accent); + color: white; + border-radius: var(--radius-md); + font-size: var(--font-base); + font-weight: 600; + transition: all var(--transition-base); + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); + position: relative; + overflow: hidden; +} + +.auth-card button[type="submit"]::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(255,255,255,0.1) 0%, transparent 50%); + pointer-events: none; +} + +.auth-card button[type="submit"]:hover:not(:disabled) { + background: var(--accent-hover); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(255, 107, 74, 0.4); +} + +.auth-card button[type="submit"]:active:not(:disabled) { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(255, 107, 74, 0.3); +} + +.auth-card button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.auth-card .error { + background: var(--error-subtle); + color: var(--error); + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + margin-bottom: var(--space-4); + font-size: var(--font-sm); + border: 1px solid rgba(239, 68, 68, 0.2); +} + +.auth-link { + margin-top: var(--space-6); + color: var(--text-muted); + font-size: var(--font-sm); +} + +.auth-link a { + color: var(--accent); + text-decoration: none; + font-weight: 500; + transition: color var(--transition-fast); +} + +.auth-link a:hover { + color: var(--accent-hover); + text-decoration: underline; +} + +/* ============================================ + ONBOARDING - Premium Step Wizard + ============================================ */ + +.onboarding { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-5); + background: var(--bg-primary); + position: relative; + overflow: hidden; +} + +.onboarding::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient( + ellipse at 30% 20%, + rgba(255, 107, 74, 0.04) 0%, + transparent 50% + ), + radial-gradient( + ellipse at 70% 80%, + rgba(99, 102, 241, 0.04) 0%, + transparent 50% + ); + pointer-events: none; +} + +.onboarding-card { + background: var(--bg-card); + padding: var(--space-8); + border-radius: var(--radius-2xl); + width: 100%; + max-width: 520px; + box-shadow: var(--shadow-elevated); + border: 1px solid var(--border); + position: relative; + z-index: 1; +} + +.steps-indicator { + display: flex; + justify-content: center; + gap: var(--space-3); + margin-bottom: var(--space-8); +} + +.steps-indicator span { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--bg-secondary); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-muted); + transition: all var(--transition-base); + border: 2px solid var(--border); +} + +.steps-indicator span.active { + background: var(--accent); + color: white; + border-color: var(--accent); + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); +} + +.step h2 { + margin-bottom: var(--space-6); + text-align: center; + font-size: var(--font-xl); +} + +.step .hint { + color: var(--text-muted); + font-size: var(--font-sm); + margin-bottom: var(--space-4); + text-align: center; +} + +.field { + margin-bottom: var(--space-4); +} + +.field label { + display: block; + margin-bottom: var(--space-2); + color: var(--text-secondary); + font-size: var(--font-sm); + font-weight: 500; +} + +.field input { + width: 100%; + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-primary); + font-size: 16px; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.field input:hover { + border-color: var(--border-hover); +} + +.field input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-subtle); +} + +.field input::placeholder { + color: var(--text-tertiary); +} + +.btn-group { + display: flex; + gap: var(--space-2); +} + +.btn-group.vertical { + flex-direction: column; +} + +.btn-group button { + flex: 1; + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + background: var(--bg-secondary); + color: var(--text-secondary); + border: 1px solid var(--border); + transition: all var(--transition-base); + font-weight: 500; + min-height: 44px; +} + +.btn-group button:hover { + border-color: var(--accent); + color: var(--text-primary); + background: var(--bg-tertiary); +} + +.btn-group button.active { + background: var(--accent); + color: white; + border-color: var(--accent); + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.25); +} + +.rm-fields { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-3); + margin-top: var(--space-2); +} + +.rm-fields .field { + margin-bottom: 0; +} + +.bodyfat-result { + background: var(--success-subtle); + color: var(--success); + padding: var(--space-4); + border-radius: var(--radius-md); + text-align: center; + margin: var(--space-4) 0; + border: 1px solid rgba(34, 197, 94, 0.2); +} + +.bodyfat-result strong { + font-size: var(--font-lg); +} + +.nav-btns { + display: flex; + gap: var(--space-3); + margin-top: var(--space-6); +} + +.nav-btns button { + flex: 1; + padding: var(--space-4); + border-radius: var(--radius-md); + font-size: var(--font-base); + transition: all var(--transition-base); + min-height: 44px; +} + +.nav-btns button:first-child { + background: var(--bg-secondary); + color: var(--text-secondary); + border: 1px solid var(--border); +} + +.nav-btns button:first-child:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + border-color: var(--border-hover); +} + +.next-btn, .finish-btn { + background: var(--accent) !important; + color: white !important; + font-weight: 600; + border: none !important; + box-shadow: 0 4px 12px rgba(255, 107, 74, 0.3); +} + +.next-btn:hover:not(:disabled), .finish-btn:hover:not(:disabled) { + background: var(--accent-hover) !important; + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(255, 107, 74, 0.4); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} /* Header logout */ -.header-left { display: flex; align-items: center; gap: 16px; } -.logout-btn { padding: 6px 12px; background: var(--bg-secondary); color: var(--text-secondary); border-radius: 6px; font-size: 0.75rem; } -.logout-btn:hover { background: var(--border); } +.header-left { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.logout-btn { + padding: var(--space-2) var(--space-3); + background: var(--bg-secondary); + color: var(--text-muted); + border-radius: var(--radius-sm); + font-size: var(--font-xs); + transition: all var(--transition-base); + border: 1px solid var(--border); +} + +.logout-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + border-color: var(--border-hover); +} + +/* ============================================ + GLOBAL INPUT ACCESSIBILITY + Ensure all inputs have font-size >= 16px to prevent iOS auto-zoom + ============================================ */ + +input[type="text"], +input[type="email"], +input[type="password"], +input[type="number"], +input[type="tel"], +select, +textarea { + font-size: 16px; +} \ No newline at end of file diff --git a/frontend/src/pages/WorkoutPage.jsx b/frontend/src/pages/WorkoutPage.jsx index 15b3e53..83204cc 100644 --- a/frontend/src/pages/WorkoutPage.jsx +++ b/frontend/src/pages/WorkoutPage.jsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react' import { Icon } from '../components/Icons' -import WeightInput from '../components/WeightInput' -import RepsInput from '../components/RepsInput' +import AlternativeModal from '../components/AlternativeModal' + +const API_URL = '/api' // Uppvärmningsövningar baserat på muskelgrupp const warmupExercises = { @@ -53,11 +54,33 @@ function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProg const [warmupDone, setWarmupDone] = useState(false) const [warmupExpanded, setWarmupExpanded] = useState(true) const [completedWarmups, setCompletedWarmups] = useState(new Set()) + const [swapExercise, setSwapExercise] = useState(null) + const [alternatives, setAlternatives] = useState([]) + const [alternativesLoading, setAlternativesLoading] = useState(false) + const [alternativesError, setAlternativesError] = useState('') + const [swappedExercises, setSwappedExercises] = useState({}) + const defaultRestSeconds = 90 + const [restSeconds, setRestSeconds] = useState(defaultRestSeconds) + const [restRunning, setRestRunning] = useState(false) useEffect(() => { loadProgressions() }, [day]) + useEffect(() => { + if (!restRunning) return + const timer = setInterval(() => { + setRestSeconds(prev => { + if (prev <= 1) { + setRestRunning(false) + return 0 + } + return prev - 1 + }) + }, 1000) + return () => clearInterval(timer) + }, [restRunning]) + const loadProgressions = async () => { const progs = {} for (const exercise of day.exercises) { @@ -68,6 +91,40 @@ function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProg setProgressions(progs) } + const openAlternatives = async (exercise) => { + if (!exercise?.exercise_id) { + setAlternativesError('Saknar övningsdata för alternativa val.') + setSwapExercise(exercise) + return + } + + setSwapExercise(exercise) + setAlternatives([]) + setAlternativesError('') + setAlternativesLoading(true) + + try { + const res = await fetch(`${API_URL}/exercises/${exercise.exercise_id}/alternatives`) + if (!res.ok) throw new Error('Failed to fetch alternatives') + const data = await res.json() + setAlternatives(data) + } catch (err) { + console.error('Failed to fetch alternatives:', err) + setAlternativesError('Kunde inte hämta alternativ.') + } finally { + setAlternativesLoading(false) + } + } + + const handleSelectAlternative = (alternative) => { + if (!swapExercise) return + setSwappedExercises(prev => ({ + ...prev, + [swapExercise.id]: alternative + })) + setSwapExercise(null) + } + const exercises = day.exercises?.filter(e => e.name) || [] const muscleGroups = getMuscleGroups(exercises) @@ -97,6 +154,26 @@ function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProg const totalWarmups = generalWarmups.length + specificWarmups.length const warmupProgress = completedWarmups.size + const formatRestTime = (totalSeconds) => { + const minutes = Math.floor(totalSeconds / 60) + const seconds = totalSeconds % 60 + return `${minutes}:${seconds.toString().padStart(2, '0')}` + } + + const startRest = (seconds = defaultRestSeconds) => { + setRestSeconds(seconds) + setRestRunning(true) + } + + const toggleRest = () => { + setRestRunning(prev => !prev) + } + + const resetRest = () => { + setRestRunning(false) + setRestSeconds(defaultRestSeconds) + } + return (
@@ -113,6 +190,29 @@ function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProg
+ {/* Vila */} +
+
+
Vilotimer
+
+ {formatRestTime(restSeconds)} +
+
+
+ + +
+
+ + + +
+
+ {/* Progress Bar */}

Övningar

- {exercises.map((exercise, idx) => ( - setExpandedExercise( - expandedExercise === exercise.id ? null : exercise.id - )} - onLogSet={onLogSet} - onDeleteSet={onDeleteSet} - /> - ))} + {exercises.map((exercise, idx) => { + const swapped = swappedExercises[exercise.id] + const displayExercise = swapped + ? { ...exercise, name: swapped.name, muscle_group: swapped.muscle_group, description: swapped.description } + : exercise + + return ( + setExpandedExercise( + expandedExercise === exercise.id ? null : exercise.id + )} + onLogSet={onLogSet} + onDeleteSet={onDeleteSet} + onStartRest={startRest} + onSwap={() => openAlternatives(exercise)} + /> + ) + })} {/* Avsluta pass */} @@ -254,13 +364,24 @@ function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProg : `Avsluta pass (${completedExercises}/${exercises.length} klara)`}
+ + setSwapExercise(null)} + />
) } -function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSet, onDeleteSet }) { +function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSet, onDeleteSet, onSwap, isSwapped, onStartRest }) { const [setList, setSetList] = useState([]) const [showAddModal, setShowAddModal] = useState(false) + const weightStep = 2.5 + const repsStep = 1 useEffect(() => { const initial = [] @@ -279,11 +400,34 @@ function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSe setSetList(prev => prev.map((s, i) => i === idx ? { ...s, [field]: value } : s)) } + const parseNumber = (value) => { + const parsed = parseFloat(value) + return Number.isFinite(parsed) ? parsed : 0 + } + + const formatWeight = (value) => { + const fixed = Number.isInteger(value) ? String(value) : value.toFixed(1) + return fixed.replace(/\.0$/, '') + } + + const handleAdjust = (idx, field, delta, min = 0) => { + const current = parseNumber(setList[idx]?.[field]) + const next = Math.max(min, current + delta) + if (field === 'weight') { + handleInputChange(idx, field, formatWeight(next)) + } else { + handleInputChange(idx, field, String(Math.round(next))) + } + } + const handleComplete = (idx) => { const input = setList[idx] const newCompleted = !input.completed setSetList(prev => prev.map((s, i) => i === idx ? { ...s, completed: newCompleted } : s)) onLogSet(exercise.id, idx + 1, input.weight, input.reps, newCompleted) + if (newCompleted) { + onStartRest?.() + } } const handleAddNormal = () => { @@ -320,12 +464,25 @@ function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSe

{exercise.name}

{exercise.muscle_group} + {isSwapped && Alternativ}
-
- {exercise.sets}×{exercise.reps_min}-{exercise.reps_max} - - {completedSets}/{setList.length} - +
+
+ {exercise.sets}×{exercise.reps_min}-{exercise.reps_max} + + {completedSets}/{setList.length} + +
+
@@ -343,31 +500,74 @@ function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSe
{setList.map((input, idx) => (
- Set {idx + 1} -
- handleInputChange(idx, 'weight', val)} - /> - × - handleInputChange(idx, 'reps', val)} - /> +
+ Set {idx + 1} + +
+
+
+ Vikt +
+ +
+ {input.weight === '' ? '0' : input.weight} + kg +
+ +
+
+
+ Reps +
+ +
+ {input.reps === '' ? '0' : input.reps} +
+ +
+
-
))}