package templates import "rideaware/internal/workout" // Template represents a predefined workout template. type Template struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` Duration int `json:"duration"` Difficulty string `json:"difficulty"` // "easy", "moderate", "hard", "very_hard" Category string `json:"category"` // "endurance", "threshold", "vo2max", "recovery", "sprint", "sweet_spot" Segments []workout.WorkoutSegment `json:"segments"` } // All returns the full list of workout templates. var All = []Template{ { ID: "recovery-spin", Name: "Recovery Spin", Description: "Easy spin to promote blood flow and recovery. Keep it light and conversational.", Type: "Recovery", Duration: 1800, // 30 min Difficulty: "easy", Category: "recovery", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 300, PowerLow: 0.30, PowerHigh: 0.45}, {Type: "steadystate", Duration: 1200, Power: 0.45, Cadence: 90}, {Type: "cooldown", Duration: 300, PowerLow: 0.45, PowerHigh: 0.30}, }, }, { ID: "endurance-60", Name: "Endurance Base", Description: "Steady zone 2 ride to build aerobic base. Maintain a comfortable, sustainable effort.", Type: "Endurance", Duration: 3600, // 60 min Difficulty: "easy", Category: "endurance", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.65}, {Type: "steadystate", Duration: 2400, Power: 0.65, Cadence: 85}, {Type: "cooldown", Duration: 600, PowerLow: 0.65, PowerHigh: 0.40}, }, }, { ID: "endurance-90", Name: "Long Endurance", Description: "Extended zone 2 ride for deep aerobic adaptation. Pack snacks.", Type: "Endurance", Duration: 5400, // 90 min Difficulty: "moderate", Category: "endurance", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.65}, {Type: "steadystate", Duration: 4200, Power: 0.68, Cadence: 85}, {Type: "cooldown", Duration: 600, PowerLow: 0.65, PowerHigh: 0.40}, }, }, { ID: "tempo-45", Name: "Tempo Ride", Description: "Sustained zone 3 effort. Comfortably hard - you can talk in short sentences.", Type: "Tempo", Duration: 2700, // 45 min Difficulty: "moderate", Category: "endurance", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.70}, {Type: "steadystate", Duration: 1200, Power: 0.78, Cadence: 90}, {Type: "steadystate", Duration: 300, Power: 0.55}, {Type: "steadystate", Duration: 300, Power: 0.78, Cadence: 90}, {Type: "cooldown", Duration: 300, PowerLow: 0.65, PowerHigh: 0.40}, }, }, { ID: "sweet-spot-60", Name: "Sweet Spot", Description: "The most time-efficient training zone. 88-94% FTP intervals with short recovery.", Type: "Threshold", Duration: 3600, // 60 min Difficulty: "moderate", Category: "sweet_spot", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.75}, {Type: "steadystate", Duration: 600, Power: 0.90, Cadence: 90}, {Type: "steadystate", Duration: 180, Power: 0.55}, {Type: "steadystate", Duration: 600, Power: 0.92, Cadence: 90}, {Type: "steadystate", Duration: 180, Power: 0.55}, {Type: "steadystate", Duration: 600, Power: 0.90, Cadence: 90}, {Type: "steadystate", Duration: 180, Power: 0.55}, {Type: "cooldown", Duration: 660, PowerLow: 0.65, PowerHigh: 0.40}, }, }, { ID: "threshold-intervals", Name: "Threshold Intervals", Description: "Classic 2x20 at FTP. The gold standard for raising your threshold.", Type: "Threshold", Duration: 3600, // 60 min Difficulty: "hard", Category: "threshold", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.75}, {Type: "steadystate", Duration: 300, Power: 0.80, Cadence: 90}, {Type: "steadystate", Duration: 1200, Power: 1.00, Cadence: 95}, {Type: "steadystate", Duration: 300, Power: 0.50}, {Type: "steadystate", Duration: 1200, Power: 1.00, Cadence: 95}, {Type: "cooldown", Duration: 600, PowerLow: 0.65, PowerHigh: 0.40}, }, }, { ID: "over-unders", Name: "Over-Under Intervals", Description: "Alternating between just below and just above FTP. Builds lactate tolerance.", Type: "Threshold", Duration: 3600, // 60 min Difficulty: "hard", Category: "threshold", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.75}, // Set 1 {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 300, Power: 0.50}, // Set 2 {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 300, Power: 0.50}, // Set 3 {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.95, Cadence: 90}, {Type: "steadystate", Duration: 60, Power: 1.05, Cadence: 95}, {Type: "cooldown", Duration: 600, PowerLow: 0.65, PowerHigh: 0.40}, }, }, { ID: "vo2max-intervals", Name: "VO2max Intervals", Description: "5x3min at 115% FTP with equal rest. Expands your aerobic ceiling.", Type: "VO2 Max", Duration: 2700, // 45 min Difficulty: "very_hard", Category: "vo2max", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.75}, {Type: "steadystate", Duration: 180, Power: 1.15, Cadence: 100}, {Type: "steadystate", Duration: 180, Power: 0.45}, {Type: "steadystate", Duration: 180, Power: 1.15, Cadence: 100}, {Type: "steadystate", Duration: 180, Power: 0.45}, {Type: "steadystate", Duration: 180, Power: 1.15, Cadence: 100}, {Type: "steadystate", Duration: 180, Power: 0.45}, {Type: "steadystate", Duration: 180, Power: 1.15, Cadence: 100}, {Type: "steadystate", Duration: 180, Power: 0.45}, {Type: "steadystate", Duration: 180, Power: 1.15, Cadence: 100}, {Type: "cooldown", Duration: 420, PowerLow: 0.55, PowerHigh: 0.35}, }, }, { ID: "vo2max-short", Name: "VO2max Short-Shorts", Description: "30/30s at 120% FTP. Accumulate VO2max time in manageable chunks.", Type: "VO2 Max", Duration: 2400, // 40 min Difficulty: "very_hard", Category: "vo2max", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.75}, // Set 1: 10x 30/30 {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 300, Power: 0.40}, // Set 2: 10x 30/30 {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "steadystate", Duration: 30, Power: 0.40}, {Type: "interval", Duration: 30, PowerLow: 1.20, PowerHigh: 1.20}, {Type: "cooldown", Duration: 300, PowerLow: 0.50, PowerHigh: 0.35}, }, }, { ID: "sprint-intervals", Name: "Sprint Power", Description: "Short maximal efforts to build neuromuscular power and sprint capacity.", Type: "VO2 Max", Duration: 2700, // 45 min Difficulty: "very_hard", Category: "sprint", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 600, PowerLow: 0.40, PowerHigh: 0.75}, {Type: "steadystate", Duration: 300, Power: 0.85, Cadence: 95}, {Type: "steadystate", Duration: 120, Power: 0.50}, // Sprints: 8x 15s all-out / 105s recovery {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "steadystate", Duration: 105, Power: 0.40}, {Type: "interval", Duration: 15, PowerLow: 1.50, PowerHigh: 2.00}, {Type: "cooldown", Duration: 480, PowerLow: 0.55, PowerHigh: 0.35}, }, }, { ID: "ramp-test", Name: "FTP Ramp Test", Description: "Progressive ramp to estimate your FTP. Ride until you can't hold the target.", Type: "Threshold", Duration: 1500, // ~25 min (most people fail around 19-22 min) Difficulty: "very_hard", Category: "threshold", Segments: []workout.WorkoutSegment{ {Type: "warmup", Duration: 300, PowerLow: 0.40, PowerHigh: 0.46}, {Type: "ramp", Duration: 60, PowerLow: 0.46, PowerHigh: 0.52}, {Type: "ramp", Duration: 60, PowerLow: 0.52, PowerHigh: 0.58}, {Type: "ramp", Duration: 60, PowerLow: 0.58, PowerHigh: 0.64}, {Type: "ramp", Duration: 60, PowerLow: 0.64, PowerHigh: 0.70}, {Type: "ramp", Duration: 60, PowerLow: 0.70, PowerHigh: 0.76}, {Type: "ramp", Duration: 60, PowerLow: 0.76, PowerHigh: 0.82}, {Type: "ramp", Duration: 60, PowerLow: 0.82, PowerHigh: 0.88}, {Type: "ramp", Duration: 60, PowerLow: 0.88, PowerHigh: 0.94}, {Type: "ramp", Duration: 60, PowerLow: 0.94, PowerHigh: 1.00}, {Type: "ramp", Duration: 60, PowerLow: 1.00, PowerHigh: 1.06}, {Type: "ramp", Duration: 60, PowerLow: 1.06, PowerHigh: 1.12}, {Type: "ramp", Duration: 60, PowerLow: 1.12, PowerHigh: 1.18}, {Type: "ramp", Duration: 60, PowerLow: 1.18, PowerHigh: 1.24}, {Type: "ramp", Duration: 60, PowerLow: 1.24, PowerHigh: 1.30}, {Type: "cooldown", Duration: 300, PowerLow: 0.50, PowerHigh: 0.30}, }, }, } // GetByID returns a template by its ID, or nil if not found. func GetByID(id string) *Template { for i := range All { if All[i].ID == id { return &All[i] } } return nil } // GetByCategory returns all templates matching a category. func GetByCategory(category string) []Template { var result []Template for _, t := range All { if t.Category == category { result = append(result, t) } } return result }