Author the machine exercise circuit with props and corrected hip machine motions

Fourteen Planet Fitness machine exercises join the rig library, each with
authored motion, info page, and schematic equipment via the new props layer
(scene shapes, cables, bars, pads) rendered in lockstep by render.py and the
in-app figure renderer. Abductor and Adductor are authored face-on with the
real seated machine motion — knees-bent legs swinging apart/together against
knee pads — replacing the earlier middle-split depiction. The watch target
now bundles the figure renderer and motion rigs too.

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
2026-07-06 16:29:31 -04:00
parent 888852cc2e
commit fce8fa4c17
130 changed files with 4116 additions and 82 deletions
+142 -43
View File
@@ -13,7 +13,7 @@ import SwiftUI
///
/// Draw order matters: left limbs behind, spine, right limbs, then the head filled
/// opaque with the background color so overhead arms pass behind the face plus the
/// nose tick (gaze) and the small R/L legend that makes opposite-limb moves readable.
/// nose tick (gaze).
/// Everything the renderer needs for one exercise, resolved once from the bundle.
struct FigureAnimation {
@@ -22,6 +22,8 @@ struct FigureAnimation {
let working: Set<String>
/// Limbs fully occluded in this view never drawn.
let hide: Set<String>
/// Equipment layer (see SYSTEM.md "The props layer").
let props: [MotionProp]
let bodyProfile: ExerciseBodyProfile
init?(exerciseName: String) {
@@ -32,6 +34,7 @@ struct FigureAnimation {
self.timeline = timeline
self.working = Set(resources.motion.working ?? [])
self.hide = Set(resources.motion.hide ?? [])
self.props = resources.motion.props ?? []
self.bodyProfile = resources.body
}
@@ -96,6 +99,9 @@ struct ExerciseFigureView: View {
CGPoint(x: Self.designSize.width - 16, y: Self.groundY + 4)],
color: .figureGround, width: 3)
// Equipment behind the figure: scene shapes and cables.
drawBackgroundProps(&ctx, geo)
// Left limbs (behind), spine, right limbs.
drawLimb(&ctx, geo, .armL)
drawLimb(&ctx, geo, .legL)
@@ -103,6 +109,9 @@ struct ExerciseFigureView: View {
drawLimb(&ctx, geo, .armR)
drawLimb(&ctx, geo, .legR)
// Joint-attached equipment over the limbs (bars, dumbbells, pads).
drawAttachedProps(&ctx, geo)
// Head last, filled with the background color so limbs pass behind the face.
let r = geo.headRadius
let headRect = CGRect(x: geo.headCenter.x - r, y: geo.headCenter.y - r,
@@ -110,9 +119,9 @@ struct ExerciseFigureView: View {
let headPath = Path(ellipseIn: headRect)
ctx.fill(headPath, with: .color(.figureHeadFill))
ctx.stroke(headPath, with: .color(.figureRight), lineWidth: 6)
stroke(&ctx, [geo.noseStart, geo.noseEnd], color: .figureRight, width: 4)
drawLegend(&ctx)
if let noseStart = geo.noseStart, let noseEnd = geo.noseEnd {
stroke(&ctx, [noseStart, noseEnd], color: .figureRight, width: 4)
}
}
private func drawLimb(_ ctx: inout GraphicsContext, _ geo: FigureGeometry, _ limb: FigureLimb) {
@@ -129,19 +138,103 @@ struct ExerciseFigureView: View {
style: StrokeStyle(lineWidth: 6, lineCap: .round, lineJoin: .round))
}
/// Small `R / L ` legend in the top-right corner, so opposite-limb moves are
/// visibly opposite.
private func drawLegend(_ ctx: inout GraphicsContext) {
let lx = Self.designSize.width - 78
stroke(&ctx, [CGPoint(x: lx, y: 16), CGPoint(x: lx + 16, y: 16)],
color: .figureRight, width: 4)
stroke(&ctx, [CGPoint(x: lx + 40, y: 16), CGPoint(x: lx + 56, y: 16)],
color: .figureLeft, width: 4)
let legendFont = Font.system(size: 11)
ctx.draw(Text("R").font(legendFont).foregroundStyle(.secondary),
at: CGPoint(x: lx + 22, y: 16), anchor: .leading)
ctx.draw(Text("L").font(legendFont).foregroundStyle(.secondary),
at: CGPoint(x: lx + 62, y: 16), anchor: .leading)
// MARK: Props
/// The extremity point a prop follows (plus the lower-bone unit direction for
/// perpendicular items). Nil when any referenced limb isn't drawn this frame.
private func jointAnchor(_ geo: FigureGeometry, _ ref: PropJointRef) -> (point: CGPoint, direction: CGVector)? {
var points: [CGPoint] = []
var direction: CGVector?
for name in ref.names {
guard let limb = FigureLimb.allCases.first(where: { $0.pinKey == name }),
let chain = geo.limbs[limb], chain.count >= 2 else { return nil }
let a = chain[chain.count - 2]
let b = chain[chain.count - 1]
points.append(b)
if direction == nil {
let d = max(hypot(b.x - a.x, b.y - a.y), 1)
direction = CGVector(dx: (b.x - a.x) / d, dy: (b.y - a.y) / d)
}
}
guard let direction, !points.isEmpty else { return nil }
let center = CGPoint(x: points.reduce(0) { $0 + $1.x } / CGFloat(points.count),
y: points.reduce(0) { $0 + $1.y } / CGFloat(points.count))
return (center, direction)
}
private func drawBackgroundProps(_ ctx: inout GraphicsContext, _ geo: FigureGeometry) {
for prop in figure.props {
switch prop.type {
case "scene":
for shape in prop.shapes ?? [] { drawSceneShape(&ctx, shape) }
case "cable":
guard let from = prop.from, from.count == 2, let to = prop.to,
let anchor = jointAnchor(geo, to) else { continue }
stroke(&ctx, [CGPoint(x: from[0], y: from[1]), anchor.point],
color: .figureEquipment, width: prop.w ?? 2)
default:
break
}
}
}
/// Bars sit at a fixed world angle (default horizontal); dumbbells and pads
/// default to perpendicular to the lower bone. Kept 1:1 with the reference
/// renderer's `resolve_props` change them in lockstep.
private func drawAttachedProps(_ ctx: inout GraphicsContext, _ geo: FigureGeometry) {
for prop in figure.props {
let defaults: (halfLen: Double, width: Double, plateR: Double)
switch prop.type {
case "bar": defaults = (24, 4, 0)
case "dumbbell": defaults = (7, 3, 4.5)
case "pad": defaults = (8, 7, 0)
default: continue
}
guard let at = prop.at, let anchor = jointAnchor(geo, at) else { continue }
let axis: CGVector
if prop.type == "bar" || prop.angle != nil {
axis = MotionSolver.direction(prop.angle ?? 0)
} else {
axis = CGVector(dx: -anchor.direction.dy, dy: anchor.direction.dx)
}
let h = prop.halfLen ?? defaults.halfLen
let a = CGPoint(x: anchor.point.x - axis.dx * h, y: anchor.point.y - axis.dy * h)
let b = CGPoint(x: anchor.point.x + axis.dx * h, y: anchor.point.y + axis.dy * h)
stroke(&ctx, [a, b], color: .figureProp, width: prop.w ?? defaults.width)
let plateR = prop.plateR ?? defaults.plateR
if plateR > 0 {
for end in [a, b] {
let rect = CGRect(x: end.x - plateR, y: end.y - plateR,
width: 2 * plateR, height: 2 * plateR)
ctx.fill(Path(ellipseIn: rect), with: .color(.figureProp))
}
}
}
}
private func drawSceneShape(_ ctx: inout GraphicsContext, _ shape: PropSceneShape) {
let color: Color = shape.color == "prop" ? .figureProp : .figureEquipment
switch shape.kind {
case "line":
guard let pts = shape.pts else { return }
stroke(&ctx, pts.compactMap { $0.count == 2 ? CGPoint(x: $0[0], y: $0[1]) : nil },
color: color, width: shape.w ?? 4)
case "circle":
guard let c = shape.c, c.count == 2, let r = shape.r else { return }
let rect = CGRect(x: c[0] - r, y: c[1] - r, width: 2 * r, height: 2 * r)
if shape.fill ?? true {
ctx.fill(Path(ellipseIn: rect), with: .color(color))
} else {
ctx.stroke(Path(ellipseIn: rect), with: .color(color), lineWidth: shape.w ?? 3)
}
case "rect":
guard let x = shape.x, let y = shape.y, let w = shape.w, let h = shape.h else { return }
let path = Path(roundedRect: CGRect(x: x, y: y, width: w, height: h),
cornerRadius: shape.r ?? 2)
ctx.fill(path, with: .color(color))
default:
break
}
}
private func color(for part: String, isLeft: Bool) -> Color {
@@ -163,44 +256,50 @@ struct ExerciseFigureView: View {
/// The reference palette (`render.py`), made dark-mode adaptive: the prominent right
/// side stays strong (near-black light gray), the recessive left side stays muted,
/// and the working teals brighten/desaturate so they read on black.
/// and the working teals brighten/desaturate so they read on black. watchOS has no
/// dynamic-provider `UIColor` and renders on black, so it takes the dark variant
/// verbatim.
private extension Color {
static func figure(light: (Double, Double, Double), dark: (Double, Double, Double)) -> Color {
#if os(watchOS)
Color(red: dark.0, green: dark.1, blue: dark.2)
#else
Color(UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(red: dark.0, green: dark.1, blue: dark.2, alpha: 1)
: UIColor(red: light.0, green: light.1, blue: light.2, alpha: 1)
})
#endif
}
/// Right-side limbs, head, nose the prominent stroke (`#3a3f4b`).
static let figureRight = Color(UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(red: 0.76, green: 0.79, blue: 0.83, alpha: 1)
: UIColor(red: 0.23, green: 0.25, blue: 0.29, alpha: 1)
})
static let figureRight = figure(light: (0.23, 0.25, 0.29), dark: (0.76, 0.79, 0.83))
/// Left-side limbs, drawn behind the recessive stroke (`#a9afba`).
static let figureLeft = Color(UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(red: 0.36, green: 0.39, blue: 0.43, alpha: 1)
: UIColor(red: 0.66, green: 0.69, blue: 0.73, alpha: 1)
})
static let figureLeft = figure(light: (0.66, 0.69, 0.73), dark: (0.36, 0.39, 0.43))
/// Working right-side parts teal accent (`#0d9488`).
static let figureRightWorking = Color(UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(red: 0.18, green: 0.83, blue: 0.75, alpha: 1)
: UIColor(red: 0.05, green: 0.58, blue: 0.53, alpha: 1)
})
static let figureRightWorking = figure(light: (0.05, 0.58, 0.53), dark: (0.18, 0.83, 0.75))
/// Working left-side parts light teal (`#86cfc5`), kept more muted than the
/// right so the R/L hierarchy holds in both modes.
static let figureLeftWorking = Color(UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(red: 0.31, green: 0.56, blue: 0.52, alpha: 1)
: UIColor(red: 0.53, green: 0.81, blue: 0.77, alpha: 1)
})
static let figureLeftWorking = figure(light: (0.53, 0.81, 0.77), dark: (0.31, 0.56, 0.52))
/// Scene equipment seats, benches, frames, cables (`#c5cad4`); sits behind
/// the figure, one step lighter than the ground line.
static let figureEquipment = figure(light: (0.77, 0.79, 0.83), dark: (0.25, 0.27, 0.31))
/// Joint-attached items bars, dumbbells, pads (`#6b7180`); darker than the
/// scene so the thing being moved reads over the limbs.
static let figureProp = figure(light: (0.42, 0.44, 0.50), dark: (0.55, 0.58, 0.63))
/// Ground line (`#b9bec9`).
static let figureGround = Color(UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(red: 0.29, green: 0.31, blue: 0.36, alpha: 1)
: UIColor(red: 0.73, green: 0.75, blue: 0.79, alpha: 1)
})
static let figureGround = figure(light: (0.73, 0.75, 0.79), dark: (0.29, 0.31, 0.36))
/// Opaque head fill the screen background, so limbs pass behind the face.
#if os(watchOS)
static let figureHeadFill = Color.black
#else
static let figureHeadFill = Color(.systemBackground)
#endif
}
+148
View File
@@ -0,0 +1,148 @@
//
// ExerciseInfo.swift
// Workouts
//
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//
import Foundation
/// Parsed form of an exercise's authored `info.md` (see `Exercise Library/SYSTEM.md`):
/// a one-paragraph summary, the Category/Type/Targets metadata bullets, then the
/// instructional sections (Setup, Execution, Cues, Common Mistakes, Progression).
/// The library's markdown is uniform, so this parses the known shape rather than
/// general Markdown SwiftUI's inline-Markdown `Text` can't render headers/lists,
/// and the app wants the structure (target chips, numbered steps) anyway.
struct ExerciseInfo {
/// The intro paragraph(s) between the `# Title` and the metadata bullets.
var summary: String
var category: String?
var type: String?
/// The comma-separated `**Targets:**` values, split for chip rendering.
var targets: [String]
var sections: [Section]
struct Section {
var title: String
var items: [Item]
}
/// One block within a section, preserving the authored list flavor: numbered
/// steps render with their ordinal, bullets with a dot, paragraphs plain.
enum Item {
case step(String)
case bullet(String)
case paragraph(String)
var text: String {
switch self {
case .step(let s), .bullet(let s), .paragraph(let s): s
}
}
}
}
extension ExerciseInfo {
/// Parse the library's `info.md` shape. Hard-wrapped lines are rejoined: a line
/// that doesn't start a new block (heading, list marker, blank) continues the
/// previous one, matching how Markdown renders soft breaks.
static func parse(markdown: String) -> ExerciseInfo {
var summaryLines: [String] = []
var category: String?
var type: String?
var targets: [String] = []
var sections: [Section] = []
var currentSection: Section?
var currentItem: Item?
func metadataValue(_ line: String, key: String) -> String? {
let prefix = "- **\(key):**"
guard line.hasPrefix(prefix) else { return nil }
return String(line.dropFirst(prefix.count)).trimmingCharacters(in: .whitespaces)
}
func flushItem() {
guard let item = currentItem else { return }
currentItem = nil
if currentSection != nil {
currentSection?.items.append(item)
} else if case .paragraph(let text) = item {
summaryLines.append(text)
}
}
func flushSection() {
flushItem()
if let section = currentSection { sections.append(section) }
currentSection = nil
}
func append(to item: Item?, _ text: String) -> Item? {
switch item {
case .step(let s): .step(s + " " + text)
case .bullet(let s): .bullet(s + " " + text)
case .paragraph(let s): .paragraph(s + " " + text)
case nil: .paragraph(text)
}
}
for rawLine in markdown.components(separatedBy: .newlines) {
let line = rawLine.trimmingCharacters(in: .whitespaces)
if line.isEmpty {
flushItem()
} else if line.hasPrefix("## ") {
flushSection()
currentSection = Section(title: String(line.dropFirst(3)), items: [])
} else if line.hasPrefix("# ") {
continue // the document title duplicates the exercise name
} else if currentSection == nil,
let value = metadataValue(line, key: "Category") {
flushItem()
category = value
} else if currentSection == nil,
let value = metadataValue(line, key: "Type") {
flushItem()
type = value
} else if currentSection == nil,
let value = metadataValue(line, key: "Targets") {
flushItem()
targets = value.components(separatedBy: ",")
.map { $0.trimmingCharacters(in: .whitespaces) }
.filter { !$0.isEmpty }
} else if let range = line.range(of: #"^\d+\.\s+"#, options: .regularExpression) {
flushItem()
currentItem = .step(String(line[range.upperBound...]))
} else if line.hasPrefix("- ") {
flushItem()
currentItem = .bullet(String(line.dropFirst(2)))
} else {
// Continuation of a hard-wrapped block (or the start of a paragraph).
currentItem = append(to: currentItem, line)
}
}
flushSection()
return ExerciseInfo(
summary: summaryLines.joined(separator: "\n\n"),
category: category,
type: type,
targets: targets,
sections: sections
)
}
}
/// Finds and parses the bundled `<Exercise Name>.info.md` exported next to the
/// motion rigs by `render.py --export`.
enum ExerciseInfoLibrary {
/// The parsed info for `exerciseName`, or `nil` when none is bundled.
static func info(for exerciseName: String) -> ExerciseInfo? {
guard
let url = Bundle.main.url(forResource: exerciseName, withExtension: "info.md"),
let markdown = try? String(contentsOf: url, encoding: .utf8)
else { return nil }
return ExerciseInfo.parse(markdown: markdown)
}
}
+75 -2
View File
@@ -39,9 +39,81 @@ struct ExerciseMotion: Codable {
let working: [String]?
/// Limbs fully occluded in this view never drawn.
let hide: [String]?
/// Equipment layer: scene shapes and cables behind the figure, joint-attached
/// items (bar/dumbbell/pad) over the limbs. See SYSTEM.md "The props layer".
let props: [MotionProp]?
let frames: [MotionKeyFrame]
}
/// A prop's joint reference: one extremity (`"hand_r"`) or the midpoint of two
/// (`["foot_r", "foot_l"]`). Extremity keys match the pin keys.
enum PropJointRef: Codable {
case single(String)
case midpoint([String])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let name = try? container.decode(String.self) {
self = .single(name)
} else {
self = .midpoint(try container.decode([String].self))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .single(let name): try container.encode(name)
case .midpoint(let names): try container.encode(names)
}
}
var names: [String] {
switch self {
case .single(let name): [name]
case .midpoint(let names): names
}
}
}
/// One static shape of a `scene` prop, in canvas coordinates.
struct PropSceneShape: Codable {
let kind: String // "line" | "circle" | "rect"
/// line: polyline points; `w` is the stroke width.
let pts: [[Double]]?
let w: Double?
/// circle: center + radius; `fill` false draws an outline.
let c: [Double]?
let r: Double?
let fill: Bool?
/// rect: origin + size (`w`/`h`), corner radius `r`; always filled.
let x: Double?
let y: Double?
let h: Double?
/// Optional palette override: `"prop"` for the darker attached-item gray.
let color: String?
}
/// One equipment prop. `type` selects the flavor; the other fields apply per type
/// (mirroring the reference renderer's `resolve_props`).
struct MotionProp: Codable {
let type: String // "scene" | "cable" | "bar" | "dumbbell" | "pad"
/// scene: the static shapes.
let shapes: [PropSceneShape]?
/// cable: fixed anchor `[x, y]` moving joint `to`.
let from: [Double]?
let to: PropJointRef?
/// bar/dumbbell/pad: the joint(s) the item is centered on.
let at: PropJointRef?
/// Fixed world angle (degrees, y-up). Default: bars are horizontal;
/// dumbbells/pads sit perpendicular to the lower bone.
let angle: Double?
let halfLen: Double?
let w: Double?
/// End-disc radius (dumbbell plates default 4.5; bars none).
let plateR: Double?
}
/// A key frame of absolute joint angles. Limbs listed in the motion's `hide` may be
/// absent entirely; a two-element array is `[upper, lower]` bone angles.
struct MotionKeyFrame: Codable {
@@ -56,8 +128,9 @@ struct MotionKeyFrame: Codable {
let spine: [Double]
/// Head direction from the neck joint.
let neck: Double
/// Nose-tick direction (the belly is on that side).
let gaze: Double
/// Nose-tick direction (the belly is on that side). Absent when the figure
/// faces the viewer no nose is drawn.
let gaze: Double?
let armR: [Double]?
let armL: [Double]?
let legR: [Double]?
+17 -11
View File
@@ -60,7 +60,8 @@ struct FigurePose {
var root: CGPoint
var spine: [Double]
var neck: Double
var gaze: Double
/// Absent for figures facing the viewer no nose tick.
var gaze: Double?
var limbs: [FigureLimb: [Double]]
var pins: [String: CGPoint]
var hold: Double
@@ -89,8 +90,9 @@ struct FigureAttachments {
struct FigureGeometry {
var headCenter: CGPoint
var headRadius: Double
var noseStart: CGPoint
var noseEnd: CGPoint
/// Nil when the pose has no gaze (face-on view).
var noseStart: CGPoint?
var noseEnd: CGPoint?
/// Quadratic Bézier through pelvis mid neck (control = 2·mid (pelvis+neck)/2).
var spineStart: CGPoint
var spineControl: CGPoint
@@ -213,7 +215,7 @@ enum MotionSolver {
y: a.root.y + (b.root.y - a.root.y) * t),
spine: zip(a.spine, b.spine).map { lerpAngle($0, $1, t) },
neck: lerpAngle(a.neck, b.neck, t),
gaze: lerpAngle(a.gaze, b.gaze, t),
gaze: (a.gaze != nil && b.gaze != nil) ? lerpAngle(a.gaze!, b.gaze!, t) : nil,
limbs: limbs, pins: pins, hold: a.hold, tween: a.tween
)
}
@@ -223,12 +225,16 @@ enum MotionSolver {
static func geometry(of pose: FigurePose, body: ExerciseBodyProfile, hide: Set<String>) -> FigureGeometry {
let at = attachments(root: pose.root, spine: pose.spine, body: body)
let head = walk(from: at.neck, angles: [pose.neck], lengths: [body.neck])[1]
let noseTip = walk(from: head, angles: [pose.gaze], lengths: [body.headR + 7])[1]
// Nose tick: 7pt outward from the head rim along the gaze direction.
let d = max(hypot(noseTip.x - head.x, noseTip.y - head.y), 1)
let ux = (noseTip.x - head.x) / d
let uy = (noseTip.y - head.y) / d
let r = body.headR
// Nose tick: 7pt outward from the head rim along the gaze direction
// (omitted for face-on poses without a gaze).
var noseStart: CGPoint?
var noseEnd: CGPoint?
if let gaze = pose.gaze {
let d = direction(gaze)
noseStart = CGPoint(x: head.x + d.dx * r, y: head.y + d.dy * r)
noseEnd = CGPoint(x: head.x + d.dx * (r + 7), y: head.y + d.dy * (r + 7))
}
var limbs: [FigureLimb: [CGPoint]] = [:]
for (limb, authored) in pose.limbs where !hide.contains(limb.rawValue) {
@@ -244,8 +250,8 @@ enum MotionSolver {
return FigureGeometry(
headCenter: head,
headRadius: r,
noseStart: CGPoint(x: head.x + ux * r, y: head.y + uy * r),
noseEnd: CGPoint(x: head.x + ux * (r + 7), y: head.y + uy * (r + 7)),
noseStart: noseStart,
noseEnd: noseEnd,
spineStart: at.pelvis,
spineControl: CGPoint(x: 2 * at.mid.x - (at.pelvis.x + at.neck.x) / 2,
y: 2 * at.mid.y - (at.pelvis.y + at.neck.y) / 2),
@@ -0,0 +1,40 @@
# Abdominal
The seated crunch machine — loaded spinal flexion with the lower back
supported.
- **Category:** Main circuit
- **Type:** Machine-based trunk flexion
- **Targets:** Rectus abdominis, deep core
- **Prescription:** 4 × 10
## Setup
Sit with the pads resting against your chest and your feet flat on the
platform. Grip the handles lightly and set the seat so your hips stay tucked
into the base of the back pad.
## Execution
1. Contract your abs to curl your ribs down toward your pelvis, driving the
chest pad forward.
2. Pause at the bottom of the crunch, feeling the abs fully shortened.
3. Return under control until the torso is tall again — don't let the weight
yank you back up.
## Cues
- Curl the spine, don't just hinge at the hips — think "shorten the front".
- Keep your lower back in contact with the pad throughout.
- Let the abs move the weight; the arms only steady the handles.
## Common Mistakes
- Pulling with the arms or shoulders instead of the abs.
- Hinging from the hips with a flat, rigid spine.
- Rushing the return and bouncing out of the top.
## Progression
Add weight once all four sets of ten are a controlled curl; slow the lowering
phase to three seconds for extra time under tension.
@@ -0,0 +1,34 @@
{
"name": "Abdominal",
"primary": 2,
"working": ["spine"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8},
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8},
{"kind": "line", "pts": [[122, 132], [122, 150]], "w": 4},
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4},
{"kind": "line", "pts": [[156, 149], [190, 149]], "w": 5}
]},
{"type": "pad", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14, "w": 6}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [128, 126],
"spine": [94, 90], "neck": 82, "gaze": -8,
"arm_r": [-12, -52], "arm_l": [-12, -52],
"leg_r": [-20, -90], "leg_l": [-20, -90],
"pins": {"hand_r": [155, 62], "hand_l": [161, 64], "foot_r": [168, 150], "foot_l": [172, 150]}
},
{
"hold": 0.4, "tween": 1.0,
"root": [128, 126],
"spine": [80, 40], "neck": 22, "gaze": -42,
"arm_r": [-12, -52], "arm_l": [-12, -52],
"leg_r": [-20, -90], "leg_l": [-20, -90],
"pins": {"hand_r": [190, 76], "hand_l": [196, 78], "foot_r": [168, 150], "foot_l": [172, 150]}
}
]
}
@@ -0,0 +1,38 @@
# Abductor
The seated hip-abduction machine — presses the knees apart to load the outer
hips and glutes.
- **Category:** Main circuit
- **Type:** Machine-based hip abduction
- **Targets:** Gluteus medius, gluteus minimus, outer thighs
- **Prescription:** 4 × 10
## Setup
Sit with your legs inside the pads, back flat against the seat, and knees
starting close together. Rest your hands on the seat sides and keep your torso
upright.
## Execution
1. Push your knees outward against the pads to spread the legs.
2. Pause at the widest point, squeezing the outer hips and glutes.
3. Bring the legs back together under control — resist the pads on the way in.
## Cues
- Stay upright; leaning forward hands the work to the lower back.
- Drive the knees out through the outer hips, not by rocking the pelvis.
- Control the return so the pads don't slam your knees closed.
## Common Mistakes
- Leaning forward to force the legs wider.
- Letting the pads snap the knees back together.
- Using a bouncy, momentum-driven range instead of steady tension.
## Progression
Add weight in small steps once all four sets of ten are smooth; pause two
seconds at full spread to build end-range strength.
@@ -0,0 +1,34 @@
{
"name": "Abductor",
"primary": 2,
"working": ["leg_r", "leg_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
{"kind": "line", "pts": [[136, 78], [136, 108]], "w": 4, "color": "prop"},
{"kind": "line", "pts": [[188, 78], [188, 108]], "w": 4, "color": "prop"},
{"kind": "line", "pts": [[213, 110], [213, 132]], "w": 9, "color": "prop"},
{"kind": "line", "pts": [[107, 110], [107, 132]], "w": 9, "color": "prop"}
]}
],
"frames": [
{
"hold": 0.5, "tween": 1.1,
"root": [160, 116],
"spine": [90, 90], "neck": 90,
"arm_r": [-48, -82], "arm_l": [-132, -98],
"leg_r": [-22, 0], "leg_l": [-158, 0],
"pins": {"foot_r": [180, 156], "foot_l": [140, 156]}
},
{
"hold": 0.5, "tween": 1.1,
"root": [160, 116],
"spine": [90, 90], "neck": 90,
"arm_r": [-48, -82], "arm_l": [-132, -98],
"leg_r": [-22, 0], "leg_l": [-158, 0],
"pins": {"foot_r": [216, 156], "foot_l": [104, 156]}
}
]
}
@@ -0,0 +1,39 @@
# Adductor
The seated hip-adduction machine — squeezes the knees together to load the
inner thighs.
- **Category:** Main circuit
- **Type:** Machine-based hip adduction
- **Targets:** Adductors, inner thighs
- **Prescription:** 4 × 10
## Setup
Sit with your legs placed outside the pads, back flat against the seat, and
knees starting spread apart. Rest your hands on the seat sides and keep your
torso upright.
## Execution
1. Squeeze your knees together against the pads using your inner thighs.
2. Pause when the knees nearly touch, holding the contraction.
3. Let the legs spread back open under control — don't let the pads fling them
apart.
## Cues
- Drive the movement from the inner thighs, not by rocking the hips.
- Keep the torso upright and the back against the seat.
- Control both directions; the opening phase trains the muscle too.
## Common Mistakes
- Letting the pads throw the legs open at the start of each rep.
- Setting too wide a start range before the muscle is warm.
- Fast, swinging reps that rely on momentum instead of the adductors.
## Progression
Add weight once all four sets of ten are controlled in both directions; pause
two seconds with the knees together to strengthen the fully-shortened position.
@@ -0,0 +1,34 @@
{
"name": "Adductor",
"primary": 2,
"working": ["leg_r", "leg_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
{"kind": "line", "pts": [[136, 78], [136, 108]], "w": 4, "color": "prop"},
{"kind": "line", "pts": [[188, 78], [188, 108]], "w": 4, "color": "prop"},
{"kind": "line", "pts": [[197, 110], [197, 132]], "w": 9, "color": "prop"},
{"kind": "line", "pts": [[123, 110], [123, 132]], "w": 9, "color": "prop"}
]}
],
"frames": [
{
"hold": 0.5, "tween": 1.1,
"root": [160, 116],
"spine": [90, 90], "neck": 90,
"arm_r": [-48, -82], "arm_l": [-132, -98],
"leg_r": [-22, 0], "leg_l": [-158, 0],
"pins": {"foot_r": [216, 156], "foot_l": [104, 156]}
},
{
"hold": 0.5, "tween": 1.1,
"root": [160, 116],
"spine": [90, 90], "neck": 90,
"arm_r": [-48, -82], "arm_l": [-132, -98],
"leg_r": [-22, 0], "leg_l": [-158, 0],
"pins": {"foot_r": [180, 156], "foot_l": [140, 156]}
}
]
}
@@ -0,0 +1,37 @@
# Arm Curl
The seated machine curl — biceps isolation with the upper arms braced flat on
the pad so only the forearms move.
- **Category:** Main circuit
- **Type:** Machine-based elbow flexion (isolation)
- **Targets:** Biceps brachii, brachialis
- **Prescription:** 4 × 10
## Setup
Sit with your upper arms flat along the pad and grip the handles. Start with
your forearms extended down along the pad, elbows just short of locked.
## Execution
1. Curl the handles up toward your shoulders, squeezing the biceps at the top.
2. Keep your upper arms pinned to the pad — only the forearms move.
3. Lower under control until the forearms are fully extended again.
## Cues
- Upper arms stay glued to the pad the entire rep.
- Squeeze hard at the top, then resist the weight all the way down.
- Wrists stay neutral — don't curl with the hands.
## Common Mistakes
- Rocking the torso or lifting the elbows to swing the weight up.
- Cutting the lowering phase short and dropping the weight.
- Not fully extending at the bottom, shortening the range.
## Progression
Add weight once all four sets of ten are strict; slow the lowering phase to
three seconds before adding load again.
@@ -0,0 +1,32 @@
{
"name": "Arm Curl",
"primary": 2,
"working": ["arm_r", "arm_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8},
{"kind": "line", "pts": [[146, 42], [176, 68]], "w": 8},
{"kind": "line", "pts": [[176, 68], [176, 128]], "w": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [130, 124],
"spine": [88, 86], "neck": 82, "gaze": -18,
"arm_r": [-35, -52], "arm_l": [-35, -52],
"leg_r": [-8, -72], "leg_l": [-8, -72],
"pins": {"foot_r": [176, 150], "foot_l": [180, 151]}
},
{
"hold": 0.4, "tween": 1.2,
"root": [130, 124],
"spine": [88, 86], "neck": 82, "gaze": -18,
"arm_r": [-35, 138], "arm_l": [-35, 138],
"leg_r": [-8, -72], "leg_l": [-8, -72],
"pins": {"foot_r": [176, 150], "foot_l": [180, 151]}
}
]
}
@@ -0,0 +1,38 @@
# Bird Dog
Anti-rotation drill — extend opposite limbs while the hips and spine refuse to
twist.
- **Category:** Warm-up and main circuit
- **Type:** Anti-rotation
- **Targets:** Spinal erectors, glutes, deep core, shoulder stability
- **Prescription:** Warm-up 8 per side; main circuit 3 × 810 per side, slow
## Setup
On all fours: hands under shoulders, knees under hips, spine neutral, gaze at
the floor.
## Execution
1. Brace, then reach one arm forward and the *opposite* leg back until both are
in line with the torso.
2. Pause for a beat at full extension — hips stay square to the floor.
3. Return under control and switch sides.
## Cues
- Imagine a cup of water balanced on the lower back — nothing spills.
- Reach *long*, not high: heel pushes back, knuckles push forward.
- Slow, no momentum; the pause is where the work happens.
## Common Mistakes
- Hips rotating open as the leg lifts.
- Lifting the leg above hip height and arching the lower back.
- Racing through reps.
## Progression
Single limb only (arm *or* leg) → opposite arm + leg → paused 3-count hold →
elbow-to-knee touch under the body between reps → hands on an unstable surface
@@ -0,0 +1,40 @@
# Calfs
Seated heel raises through a full range — a deep stretch below the platform and
a hard squeeze at the top.
- **Category:** Main circuit
- **Type:** Machine-based calf raise
- **Targets:** Soleus, gastrocnemius
- **Prescription:** 4 × 10
## Setup
Sit with the balls of your feet on the platform, heels hanging off the back
edge, and the pad snug on your lower thighs just above the knees. Hold the pad
handles lightly.
## Execution
1. Raise your heels by contracting your calves, pressing up onto the balls of
your feet against the pad.
2. Pause at the top with the calves fully shortened.
3. Slowly lower your heels below the platform level for a full stretch.
## Cues
- Chase the full range — a hard squeeze up top, a deep stretch at the bottom.
- Drive through the balls of the feet, not the toes.
- Control the tempo; the stretch under the platform is where the work is.
## Common Mistakes
- Bouncing through short, choppy reps instead of the full range.
- Rocking the weight up with the hips instead of the calves.
- Rushing the lowering and skipping the bottom stretch.
## Progression
Add weight in small increments once all four sets of ten are controlled;
single-leg raises and paused reps at the stretched bottom build the hardest end
of the range.
@@ -0,0 +1,36 @@
{
"name": "Calfs",
"primary": 2,
"working": ["leg_r", "leg_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[124, 126], [158, 124]], "w": 9},
{"kind": "line", "pts": [[138, 128], [138, 150]], "w": 5},
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5},
{"kind": "rect", "x": 198, "y": 147, "w": 34, "h": 6, "r": 1},
{"kind": "line", "pts": [[183, 62], [183, 124]], "w": 4},
{"kind": "rect", "x": 170, "y": 121, "w": 27, "h": 7, "r": 2},
{"kind": "line", "pts": [[164, 76], [183, 76]], "w": 3, "color": "prop"}
]},
{"type": "pad", "at": ["foot_r", "foot_l"], "angle": 15, "halfLen": 7, "w": 5}
],
"frames": [
{
"hold": 0.4, "tween": 0.9,
"root": [140, 118],
"spine": [100, 96], "neck": 80, "gaze": -10,
"arm_r": [-40, -15], "arm_l": [-40, -15],
"leg_r": [-18, -8], "leg_l": [-18, -8],
"pins": {"hand_r": [166, 74], "hand_l": [170, 76], "foot_r": [212, 148], "foot_l": [216, 150]}
},
{
"hold": 0.4, "tween": 1.2,
"root": [140, 118],
"spine": [100, 96], "neck": 80, "gaze": -10,
"arm_r": [-40, -15], "arm_l": [-40, -15],
"leg_r": [-14, 4], "leg_l": [-14, 4],
"pins": {"hand_r": [166, 74], "hand_l": [170, 76], "foot_r": [212, 139], "foot_l": [216, 141]}
}
]
}
@@ -0,0 +1,37 @@
# Cat-Cow
Gentle spinal mobility drill that wakes up the whole trunk before core work.
- **Category:** Warm-up
- **Type:** Spinal mobility
- **Targets:** Thoracic and lumbar spine, deep core awareness
- **Prescription:** 10 slow reps (one rep = cat + cow)
## Setup
On all fours: hands under shoulders, knees under hips, spine neutral, gaze at
the floor.
## Execution
1. **Cat** — exhale, press the floor away, round the whole spine up toward the
ceiling and tuck chin and tailbone.
2. **Cow** — inhale, let the belly drop, lift the chest and tailbone, gaze
slightly forward.
3. Flow between the two, one breath per position.
## Cues
- Move segment by segment, not as one stiff plank.
- Let the breath set the tempo — no rushing.
- Reach the mid-back toward the ceiling in cat; most people only move the neck.
## Common Mistakes
- All the motion coming from the neck and hips, none from the mid-back.
- Collapsing into the shoulders instead of pressing the floor away.
## Progression
Smaller pain-free range → full range → slower tempo with a pause at each end →
segmental rolls (peel one vertebra at a time)
@@ -0,0 +1,38 @@
# Chest Press
The seated horizontal press — the machine bench press, pushing the handles
forward from the chest.
- **Category:** Main circuit
- **Type:** Machine-based horizontal press
- **Targets:** Pectorals, anterior deltoids, triceps
## Setup
Adjust the seat so the handles sit at mid-chest height. Grip the handles, set
your back against the pad, and keep your wrists straight and stacked over the
forearms.
## Execution
1. Push the handles forward until your arms are nearly extended — stop short of
locking the elbows.
2. Pause briefly, then return slowly until the handles are back at your chest.
3. Keep your shoulder blades gently pinned to the pad throughout.
## Cues
- Drive through the palms; wrists stay straight, not bent back.
- Don't let the elbows drop far below the handles at the bottom.
- Return under control — resist the weight rather than letting it pull you back.
## Common Mistakes
- Letting the elbows sink too low, straining the shoulders.
- Bouncing the handles at the chest to start the next rep.
- Locking the elbows hard at full extension.
## Progression
Add weight in small plates once all four sets of ten are controlled; alternate
to a single-arm press to expose and correct side-to-side differences.
@@ -0,0 +1,33 @@
{
"name": "Chest Press",
"primary": 2,
"working": ["arm_r", "arm_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8},
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8},
{"kind": "line", "pts": [[122, 135], [122, 151]], "w": 5},
{"kind": "line", "pts": [[108, 151], [138, 151]], "w": 4}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 8}
],
"frames": [
{
"hold": 0.4, "tween": 1.1,
"root": [116, 122],
"spine": [104, 100], "neck": 84, "gaze": 5,
"arm_r": [-100, 40], "arm_l": [-100, 40],
"leg_r": [-22, -72], "leg_l": [-22, -72],
"pins": {"foot_r": [158, 151], "foot_l": [162, 152]}
},
{
"hold": 0.4, "tween": 1.1,
"root": [116, 122],
"spine": [104, 100], "neck": 84, "gaze": 5,
"arm_r": [-6, -14], "arm_l": [-6, -14],
"leg_r": [-22, -72], "leg_l": [-22, -72],
"pins": {"foot_r": [158, 151], "foot_l": [162, 152]}
}
]
}
@@ -0,0 +1,37 @@
# Dead Bug
Anti-extension drill — the limbs move while the trunk stays perfectly still.
- **Category:** Warm-up
- **Type:** Anti-extension
- **Targets:** Deep core (transverse abdominis), hip flexor control
- **Prescription:** 8 per side, slow and controlled
## Setup
Lie on your back. Arms straight up over the shoulders, hips and knees bent to
90°, lower back pressed into the floor.
## Execution
1. Brace and exhale as you lower one arm overhead and the *opposite* leg toward
the floor.
2. Stop just before the lower back peels off the floor.
3. Return under control and switch sides.
## Cues
- Lower back glued to the floor the entire time — that contact *is* the exercise.
- Slow negatives; the reach out should take 23 seconds.
- Ribs down, no flaring as the arm goes overhead.
## Common Mistakes
- Arching the lower back as the leg lowers.
- Moving arm and leg on the same side.
- Rushing reps and turning it into a flutter kick.
## Progression
Heels sliding on floor → bent-knee reach → straight-leg reach → both legs
lowering together → light weight held overhead
@@ -0,0 +1,37 @@
# Hollow Body Hold
The gold standard for deep core tension — a shallow, rigid banana shape.
- **Category:** Main circuit
- **Type:** Anti-extension (isometric hold)
- **Targets:** Rectus abdominis, deep core, hip flexors
- **Prescription:** 3 × 2040 s holds
## Setup
Lie on your back, arms extended overhead beside the ears, legs straight and
together.
## Execution
1. Press the lower back *into* the floor and hold it there — this never releases.
2. Lift shoulder blades and legs a few inches off the floor into a shallow
banana shape.
3. Reach long through fingers and toes and hold.
## Cues
- The lower back stays welded to the floor; if it lifts, shorten the shape.
- Chin gently tucked — look at your thighs, not the ceiling.
- Lower = harder. Height is not the goal, tension is.
## Common Mistakes
- Lower back arching off the floor (the hold has failed at that point).
- Folding at the hips into a V-sit.
- Neck cranked forward doing all the "work".
## Progression
Tuck hold (knees in) → one leg extended → full extension, arms overhead →
hollow rocks
@@ -0,0 +1,37 @@
# Lat Pull Down
The seated vertical pull — building width through the lats with a bar driven
down from overhead.
- **Category:** Main circuit
- **Type:** Machine-based vertical pull
- **Targets:** Latissimus dorsi, mid-back, rear delts, biceps
- **Prescription:** 4 × 10
## Setup
Sit upright with your knees secured under the pad and feet flat. Grip the bar
wider than shoulder-width, arms fully extended overhead, torso just off vertical.
## Execution
1. Pull the bar down to your upper chest, driving your elbows down and back.
2. Squeeze your shoulder blades together at the bottom and pause briefly.
3. Return the bar overhead under control until your arms are fully extended.
## Cues
- Lead with the elbows, not the hands — think "elbows to the ribs".
- Squeeze the shoulder blades down and together at the bottom.
- Keep the torso tall; only a slight lean back, never a swing.
## Common Mistakes
- Leaning back excessively and turning it into a row.
- Using momentum to yank the bar down.
- Stopping short — not pulling the bar all the way to the chest.
## Progression
Add weight once all four sets of ten are clean; progress to a wider or neutral
grip, then to strict pull-ups as pulling strength builds.
@@ -0,0 +1,36 @@
{
"name": "Lat Pull Down",
"primary": 2,
"working": ["arm_r", "arm_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[126, 130], [162, 130]], "w": 8},
{"kind": "line", "pts": [[124, 130], [124, 56]], "w": 5},
{"kind": "line", "pts": [[124, 56], [198, 12]], "w": 5},
{"kind": "rect", "x": 170, "y": 112, "w": 40, "h": 7, "r": 3}
]},
{"type": "cable", "from": [198, 8], "to": ["hand_r", "hand_l"]},
{"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 28}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [150, 122],
"spine": [93, 90], "neck": 85, "gaze": 55,
"arm_r": [20, 10], "arm_l": [20, 10],
"leg_r": [-12, -80], "leg_l": [-12, -80],
"pins": {"hand_r": [198, 24], "hand_l": [202, 26],
"foot_r": [193, 150], "foot_l": [197, 151]}
},
{
"hold": 0.4, "tween": 1.2,
"root": [150, 122],
"spine": [93, 90], "neck": 85, "gaze": 30,
"arm_r": [-115, -20], "arm_l": [-115, -20],
"leg_r": [-12, -80], "leg_l": [-12, -80],
"pins": {"hand_r": [165, 66], "hand_l": [169, 68],
"foot_r": [193, 150], "foot_l": [197, 151]}
}
]
}
@@ -0,0 +1,37 @@
# Leg Curl
Isolated hamstring work — the heels curl toward the glutes while a pad locks
the thighs in place.
- **Category:** Main circuit
- **Type:** Machine-based isolation (knee flexion)
- **Targets:** Hamstrings (biceps femoris, semitendinosus, semimembranosus)
- **Prescription:** 4 × 10
## Setup
Sit (or lie face down, depending on the version) with the thigh pad locked over
your legs and the roller behind your ankles. Start with the legs extended.
## Execution
1. Curl your legs toward your glutes, driving through the hamstrings.
2. Squeeze hard at the fully bent position.
3. Return the roller under control until the legs are extended again.
## Cues
- Focus on hamstring engagement — feel the pull behind the knee, not the back.
- Keep your hips and back still against the pad; no arching to cheat.
- Curl through the full range, all the way to the glutes.
## Common Mistakes
- Arching the lower back to yank the weight up.
- Using momentum instead of a controlled curl.
- Cutting the range short and never reaching full flexion.
## Progression
Add load once all four sets of ten are controlled; single-leg curls for
asymmetry, or a slow eccentric to overload the lengthening hamstring.
@@ -0,0 +1,34 @@
{
"name": "Leg Curl",
"primary": 2,
"working": ["leg_r", "leg_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7},
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
{"kind": "line", "pts": [[160, 124], [160, 150]], "w": 5},
{"kind": "line", "pts": [[104, 150], [166, 150]], "w": 5},
{"kind": "circle", "c": [114, 90], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "pad", "at": ["foot_r", "foot_l"], "halfLen": 9}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [118, 116],
"spine": [110, 106], "neck": 74, "gaze": -20,
"arm_r": [-75, -60], "arm_l": [-75, -60],
"leg_r": [10, 4], "leg_l": [10, 4]
},
{
"hold": 0.4, "tween": 1.2,
"root": [118, 116],
"spine": [110, 106], "neck": 74, "gaze": -20,
"arm_r": [-75, -60], "arm_l": [-75, -60],
"leg_r": [10, -95], "leg_l": [10, -95]
}
]
}
@@ -0,0 +1,40 @@
# Leg Extension
Isolated quadriceps work — the knee opens against the roller while the hips
stay pinned to the seat.
- **Category:** Main circuit
- **Type:** Machine-based isolation (knee extension)
- **Targets:** Quadriceps (rectus femoris, vastus lateralis/medialis)
- **Prescription:** 4 × 10
## Setup
Sit upright with your back flat against the pad and align your knees with the
machine's pivot point. Hook the roller pad against the front of your ankles,
hands resting on the side grips.
## Execution
1. Extend your legs to a straightened position — stop just short of snapping
the knees into lockout.
2. Pause briefly at the top with the quads squeezed hard.
3. Lower the roller back down with control to the start.
## Cues
- Knees stack over the pivot — realign the seat if they sit forward or back.
- Hips glued to the seat; if they lift, the weight is too heavy.
- Own the lowering — no free-falling back to the stack.
## Common Mistakes
- Jerky, momentum-driven reps instead of a smooth extension.
- Lifting the hips off the seat to swing the weight up.
- Slamming into a locked knee at the top.
## Progression
Add load in small increments once all four sets of ten are smooth; single-leg
extensions for asymmetry, or a paused top / slow eccentric for more time under
tension.
@@ -0,0 +1,33 @@
{
"name": "Leg Extension",
"primary": 2,
"working": ["leg_r", "leg_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
{"kind": "line", "pts": [[160, 124], [160, 150]], "w": 5},
{"kind": "line", "pts": [[104, 150], [166, 150]], "w": 5},
{"kind": "circle", "c": [114, 90], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "pad", "at": ["foot_r", "foot_l"], "halfLen": 9}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [118, 116],
"spine": [110, 106], "neck": 74, "gaze": -20,
"arm_r": [-75, -60], "arm_l": [-75, -60],
"leg_r": [10, -83], "leg_l": [10, -83]
},
{
"hold": 0.4, "tween": 1.2,
"root": [118, 116],
"spine": [110, 106], "neck": 74, "gaze": -20,
"arm_r": [-75, -60], "arm_l": [-75, -60],
"leg_r": [10, 4], "leg_l": [10, 4]
}
]
}
@@ -0,0 +1,39 @@
# Leg Press
The seated machine squat — maximum lower-body load with the back fully
supported.
- **Category:** Main circuit
- **Type:** Machine-based compound push
- **Targets:** Quadriceps, glutes, hamstrings
- **Prescription:** 4 × 10
## Setup
Sit with your back and hips flat against the pad. Place your feet
shoulder-width on the platform, heels never floating. Adjust the seat so your
knees start near 90°.
## Execution
1. Press through your heels until your legs are nearly straight — stop short
of locking the knees.
2. Pause briefly, then bend the knees to return under control.
3. Keep your back and hips glued to the pad the whole rep.
## Cues
- Drive through the heels, not the toes.
- Knees track over the feet — don't let them cave inward.
- Lower back stays on the pad; if the hips curl up, you've gone too deep.
## Common Mistakes
- Locking the knees hard at the top.
- Bouncing out of the bottom.
- Letting the hips roll off the pad at the bottom of the rep.
## Progression
Add weight in small plates once all four sets of ten are controlled; single-leg
presses for asymmetry work.
@@ -0,0 +1,35 @@
{
"name": "Leg Press",
"primary": 2,
"working": ["leg_r", "leg_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9},
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9},
{"kind": "line", "pts": [[138, 130], [138, 150]], "w": 5},
{"kind": "line", "pts": [[118, 150], [158, 150]], "w": 5},
{"kind": "line", "pts": [[142, 90], [142, 78]], "w": 3},
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "pad", "at": ["foot_r", "foot_l"], "angle": 88, "halfLen": 20, "w": 6}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [140, 116],
"spine": [116, 112], "neck": 75, "gaze": -15,
"arm_r": [-75, -20], "arm_l": [-75, -20],
"leg_r": [40, -58], "leg_l": [40, -58],
"pins": {"foot_r": [185, 102], "foot_l": [189, 104]}
},
{
"hold": 0.4, "tween": 1.2,
"root": [140, 116],
"spine": [116, 112], "neck": 75, "gaze": -15,
"arm_r": [-75, -20], "arm_l": [-75, -20],
"leg_r": [20, -5], "leg_l": [20, -5],
"pins": {"foot_r": [220, 101], "foot_l": [224, 103]}
}
]
}
@@ -0,0 +1,37 @@
# Leg Raises
Lower-ab builder — the slow lowering is the exercise.
- **Category:** Main circuit
- **Type:** Anti-extension (dynamic)
- **Targets:** Lower rectus abdominis, hip flexors, deep core
- **Prescription:** 3 × 815 reps, slow negatives
## Setup
**Lying:** flat on your back, legs straight, hands at your sides (or under the
tailbone to start). **Hanging:** dead hang from a bar, shoulders packed.
## Execution
1. Press the lower back into the floor and brace.
2. Raise the legs to vertical without bending the knees more than slightly.
3. Lower *slowly* — 3 seconds or more — stopping before the lower back arches
off the floor.
## Cues
- Lower back flat against the floor on lying versions, always.
- The negative is the rep: lower slower than you lift.
- Exhale on the way down to keep the ribs from flaring.
## Common Mistakes
- Lower back arching off the floor near the bottom.
- Dropping the legs instead of lowering them.
- Using momentum (swinging) on hanging versions.
## Progression
Bent-knee lying raise → straight-leg lying raise → hanging knee raise →
hanging straight-leg raise → toes-to-bar
@@ -0,0 +1,36 @@
# Plank
The baseline anti-extension hold — a rigid line from head to heels.
- **Category:** Main circuit
- **Type:** Anti-extension (isometric hold)
- **Targets:** Rectus abdominis, deep core, glutes, shoulders
- **Prescription:** 3 × 3060 s holds
## Setup
Forearms on the floor, elbows under shoulders, feet together behind you. One
straight line from the back of the head to the heels.
## Execution
1. Squeeze glutes and quads hard, tuck ribs down toward the pelvis.
2. Press the floor away through the forearms — no sagging between the shoulders.
3. Breathe shallowly behind the brace and hold.
## Cues
- Glutes and quads *tight* — a plank fails at the hips first.
- Ribs down: think "short front, long back".
- If the hips sag or the lower back takes over, the set is done.
## Common Mistakes
- Hips sagging (lower back extension — the exact thing to resist).
- Hips piked high to make the hold easier.
- Holding the breath entirely.
## Progression
Knee plank → full plank → feet elevated → RKC plank (maximum-tension, shorter
holds) → weighted plank
@@ -0,0 +1,38 @@
# Reverse Crunch
Curl the pelvis toward the ribs — lower-ab flexion without neck strain.
- **Category:** Main circuit
- **Type:** Spinal flexion (lower)
- **Targets:** Lower rectus abdominis, deep core
- **Prescription:** 3 × 1215 reps, controlled negative
## Setup
Lie on your back, knees bent toward the chest, shins roughly parallel to the
floor, arms along your sides pressing lightly into the floor.
## Execution
1. Exhale and curl the pelvis up and toward the ribs, lifting the hips off the
floor — knees travel toward the chin.
2. Pause briefly at the top.
3. Lower the hips back down slowly, one vertebra at a time.
## Cues
- Curl, don't kick: the knees drifting up should come from the pelvis tilting,
not from leg swing.
- Controlled negative — resist gravity all the way down.
- Keep the head and shoulders relaxed on the floor.
## Common Mistakes
- Swinging the legs and lifting with momentum.
- Pushing hard through the arms instead of the abs.
- Letting the hips drop back down in one piece.
## Progression
Feet-elevated pelvic tilts → reverse crunch → slow 3-count negatives →
incline (head-up) reverse crunch → dragonflag negatives
@@ -0,0 +1,39 @@
# Rotary
The rotary torso machine — controlled trunk rotation that isolates the
obliques.
- **Category:** Main circuit
- **Type:** Machine-based trunk rotation
- **Targets:** Obliques, deep core
- **Prescription:** 4 × 10
## Setup
Sit tall with your hips squared and locked against the pads. Rest your
forearms on the pads and grip the handle in front of your chest so the trunk,
not the arms, drives the machine.
## Execution
1. Rotate your torso to one side in a smooth, controlled arc, keeping your hips
still.
2. Pause at the end of the range without letting the weight pull you further.
3. Return through center and rotate to the other side with the same control.
## Cues
- Twist from the waist — the obliques generate the movement, not the arms.
- Keep the hips and pelvis pinned; only the trunk turns.
- Move slowly; momentum steals the work from the obliques.
## Common Mistakes
- Throwing the weight with momentum instead of controlling the twist.
- Pushing with the arms rather than rotating the trunk.
- Letting the hips swing along with the torso.
## Progression
Add weight once both sides move with equal control; pause two seconds at full
rotation to kill any momentum.
@@ -0,0 +1,32 @@
{
"name": "Rotary",
"primary": 1,
"working": ["spine"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[138, 130], [182, 130]], "w": 8},
{"kind": "line", "pts": [[160, 132], [160, 150]], "w": 5},
{"kind": "line", "pts": [[144, 150], [176, 150]], "w": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [160, 122],
"spine": [104, 109], "neck": 109,
"arm_r": [-50, -85], "arm_l": [-55, -90],
"leg_r": [-62, -82], "leg_l": [-118, -98],
"pins": {"hand_r": [112, 72], "hand_l": [118, 74], "foot_r": [190, 150], "foot_l": [130, 150]}
},
{
"hold": 0.4, "tween": 1.0,
"root": [160, 122],
"spine": [76, 71], "neck": 71,
"arm_r": [-125, -95], "arm_l": [-130, -90],
"leg_r": [-62, -82], "leg_l": [-118, -98],
"pins": {"hand_r": [208, 72], "hand_l": [202, 74], "foot_r": [190, 150], "foot_l": [130, 150]}
}
]
}
@@ -0,0 +1,37 @@
# Seated Row
The seated horizontal pull — thickness for the mid-back by driving the elbows
straight back against the chest pad.
- **Category:** Main circuit
- **Type:** Machine-based horizontal pull
- **Targets:** Mid-back, latissimus dorsi, rear delts, biceps
- **Prescription:** 4 × 10
## Setup
Sit with your chest firmly against the pad and feet on the footrest. Grip the
handles with your arms extended forward toward the anchor.
## Execution
1. Pull straight back, keeping your elbows close to your body.
2. Drive the handles to your ribs and retract your shoulder blades hard.
3. Return to full extension under control without rounding your back.
## Cues
- Retract the shoulder blades first, then the arms follow.
- Elbows stay tucked, tracing a line along your sides.
- Chest stays glued to the pad the whole rep.
## Common Mistakes
- Rounding the back to reach forward at the extension.
- Flaring the elbows out wide instead of driving them back.
- Heaving with the whole torso instead of pulling with the back.
## Progression
Add weight once all four sets of ten are controlled; vary the grip width to
shift emphasis between the lats and the mid-back.
@@ -0,0 +1,35 @@
{
"name": "Seated Row",
"primary": 2,
"working": ["arm_r", "arm_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8},
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8},
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5}
]},
{"type": "cable", "from": [244, 120], "to": ["hand_r", "hand_l"]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 7}
],
"frames": [
{
"hold": 0.4, "tween": 1.0,
"root": [140, 122],
"spine": [85, 82], "neck": 80, "gaze": 5,
"arm_r": [-10, -25], "arm_l": [-10, -25],
"leg_r": [-8, -75], "leg_l": [-8, -75],
"pins": {"hand_r": [200, 62], "hand_l": [204, 64],
"foot_r": [188, 148], "foot_l": [192, 149]}
},
{
"hold": 0.4, "tween": 1.2,
"root": [140, 122],
"spine": [85, 82], "neck": 80, "gaze": 5,
"arm_r": [-120, -35], "arm_l": [-120, -35],
"leg_r": [-8, -75], "leg_l": [-8, -75],
"pins": {"hand_r": [158, 72], "hand_l": [162, 74],
"foot_r": [188, 148], "foot_l": [192, 149]}
}
]
}
@@ -0,0 +1,39 @@
# Shoulder Press
The seated overhead press — vertical pushing strength with the trunk braced
against the pad.
- **Category:** Main circuit
- **Type:** Machine-based vertical press
- **Targets:** Deltoids, triceps, upper trapezius
## Setup
Sit with your back against the pad and grip the handles just outside
shoulder-width, elbows bent so the handles start at shoulder height. Plant your
feet flat and set your ribs down over your hips.
## Execution
1. Press the handles straight up until your arms are nearly extended — stop
short of locking out the elbows.
2. Pause at the top without shrugging, then lower under control back to shoulder
height.
3. Keep your neck relaxed and your back flat on the pad the whole rep.
## Cues
- Press up, not forward — the handles travel over the shoulders.
- Keep the ribs down; don't arch the lower back to help the press.
- Shoulders stay set — avoid shrugging the handles up with the traps.
## Common Mistakes
- Locking the elbows hard at the top.
- Shrugging the shoulders to finish the press.
- Arching the lower back off the pad as the weight gets heavy.
## Progression
Add weight in small increments once all four sets of ten are smooth; move to a
single-arm press for even loading and extra core demand.
@@ -0,0 +1,33 @@
{
"name": "Shoulder Press",
"primary": 2,
"working": ["arm_r", "arm_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8},
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8},
{"kind": "line", "pts": [[126, 143], [126, 151]], "w": 5},
{"kind": "line", "pts": [[110, 151], [142, 151]], "w": 4}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 10}
],
"frames": [
{
"hold": 0.4, "tween": 1.1,
"root": [120, 130],
"spine": [110, 104], "neck": 80, "gaze": 8,
"arm_r": [-60, 90], "arm_l": [-60, 90],
"leg_r": [-25, -72], "leg_l": [-25, -72],
"pins": {"foot_r": [164, 151], "foot_l": [168, 152]}
},
{
"hold": 0.4, "tween": 1.1,
"root": [120, 130],
"spine": [110, 104], "neck": 80, "gaze": 8,
"arm_r": [54, 36], "arm_l": [54, 36],
"leg_r": [-25, -72], "leg_l": [-25, -72],
"pins": {"foot_r": [164, 151], "foot_l": [168, 152]}
}
]
}
@@ -0,0 +1,36 @@
# Side Plank
Anti-lateral-flexion hold — resist the hips dropping toward the floor.
- **Category:** Main circuit
- **Type:** Anti-lateral flexion (isometric hold)
- **Targets:** Obliques, quadratus lumborum, glute medius, shoulder stability
- **Prescription:** 3 × 2045 s per side
## Setup
Lie on one side, elbow directly under the shoulder, forearm across the floor.
Feet stacked (or staggered), body in one straight line.
## Execution
1. Drive the hips up until shoulders, hips, and ankles form a straight line.
2. Stack the hips vertically — don't let the top hip roll forward or back.
3. Hold, then switch sides.
## Cues
- Hips *high*: most people sag an inch without noticing.
- Push the floor away through the forearm — no collapsing into the shoulder.
- Squeeze the bottom-side obliques and the glutes the whole hold.
## Common Mistakes
- Hips dropping toward the floor (the exact motion to resist).
- Rolling the chest toward the ceiling or the floor.
- Elbow drifting away from under the shoulder.
## Progression
Knees-down side plank → full side plank → top leg raised → reach-through
(thread the top arm under the body and back up)
@@ -0,0 +1,39 @@
# Tricep Press
The seated press-down — elbows pinned to the sides, driving the handles from the
chest to full extension.
- **Category:** Main circuit
- **Type:** Machine-based elbow extension
- **Targets:** Triceps brachii
## Setup
Sit tall with your back on the pad and your upper arms tucked close to your
sides. Take the handles at chest height with your elbows bent to about a right
angle.
## Execution
1. Press the handles down by straightening your elbows, keeping your upper arms
still against your sides.
2. Extend fully without snapping the elbows, pausing briefly at the bottom.
3. Let the handles rise back to chest height under control, elbows staying
tucked.
## Cues
- Only the forearms move — the upper arms stay glued to the torso.
- Keep the motion controlled; don't fling the handles down.
- Squeeze the triceps at the bottom rather than leaning your weight in.
## Common Mistakes
- Flaring the elbows out away from the sides.
- Using the shoulders or bodyweight to drive the handles down.
- Cutting the extension short and never reaching full lockout.
## Progression
Add weight in small increments once all four sets of ten are clean; slow the
lowering phase to three seconds to add time under tension for the triceps.
@@ -0,0 +1,33 @@
{
"name": "Tricep Press",
"primary": 2,
"working": ["arm_r", "arm_l"],
"hide": [],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8},
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8},
{"kind": "line", "pts": [[124, 137], [124, 151]], "w": 5},
{"kind": "line", "pts": [[110, 151], [140, 151]], "w": 4}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
],
"frames": [
{
"hold": 0.4, "tween": 1.1,
"root": [118, 124],
"spine": [100, 98], "neck": 86, "gaze": 6,
"arm_r": [-78, 30], "arm_l": [-78, 30],
"leg_r": [-24, -74], "leg_l": [-24, -74],
"pins": {"foot_r": [156, 151], "foot_l": [160, 152]}
},
{
"hold": 0.4, "tween": 1.1,
"root": [118, 124],
"spine": [100, 98], "neck": 86, "gaze": 6,
"arm_r": [-78, -88], "arm_l": [-78, -88],
"leg_r": [-24, -74], "leg_l": [-24, -74],
"pins": {"foot_r": [156, 151], "foot_l": [160, 152]}
}
]
}
@@ -0,0 +1,183 @@
//
// ExerciseLibraryView.swift
// Workouts
//
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
/// The Exercises library screen, pushed from Settings Library: every exercise with
/// a bundled motion rig (the same catalog the picker offers), each opening its
/// reference page.
struct ExerciseLibraryView: View {
var body: some View {
List(ExerciseMotionLibrary.exerciseNames, id: \.self) { name in
NavigationLink(name) {
ExerciseLibraryDetailView(exerciseName: name)
}
}
.navigationTitle("Exercises")
}
}
/// One library exercise, in the standard half-and-half exercise layout: the authored
/// reference content (summary, targets, setup/execution/cues) scrolls in the top
/// half, the looping form-guide figure holds the bottom. Falls back to a full-screen
/// figure when no `info.md` is bundled.
struct ExerciseLibraryDetailView: View {
let exerciseName: String
private var info: ExerciseInfo? {
ExerciseInfoLibrary.info(for: exerciseName)
}
var body: some View {
VStack(spacing: 0) {
if let info {
ExerciseInfoContent(info: info)
}
ExerciseFigureSlot(exerciseName: exerciseName)
}
.navigationTitle(exerciseName)
.navigationBarTitleDisplayMode(.inline)
}
}
/// Renders a parsed `ExerciseInfo`: summary, target chips, then each authored
/// section with its numbered steps or bullets.
private struct ExerciseInfoContent: View {
let info: ExerciseInfo
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
if !info.summary.isEmpty {
Text(info.summary)
.font(.callout)
.foregroundStyle(.secondary)
}
if !info.targets.isEmpty {
TargetChips(targets: info.targets)
}
ForEach(info.sections, id: \.title) { section in
VStack(alignment: .leading, spacing: 6) {
Text(section.title)
.font(.headline)
ForEach(Array(section.items.enumerated()), id: \.offset) { index, item in
itemRow(item, ordinal: ordinal(for: item, at: index, in: section))
}
}
}
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
/// Steps number themselves by their position among the section's steps, so an
/// interleaved paragraph doesn't break the count.
private func ordinal(for item: ExerciseInfo.Item, at index: Int, in section: ExerciseInfo.Section) -> Int? {
guard case .step = item else { return nil }
return section.items[...index].reduce(0) { count, it in
if case .step = it { count + 1 } else { count }
}
}
@ViewBuilder
private func itemRow(_ item: ExerciseInfo.Item, ordinal: Int?) -> some View {
switch item {
case .step(let text):
HStack(alignment: .firstTextBaseline, spacing: 6) {
Text("\(ordinal ?? 0).")
.foregroundStyle(.secondary)
.monospacedDigit()
Text(text)
}
.font(.callout)
case .bullet(let text):
HStack(alignment: .firstTextBaseline, spacing: 6) {
Text("")
.foregroundStyle(.secondary)
Text(text)
}
.font(.callout)
case .paragraph(let text):
Text(text)
.font(.callout)
}
}
}
/// The `Targets:` muscles as wrapping capsule chips.
private struct TargetChips: View {
let targets: [String]
var body: some View {
FlowLayout(spacing: 6) {
ForEach(targets, id: \.self) { target in
Text(target)
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(Color.accentColor.opacity(0.12), in: Capsule())
.foregroundStyle(Color.accentColor)
}
}
}
}
/// Minimal leading-aligned wrapping layout for the target chips.
private struct FlowLayout: Layout {
var spacing: CGFloat = 6
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let rows = layoutRows(maxWidth: proposal.width ?? .infinity, subviews: subviews)
let height = rows.map(\.height).reduce(0, +) + spacing * CGFloat(max(0, rows.count - 1))
return CGSize(width: proposal.width ?? rows.map(\.width).max() ?? 0, height: height)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
var y = bounds.minY
var index = 0
for row in layoutRows(maxWidth: bounds.width, subviews: subviews) {
var x = bounds.minX
for size in row.sizes {
subviews[index].place(
at: CGPoint(x: x, y: y),
proposal: ProposedViewSize(size)
)
x += size.width + spacing
index += 1
}
y += row.height + spacing
}
}
private struct Row {
var sizes: [CGSize] = []
var width: CGFloat = 0
var height: CGFloat = 0
}
private func layoutRows(maxWidth: CGFloat, subviews: Subviews) -> [Row] {
var rows: [Row] = []
var current = Row()
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
let next = current.sizes.isEmpty ? size.width : current.width + spacing + size.width
if next > maxWidth, !current.sizes.isEmpty {
rows.append(current)
current = Row()
}
current.sizes.append(size)
current.width = current.sizes.isEmpty ? 0 : current.width + (current.sizes.count == 1 ? size.width : spacing + size.width)
current.height = max(current.height, size.height)
}
if !current.sizes.isEmpty { rows.append(current) }
return rows
}
}